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
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:
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).
999x999
file has more than 30720 pixels, so that seems self-contradictory. \$\endgroup\$