从头开始输出固态PNG


11

输入:RGBA十六进制颜色c(例如FFFF00FF)和大于0且小于1000的整数n(例如200)。

输出:PNG文件的原始字节,以便在将输出保存到文件并在图像查看器中打开时,显示填充有颜色的nn图像c

规范:您的程序应准确输出:

  • PNG标头(89504E470D0A1A0A十六进制)
  • IHDR包含以下规范的块:
    • 宽度:上一个输入 n
    • 高度:上一个输入 n
    • 位深度:8RGBA
    • 颜色类型:(6带有Alpha的Truecolor)
    • 压缩方式: 0
    • 过滤方式: 0
    • 隔行扫描方法: 0
  • 一个或多个IDAT包含图像数据的块(先前输入的彩色实心图像c);可能被压缩或未压缩
  • 一个IEND图像结束区块

有关更多详细信息,请访问WikipediaW3网站或通过Google搜索。

限制条件

  • 您不得使用任何用于处理任何类型图像的图像库或功能。
  • 您的程序必须在3分钟内运行,并为所有输入输出10 MB以下的文件(完整性检查)。
  • 这是,因此最短的代码(以字节为单位)将获胜!

您说该文件可能是完全未压缩的,但是对于所有输入,该文件必须小于30kB。一个999x999文件已超过30720个像素,因此这似乎是自相矛盾的。
彼得·泰勒

@PeterTaylor Hm,出于某种原因,我认为30 KB绰绰有余。不知道我在想什么...已编辑。(我只是说过您可以使用压缩或不使用压缩;无论您愿意使用。)
门把手

宽度:4个字节高度:4个字节位深度:1个字节颜色类型:1个字节压缩方法:1个字节过滤方法:1个字节隔行扫描方法:1个字节
Technosaurus

@technosaurus ...嗯,什么?
门把手

1
您的示例不正确:位深度:8(RRGGBBAA)。位深度8是(RGBA)不是(RRGGBBAA)。
Glenn Randers-Pehrson,2014年

Answers:


6

佩尔(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

大小为180字节,-p需要选项(+1)。分数是181。

参数通过STDIN在一行中给出,用空格隔开,颜色为十六进制值(16个字符),宽度/高度的像素数,例如:

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

yellow200.png

文件大小为832字节。具有相同颜色的最大尺寸的图像(n = 999)具有6834字节(低于10 MB)。

该解决方案使用两个库:

  • use Digest::CRC crc32; 块末尾的CRC32值。
  • use IO::Compress::Deflate deflate; 压缩图像数据。

这两个库都与图像无关。

取消高尔夫:

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

编辑

  • use IO::Compress::Deflate':all';被替换use Compress::Zlib;compress默认情况下,后者确实导出deflate函数。该函数不需要引用作为参数,也可以直接返回结果。这样可以摆脱variable $o

感谢Michael的回答

  • 函数kpack可以通过使用函数中Na*N的第一个模板来删除对pack的调用。

  • packNNCV具有四个值的模板使用NNC3n六个值进行优化。

感谢VadimR对许多技巧的评论

  • use String::CRC32;比短use Digest::CRC crc32;
  • y///c-4比短-4+y///c
  • 现在,扫描线由模板构造,CH*值重复。
  • $i通过使用值引用删除。
  • 裸词而不是字符串的块类型。
  • 现在,通过将STDIN输入行(选项-p)与空格分隔符进行匹配来读取选项/ /。然后第一个选项$`进入,第二个参数进入$'
  • 选项-p也会自动打印$_
  • "\cZ"比短"\x1a"

更好的压缩

如果应用过滤,则可以以代码大小为代价进一步压缩图像数据。

  • 未过滤的文件大小FFFF0FF 200:832字节

  • 过滤器Sub(水平像素差异):560字节

    $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
  • 过滤Sub第一行和Up其余行:590字节

    $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);
  • 未过滤的第一行,然后过滤Up:586字节

    $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);
    
  • Compress::Zlib可以调整;可以通过功能compress中的压缩级别的附加选项来设置最高压缩级别,但需要两个字节:

    compress ..., 9;

    yellow200.png未经过滤的示例文件大小从832字节减少到472字节。应用于带Sub过滤器的示例,文件大小从560字节缩小到445字节(pngcrush -brute无法进一步压缩)。


答案很好(一如既往),但是打高尔夫球可以走得更远-我得到202,+ 1 -p。除了对Michael的答案(NA*NNNCV模板)有深刻见解之外,- String::CRC32默认情况下,导出y///c-4是可以的,CH*模板$i\cZ,no,裸词是可以的,-p并将/ /;参数放入匹配前和匹配后。我想知道我是否错过了什么并且分数可以低于200 :)
user2846289 2014年

1
@VadimR:非常感谢您的有用提示。我连高尔夫球进一步利用use Compress::Zlib;并获得200个以下≈10%
海科Oberdiek

5

PHP 214

我不是PHP方面的专家,这里有打高尔夫球的地方。欢迎小费。

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

生成一个PNG文件:

php png.php 20 FFFF00FF > output.png

生成base64流(将结果粘贴到浏览器地址栏中)

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

非高尔夫版本:

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

现在是214,不是吗?而且,无论是高尔夫版本还是非高尔夫版本,我都无法获得正确的图像,但是我没有PHP经验,因此,如果它适用于其他所有人,那是我做错了。
user2846289

1
@VadimR,是的214,您是对的。我已经检查过,生成的图像对我有效。
Michael M.

对我来说(我用PHP 5.4.27.0测试),图像短4个字节-不应将adler-32附加到缩小的数据上吗?IE和Chrome很高兴按原样显示图像,而FF则不然。不同的应用程序对此图像的行为也不同。
user2846289 2014年

4

Python,252个字节

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

该脚本从argv获取输入。从命令行运行此脚本,例如python 27086.py deadbeef 999

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.