ASCII 아트 생성


14

입력 으로 합리적인 무손실 형식의 흑백 이미지 가 제공되면 입력 이미지와 최대한 가까운 ASCII 아트를 출력하십시오.

규칙

  • 줄 바꿈 및 ASCII 바이트 32-127 만 사용할 수 있습니다.
  • 이미지 주변에 불필요한 공백이 없도록 입력 이미지가 잘립니다.
  • 제출물은 5 분 이내에 전체 점수 모음을 완료 할 수 있어야합니다.
  • 원시 텍스트 만 허용됩니다. 서식있는 텍스트 형식이 없습니다.
  • 스코어링에 사용 된 글꼴은 20pt Linux Libertine 입니다.
  • 출력 텍스트 파일은 아래에 설명 된대로 이미지로 변환 될 때 각 크기의 30 픽셀 내에서 입력 이미지와 동일한 크기 여야합니다.

채점

이 이미지는 스코어링에 사용됩니다.

여기 에서 이미지의 zip 파일을 다운로드 할 수 있습니다 .

제출물은이 모음에 최적화되어서는 안됩니다. 오히려 비슷한 크기의 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. 각 코퍼스 이미지에 대해 제출을 실행하여 코퍼스 .txt파일과 동일한 스템이있는 파일로 결과를 출력 하십시오 (수동으로 완료).
  2. 공백을 잘라내어 20 포인트 글꼴을 사용하여 각 텍스트 파일을 PNG 이미지로 변환합니다.
  3. Lanczos 리샘플링을 사용하여 결과 이미지의 크기를 원본 이미지의 크기로 조정하십시오.
  4. 를 사용하여 각 텍스트 이미지를 원본 이미지와 비교하십시오 dssim.
  5. 각 텍스트 파일에 대한 dssim 점수를 출력하십시오.
  6. 평균 점수를 출력합니다.

구조적 유사성 ( dssim점수 를 계산하는 메트릭)은 이미지에서 사람의 시력과 물체 식별을 기반으로하는 메트릭입니다. 간단히 말해서 : 두 이미지가 인간과 비슷해 보인다면 아마도 낮은 점수를 얻게 될 것입니다 dssim.

당첨 된 제출물은 평균 점수가 가장 낮은 제출물입니다.

관련


6
"0 / one"에서와 같이 "흑백"또는 얼마나 많은 그레이 레벨?
Luis Mendo

2
@DonMuesli 0과 1
Mego

"결과를 .txt파일로 출력"을 통해 무슨 뜻인지 알 수 있습니까? 프로그램이 파일로 파이프 될 텍스트를 출력해야합니까 아니면 파일을 직접 출력해야합니까?
DanTheMan

@DanTheMan 어느 쪽이든 허용됩니다. STDOUT으로 출력하는 경우 스코어링을 위해 출력을 파일로 경로 재지 정해야합니다.
Mego

해상도 제한을 지정하지 않아야합니까? 그렇지 않으면, 우리는 축소 할 때 원본 이미지와 아주 밀접하게 일치하는 10000 x 10000 문자 이미지를 생성 할 수 있으며 개별 문자는 읽기 어려운 점이됩니다. 출력 이미지가 큰 경우 글꼴 크기는 중요하지 않습니다.
DavidC

답변:


6

자바, 0.57058675 점

실제로 이미지 조작을하는 것은 처음이므로 어색하지만 괜찮습니다.

컴퓨터에서 dssim을 사용할 수 없었지만 PIL을 사용하여 이미지를 만들 수있었습니다.

흥미롭게도, 글꼴은 Java에서 내가 사용하는 각 문자가 width임을 알려줍니다 6. 내 프로그램에서 볼 수 FontMetrics::charWidth있습니다 6내가 사용하는 모든 문자. {}로고는 고정 폭 글꼴에서 꽤 괜찮은 보인다. 그러나 어떤 이유로 줄은 실제로 전체 텍스트 파일에 정렬되지 않습니다. 나는 합자를 비난합니다. (예, 올바른 글꼴을 사용해야 합니다.)

고정 폭 글꼴에서 :

                                                                                      .
                         .,: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.pngSTDOUT에 인쇄

소스 파일을 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
-eaassertion을 사용하려면 다음을 실행하십시오 . 어설 션은 평가할 때 프로그램이 실패 false하고 모든 어설 션이 통과 하면 동작이 변경되므로 동작이 변경되지 않습니다 (소량 만 느리게하는 경우 제외) .
CAD97

아, 패키지 선언을 삭제 한 것을 놓쳤습니다. 지금 작동합니다. 오늘 몇 분만 받으면 점수를 매길 것입니다.
Mego

board.png 의 출력은 어떤 이유로 gist.github.com/Mego/75eccefe555a81bde6022d7eade1424f의 4 줄 입니다. 실제로 PPCG 로고를 제외하고 출력 할 때 출력이 모두 잘린 것 같습니다.
Mego

@ Mego 글꼴 높이 (FontMetrics 보고서에 의한 24px)와 관련이 있다고 생각합니다. 라인 루프를 변경하여 너무 적은 라인이 아닌 하나의 너무 많은 라인의 측면에서 오류가 발생하여 이제 작동합니다. (보드는 5 라인)
CAD97

원칙적 으로이 알고리즘은 더 작은 이미지로 어려움을 겪습니다. 왜냐하면 모든 문자의 너비가 6px, 높이가 24px이며 그 수퍼 픽셀에서 켜진 픽셀 수만 보입니다.
CAD97
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.