สร้าง ASCII Art


14

ให้ภาพขาวดำในรูปแบบ lossless ที่เหมาะสมในรูปแบบอินพุตเอาต์พุต ASCII art ที่ใกล้เคียงกับภาพอินพุตมากที่สุด

กฎระเบียบ

  • สามารถใช้ได้เฉพาะไลน์ฟีดและ ASCII ไบต์ 32-127
  • ภาพอินพุตจะถูกครอบตัดเพื่อไม่ให้มีพื้นที่ว่างภายนอกล้อมรอบภาพ
  • การส่งจะต้องสามารถทำให้คลังข้อมูลคะแนนทั้งหมดสำเร็จภายในเวลาไม่ถึง 5 นาที
  • ข้อความดิบเท่านั้นที่ยอมรับได้; ไม่มีรูปแบบ Rich Text
  • ตัวอักษรที่ใช้ในการให้คะแนนคือ 20-PT ลินุกซ์ศีลธรรม
  • ไฟล์ข้อความออกเมื่อแปลงเป็นภาพตามที่อธิบายไว้ด้านล่างจะต้องมีขนาดเท่ากับภาพที่ป้อนภายใน 30 พิกเซลในมิติใดมิติหนึ่ง

เกณฑ์การให้คะแนน

ภาพเหล่านี้จะถูกใช้เพื่อให้คะแนน:

คุณสามารถดาวน์โหลด zipfile ของภาพได้ที่นี่

ไม่ควรส่งข้อมูลที่เหมาะสมสำหรับคลังข้อมูลนี้ แต่ควรใช้กับภาพดำและขาว 8 ภาพที่มีขนาดใกล้เคียงกัน ฉันขอสงวนสิทธิ์ในการเปลี่ยนภาพในคลังข้อมูลหากฉันสงสัยว่ามีการเพิ่มประสิทธิภาพการส่งสำหรับภาพเหล่านี้

การให้คะแนนจะดำเนินการผ่านสคริปต์นี้:

#!/usr/bin/env python
from __future__ import print_function
from __future__ import division
# modified from http://stackoverflow.com/a/29775654/2508324
# requires Linux Libertine fonts - get them at https://sourceforge.net/projects/linuxlibertine/files/linuxlibertine/5.3.0/
# requires dssim - get it at https://github.com/pornel/dssim
import PIL
import PIL.Image
import PIL.ImageFont
import PIL.ImageOps
import PIL.ImageDraw
import pathlib
import os
import subprocess
import sys

PIXEL_ON = 0  # PIL color to use for "on"
PIXEL_OFF = 255  # PIL color to use for "off"

def dssim_score(src_path, image_path):
    out = subprocess.check_output(['dssim', src_path, image_path])
    return float(out.split()[0])

def text_image(text_path):
    """Convert text file to a grayscale image with black characters on a white background.

    arguments:
    text_path - the content of this file will be converted to an image
    """
    grayscale = 'L'
    # parse the file into lines
    with open(str(text_path)) as text_file:  # can throw FileNotFoundError
        lines = tuple(l.rstrip() for l in text_file.readlines())

    # choose a font (you can see more detail in my library on github)
    large_font = 20  # get better resolution with larger size
    if os.name == 'posix':
        font_path = '/usr/share/fonts/linux-libertine/LinLibertineO.otf'
    else:
        font_path = 'LinLibertine_DRah.ttf'
    try:
        font = PIL.ImageFont.truetype(font_path, size=large_font)
    except IOError:
        print('Could not use Libertine font, exiting...')
        exit()

    # make the background image based on the combination of font and lines
    pt2px = lambda pt: int(round(pt * 96.0 / 72))  # convert points to pixels
    max_width_line = max(lines, key=lambda s: font.getsize(s)[0])
    # max height is adjusted down because it's too large visually for spacing
    test_string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
    max_height = pt2px(font.getsize(test_string)[1])
    max_width = pt2px(font.getsize(max_width_line)[0])
    height = max_height * len(lines)  # perfect or a little oversized
    width = int(round(max_width + 40))  # a little oversized
    image = PIL.Image.new(grayscale, (width, height), color=PIXEL_OFF)
    draw = PIL.ImageDraw.Draw(image)

    # draw each line of text
    vertical_position = 5
    horizontal_position = 5
    line_spacing = int(round(max_height * 0.8))  # reduced spacing seems better
    for line in lines:
        draw.text((horizontal_position, vertical_position),
                  line, fill=PIXEL_ON, font=font)
        vertical_position += line_spacing
    # crop the text
    c_box = PIL.ImageOps.invert(image).getbbox()
    image = image.crop(c_box)
    return image

if __name__ == '__main__':
    compare_dir = pathlib.PurePath(sys.argv[1])
    corpus_dir = pathlib.PurePath(sys.argv[2])
    images = []
    scores = []
    for txtfile in os.listdir(str(compare_dir)):
        fname = pathlib.PurePath(sys.argv[1]).joinpath(txtfile)
        if fname.suffix != '.txt':
            continue
        imgpath = fname.with_suffix('.png')
        corpname = corpus_dir.joinpath(imgpath.name)
        img = text_image(str(fname))
        corpimg = PIL.Image.open(str(corpname))
        img = img.resize(corpimg.size, PIL.Image.LANCZOS)
        corpimg.close()
        img.save(str(imgpath), 'png')
        img.close()
        images.append(str(imgpath))
        score = dssim_score(str(corpname), str(imgpath))
        print('{}: {}'.format(corpname, score))
        scores.append(score)
    print('Score: {}'.format(sum(scores)/len(scores)))

กระบวนการให้คะแนน:

  1. รันการส่งสำหรับอิมเมจ corpus แต่ละภาพแล้วส่งผลลัพธ์ไปยัง.txtไฟล์ที่มีต้นกำเนิดเดียวกันกับไฟล์ corpus (ทำด้วยตนเอง)
  2. แปลงไฟล์ข้อความแต่ละไฟล์ให้เป็นภาพ PNG โดยใช้แบบอักษร 20 จุดตัดช่องว่างออก
  3. ปรับขนาดภาพผลลัพธ์เป็นขนาดของภาพต้นฉบับโดยใช้การสุ่มภาพใหม่ของ Lanczos
  4. dssimเปรียบเทียบภาพข้อความแต่ละคนมีภาพต้นฉบับใช้
  5. เอาต์พุตคะแนน dssim สำหรับแต่ละไฟล์ข้อความ
  6. เอาท์พุทคะแนนเฉลี่ย

ความคล้ายคลึงกันของโครงสร้าง (ตัวชี้วัดที่dssimคำนวณคะแนน) เป็นตัวชี้วัดตามการมองเห็นของมนุษย์และการระบุวัตถุในภาพ ที่จะนำมันชัดถ้อยชัดคำ: ถ้าสองภาพที่มีลักษณะคล้ายกับมนุษย์พวกเขาจะ (อาจจะ) dssimมีคะแนนต่ำ

การส่งที่ชนะจะได้รับการส่งด้วยคะแนนเฉลี่ยต่ำสุด

ที่เกี่ยวข้อง


6
"ขาวดำ" ใน "ศูนย์ / หนึ่ง" หรือกี่ระดับสีเทา
Luis Mendo

2
@DonMuesli 0 และ 1
Mego

คุณช่วยอธิบายสิ่งที่คุณหมายถึงโดย "การแสดงผลลัพธ์ไปยัง.txtไฟล์"? ข้อความที่โปรแกรมส่งออกซึ่งจะถูกไพพ์ไปยังไฟล์หรือเราควรส่งออกไฟล์โดยตรง?
DanTheMan

@DanTheMan อย่างใดอย่างหนึ่งเป็นที่ยอมรับ หากคุณส่งออกไปยัง STDOUT ผลลัพธ์จะต้องถูกเปลี่ยนเส้นทางไปยังไฟล์เพื่อการให้คะแนน
Mego

คุณไม่ควรระบุข้อ จำกัด ในการแก้ไข ไม่เช่นนั้นเราสามารถสร้างภาพตัวอักษรขนาด 10,000 x 10,000 ตัวที่เมื่อปรับขนาดลงจะจับคู่ภาพต้นฉบับอย่างใกล้ชิดและอักขระแต่ละตัวจะเป็นจุดที่อ่านไม่ออก ขนาดตัวอักษรไม่สำคัญว่าภาพที่ได้จะมีขนาดใหญ่มาก
DavidC

คำตอบ:


6

Java, คะแนน 0.57058675

นี่เป็นครั้งแรกที่ฉันทำการจัดการภาพดังนั้นมันจึงค่อนข้างอึดอัด แต่ฉันคิดว่ามันก็โอเค

ฉันไม่สามารถทำงานกับเครื่องของฉันได้ แต่ฉันสามารถสร้างภาพโดยใช้ PIL ได้

ที่น่าสนใจตัวอักษรบอกฉันใน Java 6ที่ตัวละครแต่ละตัวฉันใช้มีความกว้าง คุณจะเห็นว่าในโปรแกรมของฉันFontMetrics::charWidthมี6ไว้สำหรับตัวละครทุกตัวที่ฉันใช้ {}โลโก้ดูสวยดีอยู่ในตัวอักษร monospace แต่ด้วยเหตุผลบางประการบรรทัดไม่ได้จัดเรียงในไฟล์ข้อความแบบเต็ม ฉันตำหนิหนังสติ๊ก (และใช่ฉันควรใช้แบบอักษรที่ถูกต้อง)

ในแบบอักษร monospaced:

                                                                                      .
                         .,:ff:,                                                   ,:fff::,.
                ,ff .fIIIIIf,                                                         .:fIIIIIf.:f:.
            .,:III: ,ff::                       ..,,            ,,..                      ,:fff, IIII.,
          :IIf,f:,:fff:,                  .:fIIIIIII.          .IIIIIIIf:.                 .,:fff:,ff IIf,
       ,.fIIIf,:ffff,                   ,IIIIIII:,,.            .,,:IIIIIII.                  .:ffff:,IIII,:.
     ,III.::.,,,,,.                     IIIIII:                      ,IIIIII                     ,,,,,.,:,:IIf
     IIIII :ffIIf,                      IIIIII,                      .IIIIII                      :IIIf:,.IIIIf.
  ,II,fIf.:::,..                        IIIIII,                      .IIIIII                       ..,:::,,If::II
  IIIIf.  ,:fII:                       .IIIIII,                      .IIIIII.                       IIff:.  :IIII:
 ::IIIIf:IIIf: .                  ,::fIIIIIII,                        ,fIIIIIIf::,                   ,ffIII,IIIIf,,
:IIf:::    .,fI:                  IIIIIIIII:                            :IIIIIIIIf                  If:,    .::fIIf
 IIIIII, :IIIIf                     .,:IIIIIIf                        fIIIIII:,.                    ,IIIII. fIIIII:
 ,:IIIII ff:,   f,                      IIIIII,                      .IIIIII                      f.  .::f::IIIIf,.
 fIf::,,     ,fIII                      IIIIII,                      .IIIIII                     :III:      ,,:fII.
  fIIIIIIf, :IIIIf   ,                  IIIIII,                      .IIIIII                 .,  ,IIIII. :fIIIIII,
   .:IIIIIII,ff,    :II:                IIIIIIf                      fIIIIII               .fII.   .:ff:IIIIIIf,
     :fffff:,      IIIIIf   ,            :IIIIIIIfff            fffIIIIIII:           ..   IIIII:      ::fffff,
      .fIIIIIIIf:, fIIII,   ,IIf,           ,:ffIIII.          .IIIIff:,          .:fII    fIIII,.:ffIIIIIII:
         ,fIIIIIIIIIf:,     ,IIIII:  .,::,                               .,::,  .IIIIII      ::fIIIIIIIIf:.
             :fffffff,      .fIIIII,   .IIIIIf:                     ,:fIIII:    IIIIII:       :fffffff,
              .:fIIIIIIIIIIIIffffI:      IIIIIIII.                :IIIIIII:     .fIffffIIIIIIIIIIII:,
                   ,:fIIIIIIIIIIIf,       .:fIIIII               ,IIIIIf,        :IIIIIIIIIIIff,.
                         .:ffffffffIIIIIIIIIIIfff:.              ,ffffIIIIIIIIIIIfffffff:,
                             .,:ffIIIIIIIIIIIIIIIIf,   .,,,,.  .:fIIIIIIIIIIIIIIIIff:,.
                                       ....... .,,:fffff:.,:fffff:,.  .......
                                    ..,,:fffIIIIf:,.            .,:fIIIIff::,,..
                                   .IIIIIf:,.                          .,:fIIIII
                                     f,                                      ,f

หลังจากเรียกใช้ผ่านเครื่องมือรูปภาพ:

โลโก้ {}

อย่างไรก็ตามนี่คือรหัสจริง

//package cad97;

import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import java.awt.image.Raster;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import javax.imageio.ImageIO;

public final class AsciiArt {

    private static final Font LINUX_LIBERTINE = new Font("LinLibertine_DRah", Font.PLAIN, 20);
    private static final FontMetrics LL_METRICS = Toolkit.getDefaultToolkit().getFontMetrics(LINUX_LIBERTINE);
    // Toolkit::getFontMetrics is deprecated, but that's the only way to get FontMetrics without an explicit Graphics environment.
    // If there's a better way to get the widths of characters, please tell me.

    public static void main(String[] args) throws IOException {
        File jar = new java.io.File(AsciiArt.class.getProtectionDomain().getCodeSource().getLocation().getPath());
        if (args.length != 1) {
            String jarName = jar.getName();
            System.out.println("Usage: java -jar " + jarName + " file");
        } else {
            File image = new File(args[0]);
            try (InputStream input = new FileInputStream(image)) {
                String art = createAsciiArt(ImageIO.read(input), LINUX_LIBERTINE, LL_METRICS);
                System.out.print(art); // If you want to save as a file, change this.
            } catch (FileNotFoundException fnfe) {
                System.out.println("Unable to find file " + image + ".");
                System.out.println("Please note that you need to pass the full file path.");
            }
        }
    }

    private static String createAsciiArt(BufferedImage image, Font font, FontMetrics metrics) {
        final int height = metrics.getHeight();
        final Map<Character,Integer> width = new HashMap<>();
        for (char c=32; c<127; c++) { width.put(c, metrics.charWidth(c)); }

        StringBuilder art = new StringBuilder();

        for (int i=0; i<=image.getHeight(); i+=height) {
            final int tempHeight = Math.min(height, image.getHeight()-i);
            art.append(createAsciiLine(image.getSubimage(0, i, image.getWidth(), tempHeight), width));
        }

        return art.toString();
    }

    private static String createAsciiLine(BufferedImage image, Map<Character,Integer> charWidth) {
        if (image.getWidth()<6) return "\n";
        /*
        I'm passing in the charWidth Map because I could use it, and probably a later revision if I
        come back to this will actually use non-6-pixel-wide characters. As is, I'm only using the
        6-pixel-wide characters for simplicity. They are those in this set: { !,./:;I[\]ft|}
        */
        assert charWidth.get(' ') == 6; assert charWidth.get('!') == 6;
        assert charWidth.get(',') == 6; assert charWidth.get('.') == 6;
        assert charWidth.get('/') == 6; assert charWidth.get(':') == 6;
        assert charWidth.get(';') == 6; assert charWidth.get('I') == 6;
        assert charWidth.get('[') == 6; assert charWidth.get('\\') == 6;
        assert charWidth.get(']') == 6; assert charWidth.get('f') == 6;
        assert charWidth.get('t') == 6; assert charWidth.get('|') == 6;

        // Measure whiteness of 6-pixel-wide sample
        Raster sample = image.getData(new Rectangle(6, image.getHeight()));
        int whiteCount = 0;
        for (int x=sample.getMinX(); x<sample.getMinX()+sample.getWidth(); x++) {
            for (int y=sample.getMinY(); y<sample.getMinY()+sample.getHeight(); y++) {
                int pixel = sample.getPixel(x, y, new int[1])[0];
                whiteCount += pixel==1?0:1;
            }
        }

        char next;

        int area = sample.getWidth()*sample.getHeight();

        if (whiteCount > area*0.9) {
            next = ' ';
        } else if (whiteCount > area*0.8) {
            next = '.';
        } else if (whiteCount > area*0.65) {
            next = ',';
        } else if (whiteCount > area*0.5) {
            next = ':';
        } else if (whiteCount > area*0.3) {
            next = 'f';
        } else {
            next = 'I';
        }

        return next + createAsciiLine(image.getSubimage(charWidth.get(','), 0, image.getWidth()-sample.getWidth(), image.getHeight()), charWidth);
    }

}

รวบรวม:

  • ตรวจสอบให้แน่ใจว่าคุณติดตั้งJDKแล้ว
  • ตรวจสอบให้แน่ใจว่าถังขยะ JDK อยู่ในเส้นทางของคุณ (สำหรับฉันแล้วC:\Program Files\Java\jdk1.8.0_91\bin)
  • บันทึกไฟล์เป็น AsciiArt.java
  • javac AsciiArt.java
  • jar cvfe WhateverNameYouWant.jar AsciiArt AsciiArt.class

การใช้งาน: java -jar WhateverNameYouWant.jar C:\full\file\path.pngพิมพ์ไปยัง STDOUT

ต้องใช้แฟ้มแหล่งที่มาจะถูกบันทึกไว้มีความลึก 1 1บิตและตัวอย่างสำหรับพิกเซลสีขาวจะเป็น

เกณฑ์การให้คะแนน:

corp/board.png: 0.6384
corp/Doppelspalt.png: 0.605746
corp/down.png: 1.012326
corp/img2.png: 0.528794
corp/pcgm.png: 0.243618
corp/peng.png: 0.440982
corp/phi.png: 0.929552
corp/text2image.png: 0.165276
Score: 0.57058675

1
ทำงานด้วย-eaเพื่อเปิดใช้งานการยืนยัน มันจะไม่เปลี่ยนพฤติกรรม (ยกเว้นอาจทำให้ช้าลงเล็กน้อย) เนื่องจากการยืนยันทำงานโดยล้มเหลวของโปรแกรมเมื่อประเมินfalseและยืนยันทั้งหมดเหล่านี้ผ่าน
CAD97

อ๊ะฉันพลาดที่คุณลบการประกาศแพ็คเกจ มันใช้งานได้แล้ว ฉันจะทำคะแนนเมื่อฉันได้รับไม่กี่นาทีในวันนี้
Mego

เอาท์พุทสำหรับboard.pngเป็นเพียง 4 สายยาวด้วยเหตุผลบางอย่าง: gist.github.com/Mego/75eccefe555a81bde6022d7eade1424f ที่จริงแล้วผลลัพธ์ทั้งหมดดูเหมือนว่าจะถูกตัดออกก่อนกำหนดเมื่อฉันเรียกใช้ยกเว้นโลโก้ PPCG
Mego

@ ฉันคิดว่ามันเกี่ยวกับความสูงของแบบอักษร (24 px โดยรายงาน FontMetrics) ฉันเปลี่ยน line loop ดังนั้นมันจึงเกิดข้อผิดพลาดที่ด้านข้างของหนึ่งบรรทัดมากเกินไปแทนที่จะน้อยเกินไปและควรทำงานได้แล้ว (บอร์ดคือ 5 สาย)
CAD97

ตามกฎแล้วอัลกอริทึมนี้จะต่อสู้กับภาพขนาดเล็กเนื่องจาก (คิดว่า) ตัวละครทุกตัวมีความกว้าง 6px และสูง 24px และทั้งหมดนั้นดูเป็นจำนวนพิกเซลที่เปิดใช้งานในซุปเปอร์พิกเซลนั้น
CAD97
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.