12
\$\begingroup\$

Input: An RGBA hex color c (ex. FFFF00FF) and an integer > 0 and < 1000 n (ex. 200).

Output: Raw bytes of a PNG file such that when the output is saved to a file and opened in an image viewer, an n by n image filled with the color c is displayed.

Specification: Your program should output exactly:

  • a PNG header (89504E470D0A1A0A in hex)
  • an IHDR chunk containing these specifications:
    • width: the previous input n
    • height: the previous input n
    • bit depth: 8 (RGBA)
    • color type: 6 (truecolor with alpha)
    • compression method: 0
    • filter method: 0
    • interlace method: 0
  • one or more IDAT chunks containing the image data (a solid image of color previously input c); may be compressed or uncompressed
  • an IEND image end chunk

Further details available on Wikipedia, on the W3 site, or via a Google search.

Restrictions:

  • You may not use any image libraries or functions designed to work with images of any kind.
  • Your program must run in under 3 minutes and output a file under 10 MB for all inputs (sanity check).
  • This is , so the shortest code in bytes will win!
\$\endgroup\$
7
  • \$\begingroup\$ You say that the file may be entirely uncompressed, but then that it must be under 30kB for all inputs. A 999x999 file has more than 30720 pixels, so that seems self-contradictory. \$\endgroup\$ Commented May 15, 2014 at 21:39
  • \$\begingroup\$ @PeterTaylor Hm, for some reason I thought that 30 KB was more than enough. Don't know what I was thinking... edited. (And I only said that you may or may not use compression; whichever you want.) \$\endgroup\$
    – Doorknob
    Commented May 15, 2014 at 21:40
  • \$\begingroup\$ Width: 4 bytes Height: 4 bytes Bit depth: 1 byte Color type: 1 byte Compression method: 1 byte Filter method: 1 byte Interlace method: 1 byte \$\endgroup\$ Commented May 15, 2014 at 23:12
  • \$\begingroup\$ @technosaurus ... um, what? \$\endgroup\$
    – Doorknob
    Commented May 15, 2014 at 23:27
  • 1
    \$\begingroup\$ Your example isn't correct: bit depth: 8 (RRGGBBAA). Bit depth 8 is (RGBA) not (RRGGBBAA). \$\endgroup\$ Commented May 16, 2014 at 19:36

3 Answers 3

6
\$\begingroup\$

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

Size is 180 bytes and option -p is needed (+1). Score is then 181.

The arguments are given via STDIN in a line, separated by a space, the color as hex value (16 chars) and the number of pixels for width/height, e.g.:

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

yellow200.png

The file size is 832 bytes. The maximal sized image (n=999) with the same color has 6834 bytes (way below 10 MB).

The solution uses two libraries:

  • use Digest::CRC crc32; for the CRC32 values at the chunk ends.
  • use IO::Compress::Deflate deflate; to compress the image data.

Both libraries are not related to images.

Ungolfed:

# 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');

Edits

  • use IO::Compress::Deflate':all'; is replaced by use Compress::Zlib;. The latter does export the deflate function compress by default. The function does not need references as arguments and also returns the result directly. That allows to get rid of variable $o.

Thanks for Michael's answer:

  • Function k: A call of pack could be removed by using template Na*N for the first pack in the function.

  • pack template NNCV with four values optimizes NNC3n with six values.

Thanks for VadimR's comment with lots of tips:

  • use String::CRC32; is shorter than use Digest::CRC crc32;.
  • y///c-4 is shorter than -4+y///c.
  • The scan line is now constructed by the template CH* with the repetition in the value.
  • Removal of $i by using a value reference.
  • Bare words instead of strings for the chunk types.
  • Options now read by matching a STDIN input line (option -p) with matching the space separator / /. Then the first option is in $` and the second argument goes in $'.
  • Option -p also automatically prints $_.
  • "\cZ" is shorter than "\x1a".

Better compression

At the cost of code size the image data can be further compressed, if filtering is applied.

  • Unfiltered file size for FFFF0FF 200: 832 bytes

  • Filter Sub (horizontal pixel differences): 560 bytes

    $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
    
  • Filter Sub for the first line and Up for the remaining lines: 590 bytes

    $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);
    
  • First unfiltered line, then filter Up: 586 bytes

    $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);
    
  • Also Compress::Zlib can be tuned; the highest compression level can be set by additional option for the compression level in function compress at the cost of two bytes:

    compress ..., 9;
    

    File size of example yellow200.png without filtering decreases from 832 bytes to 472 bytes. Applied to the example with Sub filter, the file size shrinks from 560 bytes to 445 bytes (pngcrush -brute can't compress further).

\$\endgroup\$
2
  • \$\begingroup\$ Great answer (as always), but golfing can go further - I get 202, + 1 for -p. In addition to insights in Michael's answer (NA*N and NNCV templates), - String::CRC32 exports by default, y///c-4 is OK, CH* template, $i be gone, \cZ, barewords are OK, -p and / /; places arguments into prematch and postmatch. I wonder if I missed something and score can get below 200 :) \$\endgroup\$ Commented May 17, 2014 at 11:51
  • 1
    \$\begingroup\$ @VadimR: Many thanks for the useful tips. I could even golf it further by using use Compress::Zlib; and got ≈ 10 % below 200. \$\endgroup\$ Commented May 17, 2014 at 15:57
5
\$\begingroup\$

PHP 214

I'm not an expert on PHP, there is place for golfing. Tips are welcomed.

<?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");

Generate a PNG file:

php png.php 20 FFFF00FF > output.png

Generate base64 stream (paste the result in your browser address bar)

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

Ungolfed version :

<?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");
\$\endgroup\$
3
  • \$\begingroup\$ It's 214 now, isn't it? And, I can get no correct image from both golfed and un-golfed versions, but I have no PHP experience, so if it works for everyone else, it's me doing it wrong. \$\endgroup\$ Commented May 17, 2014 at 11:59
  • 1
    \$\begingroup\$ @VadimR, yes 214, you're right. I've checked, the image generated is valid for me. \$\endgroup\$
    – Michael M.
    Commented May 17, 2014 at 12:06
  • \$\begingroup\$ For me (I test with PHP 5.4.27.0), image is 4 bytes short - shouldn't adler-32 be appended to deflated data? IE and Chrome are happy to display image as is, FF is not. Different apps behave differently, too, with this image. \$\endgroup\$ Commented May 17, 2014 at 12:27
4
\$\begingroup\$

Python, 252 bytes

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")

This script takes input from argv. Run this script from command line, like python 27086.py deadbeef 999

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.