Python で png 画像を自力で生成する

Ruby で png 画像を自力で生成する - まめめもPython に移植しただけw

# -*- coding: utf-8 -*-
from sys import stdout
from struct import pack
from zlib import crc32, compress

width, height = 100, 20
depth, color_type = 8, 2

# グラデーションのベタデータ
line = [[x * 255 / width, 0, 0] for x in range(width)]
raw_data = [line] * height

# チャンクのバイト列生成関数
def chunk(type, data):
  return pack('!I4s%dsi' % len(data), len(data), type, data, crc32(type + data))

# ファイルシグニチャ
stdout.write('\x89PNG\r\n\x1a\n')

# ヘッダ
stdout.write(chunk('IHDR', pack("!IIBBBBB", width, height, depth, color_type, 0, 0, 0)))

# 画像データ
img_data = ''.join(pack("%dB" % (width * 3 + 1), *sum([[0]] + line, [])) for line in raw_data)
stdout.write(chunk("IDAT", compress(img_data)))

# 終端
stdout.write(chunk('IEND', ''))

* が使える Ruby の pack が羨ましいです><

# -*- coding: utf-8 -*-

# http://www.physics.sfasu.edu/astro/color/spectra.html
def wavelength_to_rgb(n):
  if   n < 380: rgb = [0.0               , 0.0               , 0.0               ]
  elif n < 440: rgb = [(440.0 - n) / 60.0, 0.0               , 1.0               ]
  elif n < 490: rgb = [0.0               , (n - 440.0) / 50.0, 1.0               ]
  elif n < 510: rgb = [0.0               , 1.0               , (510.0 - n) / 20.0]
  elif n < 580: rgb = [(n - 510.0) / 70.0, 1.0               , 0.0               ]
  elif n < 645: rgb = [1.0               , (645 - n) / 65.0  , 0.0               ]
  elif n < 780: rgb = [1.0               , 0.0               , 0.0               ]
  else:         rgb = [0.0               , 0.0               , 0.0               ]

  if   n < 380: factor = 0.0
  elif n < 420: factor = 0.3 + 0.7 * (n - 380.0) / 40.0
  elif n < 700: factor = 1.0
  elif n < 780: factor = 0.3 + 0.7 * (780.0 - n) / 80.0
  else:         factor = 0.0

  return map(lambda c: 0 if c == 0.0 else 255 * ((c * factor) ** 0.8), rgb)

# generate png

from sys import stdout
from struct import pack
from zlib import crc32, compress

width, height = 100, 20
depth, color_type = 8, 2

line = map(lambda x: wavelength_to_rgb(380 + x * 400 / width), range(width))
raw_data = [line] * height

def chunk(type, data):
  return pack('!I4s%dsi' % len(data), len(data), type, data, crc32(type + data))

stdout.write('\x89PNG\r\n\x1a\n')
stdout.write(chunk('IHDR', pack("!IIBBBBB", width, height, depth, color_type, 0, 0, 0)))
img_data = ''.join(pack("%dB" % (width * 3 + 1), *sum([[0]] + line, [])) for line in raw_data)
stdout.write(chunk("IDAT", compress(img_data)))
stdout.write(chunk('IEND', ''))

さすがに芸が無いかなと思ったので、Sub フィルタを突っ込んでみたw

# -*- coding: utf-8 -*-
from sys import stdout
from struct import pack
from zlib import crc32, compress

width, height = 100, 20
depth, color_type = 8, 2

line = [[x * 255 / width, 0, 0] for x in range(width)]
# Sub フィルタを適用
for i in range(width - 1, 0, -1):
  line[i][0] = (line[i][0] - line[i - 1][0] + 255) % 255
raw_data = [line] * height

def chunk(type, data):
  return pack('!I4s%dsi' % len(data), len(data), type, data, crc32(type + data))

stdout.write('\x89PNG\r\n\x1a\n')
stdout.write(chunk('IHDR', pack("!IIBBBBB", width, height, depth, color_type, 0, 0, 0)))
img_data = ''.join(pack("%dB" % (width * 3 + 1), *sum([[1]] + line, [])) for line in raw_data)
stdout.write(chunk("IDAT", compress(img_data)))
stdout.write(chunk('IEND', ''))