#!/usr/bin/python """Sharpen an image using an inverted bidirectional exponential IIR filter. The idea here is basically that we compute a blurred version of the original image, which needs to be small enough to fit in RAM, and subtract a dimmed version of it from that original image, then rescale. This only works on P6 RGB PPM files like the ones the GIMP saves. It does seem to be working but I'm not sure the effect is what I'd call "sharpening". But it's probably about all I can do in an hour. """ import sys def read_ppm(infile): p6 = gets(infile) assert p6 == "P6\n" w, h = map(int, gets(infile).split()) assert gets(infile) == "255\n" def pixels(): for i, pix in enumerate(iter(lambda: infile.read(3), '')): yield tuple(map(ord, pix)) assert i == w * h - 1 return w, h, pixels() def gets(ppm_file): line = ppm_file.readline() while line.startswith("#"): line = ppm_file.readline() return line def write_ppm(outfile, w, h, pixels): outfile.write("P6\n%d %d\n255\n" % (w, h)) outfile.writelines("%c%c%c" % pix for pix in pixels) def debug(x): sys.stderr.write("%r\n" % (x,)) return x class MemoryImage: def __init__(self, w, h, pixels): self.w, self.h = w, h pixels = iter(pixels) self.pixels = [] for yy in range(h): row = [] self.pixels.append(row) for xx in range(w): row.append(pixels.next()) def iterpixels(self): for row in self.pixels: for pix in row: yield pix def whpixels(self): return self.w, self.h, self.iterpixels() def clone(self): return MemoryImage(*self.whpixels()) def __imul__(self, scalar): self.elementwise_modify(lambda pix: pix_mul(pix, scalar)) return self def __mul__(self, scalar): rv = self.clone() rv *= scalar return rv def __add__(self, other): assert (self.w, self.h) == (other.w, other.h) mypixels = self.iterpixels() otherpixels = other.iterpixels() pixels = (pix_add(a, b) for a, b in zip(mypixels, otherpixels)) return MemoryImage(self.w, self.h, pixels) def __sub__(self, other): return self + other * -1 def intify(self): self.elementwise_modify(lambda (r,g,b): (intify(r), intify(g), intify(b))) def elementwise_modify(self, f): for row in self.pixels: for i, pix in enumerate(row): row[i] = f(pix) def blur(self, weight): self.blur_right(weight) self.blur_left(weight) self.blur_down(weight) self.blur_up(weight) def blur_right(self, weight): for i, row in enumerate(self.pixels): self.pixels[i] = list(exponential_filter(row, weight, pix_mul, pix_add)) def blur_left(self, weight): for i, row in enumerate(self.pixels): self.pixels[i] = list(exponential_filter(reversed(row), weight, pix_mul, pix_add)) self.pixels[i].reverse() def blur_down(self, weight): for x in range(self.w): col = exponential_filter((row[x] for row in self.pixels), weight, pix_mul, pix_add) for pix, row in zip(col, self.pixels): row[x] = pix def blur_up(self, weight): for x in range(self.w): backrows = (row[x] for row in reversed(self.pixels)) col = exponential_filter(backrows, weight, pix_mul, pix_add) for pix, row in zip(col, reversed(self.pixels)): row[x] = pix def autostretch(self): r_floor = min(r for r,g,b in self.iterpixels()) g_floor = min(g for r,g,b in self.iterpixels()) b_floor = min(b for r,g,b in self.iterpixels()) r_ceil = float(max(r for r,g,b in self.iterpixels())) g_ceil = float(max(g for r,g,b in self.iterpixels())) b_ceil = float(max(b for r,g,b in self.iterpixels())) r_factor = 255 / (r_ceil - r_floor) g_factor = 255 / (g_ceil - g_floor) b_factor = 255 / (b_ceil - b_floor) self.elementwise_modify(lambda (r,g,b): ( r_factor * (r - r_floor), g_factor * (g - g_floor), b_factor * (b - b_floor), )) def intify(x): return min(255, max(0, int(round(x)))) def exponential_filter(seq, weight, mul, add): seq = iter(seq) acc = seq.next() yield acc for item in seq: acc = add(mul(acc, 1 - weight), mul(item, weight)) yield acc def pix_mul((r, g, b), weight): return r*weight, g*weight, b*weight def pix_add((r1, g1, b1), (r2, g2, b2)): return r1+r2, g1+g2, b1+b2 def main(): image = MemoryImage(*read_ppm(sys.stdin)) blurred = image.clone() blurred.blur(0.02) image -= blurred * 0.95 image.autostretch() image.intify() write_ppm(sys.stdout, *image.whpixels()) if __name__ == '__main__': main()