Genera ASCII Art


14

Data un'immagine in bianco e nero in qualsiasi formato senza perdita ragionevole come input, genera un'arte ASCII che sia il più vicino possibile all'immagine di input.

Regole

  • È possibile utilizzare solo linefeed e byte ASCII 32-127.
  • L'immagine in ingresso verrà ritagliata in modo che non vi siano spazi esterni estranei che circondano l'immagine.
  • Gli invii devono essere in grado di completare l'intero corpus di punteggio in meno di 5 minuti.
  • È accettabile solo testo non elaborato; nessun formato RTF.
  • Il carattere utilizzato nel punteggio è Libertine Linux a 20 punti .
  • Il file di testo di output, quando convertito in un'immagine come descritto di seguito, deve avere le stesse dimensioni dell'immagine di input, entro 30 pixel in entrambe le dimensioni.

punteggio

Queste immagini verranno utilizzate per il punteggio:

Puoi scaricare un file zip delle immagini qui .

I contributi non dovrebbero essere ottimizzati per questo corpus; piuttosto, dovrebbero funzionare per qualsiasi 8 immagini in bianco e nero di dimensioni simili. Mi riservo il diritto di modificare le immagini nel corpus se sospetto che gli invii vengano ottimizzati per queste immagini specifiche.

Il punteggio verrà eseguito tramite questo script:

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

Il processo di punteggio:

  1. Esegui l'invio per ogni immagine corpus, inviando i risultati a .txtfile con lo stesso gambo del file corpus (fatto manualmente).
  2. Converti ogni file di testo in un'immagine PNG, usando un carattere a 20 punti, ritagliando uno spazio bianco.
  3. Ridimensiona l'immagine risultante alle dimensioni dell'immagine originale usando il ricampionamento di Lanczos.
  4. Confronta ogni immagine di testo con l'immagine originale usando dssim.
  5. Emette il punteggio dssim per ciascun file di testo.
  6. Emette il punteggio medio.

La similarità strutturale (la metrica con cui dssimcalcola i punteggi) è una metrica basata sulla visione umana e sull'identificazione degli oggetti nelle immagini. Per dirla chiaramente: se due immagini sembrano simili agli umani, avranno (probabilmente) un punteggio basso da dssim.

La proposta vincente sarà quella con il punteggio medio più basso.

relazionato


6
"Bianco e nero" come in "zero / uno" o quanti livelli di grigio?
Luis Mendo,

2
@DonMuesli 0 e 1.
Mego

Potresti chiarire cosa intendi con "Trasmettere i risultati in .txtfile"? Il programma dovrebbe generare il testo che verrà reindirizzato a un file o dovremmo produrre direttamente un file?
DanTheMan,

@DanTheMan Entrambi sono accettabili. Se si esegue l'output su STDOUT, l'output dovrà essere reindirizzato in un file per scopi di calcolo del punteggio.
Mego,

Non dovresti specificare i vincoli di risoluzione? Altrimenti, potremmo produrre, per esempio, un'immagine di 10000 per 10000 caratteri che, quando, ridimensionata, corrisponderebbe abbastanza da vicino alle immagini originali e i singoli personaggi sarebbero punti illeggibili. La dimensione del carattere non ha importanza se l'immagine di output è enorme.
DavidC,

Risposte:


6

Java, punteggio 0,57058675

Questa è in realtà la prima volta che faccio la manipolazione delle immagini, quindi è un po 'imbarazzante, ma penso che sia andato tutto bene.

Non sono riuscito a far funzionare dssim sulla mia macchina, ma sono stato in grado di creare immagini usando PIL.

È interessante notare che il carattere mi dice in Java che ciascuno dei caratteri che sto usando sono larghezza 6. Puoi vedere che nel mio programma FontMetrics::charWidthè 6per tutti i personaggi che ho usato. Il {}logo sembra abbastanza decente in un carattere monospace. Ma per qualche ragione le linee non si allineano nel file di testo completo. Incolpo le legature. (E sì, dovrei usare il carattere corretto.)

In carattere monospaziato:

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

Dopo averlo eseguito tramite lo strumento immagine:

{} logo

Comunque, ecco il codice attuale.

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

}

Compilare:

  • Assicurati di avere JDK installato
  • Assicurati che il cestino JDK sia sul tuo PERCORSO (per me lo è C:\Program Files\Java\jdk1.8.0_91\bin)
  • Salva il file come AsciiArt.java
  • javac AsciiArt.java
  • jar cvfe WhateverNameYouWant.jar AsciiArt AsciiArt.class

Utilizzo java -jar WhateverNameYouWant.jar C:\full\file\path.png:, stampa su STDOUT

RICHIEDE il file di origine da salvare con 1 bit di profondità e il campione per un pixel bianco 1.

Risultato del punteggio:

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
Esegui con -eaper abilitare le asserzioni. Non cambierà il comportamento (tranne forse rallentandolo di una piccola quantità) perché le asserzioni funzionano fallendo il programma quando valutano falsee tutte queste asserzioni passano.
CAD97,

Ah, mi sono perso che hai rimosso la dichiarazione del pacchetto. Ora funziona. Lo segnerò quando avrò qualche minuto oggi.
Mego,

L'output per board.png è lungo solo 4 righe per qualche motivo: gist.github.com/Mego/75eccefe555a81bde6022d7eade1424f . In effetti, tutto l'output sembra essere troncato prematuramente quando lo eseguo, ad eccezione del logo PPCG.
Mego,

@Mego Penso che abbia a che fare con l'altezza del carattere (24 px secondo il rapporto FontMetrics). Ho cambiato il loop di linea in modo che si verifichi sul lato di troppe righe anziché di troppo poche e ora dovrebbe funzionare. (la scheda è
composta

Proprio come di regola questo algoritmo lotta con le immagini più piccole, poiché (pensa) tutti i personaggi sono 6px di larghezza e 24px di altezza, e tutto ciò che osserva sono quanti pixel sono attivi in ​​quel super-pixel.
CAD97
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.