Advertisement
creamygoat

fpicencode.py

Jan 29th, 2013
297
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 7.75 KB | None | 0 0
  1. #!/usr/bin/python
  2.  
  3. '''Encodes a file into a paletted image.
  4.  
  5. NAME
  6.  fpicencode
  7.  
  8. SYNOPSIS
  9.  fpicencode [-z] [-m sourceimage] sourcefile [outputfile]
  10.  
  11. DESCRIPTION
  12.  The fpicencode script encodes an arbtrary file as a roughly square
  13.  256-colour image.
  14.  
  15.  Options:
  16.  
  17.  -z        Store the file compressed, if the image size will be reduced
  18.            as a result.
  19.  
  20.  -m image  Use an image to be modulated by the data at the cost of
  21.            halving the data density.
  22.  
  23.  This script can be found on Pastebin.
  24.  Decoder: http://pastebin.com/YKSSX9n7
  25.  Encoder: http://pastebin.com/mysKcugD
  26.  
  27. '''
  28.  
  29. import sys
  30. import os.path
  31. from math import *
  32. from PIL import Image
  33. import zlib
  34.  
  35. gamma = 2.2
  36.  
  37. class Error (Exception):
  38.   pass
  39.  
  40. def vlengthsquared(a):
  41.   return sum(x * x for x in a)
  42.  
  43. def vlength(a):
  44.   return sqrt(vlengthsquared(a))
  45.  
  46. def vneg(a):
  47.   return tuple(-x for x in a)
  48.  
  49. def vsum(*vectorargs):
  50.   if len(vectorargs) == 1:
  51.     vectors = vectorargs[0]
  52.   else:
  53.     vectors = vectorargs
  54.   r = tuple(vectors[0])
  55.   for i in range(1, len(vectors)):
  56.     r = tuple(a + b for a, b in zip(r, vectors[i]))
  57.   return r
  58.  
  59. def vdiff(a, b):
  60.   return tuple(x - y for x, y in zip(a, b))
  61.  
  62. def vscaled(a, scale):
  63.   return tuple(x * scale for x in a)
  64.  
  65. def vlerp(a, b, t):
  66.   return vsum(
  67.     vscaled(a, 1.0 - t),
  68.     vscaled(b, t)
  69.   )
  70.  
  71. def fabulouspalette():
  72.   anchors = [
  73.     # Cube (first half)
  74.     (0, 0, 0), (0, 0, 254), (254, 0, 0), (254, 0, 254),
  75.     # Octahedron
  76.     (127, 127, 0), (127, 0, 127), (0, 127, 127),
  77.     (127, 127, 254), (127, 254, 127), (254, 127, 127),
  78.     # Centre
  79.     (127, 127, 127),
  80.     # Dark grey
  81.     (63, 63, 63),
  82.     # Cube (last half)
  83.     (0, 254, 0), (0, 254, 254), (254, 254, 0), (254, 254, 254)
  84.   ]
  85.   anomalousexcursions = {
  86.     (-1, -1, -1): (1, 1, 2),
  87.     (-1, -1, 256): (1, 1, 253),
  88.     (256, -1, -1): (254, 1, 2),
  89.     (256, -1, 256): (254, 1, 253),
  90.     (-1, 256, -1): (1, 254, 2),
  91.     (-1, 256, 256): (1, 254, 253),
  92.     (256, 256, -1): (254, 254, 2),
  93.     (256, 256, 256): (254, 254, 253),
  94.   }
  95.   p = [0] * (3 * 256)
  96.   for maincolix in range(16):
  97.     a = anchors[maincolix]
  98.     for lix in range(2):
  99.       lo = -lix
  100.       ls = 1 + 2 * lix
  101.       for gix in range(2):
  102.         for rix in range(2):
  103.           for bix in range(2):
  104.             am = (rix, gix, bix)
  105.             ucol = tuple(a[i] + lo + ls * am[i] for i in range(3))
  106.             if ucol in anomalousexcursions:
  107.               ucol = anomalousexcursions[ucol]
  108.             col = [max(0, min(255, uc)) for uc in ucol]
  109.             subcolix = (gix << 3) + (rix << 2) + (bix << 1) + lix
  110.             pix = 3 * ((maincolix << 4) + subcolix)
  111.             p[pix : pix + 3] = col
  112.   return p
  113.  
  114. def gammac(level):
  115.   return pow(level / 255.0, gamma)
  116.  
  117. def rup2(x):
  118.   return (x + 1) & ~1
  119.  
  120. def chunksizebytes(datasize):
  121.   r = bytearray(4)
  122.   for i in range(4):
  123.     r[3 - i] = (datasize >> (8 * i)) & 255
  124.   return r
  125.  
  126. def chunkidbytes(chunkid):
  127.   r = bytearray(4)
  128.   for i in range(4):
  129.     r[i] = ord(chunkid[i])
  130.   return r
  131.  
  132. def chunkhdr(chunkid, datasize):
  133.   return chunkidbytes(chunkid) + chunksizebytes(datasize)
  134.  
  135. def newchunk(chunkid, data):
  136.   r = chunkhdr(chunkid, len(data)) + data
  137.   if len(data) & 1:
  138.     a = bytearray(1)
  139.     a[0] = 0
  140.     r += a
  141.   return r
  142.  
  143. def chunksize(chunkhdr):
  144.   r = 0
  145.   for i in range(4):
  146.     r += (chunkhdr[7 - i] & 0x00FF) << (8 * i)
  147.   r = 4 + rup2(r)
  148.   return r
  149.  
  150. def main():
  151.  
  152.   infname = None
  153.   picfname = None
  154.   outfname = 'encoded.png'
  155.   dozip = False
  156.   iszipped = False
  157.   domodulate = False
  158.   fname = infname
  159.   rc = 0
  160.   args = list(sys.argv)
  161.   cn = args[0]
  162.   opts = []
  163.   params = []
  164.   argix = 1
  165.   while argix < len(args):
  166.     arg = args[argix]
  167.     nextarg = args[argix + 1] if argix + 1 < len(args) else None
  168.     if len(params) == 0 and len(arg) >= 2 and arg[0] == '-':
  169.       opt = arg[:2]
  170.       hassep = len(arg) >= 3 and arg[2] in ':='
  171.       if opt == '-z':
  172.         opts.append((opt, None))
  173.       elif opt == '-m':
  174.         if hassep:
  175.           opts.append((opt, arg[3:]))
  176.         else:
  177.           opts.append((opt, nextarg))
  178.           argix += 1
  179.       else:
  180.         opts.append(arg)
  181.     else:
  182.       params.append(arg)
  183.     argix += 1
  184.   for opt, value in opts:
  185.     if opt == '-z':
  186.       dozip = True
  187.     elif opt == '-m':
  188.       picfname = value
  189.       domodulate = picfname is not None and picfname != ''
  190.   if len(params) >= 1:
  191.     infname = params[0]
  192.     params = params[1:]
  193.   if len(params) >= 1:
  194.     outfname = params[0]
  195.     params = params[1:]
  196.  
  197.   if infname is None or infname == '':
  198.     print cn + ': A source filename is expected.'
  199.     rc = 1
  200.   elif len(params) > 0:
  201.     print cn + ': Too many parameters.'
  202.     rc = 1
  203.  
  204.   if rc:
  205.     return rc
  206.  
  207.   fname = os.path.split(infname)[1]
  208.   fdata = None
  209.   fh = open(infname, 'rb')
  210.   try:
  211.     rc = 2
  212.     fdata = bytearray(fh.read())
  213.     rc = 0
  214.   finally:
  215.     fh.close()
  216.   if rc:
  217.     return rc
  218.  
  219.   cdata = fdata
  220.  
  221.   if dozip:
  222.     zdata = zlib.compress(str(cdata), 9)
  223.     if len(zdata) < len(cdata):
  224.       cdata = bytearray(zdata)
  225.       iszipped = True
  226.  
  227.   hdr = bytearray([0] * 20)
  228.   hdr[0] = 1 if iszipped else 0
  229.  
  230.   ct = chunkidbytes('Raw ')
  231.   hc = newchunk('RDHD', hdr)
  232.   nc = newchunk('NAME', bytearray(fname + chr(0)))
  233.   dc = newchunk('DATA', cdata)
  234.   iffdata = ct + hc + nc + dc
  235.   iffdatasize = len(iffdata)
  236.  
  237.   edata = chunkidbytes('FORM') + chunksizebytes(iffdatasize) +  iffdata;
  238.  
  239.   #fh = open('data.iff', 'wb')
  240.   #try:
  241.   #  fh.write(str(edata))
  242.   #finally:
  243.   #  fh.close()
  244.  
  245.   mdata = edata
  246.  
  247.   if domodulate:
  248.     mdata = bytearray(2 * len(edata))
  249.     for i in range(len(edata)):
  250.       e = edata[i]
  251.       mdata[2 * i] = (e >> 4) & 15
  252.       mdata[2 * i + 1] = e & 15
  253.  
  254.   imgfmt = 'P'
  255.   palette = fabulouspalette()
  256.  
  257.   reqpix = len(mdata)
  258.   w = 8 * int(round(sqrt(reqpix * 4.0/3.0) / 8.0))
  259.   h = w * 3 // 4
  260.   while w * h < reqpix:
  261.     w += 8
  262.     h = w * 3 // 4
  263.   im = Image.new(imgfmt, (w, h))
  264.   im.putpalette(palette)
  265.  
  266.   pic = None
  267.   pixrem = w * h;
  268.   x = 0
  269.   y = 0
  270.   mix = 0
  271.  
  272.   if domodulate:
  273.     pic = Image.open(picfname).convert('RGB').resize((w, h), Image.CUBIC)
  274.     gcpalette = tuple(gammac(c) for c in palette)
  275.     errshares = [rw / 16.0 for rw in [7, 3, 5, 1]]
  276.     fwderr = None
  277.     rowerrs = None
  278.     nextrowerrs = [(0.0, 0.0, 0.0)] * (w + 2)
  279.     while pixrem:
  280.       if x == 0:
  281.         fwderr = (0.0, 0.0, 0.0)
  282.         rowerrs = nextrowerrs
  283.         nextrowerrs = [(0.0, 0.0, 0.0)] * (w + 2)
  284.       m = mdata[mix]
  285.       nlsrcpx = pic.getpixel((x, y))
  286.       srcpx = tuple(gammac(c) for c in nlsrcpx)
  287.       idealcol = vdiff(srcpx, vsum(fwderr, rowerrs[1 + x]))
  288.       bestcix = i << 4
  289.       beste2 = 1e9
  290.       for i in range(16):
  291.         cix = (i << 4) + (m & 15)
  292.         col = gcpalette[3 * cix : 3 * cix + 3]
  293.         e2 = vlengthsquared(vdiff(col, idealcol))
  294.         if e2 < beste2:
  295.           bestcix = cix
  296.           beste2 = e2
  297.       cix = bestcix
  298.       col = gcpalette[3 * cix : 3 * cix + 3]
  299.       im.putpixel((x, y), cix)
  300.       err = vdiff(col, idealcol)
  301.       fwderr = vscaled(err, errshares[0])
  302.       nextrowerrs[x] = vsum(nextrowerrs[x], vscaled(err, errshares[1]))
  303.       nextrowerrs[x + 1] = vsum(nextrowerrs[x + 1], vscaled(err, errshares[2]))
  304.       nextrowerrs[x + 2] = vsum(nextrowerrs[x + 2], vscaled(err, errshares[3]))
  305.       mix = (mix + 1) % len(mdata)
  306.       x = (x + 1) % w
  307.       y += 0 if x else 1
  308.       pixrem -= 1
  309.   else:
  310.     while pixrem:
  311.       px = mdata[mix]
  312.       im.putpixel((x, y), px)
  313.       mix = (mix + 1) % len(mdata)
  314.       x = (x + 1) % w
  315.       y += 0 if x else 1
  316.       pixrem -= 1
  317.  
  318.   ext = os.path.splitext(outfname)[1]
  319.   fmt = 'GIF' if ext in ['.gif', '.GIF'] else 'PNG'
  320.  
  321.   im.save(outfname, fmt)
  322.  
  323.   return rc
  324.  
  325. if __name__ == '__main__':
  326.   sys.exit(main())
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement