ASCII Sanatı Yaratın


14

Giriş olarak makul kayıpsız biçimde siyah beyaz bir görüntü verildiğinde, giriş görüntüsüne mümkün olduğunca yakın olan ASCII resmini çıktılayın.

kurallar

  • Yalnızca satır beslemeleri ve ASCII bayt 32-127 kullanılabilir.
  • Giriş görüntüsü kırpılır, böylece görüntüyü çevreleyen yabancı bir boşluk kalmaz.
  • Başvurular tüm puanlama birimini 5 dakikadan daha kısa sürede tamamlayabilmelidir.
  • Yalnızca ham metin kabul edilebilir; zengin metin biçimi yok.
  • Puanlamada kullanılan yazı tipi 20 puntoluk Linux Libertine'dir .
  • Çıktı metin dosyası, aşağıda açıklandığı gibi bir görüntüye dönüştürüldüğünde, giriş boyutuyla aynı boyutlarda olmalı ve her iki boyutta da 30 piksel olmalıdır.

puanlama

Bu görüntüler puanlama için kullanılacaktır:

Sen görüntülerin bir zipfile indirebilirsiniz burada .

Başvurular bu şirket için optimize edilmemelidir; bunun yerine, benzer boyutlardaki 8 siyah beyaz görüntü için çalışması gerekir. Gönderimlerin bu belirli görüntüler için optimize edildiğinden şüphelenirim, corpus'taki görüntüleri değiştirme hakkını saklı tutarım.

Puanlama bu komut dosyası üzerinden yapılacaktır:

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

Puanlama işlemi:

  1. Her bir corpus görüntüsü için gönderimi çalıştırın ve sonuçları .txtcorpus dosyasıyla aynı gövdeye sahip dosyalara çıktılayın (manuel olarak yapılır).
  2. Boşlukları kırparak, 20 puntoluk yazı tipini kullanarak her metin dosyasını bir PNG görüntüsüne dönüştürün.
  3. Lanczos yeniden örnekleme kullanarak sonuç görüntüsünü orijinal görüntünün boyutlarına göre yeniden boyutlandırın.
  4. Her metin görüntüsünü kullanarak orijinal görüntüyle karşılaştırın dssim.
  5. Her metin dosyası için dssim puanını çıktılar.
  6. Ortalama skorun çıktısını alın.

Yapısal Benzerlik ( dssimpuanları hesaplayan metrik ), görüntülerdeki insan vizyonuna ve nesne tanımlamasına dayanan bir metriktir. Açıkça söylemek gerekirse: iki görüntü insanlara benziyorsa, (muhtemelen) düşük bir puan alacaktır dssim.

Kazanan sunum, en düşük ortalama puana sahip sunum olacaktır.

ilişkili


6
"Siyah / beyaz", "sıfır / bir" veya kaç gri düzeyi olduğu gibi?
Luis Mendo

2
@DonMuesli 0 ve 1.
Mego

"Sonuçların .txtdosyalara gönderilmesi" ile ne demek istediğinizi açıklayabilir misiniz ? Program bir dosyaya aktarılacak metni çıkarmalı mı yoksa doğrudan bir dosya mı çıkarmalıyız?
DanTheMan

@DanTheMan Ya kabul edilebilir. STDOUT'a çıktı gönderirseniz, çıktının puanlama amacıyla bir dosyaya yönlendirilmesi gerekir.
Mego

Çözünürlük kısıtlamaları belirtmemeli misiniz? Aksi takdirde, diyelim ki, ölçeklendiğinde orijinal görüntülerle oldukça yakından eşleştiğinde ve tek tek karakterlerin okunaksız noktalar olacağı 10000 x 10000 karakterlik bir resim üretebiliriz. Çıktı görüntüsünün büyük olması durumunda yazı tipi boyutu önemli değildir.
DavidC

Yanıtlar:


6

Java, puan 0.57058675

Bu aslında ilk kez görüntü manipülasyonu yapıyor, bu yüzden biraz garip ama sanırım tamam çıktı.

Dssim'i makinemde çalıştıramadım, ancak PIL kullanarak görüntü elde edebildim.

İlginç bir şekilde, yazı tipi Java'da kullandığım karakterlerin her birinin genişlik olduğunu söylüyor 6. Sen benim programda görebiliriz FontMetrics::charWidtholduğunu 6ben kullandım tüm karakterler için. {}Logosu tek aralıklı yazı oldukça iyi görünüyor. Ancak bazı nedenlerden dolayı satırlar tam metin dosyasında sıralanmaz. Bitişik harfleri suçlarım. (Ve evet, gereken doğru yazı tipini kullanıyor.)

Eş aralıklı yazı tipinde:

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

Görüntü aracıyla çalıştırdıktan sonra:

{} logo

Her neyse, işte asıl kod.

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

}

Derleme:

  • JDK'nın kurulu olduğundan emin olun
  • JDK kutusunun PATH'nizde olduğundan emin olun (benim için C:\Program Files\Java\jdk1.8.0_91\bin)
  • Dosyayı farklı kaydet AsciiArt.java
  • javac AsciiArt.java
  • jar cvfe WhateverNameYouWant.jar AsciiArt AsciiArt.class

Kullanımı:, java -jar WhateverNameYouWant.jar C:\full\file\path.pngSTDOUT'a yazdırır

1-bit derinlikte kaydedilecek kaynak dosya ve beyaz piksel olması için örnek GEREKTİRİR 1.

Puanlama çıktısı:

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
İle çalıştırın -eaiddialarını etkinleştirmek için. Davranışı değiştirmeyecektir (belki az miktarda yavaşlatmak hariç) çünkü değerlendirmeler değerlendirilirken program başarısız olur falseve tüm bu iddialar geçer.
CAD97

Ahh, paket bildirimini kaldırdığın için özledim. Şu an çalışıyor. Bugün birkaç dakika aldığımda puan vereceğim.
Mego

Board.png çıktısı herhangi bir nedenle sadece 4 satır uzunluğundadır: gist.github.com/Mego/75eccefe555a81bde6022d7eade1424f . Aslında, PPCG logosu hariç, tüm çıktıları çalıştırdığımda erken kesilmiş gibi görünüyor.
Mego

@Mego Bence yazı tipi yüksekliği ile ilgili (FontMetrics raporu tarafından 24 piksel). Çizgi döngüsünü değiştirdim, bu yüzden çok az değil, çok fazla çizginin yan tarafında erir ve şimdi çalışmalıdır. (tahta 5 satırdır)
CAD

Kural olarak, bu algoritma daha küçük görüntülerle mücadele eder, çünkü (tüm karakterler 6 piksel genişliğinde ve 24 piksel yüksekliğindedir ve tek göründüğü kadarıyla bu süper pikselde kaç piksel açıktır).
CAD97
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.