Wyjście pełnego PNG od zera


11

Dane wejściowe : kolor szesnastkowy RGBA c(np. FFFF00FF) I liczba całkowita> 0 i <1000 n(np. 200).

Dane wyjściowe : surowe bajty pliku PNG, tak że gdy dane wyjściowe są zapisywane w pliku i otwierane w przeglądarce zdjęć, wyświetlany njest nobraz wypełniony kolorem c.

Specyfikacja : Twój program powinien wypisywać dokładnie :

  • nagłówek PNG ( 89504E470D0A1A0Aszesnastkowo)
  • IHDRfragment zawierający następujące dane:
    • szerokość: poprzednie wejście n
    • wysokość: poprzednie wejście n
    • głębia bitowa: 8( RGBA)
    • rodzaj koloru: 6(truecolor z alfa)
    • metoda kompresji: 0
    • metoda filtrowania: 0
    • metoda z przeplotem: 0
  • jeden lub więcej IDATfragmentów zawierających dane obrazu (wcześniej wprowadzono jednolity obraz koloru c); może być skompresowany lub nieskompresowany
  • IENDfragment końcowy obraz

Dalsze szczegóły dostępne są w Wikipedii , na stronie W3 lub w wyszukiwarce Google.

Ograniczenia :

  • Nie wolno używać bibliotek obrazów ani funkcji zaprojektowanych do pracy z obrazami dowolnego rodzaju.
  • Twój program musi zostać uruchomiony w czasie krótszym niż 3 minuty i wygenerować plik poniżej 10 MB dla wszystkich danych wejściowych (kontrola poprawności).
  • To jest , więc wygra najkrótszy kod w bajtach!

Mówisz, że plik może być całkowicie nieskompresowany, ale wtedy musi mieć mniej niż 30 kB dla wszystkich danych wejściowych. 999x999Plik ma więcej niż 30720 pikseli, dzięki czemu wydaje się wewnętrznie sprzeczne.
Peter Taylor

@PeterTaylor Hm, z jakiegoś powodu myślałem, że 30 KB to więcej niż wystarcza. Nie wiem o czym myślałem ... edytowałem. (I powiedziałem tylko, że możesz, ale nie musisz, stosować kompresję; cokolwiek chcesz.)
Klamka

Szerokość: 4 bajty Wysokość: 4 bajty Głębokość bitów: 1 bajt Rodzaj koloru: 1 bajt Metoda kompresji: 1 bajt Metoda filtrowania: 1 bajt Metoda z przeplotem: 1 bajt
technozaur

@technosaurus ... um, co?
Klamka

1
Twój przykład jest niepoprawny: głębia bitowa: 8 (RRGGBBAA). Głębia bitowa 8 to (RGBA) nie (RRGGBBAA).
Glenn Randers-Pehrson

Odpowiedzi:


6

Perl, 181

/ /;use String::CRC32;use Compress::Zlib;sub k{$_=pop;pack'Na*N',y///c-4,$_,crc32$_}$_="\x89PNG\r\n\cZ\n".k(IHDR.pack NNCV,$',$',8,6).k(IDAT.compress pack('CH*',0,$`x$')x$').k IEND

Rozmiar to 180 bajtów i -ppotrzebna jest opcja (+1). Wynik to wtedy 181.

Argumenty podawane są przez STDIN w linii oddzielonej spacją, kolorem jako wartością szesnastkową (16 znaków) i liczbą pikseli dla szerokości / wysokości, np .:

 echo "FFFF00FF 200" | perl -p solidpng.pl >yellow200.png

yellow200.png

Rozmiar pliku to 832 bajty. Maksymalny rozmiar obrazu (n = 999) tego samego koloru ma 6834 bajty (znacznie poniżej 10 MB).

Rozwiązanie wykorzystuje dwie biblioteki:

  • use Digest::CRC crc32; dla wartości CRC32 na końcach porcji.
  • use IO::Compress::Deflate deflate; kompresować dane obrazu.

Obie biblioteki nie są powiązane z obrazami.

Nie golfowany:

# Perl option "-p" adds the following around the program:
#     LINE:
#     while (<>) {
#         ... # the program goes here
#     } continue {
#         print or die "-p destination: $!\n";

/ /;    # match the separator of the arguments in the input line
        # first argument, color in hex:  $`
        # second argument, width/height: $'                              #'

# load the libraries for the CRC32 fields and the data compression
use String::CRC32;
use Compress::Zlib;

# function that generates a PNG chunk:
#   N (4 bytes, big-endian: data length
#   N:                      chunk type
#   a* (binary data):       data
#   N:                      CRC32 of chunk type and data
sub k {
    $_ = pop; # chunk data including chunk type and
              # excluding length and CRC32 fields
    pack 'Na*N',
        y///c - 4,   # chunk length                                      #/
                     # netto length without length, type, and CRC32 fields
        $_,          # chunk type and data
        crc32($_)    # checksum field
}

$_ =                      # $_ is printed by option "-p".
    "\x89PNG\r\n\cZ\n"    # PNG header
        # IHDR chunk: image header with
        #   width, height,
        #   bit depth (8), color type (6),
        #   compresson method (0), filter method (0), interlace method (0)
    . k('IHDR' . pack NNCV, $', $', 8, 6)
        # IDAT chunk: image data
    . k('IDAT' .
          compress        # compress/deflate data
          pack('CH*',     # scan line with filter byte
              0,          # filter byte: None
              ($` x $')   # pixel data for one scan line                 #'`
          ) x $'          # n lines                                      #'
      )
        # IHDR chunk: image end
    . k('IEND');

Edycje

  • use IO::Compress::Deflate':all';zastępuje się przez use Compress::Zlib;. Ten ostatni compressdomyślnie eksportuje funkcję deflacji . Funkcja nie potrzebuje referencji jako argumentów, a także zwraca wynik bezpośrednio. To pozwala pozbyć się zmiennej $o.

Dzięki za odpowiedź Michaela :

  • Funkcja k: Wywołanie packmoże zostać usunięte przy użyciu szablonu Na*Ndla pierwszej packfunkcji.

  • packszablon NNCVz czterema wartościami optymalizuje się NNC3nz sześcioma wartościami.

Dzięki za komentarz VadimR z dużą ilością wskazówek:

  • use String::CRC32;jest krótszy niż use Digest::CRC crc32;.
  • y///c-4jest krótszy niż -4+y///c.
  • Linia skanowania jest teraz konstruowana przez szablon CH*z powtórzeniem wartości.
  • Usunięcie $iza pomocą odwołania do wartości.
  • Nagie słowa zamiast ciągów dla typów porcji.
  • Opcje są teraz odczytywane poprzez dopasowanie wiersza wejściowego STDIN (opcja -p) z dopasowaniem separatora spacji / /. Potem jest pierwsza opcja $`i wchodzi drugi argument $'.
  • Opcja -pdrukuje również automatycznie $_.
  • "\cZ"jest krótszy niż "\x1a".

Lepsza kompresja

Kosztem rozmiaru kodu dane obrazu mogą być dalej kompresowane, jeśli zastosowane jest filtrowanie.

  • Rozmiar niefiltrowanego pliku dla FFFF0FF 200: 832 bajtów

  • Filtr Sub(różnice poziomych pikseli): 560 bajtów

    $i = (                            # scan line:
             "\1"                     # filter "Sub"
             . pack('H*',$c)          # first pixel in scan line
             . ("\0" x (4 * $n - 4))  # fill rest of line with zeros
          ) x $n;                     # $n scan lines
  • Filtruj Subdla pierwszego wiersza i Updla pozostałych wierszy: 590 bajtów

    $i = # first scan line
         "\1"                     # filter "Sub"
         . pack('H*',$c)          # first pixel in scan line
         . ("\0" x (4 * $n - 4))  # fill rest of line with zeros
         # remaining scan lines 
         . (
               "\2"               # filter "Up"  
               . "\0" x (4 * $n)  # fill rest of line with zeros
           ) x ($n - 1);
  • Najpierw niefiltrowana linia, a następnie filtruj Up: 586 bajtów

    $i = # first scan line
         pack('H*', ("00" . ($c x $n)))  # scan line with filter byte: none
         # remaining scan lines 
         . (
               "\2"               # filter "Up"
               . "\0" x (4 * $n)  # fill rest of line with zeros
           ) x ($n - 1);
    
  • Również Compress::Zlibmożna dostroić; najwyższy poziom kompresji można ustawić za pomocą dodatkowej opcji dla poziomu kompresji działającego compresskosztem dwóch bajtów:

    compress ..., 9;

    Rozmiar pliku yellow200.pngbez filtrowania zmniejsza się z 832 bajtów do 472 bajtów. W przypadku przykładu z Subfiltrem rozmiar pliku zmniejsza się z 560 bajtów do 445 bajtów ( pngcrush -brutenie można dalej kompresować).


Świetna odpowiedź (jak zawsze), ale golf może iść dalej - dostaję 202, + 1 za -p. Oprócz wglądu w odpowiedź Michaela ( NA*Ni NNCVszablony): - String::CRC32domyślnie eksport y///c-4jest OK, CH*szablon $izniknął \cZ, słowa bez słów są w porządku -pi / /;umieszcza argumenty w przedmeczu i postmatchu. Zastanawiam się, czy coś przeoczyłem, a wynik może
spaść

1
@VadimR: Wielkie dzięki za przydatne wskazówki. Mogę nawet use Compress::Zlib;
zagrać w

5

PHP 214

Nie jestem ekspertem od PHP, jest miejsce na golfa. Wskazówki są mile widziane.

<?function c($d){echo pack("Na*N",strlen($d)-4,$d,crc32($d));}echo"\x89PNG\r\n\x1a\n";c("IHDR".pack("NNCV",$n=$argv[1],$n,8,6));c("IDATx^".gzdeflate(str_repeat("\0".str_repeat(hex2bin($argv[2]),$n),$n)));c("IEND");

Wygeneruj plik PNG:

php png.php 20 FFFF00FF > output.png

Wygeneruj strumień base64 (wklej wynik w pasku adresu przeglądarki)

echo "data:image/png;base64,`php png.php 200 0000FFFF | base64`"

Wersja bez golfa:

<?php 

//function used to create a PNG chunck
function chunck($data) {
  return pack("Na*N", //write a big-endian integer, a string and another integer
    strlen($data)-4,     //size of data minus the 4 char of the type
    $data,               //data
    crc32($data));       //compute CRC of data
}

//png header
echo "\x89PNG\r\n\x1a\n"; 

//IHDR chunck
echo chunck("IHDR".pack("NNCV", //2 big-endian integer, a single byte and a little-endian integer
                   $n=$argv[1], $n,
                   8, 6)); //6 also write 3 zeros (little endian integer)

//IDAT chunck
//create a binary string of the raw image, each line begin with 0 (none filter)
$d = str_repeat("\0".str_repeat(hex2bin($argv[2]),$n),$n);
echo chunck("IDATx^".
       gzdeflate($d)); //compress raw data

//IEND chunck
echo chunck("IEND");

Teraz jest 214, prawda? I nie mogę uzyskać poprawnego obrazu zarówno z wersji golfowej, jak i nie golfowej, ale nie mam doświadczenia w PHP, więc jeśli działa dla wszystkich innych, to robię to źle.
user2846289

1
@VadimR, tak 214, masz rację. Sprawdziłem, wygenerowany obraz jest dla mnie ważny.
Michael M.

Dla mnie (testuję w PHP 5.4.27.0) obraz ma 4 bajty długości - czy adler-32 nie powinien być dołączany do danych deflowanych? IE i Chrome chętnie wyświetlają obraz takim, jaki jest, FF nie. Z tym obrazem różne aplikacje również zachowują się inaczej.
user2846289

4

Python, 252 bajty

import struct,sys,zlib as Z
P=struct.pack
A=sys.argv
I=lambda i:P(">I",i)
K=lambda d:I(len(d)-4)+d+I(Z.crc32(d)&(2<<31)-1)
j=int(A[2])
print "\x89PNG\r\n\x1A\n"+K("IHDR"+P(">IIBI",j,j,8,6<<24))+K("IDAT"+Z.compress(("\0"+I(int(A[1],16))*j)*j))+K("IEND")

Ten skrypt pobiera dane wejściowe z argv. Uruchom ten skrypt z wiersza poleceń, nppython 27086.py deadbeef 999

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.