Advertisement
Guest User

slow floodfill

a guest
Jan 5th, 2015
217
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 5.68 KB | None | 0 0
  1. #!/usr/bin/env python
  2.  
  3. # slow floodfill for /tg/
  4.  
  5. from __future__ import print_function
  6. from PIL import Image, GifImagePlugin
  7. import sys
  8. import struct
  9. import subprocess
  10. import optparse
  11.  
  12. # hard-coded gif color indexes -- change these to suit the image, or vice-versa
  13. #
  14. # floodfilling will start from FILL pixels and fill EMPTY pixels if you don't
  15. # provide a starting coordinate
  16. # otherwise, just set FILL, FILLEDGE, and TRANSPARENT to something unoccupied
  17. WALL=0
  18. EMPTY=1
  19. INVISIBLEWALL=2
  20. FILL=3
  21. FILLEDGE=4
  22. TRANSPARENT=255
  23.  
  24. def neighbors(pixel):
  25.    x, y = pixel
  26.    return [(x-1,y), (x+1, y), (x, y-1), (x, y+1)]
  27.  
  28. def moore_neighbors(pixel):
  29.    x, y = pixel
  30.    return [(x-1,y-1), (x, y-1), (x+1, y-1), (x-1, y), (x+1, y), (x-1, y+1), (x, y+1), (x+1, y+1)]
  31.  
  32. def fillpixel(unfilled, pixel, neighborfn):
  33.    nextfilled = set()
  34.    for neighbor in neighborfn(pixel):
  35.       if neighbor in unfilled:
  36.          nextfilled.add(neighbor)
  37.    return nextfilled
  38.  
  39. def allneighbors(unfilled, tofill, neighborfn):
  40.    nextfilled = set()
  41.    for pixel in tofill:
  42.       neighbors = fillpixel(unfilled, pixel, neighborfn)
  43.       unfilled.difference_update(neighbors)
  44.       nextfilled.update(neighbors)
  45.    return nextfilled
  46.  
  47. def fillpixels(img, unfilled, tofill, neighborfn, frames=1):
  48.    nextfilled = set()
  49.    for pixel in tofill:
  50.       try:
  51.          img.putpixel(pixel, FILL)
  52.       except IndexError:
  53.          pass
  54.    for frame in range(frames):
  55.       tofill = allneighbors(unfilled, tofill, neighborfn)
  56.       nextfilled.update(tofill)
  57.    for pixel in nextfilled:
  58.       img.putpixel(pixel, FILLEDGE)
  59.    return nextfilled
  60.  
  61. def setpalette(img):
  62.    palette = img.getpalette()
  63.    palette[FILLEDGE*3:FILLEDGE*3+3] = [0x00, 0x00, 0xff]
  64.    palette[FILL*3:FILL*3+3] = [0xff, 0x00, 0x00]
  65.    palette[INVISIBLEWALL*3:INVISIBLEWALL*3+3] = palette[WALL*3:WALL*3+3]
  66.    img.putpalette(palette)
  67.    img.info["transparency"] = TRANSPARENT
  68.  
  69. def getcolorpixels(img):
  70.    colors = {}
  71.    maxx, maxy = img.size
  72.    for y in range(maxy):
  73.       for x in range(maxx):
  74.          pixel = (x,y)
  75.          color = img.getpixel(pixel)
  76.          colors.setdefault(color, set()).add(pixel)
  77.    return colors
  78.  
  79. def clearimg(img, pixel):
  80.    maxx, maxy = img.size
  81.    img.putdata([pixel for x in range(maxx * maxy)])
  82.  
  83. def floodfill_frames(img, neighborfn, startcoord=None, framestep=1):
  84.    maxx, maxy = img.size
  85.    emptyframe = img.copy()
  86.    clearimg(emptyframe, TRANSPARENT)
  87.    colors = getcolorpixels(img)
  88.    if startcoord == None:
  89.       unfilled = colors[EMPTY]
  90.       nextfilled = allneighbors(unfilled, colors[FILL], neighborfn)
  91.    else:
  92.       startx = min(maxx, max(0, startcoord[0]))
  93.       starty = min(maxy, max(0, startcoord[1]))
  94.       startcolor = img.getpixel((startx, starty))
  95.       unfilled = colors[startcolor]
  96.       nextfilled = set([startcoord])
  97.    count = 1
  98.    total = len(unfilled)
  99.    while True:
  100.       print("frame {}: {}/{} pixels".format(count, total - len(unfilled), total))
  101.       frame = emptyframe.copy()
  102.       oldfilled = nextfilled
  103.       nextfilled = fillpixels(frame, unfilled, nextfilled, neighborfn, framestep)
  104.       if len(oldfilled) > 0:
  105.          xlist, ylist = zip(*list(oldfilled.union(nextfilled)))
  106.          bbox = [
  107.             max(0, min(xlist)),
  108.             max(0, min(ylist)),
  109.             min(max(xlist) + 1, maxx),
  110.             min(max(ylist) + 1, maxy)
  111.          ]
  112.          print(bbox)
  113.          yield (frame, bbox)
  114.       else:
  115.          return
  116.       count += 1
  117.  
  118. # return a Graphics Control Extension per-frame header
  119. def getgce():
  120.    header = 0xf921 # technically two fields, 0x21 and 0xf9, in that order
  121.    blocksize = 4
  122.    disposal = 1 # don't dispose
  123.    flag_userinput = 0
  124.    flag_transparency = 1
  125.    disposal_byte = disposal << 2 | flag_userinput << 1 | flag_transparency
  126.    delay = 5 # 5ms between frames, most browsers won't go this low.
  127.    transparent_idx = TRANSPARENT
  128.    return struct.pack("<HBBHBB", header, blocksize, disposal_byte, delay, transparent_idx, 0)
  129.  
  130. def writeframes(img, frames, fp):
  131.    gce = getgce()
  132.    for s in GifImagePlugin.getheader(img)[0] + [gce] + GifImagePlugin.getdata(img):
  133.       fp.write(s)
  134.    for frame, bbox in frames:
  135.       fp.write(gce)
  136.       for s in GifImagePlugin.getdata(frame.crop(bbox), offset = bbox[:2]):
  137.          fp.write(s)
  138.    fp.write(";")
  139.  
  140. if __name__ == "__main__":
  141.    parser = optparse.OptionParser()
  142.    parser.add_option("-i", "--in", dest="infilename", default="img.gif",
  143.       help="input filename (default img.gif)")
  144.    parser.add_option("-o", "--out", dest="outfilename", default="out.gif",
  145.       help="output filename (default out.gif)")
  146.    parser.add_option("-s", "--start-coord", dest="startcoord", default=None,
  147.       type="int", nargs=2,
  148.       help="starting coordinate, in pixels from the upper-left-hand corner")
  149.    parser.add_option("-d", "--diagonal", dest="diagonal", default=False,
  150.       action="store_true", help="flood-fill diagonally")
  151.    parser.add_option("-f", "--framestep", dest="framestep", default=1,
  152.       type="int", help="number of fill steps per frame (default 1)")
  153.    options, args = parser.parse_args()
  154.    startcoord = None if options.startcoord is None else tuple(options.startcoord)
  155.    neighborfn = moore_neighbors if options.diagonal else neighbors
  156.    
  157.    img = Image.open(options.infilename)
  158.    img.load()
  159.    setpalette(img)
  160.  
  161.    with open(options.outfilename, "wb") as fp:
  162.       gifsicle = subprocess.Popen(["gifsicle", "-O3"], stdin=subprocess.PIPE, stdout=fp)
  163.       frames = floodfill_frames(img, neighborfn, startcoord, options.framestep)
  164.       writeframes(img, frames, gifsicle.stdin)
  165.       print("Optimizing...")
  166.       gifsicle.stdin.flush()
  167.       gifsicle.stdin.close()
  168.       gifsicle.wait()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement