Hasilkan Seni ASCII

14

Diberikan gambar hitam-putih dalam format lossless yang masuk akal sebagai input, menghasilkan seni ASCII yang sedekat mungkin dengan gambar input.

Aturan

  • Hanya baris baris dan ASCII byte 32-127 yang dapat digunakan.
  • Gambar input akan dipotong sehingga tidak ada spasi kosong di sekitar gambar.
  • Pengajuan harus dapat menyelesaikan seluruh corpus penilaian dalam waktu kurang dari 5 menit.
  • Hanya teks mentah yang dapat diterima; tidak ada format teks kaya.
  • Font yang digunakan dalam penilaian adalah 20-pt Linux Libertine .
  • File teks output, ketika dikonversi ke gambar seperti dijelaskan di bawah, harus memiliki dimensi yang sama dengan gambar input, dalam 30 piksel dalam dimensi mana pun.

Mencetak gol

Gambar-gambar ini akan digunakan untuk mencetak:

Anda dapat mengunduh file zip dari gambar di sini .

Pengajuan tidak harus dioptimalkan untuk korpus ini; alih-alih, mereka harus bekerja untuk 8 gambar hitam putih dengan dimensi yang sama. Saya berhak untuk mengubah gambar dalam korpus jika saya menduga pengiriman sedang dioptimalkan untuk gambar-gambar spesifik ini.

Skor akan dilakukan melalui skrip ini:

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

Proses penilaian:

  1. Jalankan pengajuan untuk setiap gambar corpus, mengeluarkan hasilnya ke .txtfile dengan batang yang sama dengan file corpus (dilakukan secara manual).
  2. Konversi setiap file teks ke gambar PNG, menggunakan font 20-point, memotong spasi.
  3. Ubah ukuran gambar hasil ke dimensi gambar asli menggunakan Lanczos resampling.
  4. Bandingkan setiap gambar teks dengan gambar asli yang digunakan dssim.
  5. Keluarkan skor dssim untuk setiap file teks.
  6. Keluarkan skor rata-rata.

Kesamaan Struktural (metrik yang digunakan untuk dssimmenghitung skor) adalah metrik yang didasarkan pada penglihatan manusia dan identifikasi objek dalam gambar. Sederhananya: jika dua gambar terlihat mirip dengan manusia, mereka akan (mungkin) memiliki skor rendah dssim.

Pengajuan yang menang akan menjadi pengajuan dengan skor rata-rata terendah.

terkait

Mego
sumber
6
"Hitam dan putih" seperti pada "nol / satu" atau berapa tingkat abu-abu?
Luis Mendo
2
@DonMuesli 0 dan 1.
Mego
Bisakah Anda mengklarifikasi apa yang Anda maksud dengan "Mengeluarkan hasil ke .txtfile"? Haruskah teks output program yang akan disalurkan ke file atau haruskah kita output file secara langsung?
DanTheMan
@DanTheMan Dapat diterima. Jika Anda meng-output ke STDOUT, output akan perlu diarahkan ke file untuk tujuan scoring.
Mego
Tidakkah Anda harus menentukan batasan resolusi? Kalau tidak, kita bisa menghasilkan, katakanlah, gambar 10.000 dengan 10.000 karakter yang, ketika, diperkecil, akan cocok dengan gambar asli cukup dekat, dan karakter individu akan menjadi titik-titik yang tidak terbaca. Ukuran font tidak masalah jika output gambarnya besar.
DavidC

Jawaban:

6

Java, skor 0,57058675

Ini sebenarnya pertama kali saya melakukan manipulasi gambar jadi agak canggung tapi saya pikir ternyata baik-baik saja.

Saya tidak bisa membuat dssim bekerja di komputer saya, tetapi saya bisa membuat gambar menggunakan PIL.

Menariknya, font memberitahu saya di Jawa bahwa masing-masing karakter yang saya gunakan lebar 6. Anda dapat melihat bahwa dalam program saya FontMetrics::charWidthadalah 6untuk semua karakter yang telah saya gunakan. The {}logo terlihat cukup baik dalam font monospace. Tetapi untuk beberapa alasan, baris-baris tersebut tidak benar-benar berbaris dalam file teks lengkap. Saya menyalahkan ligatur. (Dan ya, saya harus menggunakan font yang benar.)

Dalam font monospasi:

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

Setelah menjalankannya melalui alat gambar:

{} logo

Bagaimanapun, ini kode yang sebenarnya.

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

}

Menyusun:

  • Pastikan Anda telah menginstal JDK
  • Pastikan bahwa tempat sampah JDK ada di PATH Anda (bagi saya itu C:\Program Files\Java\jdk1.8.0_91\bin)
  • Simpan file sebagai AsciiArt.java
  • javac AsciiArt.java
  • jar cvfe WhateverNameYouWant.jar AsciiArt AsciiArt.class

Penggunaan java -jar WhateverNameYouWant.jar C:\full\file\path.png:, mencetak ke STDOUT

MEMBUTUHKAN file sumber yang akan disimpan dengan kedalaman 1-bit dan sampel untuk piksel putih menjadi 1 .

Mencetak skor:

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
sumber
1
Jalankan dengan -eauntuk mengaktifkan pernyataan. Itu tidak akan mengubah perilaku (kecuali mungkin memperlambatnya dalam jumlah kecil) karena asersi bekerja dengan kegagalan program ketika mereka mengevaluasi falsedan semua asersi ini berlalu.
CAD97
Ahh, saya melewatkan bahwa Anda menghapus deklarasi paket. Ini berfungsi sekarang. Saya akan skor ketika saya mendapatkan beberapa menit hari ini.
Mego
Output untuk board.png hanya panjang 4 baris untuk beberapa alasan: gist.github.com/Mego/75eccefe555a81bde6022d7eade1424f . Bahkan, semua output tampaknya terputus prematur ketika saya menjalankannya, dengan pengecualian logo PPCG.
Mego
@Mego Saya pikir itu ada hubungannya dengan ketinggian font (24 px oleh laporan FontMetrics). Saya mengubah loop garis sehingga keliru di sisi satu baris terlalu banyak daripada satu terlalu sedikit, dan itu harus berfungsi sekarang. (board is 5 lines)
CAD97
Sama seperti aturan algoritma ini berjuang dengan gambar yang lebih kecil, karena (ia berpikir) semua karakter adalah 6px lebar dan 24px tinggi, dan semua yang terlihat adalah berapa banyak piksel dihidupkan dalam super-pixel itu.
CAD97