Generiere ASCII Art

14

Wenn Sie ein Schwarzweißbild in einem vernünftigen verlustfreien Format als Eingabe verwenden, geben Sie ASCII-Grafiken aus, die dem Eingabebild so nahe wie möglich kommen.

Regeln

  • Es dürfen nur Linefeeds und ASCII-Bytes 32-127 verwendet werden.
  • Das Eingabebild wird so beschnitten, dass das Bild nicht von einem Leerzeichen umgeben ist.
  • Einreichungen müssen in der Lage sein, den gesamten Wertungskorpus in weniger als 5 Minuten zu vervollständigen.
  • Nur roher Text ist akzeptabel. Keine Rich-Text-Formate.
  • Die für die Bewertung verwendete Schriftart ist 20 pt Linux Libertine .
  • Die Ausgabe-Textdatei muss, wenn sie wie unten beschrieben in ein Bild konvertiert wird, die gleichen Abmessungen wie das Eingabebild haben und darf in keiner der beiden Dimensionen 30 Pixel enthalten.

Wertung

Diese Bilder werden für die Bewertung verwendet:

Sie können eine ZIP - Datei der Bilder herunterladen hier .

Einsendungen sollten nicht für dieses Korpus optimiert werden. Sie sollten vielmehr für 8 beliebige Schwarzweißbilder mit ähnlichen Abmessungen funktionieren. Ich behalte mir das Recht vor, die Bilder im Korpus zu ändern, wenn ich vermute, dass die Einsendungen für diese spezifischen Bilder optimiert werden.

Das Scoring wird über dieses Skript durchgeführt:

#!/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)))

Der Bewertungsprozess:

  1. Führen Sie die Übermittlung für jedes Korpusbild aus und geben Sie die Ergebnisse in .txtDateien mit demselben Stamm wie die Korpusdatei aus (manuell durchgeführt).
  2. Konvertieren Sie jede Textdatei in ein PNG-Bild, indem Sie eine 20-Punkt-Schriftart verwenden und Leerzeichen entfernen.
  3. Passen Sie die Größe des Ergebnisbilds mit Lanczos-Resampling an die Abmessungen des Originalbilds an.
  4. Vergleichen Sie jedes Textbild mit dem Originalbild mit dssim.
  5. Geben Sie die dssim-Punktzahl für jede Textdatei aus.
  6. Geben Sie die durchschnittliche Punktzahl aus.

Strukturelle Ähnlichkeit (die Metrik, anhand derer dssimPunktzahlen berechnet werden) ist eine Metrik, die auf dem menschlichen Sehen und der Objektidentifikation in Bildern basiert. Um es einfach auszudrücken: Wenn zwei Bilder Menschen ähnlich sehen, werden sie (wahrscheinlich) eine niedrige Punktzahl von haben dssim.

Die gewinnende Einsendung ist die Einsendung mit der niedrigsten Durchschnittspunktzahl.

verbunden

Mego
quelle
6
"Schwarzweiß" wie "Null / Eins" oder wie viele Graustufen?
Luis Mendo
2
@ DonMuesli 0 und 1.
Mego
Können Sie klarstellen, was Sie unter "Ausgabe der Ergebnisse in .txtDateien" verstehen ? Soll das Programm Text ausgeben, der an eine Datei weitergeleitet wird, oder sollten wir eine Datei direkt ausgeben?
DanTheMan
@DanTheMan Beides ist akzeptabel. Wenn Sie auf STDOUT ausgeben, muss die Ausgabe jedoch zu Bewertungszwecken in eine Datei umgeleitet werden.
Mego
Sollten Sie keine Auflösungsbeschränkungen angeben? Andernfalls könnten wir beispielsweise ein Bild mit 10000 mal 10000 Zeichen erzeugen, das, wenn es verkleinert wird, den Originalbildern ziemlich genau entspricht und die einzelnen Zeichen unleserliche Punkte sind. Die Schriftgröße spielt keine Rolle, wenn das Ausgabebild sehr groß ist.
DavidC

Antworten:

6

Java, Ergebnis 0.57058675

Dies ist eigentlich mein erstes Mal, dass ich Bilder bearbeite, es ist also etwas umständlich, aber ich denke, es hat sich als in Ordnung herausgestellt.

Ich konnte dssim nicht dazu bringen, auf meinem Computer zu arbeiten, aber ich konnte mit PIL Bilder erstellen.

Interessanterweise sagt mir die Schriftart in Java, dass jedes der Zeichen, die ich verwende, die Breite hat 6 . Sie können in meinem Programm sehen , dass FontMetrics::charWidthist 6für alle Charaktere , die ich verwendet habe. Das {}Logo sieht in einer Monospace-Schrift ziemlich anständig aus. Aus irgendeinem Grund werden die Zeilen jedoch nicht in der Volltextdatei angezeigt. Ich beschuldige Ligaturen. (Und ja, ich sollte die richtige Schriftart verwenden.)

In monospaced Schriftart:

                                                                                      .
                         .,: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

Nachdem Sie es durch das Bild-Tool ausgeführt haben:

{} Logo

Sowieso ist hier der tatsächliche Code.

//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);
    }

}

Kompilieren:

  • Stellen Sie sicher, dass Sie die haben JDK installiert haben
  • Stellen Sie sicher, dass sich der JDK - Bin in Ihrem PATH befindet (für mich ist es C:\Program Files\Java\jdk1.8.0_91\bin )
  • Speichern Sie die Datei als AsciiArt.java
  • javac AsciiArt.java
  • jar cvfe WhateverNameYouWant.jar AsciiArt AsciiArt.class

Verwendung: java -jar WhateverNameYouWant.jar C:\full\file\path.png Druckt nach STDOUT

Benötigt die Quelldatei mit 1-Bit-Tiefe und das Beispiel für ein weißes Pixel gespeichert werden 1 .

Scoring-Ausgabe:

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
CAD97
quelle
1
Führen Sie mit aus -ea, um Zusicherungen zu aktivieren. Das Verhalten wird dadurch nicht geändert (außer es wird möglicherweise etwas verlangsamt), da Zusicherungen funktionieren, indem das Programm bei der Auswertung fehlschlägt falseund alle diese Zusicherungen bestehen.
CAD97
Ahh, ich habe vermisst, dass Sie die Paketdeklaration entfernt haben. Es funktioniert jetzt. Ich treffe, wenn ich heute ein paar Minuten Zeit habe.
Mego
Die Ausgabe für board.png ist aus irgendeinem Grund nur 4 Zeilen lang: gist.github.com/Mego/75eccefe555a81bde6022d7eade1424f . Tatsächlich scheint die gesamte Ausgabe vorzeitig abgeschnitten zu sein, wenn ich sie ausführe, mit Ausnahme des PPCG-Logos.
Mego
@Mego Ich denke, es hat mit der Höhe der Schrift zu tun (24 px durch den FontMetrics-Bericht). Ich habe die Leitungsschleife so geändert, dass sie auf der Seite einer zu vielen Leitung und nicht einer zu wenigen fehlerhaft ist, und sie sollte jetzt funktionieren. (Tafel ist 5 Zeilen)
CAD97
In der Regel hat dieser Algorithmus mit den kleineren Bildern zu kämpfen, da (er denkt) alle Zeichen 6 Pixel breit und 24 Pixel hoch sind und nur die Anzahl der Pixel in diesem Superpixel angezeigt wird.
CAD97