| 1 | #!/usr/bin/env python |
|---|
| 2 | |
|---|
| 3 | import math, gd, random, sys, os |
|---|
| 4 | |
|---|
| 5 | # Select which chapters to run |
|---|
| 6 | def chapter(n): |
|---|
| 7 | if len(sys.argv) == 1: |
|---|
| 8 | return True |
|---|
| 9 | return str(n) in sys.argv |
|---|
| 10 | |
|---|
| 11 | ############################################################################## |
|---|
| 12 | |
|---|
| 13 | # Tiny image class to make examples short and readable |
|---|
| 14 | class Image(gd.image): |
|---|
| 15 | gd.gdMaxColors = 256 * 256 * 256 |
|---|
| 16 | def __init__(self, *args): |
|---|
| 17 | if args[0].__class__ == str: |
|---|
| 18 | print "[LOAD] %s" % (args[0],) |
|---|
| 19 | gd.image.__init__(self, *args) |
|---|
| 20 | def save(self, name): |
|---|
| 21 | print "[PNG] %s" % (name,) |
|---|
| 22 | self.writePng(name) |
|---|
| 23 | def getGray(self, x, y): |
|---|
| 24 | p = self.getPixel((x, y)) |
|---|
| 25 | c = self.colorComponents(p)[0] / 255.0 |
|---|
| 26 | return c |
|---|
| 27 | def getRgb(self, x, y): |
|---|
| 28 | p = self.getPixel((x, y)) |
|---|
| 29 | rgb = self.colorComponents(p) |
|---|
| 30 | return [rgb[0] / 255.0, rgb[1] / 255.0, rgb[2] / 255.0] |
|---|
| 31 | def setGray(self, x, y, t): |
|---|
| 32 | p = (int)(t * 255.999) |
|---|
| 33 | c = self.colorResolve((p, p, p)) |
|---|
| 34 | self.setPixel((x, y), c) |
|---|
| 35 | def setRgb(self, x, y, r, g, b): |
|---|
| 36 | r = (int)(r * 255.999) |
|---|
| 37 | g = (int)(g * 255.999) |
|---|
| 38 | b = (int)(b * 255.999) |
|---|
| 39 | c = self.colorResolve((r, g, b)) |
|---|
| 40 | self.setPixel((x, y), c) |
|---|
| 41 | def getRegion(self, x, y, w, h): |
|---|
| 42 | dest = Image((w, h), True) |
|---|
| 43 | self.copyTo(dest, (-x, -y)) |
|---|
| 44 | return dest |
|---|
| 45 | def getZoom(self, z): |
|---|
| 46 | (w, h) = self.size() |
|---|
| 47 | dest = Image((w * z, h * z), True) |
|---|
| 48 | for y in range(h): |
|---|
| 49 | for x in range(w): |
|---|
| 50 | rgb = self.getRgb(x, y) |
|---|
| 51 | for j in range(z): |
|---|
| 52 | for i in range(z): |
|---|
| 53 | dest.setRgb(x * z + i, y * z + j, *rgb) |
|---|
| 54 | return dest |
|---|
| 55 | |
|---|
| 56 | # Manipulate gamma values |
|---|
| 57 | class Gamma: |
|---|
| 58 | def CtoI(x): |
|---|
| 59 | if x < 0: |
|---|
| 60 | return - math.pow(-x, 2.2) |
|---|
| 61 | return math.pow(x, 2.2) |
|---|
| 62 | def ItoC(x): |
|---|
| 63 | if x < 0: |
|---|
| 64 | return - math.pow(-x, 1 / 2.2) |
|---|
| 65 | return math.pow(x, 1 / 2.2) |
|---|
| 66 | CtoI = staticmethod(CtoI) |
|---|
| 67 | ItoC = staticmethod(ItoC) |
|---|
| 68 | def Cto2(x): |
|---|
| 69 | if x < Gamma.CtoI(0.50): |
|---|
| 70 | return 0. |
|---|
| 71 | return 1. |
|---|
| 72 | def Cto3(x): |
|---|
| 73 | if x < Gamma.CtoI(0.25): |
|---|
| 74 | return 0. |
|---|
| 75 | elif x < Gamma.CtoI(0.75): |
|---|
| 76 | return Gamma.CtoI(0.5) |
|---|
| 77 | return 1. |
|---|
| 78 | def Cto4(x): |
|---|
| 79 | if x < Gamma.CtoI(0.17): |
|---|
| 80 | return 0. |
|---|
| 81 | elif x < Gamma.CtoI(0.50): |
|---|
| 82 | return Gamma.CtoI(0.3333) |
|---|
| 83 | elif x < Gamma.CtoI(0.83): |
|---|
| 84 | return Gamma.CtoI(0.6666) |
|---|
| 85 | return 1. |
|---|
| 86 | Cto2 = staticmethod(Cto2) |
|---|
| 87 | Cto3 = staticmethod(Cto3) |
|---|
| 88 | Cto4 = staticmethod(Cto4) |
|---|
| 89 | |
|---|
| 90 | # Create matrices |
|---|
| 91 | def Matrix(w, h, val = 0): |
|---|
| 92 | return [[val] * w for n in range(h)] |
|---|
| 93 | |
|---|
| 94 | ############################################################################## |
|---|
| 95 | |
|---|
| 96 | # Create SVG files from matrix data |
|---|
| 97 | class Svg: |
|---|
| 98 | _data = '' |
|---|
| 99 | _w, _h = 0, 0 |
|---|
| 100 | def _colorise(val): |
|---|
| 101 | return '#fff' |
|---|
| 102 | def _reduce(this, val): |
|---|
| 103 | if type(val) == float: |
|---|
| 104 | for x in range(1, 1000): |
|---|
| 105 | if abs(val * x - round(val * x)) < 0.001: |
|---|
| 106 | return (int)(round(val * x)), x |
|---|
| 107 | return val, 1 |
|---|
| 108 | def __init__(self, mat, colorise = _colorise): |
|---|
| 109 | # Check whether it is an error diffusion matrix |
|---|
| 110 | ed = False |
|---|
| 111 | for l in mat: |
|---|
| 112 | for x in l: |
|---|
| 113 | if type(x) == str or math.floor(x) != x: |
|---|
| 114 | ed = True |
|---|
| 115 | # Generate SVG file |
|---|
| 116 | (w, h) = (len(mat[0]), len(mat)) |
|---|
| 117 | s = \ |
|---|
| 118 | '<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n' \ |
|---|
| 119 | '<svg\n' \ |
|---|
| 120 | ' xmlns="http://www.w3.org/2000/svg"\n' \ |
|---|
| 121 | ' xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"\n' \ |
|---|
| 122 | ' xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"\n' \ |
|---|
| 123 | ' width="%u"\n' \ |
|---|
| 124 | ' height="%u">\n' \ |
|---|
| 125 | ' <sodipodi:namedview inkscape:document-units="px" />\n' \ |
|---|
| 126 | ' <g>\n' % (64 * w + 2, 64 * h + 2) |
|---|
| 127 | line = \ |
|---|
| 128 | ' <path sodipodi:nodetypes="cc" d="M %u,%u L %u,%u" ' \ |
|---|
| 129 | 'style="stroke:#000;stroke-width:1" />\n' |
|---|
| 130 | box = \ |
|---|
| 131 | ' <path sodipodi:nodetypes="cccc" ' \ |
|---|
| 132 | 'd="M %u,%u L %u,%u L %u,%u L %u,%u z" ' \ |
|---|
| 133 | 'style="fill:%s;stroke:#000;stroke-width:2" />\n' |
|---|
| 134 | text = \ |
|---|
| 135 | ' <text style="font-size:%upx;text-align:center;text-anchor:middle;' \ |
|---|
| 136 | 'font-family:Bitstream Vera Serif" x="%u" y="%u"%s>%s</text>\n' |
|---|
| 137 | for y in range(h): |
|---|
| 138 | for x in range(w): |
|---|
| 139 | val = mat[y][x] |
|---|
| 140 | if not ed and val == -1: |
|---|
| 141 | continue |
|---|
| 142 | if ed and val == 0: |
|---|
| 143 | continue |
|---|
| 144 | # Put box |
|---|
| 145 | (ix, iy) = (64. * x + 1, 64. * y + 1) |
|---|
| 146 | if ed and val == -1: |
|---|
| 147 | val = '' |
|---|
| 148 | c = '#fbb' |
|---|
| 149 | else: |
|---|
| 150 | c = colorise(val) |
|---|
| 151 | s += box % (ix, iy, ix + 64, iy, ix + 64, iy + 64, ix, \ |
|---|
| 152 | iy + 64, c) |
|---|
| 153 | # Put value |
|---|
| 154 | a, b = self._reduce(val) |
|---|
| 155 | extra = '' |
|---|
| 156 | if b == 1: |
|---|
| 157 | (tx, ty) = (ix + 32, iy + 44) |
|---|
| 158 | n = len(str(val)) |
|---|
| 159 | if n > 3 and type(val) == str: |
|---|
| 160 | extra = ' transform="scale(0.7,0.7)"' |
|---|
| 161 | tx = tx / 0.7 |
|---|
| 162 | ty = ty / 0.7 |
|---|
| 163 | elif n > 3: |
|---|
| 164 | extra = ' transform="scale(0.7,1.)"' |
|---|
| 165 | tx = tx / 0.7 |
|---|
| 166 | elif n > 2: |
|---|
| 167 | extra = ' transform="scale(0.8,1.)"' |
|---|
| 168 | tx = tx / 0.8 |
|---|
| 169 | s += text % (32, tx, ty, extra, val) |
|---|
| 170 | else: |
|---|
| 171 | s += line % (ix + 8, iy + 32, ix + 56, iy + 32) |
|---|
| 172 | (tx, ty) = (ix + 32, iy + 26) |
|---|
| 173 | s += text % (24, tx, ty, extra, a) |
|---|
| 174 | (tx, ty) = (ix + 32, iy + 56) |
|---|
| 175 | s += text % (24, tx, ty, extra, b) |
|---|
| 176 | s += \ |
|---|
| 177 | ' </g>\n' \ |
|---|
| 178 | '</svg>\n' |
|---|
| 179 | self._w = 64 * w + 2 |
|---|
| 180 | self._h = 64 * h + 2 |
|---|
| 181 | self._data = s |
|---|
| 182 | def save(self, name, size): |
|---|
| 183 | svgname = name + ".tmp.svg" |
|---|
| 184 | f = open(svgname, 'w') |
|---|
| 185 | f.write(self._data) |
|---|
| 186 | f.close() |
|---|
| 187 | f = os.popen("inkscape %s -a %u:%u:%u:%u -w%u -h%u -e %s >/dev/null 2>&1" % (svgname, 0, 0, self._w, self._h, size * self._w / 64., size * self._h / 64., name)) |
|---|
| 188 | print "[SVG] %s" % (name,) |
|---|
| 189 | f.close() |
|---|
| 190 | os.unlink(svgname) |
|---|
| 191 | |
|---|
| 192 | ############################################################################## |
|---|
| 193 | print "Initialisation" |
|---|
| 194 | |
|---|
| 195 | # Load the original Lenna image |
|---|
| 196 | lenna512 = Image("lenna512.png") |
|---|
| 197 | (w, h) = lenna512.size() |
|---|
| 198 | |
|---|
| 199 | # Image 1: greyscale conversion |
|---|
| 200 | # Read the compression FAQ [55] for the rationale behind using the green |
|---|
| 201 | # channel (http://www.faqs.org/faqs/compression-faq/part1/section-30.html) |
|---|
| 202 | if chapter(0): |
|---|
| 203 | (w, h) = lenna512.size() |
|---|
| 204 | lenna512bw = Image((w, h)) |
|---|
| 205 | for y in range(h): |
|---|
| 206 | for x in range(w): |
|---|
| 207 | rgb = lenna512.getRgb(x, y) |
|---|
| 208 | c = rgb[1] |
|---|
| 209 | lenna512bw.setGray(x, y, c) |
|---|
| 210 | lenna512bw.save("lenna512bw.png") |
|---|
| 211 | else: |
|---|
| 212 | lenna512bw = Image("lenna512bw.png") |
|---|
| 213 | |
|---|
| 214 | # Image 2: 50% greyscale |
|---|
| 215 | # Image 3: 50% scaling |
|---|
| 216 | if chapter(0): |
|---|
| 217 | (w, h) = lenna512.size() |
|---|
| 218 | lenna256bw = Image((w / 2, h / 2)) |
|---|
| 219 | lenna256 = Image((w / 2, h / 2), True) |
|---|
| 220 | for y in range(h / 2): |
|---|
| 221 | for x in range(w / 2): |
|---|
| 222 | r = g = b = 0. |
|---|
| 223 | for j in range(2): |
|---|
| 224 | for i in range(2): |
|---|
| 225 | rgb = lenna512.getRgb(x * 2 + i, y * 2 + j) |
|---|
| 226 | r += Gamma.CtoI(rgb[0]) |
|---|
| 227 | g += Gamma.CtoI(rgb[1]) |
|---|
| 228 | b += Gamma.CtoI(rgb[2]) |
|---|
| 229 | r = Gamma.ItoC(r / 4) |
|---|
| 230 | g = Gamma.ItoC(g / 4) |
|---|
| 231 | b = Gamma.ItoC(b / 4) |
|---|
| 232 | lenna256bw.setGray(x, y, g) |
|---|
| 233 | lenna256.setRgb(x, y, r, g, b) |
|---|
| 234 | lenna256bw.save("lenna256bw.png") |
|---|
| 235 | lenna256.save("lenna256.png") |
|---|
| 236 | else: |
|---|
| 237 | lenna256bw = Image("lenna256bw.png") |
|---|
| 238 | lenna256 = Image("lenna256.png") |
|---|
| 239 | |
|---|
| 240 | # Create a 32x256 greyscale gradient |
|---|
| 241 | if chapter(0): |
|---|
| 242 | grad256bw = Image((32, 256)) |
|---|
| 243 | for y in range(256): |
|---|
| 244 | for x in range(32): |
|---|
| 245 | grad256bw.setGray(x, 255 - y, y / 255.) |
|---|
| 246 | grad256bw.save("gradient256bw.png") |
|---|
| 247 | else: |
|---|
| 248 | grad256bw = Image("gradient256bw.png") |
|---|
| 249 | |
|---|
| 250 | # Create a 64x256 colour gradient |
|---|
| 251 | if chapter(0): |
|---|
| 252 | grad256 = Image((64, 256), True) |
|---|
| 253 | for y in range(255, -1, -1): |
|---|
| 254 | for x in range(64): |
|---|
| 255 | grad256.setRgb(x, y, x / 63., (255. - y) / 255, x / 63.) |
|---|
| 256 | grad256.save("gradient256.png") |
|---|
| 257 | else: |
|---|
| 258 | grad256 = Image("gradient256.png") |
|---|
| 259 | |
|---|
| 260 | ############################################################################## |
|---|
| 261 | if chapter(1): |
|---|
| 262 | print "Chapter 1. Colour quantisation" |
|---|
| 263 | |
|---|
| 264 | # Output 1.1.1: 50% threshold |
|---|
| 265 | # Output 1.1.2: 40% threshold |
|---|
| 266 | # Output 1.1.3: 60% threshold |
|---|
| 267 | def test11x(src, threshold): |
|---|
| 268 | (w, h) = src.size() |
|---|
| 269 | dest = Image((w, h)) |
|---|
| 270 | for y in range(h): |
|---|
| 271 | for x in range(w): |
|---|
| 272 | c = src.getGray(x, y) > threshold |
|---|
| 273 | dest.setGray(x, y, c) |
|---|
| 274 | return dest |
|---|
| 275 | |
|---|
| 276 | if chapter(1): |
|---|
| 277 | test11x(grad256bw, 0.5).save("grad1-1-1.png") |
|---|
| 278 | test11x(lenna256bw, 0.5).save("out1-1-1.png") |
|---|
| 279 | test11x(grad256bw, 0.4).save("grad1-1-2.png") |
|---|
| 280 | test11x(lenna256bw, 0.4).save("out1-1-2.png") |
|---|
| 281 | test11x(grad256bw, 0.6).save("grad1-1-3.png") |
|---|
| 282 | test11x(lenna256bw, 0.6).save("out1-1-3.png") |
|---|
| 283 | |
|---|
| 284 | # Output 1.2.1: 3-colour threshold |
|---|
| 285 | # Output 1.2.2: 5-colour threshold |
|---|
| 286 | def test12x(src, colors): |
|---|
| 287 | (w, h) = src.size() |
|---|
| 288 | dest = Image((w, h)) |
|---|
| 289 | q = colors - 1 |
|---|
| 290 | p = -.00001 + colors |
|---|
| 291 | for y in range(h): |
|---|
| 292 | for x in range(w): |
|---|
| 293 | c = src.getGray(x, y) |
|---|
| 294 | c = math.floor(c * p) / q |
|---|
| 295 | dest.setGray(x, y, c) |
|---|
| 296 | return dest |
|---|
| 297 | |
|---|
| 298 | if chapter(1): |
|---|
| 299 | test12x(grad256bw, 3).save("grad1-2-1.png") |
|---|
| 300 | test12x(lenna256bw, 3).save("out1-2-1.png") |
|---|
| 301 | test12x(grad256bw, 5).save("grad1-2-2.png") |
|---|
| 302 | test12x(lenna256bw, 5).save("out1-2-2.png") |
|---|
| 303 | |
|---|
| 304 | # Output 1.2.3: 3-colour threshold, minimal error |
|---|
| 305 | # Output 1.2.4: 5-colour threshold, minimal error |
|---|
| 306 | def test12y(src, colors): |
|---|
| 307 | (w, h) = src.size() |
|---|
| 308 | dest = Image((w, h)) |
|---|
| 309 | q = colors - 1 |
|---|
| 310 | p = -.00001 + colors - 1 |
|---|
| 311 | for y in range(h): |
|---|
| 312 | for x in range(w): |
|---|
| 313 | c = src.getGray(x, y) |
|---|
| 314 | c = math.floor((c + 0.5 / p) * p) / q |
|---|
| 315 | dest.setGray(x, y, c) |
|---|
| 316 | return dest |
|---|
| 317 | |
|---|
| 318 | if chapter(1): |
|---|
| 319 | test12y(grad256bw, 3).save("grad1-2-3.png") |
|---|
| 320 | test12y(lenna256bw, 3).save("out1-2-3.png") |
|---|
| 321 | test12y(grad256bw, 5).save("grad1-2-4.png") |
|---|
| 322 | test12y(lenna256bw, 5).save("out1-2-4.png") |
|---|
| 323 | |
|---|
| 324 | # Output 1.3.1: 2-colour threshold, dynamic thresholding |
|---|
| 325 | # Output 1.3.2: 5-colour threshold, dynamic thresholding |
|---|
| 326 | def test13x(src, n, fuzzy = None): |
|---|
| 327 | random.seed(0) |
|---|
| 328 | (w, h) = src.size() |
|---|
| 329 | dest = Image((w, h)) |
|---|
| 330 | # Compute histogram |
|---|
| 331 | histo = [0] * 256 |
|---|
| 332 | for y in range(h): |
|---|
| 333 | for x in range(w): |
|---|
| 334 | histo[(int)(src.getGray(x, y) * 255.9999)] += 1 |
|---|
| 335 | thresholds = [1. * (1. + i) / n for i in range(n - 1)] |
|---|
| 336 | values = [i / (n - 1.) for i in range(n)] |
|---|
| 337 | # Parse histogram |
|---|
| 338 | total = 0 |
|---|
| 339 | t = 0 |
|---|
| 340 | for i in range(256): |
|---|
| 341 | total += histo[i] |
|---|
| 342 | if total > thresholds[t] * w * h: |
|---|
| 343 | thresholds[t] = i / 255.0 |
|---|
| 344 | t += 1 |
|---|
| 345 | if t + 1 > n - 1: |
|---|
| 346 | break |
|---|
| 347 | # Compute image |
|---|
| 348 | for y in range(h): |
|---|
| 349 | for x in range(w): |
|---|
| 350 | c = src.getGray(x, y) |
|---|
| 351 | for (i, t) in enumerate(thresholds): |
|---|
| 352 | if fuzzy: |
|---|
| 353 | t += fuzzy() |
|---|
| 354 | if c < t: |
|---|
| 355 | dest.setGray(x, y, values[i]) |
|---|
| 356 | break |
|---|
| 357 | else: |
|---|
| 358 | dest.setGray(x, y, values[n - 1]) |
|---|
| 359 | return dest |
|---|
| 360 | |
|---|
| 361 | if chapter(1): |
|---|
| 362 | test13x(grad256bw, 2).save("grad1-3-1.png") |
|---|
| 363 | test13x(lenna256bw, 2).save("out1-3-1.png") |
|---|
| 364 | test13x(grad256bw, 5).save("grad1-3-2.png") |
|---|
| 365 | test13x(lenna256bw, 5).save("out1-3-2.png") |
|---|
| 366 | |
|---|
| 367 | # Output 1.4.1: uniform random dithering |
|---|
| 368 | def test141(src): |
|---|
| 369 | random.seed(0) |
|---|
| 370 | (w, h) = src.size() |
|---|
| 371 | dest = Image((w, h)) |
|---|
| 372 | for y in range(h): |
|---|
| 373 | for x in range(w): |
|---|
| 374 | c = src.getGray(x, y) |
|---|
| 375 | d = c > random.random() |
|---|
| 376 | dest.setGray(x, y, d) |
|---|
| 377 | return dest |
|---|
| 378 | |
|---|
| 379 | if chapter(1): |
|---|
| 380 | test141(grad256bw).save("grad1-4-1.png") |
|---|
| 381 | test141(lenna256bw).save("out1-4-1.png") |
|---|
| 382 | |
|---|
| 383 | # Output 1.4.2: gaussian random dithering |
|---|
| 384 | def test142(src): |
|---|
| 385 | random.seed(0) |
|---|
| 386 | (w, h) = src.size() |
|---|
| 387 | dest = Image((w, h)) |
|---|
| 388 | for y in range(h): |
|---|
| 389 | for x in range(w): |
|---|
| 390 | c = src.getGray(x, y) |
|---|
| 391 | d = c > random.gauss(0.5, 0.15) |
|---|
| 392 | dest.setGray(x, y, d) |
|---|
| 393 | return dest |
|---|
| 394 | |
|---|
| 395 | if chapter(1): |
|---|
| 396 | test142(grad256bw).save("grad1-4-2.png") |
|---|
| 397 | test142(lenna256bw).save("out1-4-2.png") |
|---|
| 398 | |
|---|
| 399 | # Output 1.4.3: 3-colour threshold, dynamic thresholding, gaussian perturbation |
|---|
| 400 | if chapter(1): |
|---|
| 401 | fuzzy = lambda : random.gauss(0., 0.08) |
|---|
| 402 | test13x(grad256bw, 4, fuzzy).save("grad1-4-3.png") |
|---|
| 403 | test13x(lenna256bw, 4, fuzzy).save("out1-4-3.png") |
|---|
| 404 | |
|---|
| 405 | ############################################################################## |
|---|
| 406 | if chapter(2): |
|---|
| 407 | print "Chapter 2. Halftoning patterns" |
|---|
| 408 | |
|---|
| 409 | # Pattern 2.1.1: a 50% halftone pattern with various block sizes |
|---|
| 410 | # Pattern 2.1.2: 25% and 75% halftone patterns with various block sizes |
|---|
| 411 | if chapter(2): |
|---|
| 412 | dest = Image((320, 80)) |
|---|
| 413 | for x in range(320): |
|---|
| 414 | d = 8 >> (x / 80) |
|---|
| 415 | for y in range(80): |
|---|
| 416 | c = (x / d + y / d) & 1 |
|---|
| 417 | dest.setGray(x, y, c) |
|---|
| 418 | dest.save("pat2-1-1.png") |
|---|
| 419 | |
|---|
| 420 | dest = Image((320, 80)) |
|---|
| 421 | for x in range(320): |
|---|
| 422 | d = 8 >> (x / 80) |
|---|
| 423 | for y in range(40): |
|---|
| 424 | c = ((x / d + y / d) & 1) or (y / d & 1) |
|---|
| 425 | dest.setGray(x, y, c) |
|---|
| 426 | for y in range(40, 80): |
|---|
| 427 | c = ((x / d + y / d) & 1) and (y / d & 1) |
|---|
| 428 | dest.setGray(x, y, c) |
|---|
| 429 | dest.save("pat2-1-2.png") |
|---|
| 430 | |
|---|
| 431 | # Output 2.1.1: 20/40/60/80% threshold with 25/50/75% patterns inbetween: |
|---|
| 432 | def test211(src): |
|---|
| 433 | (w, h) = src.size() |
|---|
| 434 | dest = Image((w, h)) |
|---|
| 435 | for y in range(h): |
|---|
| 436 | for x in range(w): |
|---|
| 437 | c = src.getGray(x, y) |
|---|
| 438 | if c < 0.2: |
|---|
| 439 | c = 0. |
|---|
| 440 | elif c < 0.4: |
|---|
| 441 | c = ((x + y) & 1) and (y & 1) |
|---|
| 442 | elif c < 0.6: |
|---|
| 443 | c = (x + y) & 1 |
|---|
| 444 | elif c < 0.8: |
|---|
| 445 | c = ((x + y) & 1) or (y & 1) |
|---|
| 446 | else: |
|---|
| 447 | c = 1. |
|---|
| 448 | dest.setGray(x, y, c) |
|---|
| 449 | return dest |
|---|
| 450 | |
|---|
| 451 | if chapter(2): |
|---|
| 452 | test211(grad256bw).save("grad2-1-1.png") |
|---|
| 453 | test211(lenna256bw).save("out2-1-1.png") |
|---|
| 454 | |
|---|
| 455 | # Pattern 2.2.1: vertical, mixed and horizontal black-white halftones |
|---|
| 456 | # Pattern 2.2.2: two different 25% patterns |
|---|
| 457 | if chapter(2): |
|---|
| 458 | dest = Image((240, 80)) |
|---|
| 459 | for y in range(80): |
|---|
| 460 | for x in range(80): |
|---|
| 461 | c = x & 1 |
|---|
| 462 | dest.setGray(x, y, c) |
|---|
| 463 | for x in range(80, 160): |
|---|
| 464 | c = (x / d + y / d) & 1 |
|---|
| 465 | dest.setGray(x, y, c) |
|---|
| 466 | for x in range(160, 240): |
|---|
| 467 | c = y & 1 |
|---|
| 468 | dest.setGray(x, y, c) |
|---|
| 469 | dest.save("pat2-2-1.png") |
|---|
| 470 | |
|---|
| 471 | dest = Image((320, 80)) |
|---|
| 472 | for y in range(80): |
|---|
| 473 | for x in range(80): |
|---|
| 474 | c = (x / 2 & 1) and (y / 2 & 1) |
|---|
| 475 | dest.setGray(x, y, c) |
|---|
| 476 | for x in range(80, 160): |
|---|
| 477 | c = (x & 1) and (y & 1) |
|---|
| 478 | dest.setGray(x, y, c) |
|---|
| 479 | for x in range(160, 240): |
|---|
| 480 | c = (x & 1) and ((y + x / 2) & 1) |
|---|
| 481 | dest.setGray(x, y, c) |
|---|
| 482 | for x in range(240, 320): |
|---|
| 483 | c = (x / 2 & 1) and ((y / 2 + x / 4) & 1) |
|---|
| 484 | dest.setGray(x, y, c) |
|---|
| 485 | dest.save("pat2-2-2.png") |
|---|
| 486 | |
|---|
| 487 | # Output 2.3.1: 4x4 Bayer dithering |
|---|
| 488 | # Output 2.3.2: 4x4 cluster dot |
|---|
| 489 | # Output 2.3.3: 5x3 line dithering |
|---|
| 490 | def ordereddither(src, mat): |
|---|
| 491 | (w, h) = src.size() |
|---|
| 492 | dest = Image((w, h)) |
|---|
| 493 | dx = len(mat[0]) |
|---|
| 494 | dy = len(mat) |
|---|
| 495 | for y in range(h): |
|---|
| 496 | for x in range(w): |
|---|
| 497 | c = src.getGray(x, y) |
|---|
| 498 | threshold = (1. + mat[y % dy][x % dx]) / (dx * dy + 1) |
|---|
| 499 | c = c > threshold |
|---|
| 500 | dest.setGray(x, y, c) |
|---|
| 501 | return dest |
|---|
| 502 | |
|---|
| 503 | def makebayer(rank, mat = False): |
|---|
| 504 | if not mat: |
|---|
| 505 | mat = Matrix(1, 1) |
|---|
| 506 | if not rank: |
|---|
| 507 | return mat |
|---|
| 508 | n = len(mat) |
|---|
| 509 | newmat = Matrix(n * 2, n * 2) |
|---|
| 510 | for j in range(n): |
|---|
| 511 | for i in range(n): |
|---|
| 512 | x = mat[j][i] |
|---|
| 513 | newmat[j * 2][i * 2] = x |
|---|
| 514 | newmat[j * 2][i * 2 + 1] = x + n * n * 3 |
|---|
| 515 | newmat[j * 2 + 1][i * 2] = x + n * n * 2 |
|---|
| 516 | newmat[j * 2 + 1][i * 2 + 1] = x + n * n |
|---|
| 517 | return makebayer(rank - 1, newmat) |
|---|
| 518 | |
|---|
| 519 | DITHER_BAYER22 = makebayer(1) |
|---|
| 520 | DITHER_BAYER44 = makebayer(2) |
|---|
| 521 | DITHER_BAYER88 = makebayer(3) |
|---|
| 522 | |
|---|
| 523 | DITHER_CLUSTER44 = \ |
|---|
| 524 | [[ 12, 5, 6, 13], |
|---|
| 525 | [ 4, 0, 1, 7], |
|---|
| 526 | [ 11, 3, 2, 8], |
|---|
| 527 | [ 15, 10, 9, 14]] |
|---|
| 528 | DITHER_CLUSTER88 = \ |
|---|
| 529 | [[ 24, 10, 12, 26, 35, 47, 49, 37], |
|---|
| 530 | [ 8, 0, 2, 14, 45, 59, 61, 51], |
|---|
| 531 | [ 22, 6, 4, 16, 43, 57, 63, 53], |
|---|
| 532 | [ 30, 20, 18, 28, 33, 41, 55, 39], |
|---|
| 533 | [ 34, 46, 48, 36, 25, 11, 13, 27], |
|---|
| 534 | [ 44, 58, 60, 50, 9, 1, 3, 15], |
|---|
| 535 | [ 42, 56, 62, 52, 23, 7, 5, 17], |
|---|
| 536 | [ 32, 40, 54, 38, 31, 21, 19, 29]] |
|---|
| 537 | DITHER_LINE53 = \ |
|---|
| 538 | [[ 9, 3, 0, 6, 12], |
|---|
| 539 | [ 10, 4, 1, 7, 13], |
|---|
| 540 | [ 11, 5, 2, 8, 14]] |
|---|
| 541 | |
|---|
| 542 | def bayercol(val): return ['#fdf', '#dfd', '#ffd', '#dff'][val % 4] |
|---|
| 543 | |
|---|
| 544 | if chapter(2): |
|---|
| 545 | ordereddither(grad256bw, DITHER_BAYER22).save("grad2-3-0.png") |
|---|
| 546 | ordereddither(lenna256bw, DITHER_BAYER22).save("out2-3-0.png") |
|---|
| 547 | |
|---|
| 548 | Svg(DITHER_BAYER44, bayercol).save("fig2-3-2.png", 40) |
|---|
| 549 | ordereddither(grad256bw, DITHER_BAYER44).save("grad2-3-1.png") |
|---|
| 550 | ordereddither(lenna256bw, DITHER_BAYER44).save("out2-3-1.png") |
|---|
| 551 | |
|---|
| 552 | Svg(DITHER_BAYER88, bayercol).save("fig2-3-2b.png", 30) |
|---|
| 553 | ordereddither(grad256bw, DITHER_BAYER88).save("grad2-3-1b.png") |
|---|
| 554 | ordereddither(lenna256bw, DITHER_BAYER88).save("out2-3-1b.png") |
|---|
| 555 | |
|---|
| 556 | Svg(DITHER_CLUSTER44).save("fig2-3-3.png", 40) |
|---|
| 557 | ordereddither(grad256bw, DITHER_CLUSTER44).save("grad2-3-2.png") |
|---|
| 558 | ordereddither(lenna256bw, DITHER_CLUSTER44).save("out2-3-2.png") |
|---|
| 559 | |
|---|
| 560 | Svg(DITHER_CLUSTER88).save("fig2-3-3b.png", 30) |
|---|
| 561 | ordereddither(grad256bw, DITHER_CLUSTER88).save("grad2-3-2b.png") |
|---|
| 562 | ordereddither(lenna256bw, DITHER_CLUSTER88).save("out2-3-2b.png") |
|---|
| 563 | |
|---|
| 564 | Svg(DITHER_LINE53).save("fig2-3-4.png", 40) |
|---|
| 565 | ordereddither(grad256bw, DITHER_LINE53).save("grad2-3-3.png") |
|---|
| 566 | ordereddither(lenna256bw, DITHER_LINE53).save("out2-3-3.png") |
|---|
| 567 | |
|---|
| 568 | # Output 2.4.1: 4x4 Bayer dithering with gaussian perturbation |
|---|
| 569 | def test241(src, mat): |
|---|
| 570 | random.seed(0) |
|---|
| 571 | (w, h) = src.size() |
|---|
| 572 | dest = Image((w, h)) |
|---|
| 573 | dx = len(mat[0]) |
|---|
| 574 | dy = len(mat) |
|---|
| 575 | for y in range(h): |
|---|
| 576 | for x in range(w): |
|---|
| 577 | c = src.getGray(x, y) |
|---|
| 578 | threshold = (1. + mat[y % dy][x % dx]) / (dx * dy + 1) |
|---|
| 579 | threshold += random.gauss(0, 0.08) |
|---|
| 580 | c = c > threshold |
|---|
| 581 | dest.setGray(x, y, c) |
|---|
| 582 | return dest |
|---|
| 583 | |
|---|
| 584 | if chapter(2): |
|---|
| 585 | test241(grad256bw, DITHER_BAYER88).save("grad2-4-1.png") |
|---|
| 586 | test241(lenna256bw, DITHER_BAYER88).save("out2-4-1.png") |
|---|
| 587 | |
|---|
| 588 | # Output 2.4.2: random dither matrice selection |
|---|
| 589 | def test242(src, mlist): |
|---|
| 590 | random.seed(0) |
|---|
| 591 | (w, h) = src.size() |
|---|
| 592 | dest = Image((w, h)) |
|---|
| 593 | dx = len(mlist[0][0]) |
|---|
| 594 | dy = len(mlist[0]) |
|---|
| 595 | for y in range(h / dy): |
|---|
| 596 | for x in range(w / dx): |
|---|
| 597 | mat = random.choice(mlist) |
|---|
| 598 | for j in range(dy): |
|---|
| 599 | for i in range(dx): |
|---|
| 600 | c = src.getGray(x * dx + i, y * dy + j) |
|---|
| 601 | threshold = (1. + mat[j][i]) / (dx * dy + 1) |
|---|
| 602 | d = c > threshold |
|---|
| 603 | dest.setGray(x * dx + i, y * dy + j, d) |
|---|
| 604 | return dest |
|---|
| 605 | |
|---|
| 606 | if chapter(2): |
|---|
| 607 | m1 = [[1, 4, 7], |
|---|
| 608 | [6, 0, 2], |
|---|
| 609 | [3, 8, 5]] |
|---|
| 610 | m2 = [[4, 6, 3], |
|---|
| 611 | [8, 1, 5], |
|---|
| 612 | [0, 3, 7]] |
|---|
| 613 | m3 = [[5, 0, 3], |
|---|
| 614 | [2, 8, 6], |
|---|
| 615 | [7, 4, 1]] |
|---|
| 616 | m4 = [[8, 2, 5], |
|---|
| 617 | [6, 4, 0], |
|---|
| 618 | [1, 7, 3]] |
|---|
| 619 | m5 = [[2, 5, 8], |
|---|
| 620 | [0, 7, 3], |
|---|
| 621 | [4, 1, 6]] |
|---|
| 622 | m6 = [[7, 4, 1], |
|---|
| 623 | [3, 6, 8], |
|---|
| 624 | [2, 0, 5]] |
|---|
| 625 | mlist = [m1, m2, m3, m4, m5, m6] |
|---|
| 626 | test242(grad256bw, mlist).save("grad2-4-2.png") |
|---|
| 627 | test242(lenna256bw, mlist).save("out2-4-2.png") |
|---|
| 628 | |
|---|
| 629 | # Output 2.5.1: cross pattern |
|---|
| 630 | # Output 2.5.2: hex pattern |
|---|
| 631 | # Output 2.5.3: square pattern |
|---|
| 632 | def test25x(src, mat, vec): |
|---|
| 633 | # 1. count positive cells |
|---|
| 634 | n = 0 |
|---|
| 635 | for line in mat: |
|---|
| 636 | for x in line: |
|---|
| 637 | if x >= 0: |
|---|
| 638 | n += 1 |
|---|
| 639 | # 2. create list of vectors |
|---|
| 640 | l = [] |
|---|
| 641 | x = y = 0 |
|---|
| 642 | while (x, y) not in l: |
|---|
| 643 | l.append((x, y)) |
|---|
| 644 | (x, y) = ((x + vec[0][0]) % n, (y + vec[0][1]) % n) |
|---|
| 645 | if (x, y) in l: |
|---|
| 646 | (x, y) = ((x + vec[1][0]) % n, (y + vec[1][1]) % n) |
|---|
| 647 | # 3. create big matrix |
|---|
| 648 | m = Matrix(n, n) |
|---|
| 649 | for v in l: |
|---|
| 650 | for y in range(len(mat)): |
|---|
| 651 | for x in range(len(mat[0])): |
|---|
| 652 | if mat[y][x] < 0: |
|---|
| 653 | continue |
|---|
| 654 | m[(v[1] + y + n) % n][(v[0] + x + n) % n] = mat[y][x] |
|---|
| 655 | # 4. dither image |
|---|
| 656 | (w, h) = src.size() |
|---|
| 657 | dest = Image((w, h)) |
|---|
| 658 | for y in range(h): |
|---|
| 659 | for x in range(w): |
|---|
| 660 | c = src.getGray(x, y) |
|---|
| 661 | threshold = (1. + m[y % n][x % n]) / (1. + n) |
|---|
| 662 | d = c > threshold |
|---|
| 663 | dest.setGray(x, y, d) |
|---|
| 664 | return dest |
|---|
| 665 | |
|---|
| 666 | if chapter(2): |
|---|
| 667 | mat = [[-1, 4, -1], |
|---|
| 668 | [ 3, 0, 1], |
|---|
| 669 | [-1, 2, -1]] |
|---|
| 670 | vec = [(2, -1), (1, 2)] |
|---|
| 671 | test25x(grad256bw, mat, vec).save("grad2-5-1.png") |
|---|
| 672 | test25x(lenna256bw, mat, vec).save("out2-5-1.png") |
|---|
| 673 | mat = [[-1, 4, 2, -1], |
|---|
| 674 | [ 6, 0, 5, 3], |
|---|
| 675 | [-1, 7, 1, -1]] |
|---|
| 676 | vec = [(2, -2), (3, 1)] |
|---|
| 677 | test25x(grad256bw, mat, vec).save("grad2-5-2.png") |
|---|
| 678 | test25x(lenna256bw, mat, vec).save("out2-5-2.png") |
|---|
| 679 | mat = [[-1, -1, -1, 7, -1], |
|---|
| 680 | [-1, 2, 6, 9, 8], |
|---|
| 681 | [ 3, 0, 1, 5, -1], |
|---|
| 682 | [-1, 4, -1, -1, -1]] |
|---|
| 683 | vec = [(2, 4), (3, 1)] |
|---|
| 684 | test25x(grad256bw, mat, vec).save("grad2-5-3.png") |
|---|
| 685 | test25x(lenna256bw, mat, vec).save("out2-5-3.png") |
|---|
| 686 | mat = [[-1, -1, 2, -1], |
|---|
| 687 | [ 0, 1, 3, 8], |
|---|
| 688 | [ 5, 4, 7, 6], |
|---|
| 689 | [-1, 9, -1, -1]] |
|---|
| 690 | vec = [(2, 2), (0, 5)] |
|---|
| 691 | test25x(grad256bw, mat, vec).save("grad2-5-4.png") |
|---|
| 692 | test25x(lenna256bw, mat, vec).save("out2-5-4.png") |
|---|
| 693 | |
|---|
| 694 | # Output 2.6.1: 4-wise cross pattern |
|---|
| 695 | # Output 2.6.2: 3-wise hex pattern |
|---|
| 696 | # Output 2.6.3: 4-wise square pattern |
|---|
| 697 | if chapter(2): |
|---|
| 698 | mat = [[-1, -1, -1, 18, -1, -1], |
|---|
| 699 | [-1, 16, 14, 2, 6, -1], |
|---|
| 700 | [12, 0, 4, 10, 17, -1], |
|---|
| 701 | [-1, 8, 19, 13, 1, 5], |
|---|
| 702 | [-1, 15, 3, 7, 9, -1], |
|---|
| 703 | [-1, -1, 11, -1, -1, -1]] |
|---|
| 704 | vec = [(4, -2), (2, 4)] |
|---|
| 705 | test25x(grad256bw, mat, vec).save("grad2-6-1.png") |
|---|
| 706 | test25x(lenna256bw, mat, vec).save("out2-6-1.png") |
|---|
| 707 | |
|---|
| 708 | mat = [[-1, 12, 6, -1, -1, -1, -1], |
|---|
| 709 | [18, 0, 15, 9, 14, 8, -1], |
|---|
| 710 | [-1, 21, 3, 20, 2, 17, 11], |
|---|
| 711 | [-1, -1, 13, 7, 23, 5, -1], |
|---|
| 712 | [-1, 19, 1, 16, 10, -1, -1], |
|---|
| 713 | [-1, -1, 22, 4, -1, -1, -1]] |
|---|
| 714 | vec = [(5, -1), (-1, 5)] |
|---|
| 715 | test25x(grad256bw, mat, vec).save("grad2-6-2.png") |
|---|
| 716 | test25x(lenna256bw, mat, vec).save("out2-6-2.png") |
|---|
| 717 | |
|---|
| 718 | mat = [[-1, -1, -1, -1, 30, -1, -1, -1, -1], |
|---|
| 719 | [-1, -1, 10, 26, 38, 34, -1, 29, -1], |
|---|
| 720 | [-1, 14, 2, 6, 22, 9, 25, 37, 33], |
|---|
| 721 | [-1, -1, 18, 28, 13, 1, 5, 21, -1], |
|---|
| 722 | [-1, 8, 24, 36, 32, 17, 31, -1, -1], |
|---|
| 723 | [12, 0, 4, 20, 11, 27, 39, 35, -1], |
|---|
| 724 | [-1, 16, -1, 15, 3, 7, 23, -1, -1], |
|---|
| 725 | [-1, -1, -1, -1, 19, -1, -1, -1, -1]] |
|---|
| 726 | vec = [(6, 2), (-2, 6)] |
|---|
| 727 | test25x(grad256bw, mat, vec).save("grad2-6-3.png") |
|---|
| 728 | test25x(lenna256bw, mat, vec).save("out2-6-3.png") |
|---|
| 729 | |
|---|
| 730 | mat = [[-1, -1, 3, 35, -1, -1, 1, 33, -1, -1, -1, -1, -1], |
|---|
| 731 | [-1, -1, -1, 19, 51, -1, -1, 17, 49, -1, -1, -1, -1], |
|---|
| 732 | [ 7, 39, 11, 43, 5, 37, 9, 41, -1, -1, -1, -1, -1], |
|---|
| 733 | [-1, 23, 55, 27, 59, 21, 53, 25, 57, -1, -1, -1, -1], |
|---|
| 734 | [15, 47, -1, -1, 13, 45, 2, 34, -1, -1, 0, 32, -1], |
|---|
| 735 | [-1, 31, 63, -1, -1, 29, 61, 18, 50, -1, -1, 16, 48], |
|---|
| 736 | [-1, -1, -1, -1, 6, 38, 10, 42, 4, 36, 8, 40, -1], |
|---|
| 737 | [-1, -1, -1, -1, -1, 22, 54, 26, 58, 20, 52, 24, 56], |
|---|
| 738 | [-1, -1, -1, -1, 14, 46, -1, -1, 12, 44, -1, -1, -1], |
|---|
| 739 | [-1, -1, -1, -1, -1, 30, 62, -1, -1, 28, 60, -1, -1]] |
|---|
| 740 | vec = [(8, 0), (0, 8)] |
|---|
| 741 | def colorise(val): |
|---|
| 742 | if val == 0: return '#fff' |
|---|
| 743 | if (val % 64) == 32: return '#ddf' |
|---|
| 744 | if (val % 32) == 16: return '#fdd' |
|---|
| 745 | if (val % 16) == 8: return '#dff' |
|---|
| 746 | if (val % 8) == 4: return '#ffd' |
|---|
| 747 | if (val % 4) == 2: return '#fdf' |
|---|
| 748 | return '#dfd' |
|---|
| 749 | Svg(mat, colorise).save("fig2-6-4.png", 25) |
|---|
| 750 | test25x(grad256bw, mat, vec).save("grad2-6-4.png") |
|---|
| 751 | test25x(lenna256bw, mat, vec).save("out2-6-4.png") |
|---|
| 752 | |
|---|
| 753 | mat = [[ -1, -1, -1, -1, -1, -1, 8, -1], |
|---|
| 754 | [ -1, -1, 6, -1, 2, 5, 11, 26], |
|---|
| 755 | [ 0, 3, 9, 24, 17, 14, 23, 20], |
|---|
| 756 | [ 15, 12, 21, 18, 7, 29, -1, -1], |
|---|
| 757 | [ -1, 27, 1, 4, 10, 25, -1, -1], |
|---|
| 758 | [ -1, -1, 16, 13, 22, 19, -1, -1], |
|---|
| 759 | [ -1, -1, -1, 28, -1, -1, -1, -1]] |
|---|
| 760 | vec = [(6, 1), (0, 5)] |
|---|
| 761 | def colorise(val): return ['#dff', '#ffd', '#fdf'][val % 3] |
|---|
| 762 | #Svg(mat, colorise).save("fig2-6-5.png", 30) |
|---|
| 763 | test25x(grad256bw, mat, vec).save("grad2-6-5.png") |
|---|
| 764 | test25x(lenna256bw, mat, vec).save("out2-6-5.png") |
|---|
| 765 | |
|---|
| 766 | mat = [[ -1, -1, -1, -1, -1, -1, 24, -1, -1, -1, -1, -1, -1, -1], |
|---|
| 767 | [ -1, -1, 18, -1, 6, 15, 33, 78, -1, -1, -1, -1, 26, -1], |
|---|
| 768 | [ 0, 9, 27, 72, 51, 42, 69, 60, 20, -1, 8, 17, 35, 80], |
|---|
| 769 | [ 45, 36, 63, 54, 21, 87, 2, 11, 29, 74, 53, 44, 71, 62], |
|---|
| 770 | [ -1, 81, 3, 12, 30, 75, 47, 38, 65, 56, 23, 89, -1, -1], |
|---|
| 771 | [ -1, -1, 48, 39, 66, 57, 25, 83, 5, 14, 32, 77, -1, -1], |
|---|
| 772 | [ -1, -1, 19, 84, 7, 16, 34, 79, 50, 41, 68, 59, -1, -1], |
|---|
| 773 | [ 1, 10, 28, 73, 52, 43, 70, 61, -1, 86, -1, -1, -1, -1], |
|---|
| 774 | [ 46, 37, 64, 55, 22, 88, -1, -1, -1, -1, -1, -1, -1, -1], |
|---|
| 775 | [ -1, 82, 4, 13, 31, 76, -1, -1, -1, -1, -1, -1, -1, -1], |
|---|
| 776 | [ -1, -1, 49, 40, 67, 58, -1, -1, -1, -1, -1, -1, -1, -1], |
|---|
| 777 | [ -1, -1, -1, 85, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1]] |
|---|
| 778 | test25x(grad256bw, mat, vec).save("grad2-6-6.png") |
|---|
| 779 | test25x(lenna256bw, mat, vec).save("out2-6-6.png") |
|---|
| 780 | |
|---|
| 781 | # Output 2.7.1: void and cluster matrix generation |
|---|
| 782 | def makegauss(n): |
|---|
| 783 | c = (-1. + n) / 2 |
|---|
| 784 | mat = Matrix(n, n) |
|---|
| 785 | for y in range(n): |
|---|
| 786 | for x in range(n): |
|---|
| 787 | mat[y][x] = math.exp(- ((c - x) * (c - x) + (c - y) * (c - y)) / (0.05 * n * n)) |
|---|
| 788 | return mat |
|---|
| 789 | |
|---|
| 790 | def countones(mat): |
|---|
| 791 | total = 0 |
|---|
| 792 | for l in mat: |
|---|
| 793 | for x in l: |
|---|
| 794 | if x: |
|---|
| 795 | total += 1 |
|---|
| 796 | return total |
|---|
| 797 | |
|---|
| 798 | GAUSS77 = makegauss(7) |
|---|
| 799 | GAUSS99 = makegauss(9) |
|---|
| 800 | |
|---|
| 801 | def getminmax(mat, c): |
|---|
| 802 | min = 9999. |
|---|
| 803 | max = 0. |
|---|
| 804 | h = len(mat) |
|---|
| 805 | w = len(mat[0]) |
|---|
| 806 | for y in range(h): |
|---|
| 807 | for x in range(w): |
|---|
| 808 | if mat[y][x] != c: |
|---|
| 809 | continue |
|---|
| 810 | total = 0. |
|---|
| 811 | for j in range(7): |
|---|
| 812 | for i in range(7): |
|---|
| 813 | total += mat[(y + j - 3 + h) % h][(x + i - 3 + w) % w] \ |
|---|
| 814 | * GAUSS77[j][i] |
|---|
| 815 | if total > max: |
|---|
| 816 | (max, max_x, max_y) = (total, x, y) |
|---|
| 817 | if total < min: |
|---|
| 818 | (min, min_x, min_y) = (total, x, y) |
|---|
| 819 | return (min_x, min_y, max_x, max_y) |
|---|
| 820 | |
|---|
| 821 | def makeuniform(n): |
|---|
| 822 | random.seed(0) |
|---|
| 823 | mat = Matrix(n, n) |
|---|
| 824 | for t in range(n * n / 10): |
|---|
| 825 | x = (int)(random.random() * n) |
|---|
| 826 | y = (int)(random.random() * n) |
|---|
| 827 | mat[y][x] = 1 |
|---|
| 828 | while True: |
|---|
| 829 | (dummy0, dummy1, x, y) = getminmax(mat, 1.) |
|---|
| 830 | mat[y][x] = 0. |
|---|
| 831 | (x2, y2, dummy0, dummy1) = getminmax(mat, 0.) |
|---|
| 832 | mat[y2][x2] = 1. |
|---|
| 833 | if x2 == x and y2 == y: |
|---|
| 834 | break |
|---|
| 835 | return mat |
|---|
| 836 | |
|---|
| 837 | def makevoidandcluster(n): |
|---|
| 838 | vnc = Matrix(n, n) |
|---|
| 839 | # Step 1: step down to zero |
|---|
| 840 | mat = makeuniform(n) |
|---|
| 841 | rank = countones(mat) |
|---|
| 842 | while rank > 0: |
|---|
| 843 | rank -= 1 |
|---|
| 844 | (dummy0, dummy1, x, y) = getminmax(mat, 1.) |
|---|
| 845 | mat[y][x] = 0. |
|---|
| 846 | vnc[y][x] = rank |
|---|
| 847 | # Step 2: step up to n * n |
|---|
| 848 | mat = makeuniform(n) |
|---|
| 849 | rank = countones(mat) |
|---|
| 850 | while rank < n * n: |
|---|
| 851 | (x, y, dummy0, dummy1) = getminmax(mat, 0.) |
|---|
| 852 | mat[y][x] = 1. |
|---|
| 853 | vnc[y][x] = rank |
|---|
| 854 | rank += 1 |
|---|
| 855 | return vnc |
|---|
| 856 | |
|---|
| 857 | def vnccol(n): |
|---|
| 858 | return lambda val : ['#fff', '#ddd'][val < n * n / 10] |
|---|
| 859 | |
|---|
| 860 | if chapter(2): |
|---|
| 861 | tmp = makevoidandcluster(14) |
|---|
| 862 | Svg(tmp, vnccol(14)).save("fig2-7-1.png", 25) |
|---|
| 863 | ordereddither(grad256bw, tmp).save("grad2-7-1.png") |
|---|
| 864 | ordereddither(lenna256bw, tmp).save("out2-7-1.png") |
|---|
| 865 | tmp = makevoidandcluster(25) |
|---|
| 866 | Svg(tmp, vnccol(25)).save("fig2-7-2.png", 20) |
|---|
| 867 | ordereddither(grad256bw, tmp).save("grad2-7-2.png") |
|---|
| 868 | ordereddither(lenna256bw, tmp).save("out2-7-2.png") |
|---|
| 869 | |
|---|
| 870 | ############################################################################## |
|---|
| 871 | if chapter(3): |
|---|
| 872 | print "Chapter 3. Error diffusion" |
|---|
| 873 | |
|---|
| 874 | # Output 3.0.1: naive error diffusion |
|---|
| 875 | # Output 3.1.1: standard Floyd-Steinberg |
|---|
| 876 | # Output 3.1.2: serpentine Floyd-Steinberg |
|---|
| 877 | # FIXME: serpentine only works if rows == offset * 2 + 1 |
|---|
| 878 | # Output 3.2.1: Fan (modified Floyd-Steinberg) |
|---|
| 879 | # Output 3.2.1b: Shiau-Fan 1 |
|---|
| 880 | # Output 3.2.1c: Shiau-Fan 2 |
|---|
| 881 | # Output 3.2.2: Jarvis, Judice and Ninke |
|---|
| 882 | # Output 3.2.3: Stucki |
|---|
| 883 | # Output 3.2.4: Burkes |
|---|
| 884 | # Output 3.2.5: Sierra |
|---|
| 885 | # Output 3.2.6: Two-line Sierra |
|---|
| 886 | # Output 3.2.7: Sierra's Filter Lite |
|---|
| 887 | # Output 3.2.8: Atkinson |
|---|
| 888 | def test3xx(src, mat, serpentine): |
|---|
| 889 | (w, h) = src.size() |
|---|
| 890 | dest = Image((w, h)) |
|---|
| 891 | lines = len(mat) |
|---|
| 892 | rows = len(mat[0]) |
|---|
| 893 | offset = mat[0].index(-1) |
|---|
| 894 | ey = Matrix(w + rows - 1, lines, 0.) |
|---|
| 895 | for y in range(h): |
|---|
| 896 | ex = [0.] * (rows - offset) |
|---|
| 897 | if serpentine and y & 1: |
|---|
| 898 | xrange = range(w - 1, -1, -1) |
|---|
| 899 | else: |
|---|
| 900 | xrange = range(w) |
|---|
| 901 | for x in xrange: |
|---|
| 902 | # Set pixel |
|---|
| 903 | c = src.getGray(x, y) + ex[0] + ey[0][x + offset] |
|---|
| 904 | d = c > 0.5 |
|---|
| 905 | dest.setGray(x, y, d) |
|---|
| 906 | error = c - d |
|---|
| 907 | # Propagate first line of error |
|---|
| 908 | for dx in range(rows - offset - 2): |
|---|
| 909 | ex[dx] = ex[dx + 1] + error * mat[0][offset + 1 + dx] |
|---|
| 910 | ex[rows - offset - 2] = error * mat[0][rows - 1] |
|---|
| 911 | # Propagate next lines |
|---|
| 912 | if serpentine and y & 1: |
|---|
| 913 | for dy in range(1, lines): |
|---|
| 914 | for dx in range(rows): |
|---|
| 915 | ey[dy][x + dx] += error * mat[dy][rows - 1 - dx] |
|---|
| 916 | else: |
|---|
| 917 | for dy in range(1, lines): |
|---|
| 918 | for dx in range(rows): |
|---|
| 919 | ey[dy][x + dx] += error * mat[dy][dx] |
|---|
| 920 | for dy in range(lines - 1): |
|---|
| 921 | ey[dy] = ey[dy + 1] |
|---|
| 922 | ey[lines - 1] = [0.] * (w + rows - 1) |
|---|
| 923 | return dest |
|---|
| 924 | |
|---|
| 925 | ERROR_NAIVE = \ |
|---|
| 926 | [[ -1, 1]] |
|---|
| 927 | ERROR_FSTEIN = \ |
|---|
| 928 | [[ 0., -1, 7./16], |
|---|
| 929 | [ 3./16, 5./16, 1./16]] |
|---|
| 930 | ERROR_FAN = \ |
|---|
| 931 | [[ 0., 0., -1, 7./16], |
|---|
| 932 | [ 1./16, 3./16, 5./16, 0.]] |
|---|
| 933 | ERROR_SHIAUFAN = \ |
|---|
| 934 | [[ 0., 0., -1, 8./16], |
|---|
| 935 | [ 2./16, 2./16, 4./16, 0.]] |
|---|
| 936 | ERROR_SHIAUFAN2 = \ |
|---|
| 937 | [[ 0., 0., 0., -1, 8./16], |
|---|
| 938 | [ 1./16, 1./16, 2./16, 4./16, 0.]] |
|---|
| 939 | ERROR_JAJUNI = \ |
|---|
| 940 | [[ 0., 0., -1, 7./48, 5./48], |
|---|
| 941 | [ 3./48, 5./48, 7./48, 5./48, 3./48], |
|---|
| 942 | [ 1./48, 3./48, 5./48, 3./48, 1./48]] |
|---|
| 943 | ERROR_STUCKI = \ |
|---|
| 944 | [[ 0., 0., -1, 8./42, 4./42], |
|---|
| 945 | [ 2./42, 4./42, 8./42, 4./42, 2./42], |
|---|
| 946 | [ 1./42, 2./42, 4./42, 2./42, 1./42]] |
|---|
| 947 | ERROR_BURKES = \ |
|---|
| 948 | [[ 0., 0., -1, 8./32, 4./32], |
|---|
| 949 | [ 2./32, 4./32, 8./32, 4./32, 2./32]] |
|---|
| 950 | ERROR_SIERRA = \ |
|---|
| 951 | [[ 0., 0., -1, 5./32, 3./32], |
|---|
| 952 | [ 2./32, 4./32, 5./32, 4./32, 2./32], |
|---|
| 953 | [ 0., 2./32, 3./32, 2./32, 0.]] |
|---|
| 954 | ERROR_SIERRA2 = \ |
|---|
| 955 | [[ 0., 0., -1, 4./16, 3./16], |
|---|
| 956 | [ 1./16, 2./16, 3./16, 2./16, 1./16]] |
|---|
| 957 | ERROR_FILTERLITE = \ |
|---|
| 958 | [[ 0., -1, 2./4], |
|---|
| 959 | [ 1./4, 1./4, 0.]] |
|---|
| 960 | ERROR_ATKINSON = \ |
|---|
| 961 | [[ 0., -1, 1./8, 1./8], |
|---|
| 962 | [ 1./8, 1./8, 1./8, 0.], |
|---|
| 963 | [ 0., 1./8, 0., 0.]] |
|---|
| 964 | ## This is Stevenson-Arce in hex lattice |
|---|
| 965 | #ERROR_STAR = \ |
|---|
| 966 | # [[ 0., 0., 0., -1, 0., 32./200, 0.], |
|---|
| 967 | # [ 12./200, 0., 26./200, 0., 30./200, 0., 16./200], |
|---|
| 968 | # [ 0., 12./200, 0., 26./200, 0., 12./200, 0.], |
|---|
| 969 | # [ 5./200, 0., 12./200, 0., 12./200, 0., 5./200]] |
|---|
| 970 | ## This is an attempt at implementing Stevenson-Arce in square lattice |
|---|
| 971 | #ERROR_STAR = \ |
|---|
| 972 | # [[ 0., 0., -1, 32./200, 0.], |
|---|
| 973 | # [ 6./200, 19./200, 28./200, 23./200, 8./200], |
|---|
| 974 | # [ 0., 12./200, 26./200, 12./200, 0.], |
|---|
| 975 | # [ 2./200, 9./200, 12./200, 9./200, 2./200]] |
|---|
| 976 | |
|---|
| 977 | if chapter(3): |
|---|
| 978 | test3xx(grad256bw, ERROR_NAIVE, False).save("grad3-0-1.png") |
|---|
| 979 | test3xx(lenna256bw, ERROR_NAIVE, False).save("out3-0-1.png") |
|---|
| 980 | |
|---|
| 981 | Svg(ERROR_FSTEIN).save("fig3-1-1.png", 40) |
|---|
| 982 | test3xx(grad256bw, ERROR_FSTEIN, False).save("grad3-1-1.png") |
|---|
| 983 | test3xx(lenna256bw, ERROR_FSTEIN, False).save("out3-1-1.png") |
|---|
| 984 | test3xx(grad256bw, ERROR_FSTEIN, True).save("grad3-1-2.png") |
|---|
| 985 | test3xx(lenna256bw, ERROR_FSTEIN, True).save("out3-1-2.png") |
|---|
| 986 | |
|---|
| 987 | Svg(ERROR_FAN).save("fig3-2-1.png", 40) |
|---|
| 988 | test3xx(grad256bw, ERROR_FAN, False).save("grad3-2-1.png") |
|---|
| 989 | test3xx(lenna256bw, ERROR_FAN, False).save("out3-2-1.png") |
|---|
| 990 | Svg(ERROR_SHIAUFAN).save("fig3-2-1b.png", 40) |
|---|
| 991 | test3xx(grad256bw, ERROR_SHIAUFAN, False).save("grad3-2-1b.png") |
|---|
| 992 | test3xx(lenna256bw, ERROR_SHIAUFAN, False).save("out3-2-1b.png") |
|---|
| 993 | Svg(ERROR_SHIAUFAN2).save("fig3-2-1c.png", 40) |
|---|
| 994 | test3xx(grad256bw, ERROR_SHIAUFAN2, False).save("grad3-2-1c.png") |
|---|
| 995 | test3xx(lenna256bw, ERROR_SHIAUFAN2, False).save("out3-2-1c.png") |
|---|
| 996 | |
|---|
| 997 | Svg(ERROR_JAJUNI).save("fig3-2-2.png", 40) |
|---|
| 998 | test3xx(grad256bw, ERROR_JAJUNI, False).save("grad3-2-2.png") |
|---|
| 999 | test3xx(lenna256bw, ERROR_JAJUNI, False).save("out3-2-2.png") |
|---|
| 1000 | |
|---|
| 1001 | Svg(ERROR_STUCKI).save("fig3-2-3.png", 40) |
|---|
| 1002 | test3xx(grad256bw, ERROR_STUCKI, False).save("grad3-2-3.png") |
|---|
| 1003 | test3xx(lenna256bw, ERROR_STUCKI, False).save("out3-2-3.png") |
|---|
| 1004 | |
|---|
| 1005 | Svg(ERROR_BURKES).save("fig3-2-4.png", 40) |
|---|
| 1006 | test3xx(grad256bw, ERROR_BURKES, False).save("grad3-2-4.png") |
|---|
| 1007 | test3xx(lenna256bw, ERROR_BURKES, False).save("out3-2-4.png") |
|---|
| 1008 | |
|---|
| 1009 | Svg(ERROR_SIERRA).save("fig3-2-5.png", 40) |
|---|
| 1010 | test3xx(grad256bw, ERROR_SIERRA, False).save("grad3-2-5.png") |
|---|
| 1011 | test3xx(lenna256bw, ERROR_SIERRA, False).save("out3-2-5.png") |
|---|
| 1012 | |
|---|
| 1013 | Svg(ERROR_SIERRA2).save("fig3-2-6.png", 40) |
|---|
| 1014 | test3xx(grad256bw, ERROR_SIERRA2, False).save("grad3-2-6.png") |
|---|
| 1015 | test3xx(lenna256bw, ERROR_SIERRA2, False).save("out3-2-6.png") |
|---|
| 1016 | |
|---|
| 1017 | Svg(ERROR_FILTERLITE).save("fig3-2-7.png", 40) |
|---|
| 1018 | test3xx(grad256bw, ERROR_FILTERLITE, False).save("grad3-2-7.png") |
|---|
| 1019 | test3xx(lenna256bw, ERROR_FILTERLITE, False).save("out3-2-7.png") |
|---|
| 1020 | |
|---|
| 1021 | Svg(ERROR_ATKINSON).save("fig3-2-8.png", 40) |
|---|
| 1022 | test3xx(grad256bw, ERROR_ATKINSON, False).save("grad3-2-8.png") |
|---|
| 1023 | test3xx(lenna256bw, ERROR_ATKINSON, False).save("out3-2-8.png") |
|---|
| 1024 | |
|---|
| 1025 | #test3xx(grad256bw, ERROR_STAR, False).save("grad3-2-9.png") |
|---|
| 1026 | #test3xx(lenna256bw, ERROR_STAR, False).save("out3-2-9.png") |
|---|
| 1027 | |
|---|
| 1028 | #test3xx(grad256bw, ERROR_STAR, False).save("grad3-2-9.png") |
|---|
| 1029 | #test3xx(lenna256bw, ERROR_STAR, False).save("out3-2-9.png") |
|---|
| 1030 | |
|---|
| 1031 | # Output 3.3.1: Floyd-Steinberg on grey 90% |
|---|
| 1032 | # Output 3.3.2: serpentine Floyd-Steinberg on grey 90% |
|---|
| 1033 | if chapter(3): |
|---|
| 1034 | tmp = Image((128, 128)) |
|---|
| 1035 | for y in range(128): |
|---|
| 1036 | for x in range(128): |
|---|
| 1037 | tmp.setGray(x, y, 0.90) |
|---|
| 1038 | test3xx(tmp, ERROR_FSTEIN, True).getZoom(2).save("out3-3-2.png") |
|---|
| 1039 | test3xx(tmp, ERROR_FSTEIN, False).getZoom(2).save("out3-3-1.png") |
|---|
| 1040 | |
|---|
| 1041 | # Output 3.3.3: Riemersma dither on a Hilbert curve |
|---|
| 1042 | # Output 3.3.4: Riemersma dither on a Hilbert 2 curve |
|---|
| 1043 | def hilbert(x, y, n): |
|---|
| 1044 | d1 = [0, 4, 3, 2, 1] |
|---|
| 1045 | d2 = [0, 3, 4, 1, 2] |
|---|
| 1046 | m = n/2 |
|---|
| 1047 | |
|---|
| 1048 | if x == n - 1 and y == 0: return 0 |
|---|
| 1049 | if x == 0 and y == m - 1: return 1 |
|---|
| 1050 | if x == m - 1 and y == m: return 4 |
|---|
| 1051 | if x == n - 1 and y == m: return 2 |
|---|
| 1052 | |
|---|
| 1053 | if y >= m: return hilbert(x % m, y - m, m) |
|---|
| 1054 | if x < m: return d1[hilbert(y, x, m)] |
|---|
| 1055 | else: return d2[hilbert(m - 1 - y, n - 1 - x, m)] |
|---|
| 1056 | |
|---|
| 1057 | def hilbert2(x, y, n): |
|---|
| 1058 | d1 = [0, 2, 1, 3, 4] |
|---|
| 1059 | d2 = [0, 1, 2, 4, 3] |
|---|
| 1060 | d3 = [0, 2, 1, 4, 3] |
|---|
| 1061 | m = n/3 |
|---|
| 1062 | |
|---|
| 1063 | if x == n - 1 and y == n - 1: return 0 |
|---|
| 1064 | if x == m - 1 and y == m - 1: return 4 |
|---|
| 1065 | if x == 2 * m - 1 and y == 0: return 4 |
|---|
| 1066 | if x == n - 1 and y == m - 1: return 1 |
|---|
| 1067 | if x == 0 and y == 2 * m - 1: return 1 |
|---|
| 1068 | if x == m and y == m: return 3 |
|---|
| 1069 | if x == m * 2 and y == m * 2 - 1: return 3 |
|---|
| 1070 | if x == m - 1 and y == n - 1: return 4 |
|---|
| 1071 | if x == 2 * m - 1 and y == 2 * m: return 4 |
|---|
| 1072 | |
|---|
| 1073 | if (x < m or x >= m * 2) and (y < m or y >= m * 2): |
|---|
| 1074 | return hilbert2(x % m, y % m, m) |
|---|
| 1075 | if x >= m and x < m * 2 and y >= m and y < m * 2: |
|---|
| 1076 | return d3[hilbert2(2 * m - 1 - x, 2 * m - 1 - y, m)] |
|---|
| 1077 | if x >= m and x < m * 2: |
|---|
| 1078 | return d1[hilbert2(x - m, m - 1 - (y % m), m)] |
|---|
| 1079 | else: # if y >= m and y < m * 2 |
|---|
| 1080 | return d2[hilbert2(m - 1 - (x % m), y - m, m)] |
|---|
| 1081 | |
|---|
| 1082 | def riemersma(src, iterator, order): |
|---|
| 1083 | (w, h) = src.size() |
|---|
| 1084 | dest = Image((w, h)) |
|---|
| 1085 | q = 16 |
|---|
| 1086 | r = 16 |
|---|
| 1087 | size = 1 |
|---|
| 1088 | while size < w or size < h: size *= order |
|---|
| 1089 | coord = [(0, 0), (0, 1), (0, -1), (-1, 0), (1, 0)] |
|---|
| 1090 | err = [0] * q |
|---|
| 1091 | list = [math.exp(math.log(r) * i / (q - 1)) / r for i in range(q)] |
|---|
| 1092 | (x, y) = (0, 0) |
|---|
| 1093 | out = False |
|---|
| 1094 | while True: |
|---|
| 1095 | if not out: |
|---|
| 1096 | a = c = src.getGray(x, y) |
|---|
| 1097 | for i in range(q): |
|---|
| 1098 | a += err[i] * list[i] |
|---|
| 1099 | d = a > 0.5 |
|---|
| 1100 | dest.setGray(x, y, d) |
|---|
| 1101 | for i in range(q - 1): |
|---|
| 1102 | err[i] = err[i + 1] |
|---|
| 1103 | err[q - 1] = c - d |
|---|
| 1104 | t = iterator(x, y, size) |
|---|
| 1105 | if t == 0: |
|---|
| 1106 | break |
|---|
| 1107 | dx, dy = coord[t] |
|---|
| 1108 | x += dx |
|---|
| 1109 | y += dy |
|---|
| 1110 | if out: |
|---|
| 1111 | if x < w and y < h: |
|---|
| 1112 | err = [0] * q |
|---|
| 1113 | out = False |
|---|
| 1114 | continue |
|---|
| 1115 | # Did we fall off the screen? |
|---|
| 1116 | out = (x > w + q or y > h + q) |
|---|
| 1117 | return dest |
|---|
| 1118 | |
|---|
| 1119 | if chapter(3): |
|---|
| 1120 | riemersma(grad256bw, hilbert, 2).save("grad3-3-3.png") |
|---|
| 1121 | riemersma(lenna256bw, hilbert, 2).save("out3-3-3.png") |
|---|
| 1122 | riemersma(grad256bw, hilbert2, 3).save("grad3-3-4.png") |
|---|
| 1123 | riemersma(lenna256bw, hilbert2, 3).save("out3-3-4.png") |
|---|
| 1124 | |
|---|
| 1125 | # Output 3.3.5: spatial Hilbert dither on a Hilbert curve |
|---|
| 1126 | # Output 3.3.6: spatial Hilbert dither on a Hilbert 2 curve |
|---|
| 1127 | def spatialhilbert(src, iterator, order): |
|---|
| 1128 | (w, h) = src.size() |
|---|
| 1129 | dest = Image((w, h)) |
|---|
| 1130 | q = 16 |
|---|
| 1131 | size = 1 |
|---|
| 1132 | while size < w or size < h: size *= order |
|---|
| 1133 | coord = [(0, 0), (0, 1), (0, -1), (-1, 0), (1, 0)] |
|---|
| 1134 | err = [0] * q |
|---|
| 1135 | dx = [0] * q |
|---|
| 1136 | dy = [0] * q |
|---|
| 1137 | dist = [0] * q |
|---|
| 1138 | (x, y) = (0, 0) |
|---|
| 1139 | out = False |
|---|
| 1140 | while True: |
|---|
| 1141 | if not out: |
|---|
| 1142 | c = src.getGray(x, y) + err[0] |
|---|
| 1143 | d = c > 0.5 |
|---|
| 1144 | dest.setGray(x, y, d) |
|---|
| 1145 | t = iterator(x, y, size) |
|---|
| 1146 | if t == 0: |
|---|
| 1147 | break |
|---|
| 1148 | dx[0], dy[0] = coord[t] |
|---|
| 1149 | if not out: |
|---|
| 1150 | error = c - d |
|---|
| 1151 | errdiv = 0. |
|---|
| 1152 | for i in range(q - 1): |
|---|
| 1153 | t = coord[iterator(x + dx[i], y + dy[i], size)] |
|---|
| 1154 | dx[i + 1] = dx[i] + t[0] |
|---|
| 1155 | dy[i + 1] = dy[i] + t[1] |
|---|
| 1156 | for i in range(q): |
|---|
| 1157 | dist[i] = dx[i] * dx[i] + dy[i] * dy[i] |
|---|
| 1158 | errdiv += 1. / dist[i] |
|---|
| 1159 | error /= errdiv |
|---|
| 1160 | for i in range(q - 1): |
|---|
| 1161 | err[i] = err[i + 1] + error / dist[i] |
|---|
| 1162 | err[q - 1] = error / dist[q - 1] |
|---|
| 1163 | x += dx[0] |
|---|
| 1164 | y += dy[0] |
|---|
| 1165 | if out: |
|---|
| 1166 | if x < w and y < h: |
|---|
| 1167 | err = [0] * q |
|---|
| 1168 | out = False |
|---|
| 1169 | continue |
|---|
| 1170 | # Did we fall off the screen? |
|---|
| 1171 | out = (x > w + q or y > h + q) |
|---|
| 1172 | return dest |
|---|
| 1173 | |
|---|
| 1174 | if chapter(3): |
|---|
| 1175 | spatialhilbert(grad256bw, hilbert, 2).save("grad3-3-5.png") |
|---|
| 1176 | spatialhilbert(lenna256bw, hilbert, 2).save("out3-3-5.png") |
|---|
| 1177 | spatialhilbert(grad256bw, hilbert2, 3).save("grad3-3-6.png") |
|---|
| 1178 | spatialhilbert(lenna256bw, hilbert2, 3).save("out3-3-6.png") |
|---|
| 1179 | |
|---|
| 1180 | # Output 3.3.7: Knuth's dot diffusion |
|---|
| 1181 | # Output 3.3.8: Knuth's dot diffusion, sharpen = 0.9 |
|---|
| 1182 | # Output 3.3.9: Knuth's dot diffusion, sharpen = 0.9, zeta = 0.2 |
|---|
| 1183 | # Output 3.3.10: serpentine Floyd-Steinberg, sharpen = 0.9 |
|---|
| 1184 | def sharpen(src, sharpening): |
|---|
| 1185 | (w, h) = src.size() |
|---|
| 1186 | dest = Image((w, h)) |
|---|
| 1187 | for y in range(h): |
|---|
| 1188 | for x in range(w): |
|---|
| 1189 | c = src.getGray(x, y) |
|---|
| 1190 | total = 0. |
|---|
| 1191 | for j in range(-1, 2): |
|---|
| 1192 | for i in range(-1, 2): |
|---|
| 1193 | total += src.getGray(x + i, y + j) |
|---|
| 1194 | total /= 9. |
|---|
| 1195 | d = (c - sharpening * total) / (1.0 - sharpening) |
|---|
| 1196 | if d < 0.: |
|---|
| 1197 | d = 0. |
|---|
| 1198 | elif d > 1.: |
|---|
| 1199 | d = 1. |
|---|
| 1200 | dest.setGray(x, y, d) |
|---|
| 1201 | return dest |
|---|
| 1202 | |
|---|
| 1203 | def test337(src, mat, propagate, zeta): |
|---|
| 1204 | (w, h) = src.size() |
|---|
| 1205 | dest = Image((w, h)) |
|---|
| 1206 | dx = len(mat[0]) |
|---|
| 1207 | dy = len(mat) |
|---|
| 1208 | # 0. analyse diffusion matrix to speed up things later |
|---|
| 1209 | diff = [] |
|---|
| 1210 | cx, cy = -1, -1 |
|---|
| 1211 | for y in range(len(propagate)): |
|---|
| 1212 | for x in range(len(propagate[0])): |
|---|
| 1213 | if propagate[y][x] == -1: |
|---|
| 1214 | cx, cy = x, y |
|---|
| 1215 | for y in range(len(propagate)): |
|---|
| 1216 | for x in range(len(propagate[0])): |
|---|
| 1217 | diff.append((x - cx, y - cy, propagate[y][x])) |
|---|
| 1218 | # 1. analyse order matrix to get equivalence classes |
|---|
| 1219 | nclasses = 0 |
|---|
| 1220 | for l in mat: |
|---|
| 1221 | for v in l: |
|---|
| 1222 | if v + 1 > nclasses: |
|---|
| 1223 | nclasses = v + 1 |
|---|
| 1224 | classes = [[] for n in range(nclasses)] |
|---|
| 1225 | for y in range(h): |
|---|
| 1226 | for x in range(w): |
|---|
| 1227 | classes[mat[y % dy][x % dx]].append((x, y)) |
|---|
| 1228 | # 2. copy image |
|---|
| 1229 | img = [[src.getGray(x, y) for x in range(w)] for y in range(h)] |
|---|
| 1230 | aa = Matrix(w, h, 1.) |
|---|
| 1231 | # 3. parse all classes |
|---|
| 1232 | for l in classes: |
|---|
| 1233 | for x, y in l: |
|---|
| 1234 | c = img[y][x] |
|---|
| 1235 | if aa[y][x] == 1.: |
|---|
| 1236 | error = c + 4. * zeta |
|---|
| 1237 | else: |
|---|
| 1238 | error = c - zeta |
|---|
| 1239 | if x > 0 and aa[y][x-1] == 1.: error += zeta |
|---|
| 1240 | if y > 0 and aa[y-1][x] == 1.: error += zeta |
|---|
| 1241 | if x < w-1 and aa[y][x+1] == 1.: error += zeta |
|---|
| 1242 | if y < h-1 and aa[y+1][x] == 1.: error += zeta |
|---|
| 1243 | if c + error < 1.: |
|---|
| 1244 | aa[y][x] = 0. |
|---|
| 1245 | if x > 0 and aa[y][x-1] == 1.: aa[y][x-1] = .5 |
|---|
| 1246 | if y > 0 and aa[y-1][x] == 1.: aa[y-1][x] = .5 |
|---|
| 1247 | if x < w-1 and aa[y][x+1] == 1.: aa[y][x+1] = .5 |
|---|
| 1248 | if y < h-1 and aa[y+1][x] == 1.: aa[y+1][x] = .5 |
|---|
| 1249 | else: |
|---|
| 1250 | error = c - 1. |
|---|
| 1251 | # Propagate first line of error |
|---|
| 1252 | total = 0 |
|---|
| 1253 | err = [] |
|---|
| 1254 | for (i, j, e) in diff: |
|---|
| 1255 | if x + i < 0 or x + i >= w \ |
|---|
| 1256 | or y + j < 0 or y + j >= h: |
|---|
| 1257 | continue |
|---|
| 1258 | n = mat[(y + j) % dy][(x + i) % dx] - mat[y % dy][x % dx] |
|---|
| 1259 | if n <= 0: |
|---|
| 1260 | continue |
|---|
| 1261 | err.append((i, j, e)) |
|---|
| 1262 | total += e |
|---|
| 1263 | for (i, j, e) in err: |
|---|
| 1264 | img[y + j][x + i] += error * e / total |
|---|
| 1265 | # 4. copy image, replacing grey with white |
|---|
| 1266 | for y in range(h): |
|---|
| 1267 | for x in range(w): |
|---|
| 1268 | dest.setGray(x, y, aa[y][x] > 0.) |
|---|
| 1269 | return dest |
|---|
| 1270 | |
|---|
| 1271 | ERROR_DOT = \ |
|---|
| 1272 | [[1./12, 1./6, 1./12], |
|---|
| 1273 | [ 1./6, -1, 1./6], |
|---|
| 1274 | [1./12, 1./6, 1./12]] |
|---|
| 1275 | |
|---|
| 1276 | if chapter(3): |
|---|
| 1277 | Svg(ERROR_DOT).save("fig3-3-7b.png", 40) |
|---|
| 1278 | test337(grad256bw, DITHER_CLUSTER88, ERROR_DOT, 0.).save("grad3-3-7.png") |
|---|
| 1279 | test337(lenna256bw, DITHER_CLUSTER88, ERROR_DOT, 0.).save("out3-3-7.png") |
|---|
| 1280 | tmp = sharpen(grad256bw, 0.9) |
|---|
| 1281 | test337(tmp, DITHER_CLUSTER88, ERROR_DOT, 0.).save("grad3-3-8.png") |
|---|
| 1282 | test337(tmp, DITHER_CLUSTER88, ERROR_DOT, 0.2).save("grad3-3-9.png") |
|---|
| 1283 | test3xx(tmp, ERROR_FSTEIN, True).save("grad3-3-10.png") |
|---|
| 1284 | tmp = sharpen(lenna256bw, 0.9) |
|---|
| 1285 | test337(tmp, DITHER_CLUSTER88, ERROR_DOT, 0.).save("out3-3-8.png") |
|---|
| 1286 | test337(tmp, DITHER_CLUSTER88, ERROR_DOT, 0.2).save("out3-3-9.png") |
|---|
| 1287 | test3xx(tmp, ERROR_FSTEIN, True).save("out3-3-10.png") |
|---|
| 1288 | |
|---|
| 1289 | # Output 3.3.11: omni-directional error diffusion |
|---|
| 1290 | def test3311(src): |
|---|
| 1291 | w, h = src.size() |
|---|
| 1292 | g = { 0: 0, 1: 1, 2: 1 } |
|---|
| 1293 | g[-1] = g[2] - g[1] |
|---|
| 1294 | g[-2] = g[1] - g[0] |
|---|
| 1295 | n = 2 |
|---|
| 1296 | while g[n] < w and g[n] < h: |
|---|
| 1297 | n += 1 |
|---|
| 1298 | g[n] = g[n - 1] + g[n - 3] |
|---|
| 1299 | g[-n] = g[-n + 3] - g[-n + 2] |
|---|
| 1300 | u = g[n - 2] |
|---|
| 1301 | v = g[n - 1] |
|---|
| 1302 | a = [[(i * u + j * v) % g[n] for i in range(g[n])] for j in range(g[n])] |
|---|
| 1303 | return a |
|---|
| 1304 | |
|---|
| 1305 | ERROR_OMNI = \ |
|---|
| 1306 | [[0.1, 0.2, 0.1], |
|---|
| 1307 | [0.1, -1, 0.1], |
|---|
| 1308 | [0.1, 0.2, 0.1]] |
|---|
| 1309 | |
|---|
| 1310 | if chapter(3): |
|---|
| 1311 | Svg(ERROR_OMNI).save("fig3-3-11.png", 40) |
|---|
| 1312 | |
|---|
| 1313 | mat = test3311(grad256bw) |
|---|
| 1314 | test337(grad256bw, mat, ERROR_OMNI, 0.).save("grad3-3-11.png") |
|---|
| 1315 | |
|---|
| 1316 | mat = test3311(lenna256bw) |
|---|
| 1317 | tmp = [[str(mat[y][x]) for x in range(16)] for y in range(12)] |
|---|
| 1318 | for x in range(16): tmp[11][x] = '...' |
|---|
| 1319 | for y in range(12): tmp[y][15] = '...' |
|---|
| 1320 | Svg(tmp).save("fig3-3-11b.png", 20) |
|---|
| 1321 | test337(lenna256bw, mat, ERROR_OMNI, 0.).save("out3-3-11.png") |
|---|
| 1322 | |
|---|
| 1323 | # Output 3.4.1: Ostromoukhov's variable error diffusion |
|---|
| 1324 | def test341(src, serpentine): |
|---|
| 1325 | m = [[13, 0, 5], [13, 0, 5], [21, 0, 10], [7, 0, 4], |
|---|
| 1326 | [8, 0, 5], [47, 3, 28], [23, 3, 13], [15, 3, 8], |
|---|
| 1327 | [22, 6, 11], [43, 15, 20], [7, 3, 3], [501, 224, 211], |
|---|
| 1328 | [249, 116, 103], [165, 80, 67], [123, 62, 49], [489, 256, 191], |
|---|
| 1329 | [81, 44, 31], [483, 272, 181], [60, 35, 22], [53, 32, 19], |
|---|
| 1330 | [237, 148, 83], [471, 304, 161], [3, 2, 1], [481, 314, 185], |
|---|
| 1331 | [354, 226, 155], [1389, 866, 685], [227, 138, 125], [267, 158, 163], |
|---|
| 1332 | [327, 188, 220], [61, 34, 45], [627, 338, 505], [1227, 638, 1075], |
|---|
| 1333 | [20, 10, 19], [1937, 1000, 1767], [977, 520, 855], [657, 360, 551], |
|---|
| 1334 | [71, 40, 57], [2005, 1160, 1539], [337, 200, 247], [2039, 1240, 1425], |
|---|
| 1335 | [257, 160, 171], [691, 440, 437], [1045, 680, 627], [301, 200, 171], |
|---|
| 1336 | [177, 120, 95], [2141, 1480, 1083], [1079, 760, 513], [725, 520, 323], |
|---|
| 1337 | [137, 100, 57], [2209, 1640, 855], [53, 40, 19], [2243, 1720, 741], |
|---|
| 1338 | [565, 440, 171], [759, 600, 209], [1147, 920, 285], [2311, 1880, 513], |
|---|
| 1339 | [97, 80, 19], [335, 280, 57], [1181, 1000, 171], [793, 680, 95], |
|---|
| 1340 | [599, 520, 57], [2413, 2120, 171], [405, 360, 19], [2447, 2200, 57], |
|---|
| 1341 | [11, 10, 0], [158, 151, 3], [178, 179, 7], [1030, 1091, 63], |
|---|
| 1342 | [248, 277, 21], [318, 375, 35], [458, 571, 63], [878, 1159, 147], |
|---|
| 1343 | [5, 7, 1], [172, 181, 37], [97, 76, 22], [72, 41, 17], |
|---|
| 1344 | [119, 47, 29], [4, 1, 1], [4, 1, 1], [4, 1, 1], |
|---|
| 1345 | [4, 1, 1], [4, 1, 1], [4, 1, 1], [4, 1, 1], |
|---|
| 1346 | [4, 1, 1], [4, 1, 1], [65, 18, 17], [95, 29, 26], |
|---|
| 1347 | [185, 62, 53], [30, 11, 9], [35, 14, 11], [85, 37, 28], |
|---|
| 1348 | [55, 26, 19], [80, 41, 29], [155, 86, 59], [5, 3, 2], |
|---|
| 1349 | [5, 3, 2], [5, 3, 2], [5, 3, 2], [5, 3, 2], |
|---|
| 1350 | [5, 3, 2], [5, 3, 2], [5, 3, 2], [5, 3, 2], |
|---|
| 1351 | [5, 3, 2], [5, 3, 2], [5, 3, 2], [5, 3, 2], |
|---|
| 1352 | [305, 176, 119], [155, 86, 59], [105, 56, 39], [80, 41, 29], |
|---|
| 1353 | [65, 32, 23], [55, 26, 19], [335, 152, 113], [85, 37, 28], |
|---|
| 1354 | [115, 48, 37], [35, 14, 11], [355, 136, 109], [30, 11, 9], |
|---|
| 1355 | [365, 128, 107], [185, 62, 53], [25, 8, 7], [95, 29, 26], |
|---|
| 1356 | [385, 112, 103], [65, 18, 17], [395, 104, 101], [4, 1, 1]] |
|---|
| 1357 | (w, h) = src.size() |
|---|
| 1358 | dest = Image((w, h)) |
|---|
| 1359 | ey = [0.] * (w + 2) |
|---|
| 1360 | for y in range(h): |
|---|
| 1361 | ex = 0 |
|---|
| 1362 | newey = [0.] * (w + 2) |
|---|
| 1363 | if serpentine and y & 1: |
|---|
| 1364 | xrange = range(w - 1, -1, -1) |
|---|
| 1365 | else: |
|---|
| 1366 | xrange = range(w) |
|---|
| 1367 | for x in xrange: |
|---|
| 1368 | # Set pixel |
|---|
| 1369 | c = src.getGray(x, y) + ex + ey[x + 1] |
|---|
| 1370 | d = c > 0.5 |
|---|
| 1371 | dest.setGray(x, y, d) |
|---|
| 1372 | error = c - d |
|---|
| 1373 | i = (int)(c * 255.9999) |
|---|
| 1374 | if i > 127: |
|---|
| 1375 | i = 255 - i |
|---|
| 1376 | (d1, d2, d3) = m[i] |
|---|
| 1377 | t = d1 + d2 + d3 |
|---|
| 1378 | # Propagate error |
|---|
| 1379 | ex = error * d1 / t |
|---|
| 1380 | if serpentine and y & 1: |
|---|
| 1381 | newey[x + 2] += error * d3 / t |
|---|
| 1382 | newey[x + 1] += error * d2 / t |
|---|
| 1383 | else: |
|---|
| 1384 | newey[x] += error * d2 / t |
|---|
| 1385 | newey[x + 1] += error * d3 / t |
|---|
| 1386 | ey = newey |
|---|
| 1387 | return dest |
|---|
| 1388 | |
|---|
| 1389 | if chapter(3): |
|---|
| 1390 | mat = [[0, -1, |
|---|
| 1391 | '<tspan style="font-style:italic">d1(i)</tspan>'], |
|---|
| 1392 | ['<tspan style="font-style:italic">d2(i)</tspan>', |
|---|
| 1393 | '<tspan style="font-style:italic">d3(i)</tspan>', 0]] |
|---|
| 1394 | Svg(mat).save("fig3-4-1.png", 40) |
|---|
| 1395 | test341(grad256bw, True).save("grad3-4-1.png") |
|---|
| 1396 | test341(lenna256bw, True).save("out3-4-1.png") |
|---|
| 1397 | |
|---|
| 1398 | def test351(src, mat, tiles, diff, serpentine, glob): |
|---|
| 1399 | random.seed(0) |
|---|
| 1400 | (w, h) = src.size() |
|---|
| 1401 | dest = Image((w, h)) |
|---|
| 1402 | ntiles = len(tiles) |
|---|
| 1403 | ty = len(tiles[0]) |
|---|
| 1404 | tx = len(tiles[0][0]) |
|---|
| 1405 | cur = Matrix(tx, ty, 0.) |
|---|
| 1406 | w, h = w / tx, h / ty |
|---|
| 1407 | lines = len(mat) |
|---|
| 1408 | rows = len(mat[0]) |
|---|
| 1409 | offset = mat[0].index(-1) |
|---|
| 1410 | ey = Matrix(w + rows - 1, lines, 0.) |
|---|
| 1411 | for y in range(h): |
|---|
| 1412 | ex = [0.] * (rows - offset) |
|---|
| 1413 | if serpentine and y & 1: |
|---|
| 1414 | xrange = range(w - 1, -1, -1) |
|---|
| 1415 | else: |
|---|
| 1416 | xrange = range(w) |
|---|
| 1417 | for x in xrange: |
|---|
| 1418 | # Get block value |
|---|
| 1419 | for j in range(ty): |
|---|
| 1420 | for i in range(tx): |
|---|
| 1421 | e = ex[0] + ey[0][x + offset] |
|---|
| 1422 | cur[j][i] = src.getGray(x * tx + i, y * ty + j) \ |
|---|
| 1423 | + diff[j][i] * e |
|---|
| 1424 | # Compute closest block and associated error |
|---|
| 1425 | dist = tx * ty |
|---|
| 1426 | for n in range(ntiles): |
|---|
| 1427 | e = 0. |
|---|
| 1428 | d1 = 0. |
|---|
| 1429 | d2 = random.random() / 1000. |
|---|
| 1430 | for j in range(ty): |
|---|
| 1431 | for i in range(tx): |
|---|
| 1432 | e += cur[j][i] - tiles[n][j][i] |
|---|
| 1433 | d1 += diff[j][i] * (cur[j][i] - tiles[n][j][i]) |
|---|
| 1434 | d2 += diff[j][i] * abs(cur[j][i] - tiles[n][j][i]) |
|---|
| 1435 | if glob: |
|---|
| 1436 | d = abs(d1) + d2 / 1000. |
|---|
| 1437 | else: |
|---|
| 1438 | d = abs(d1) / (tx * ty) + d2 |
|---|
| 1439 | if d < dist: |
|---|
| 1440 | dist = d |
|---|
| 1441 | error = e |
|---|
| 1442 | best = n |
|---|
| 1443 | # Set pixel |
|---|
| 1444 | for j in range(ty): |
|---|
| 1445 | for i in range(tx): |
|---|
| 1446 | dest.setGray(x * tx + i, y * ty + j, tiles[best][j][i]) |
|---|
| 1447 | # Propagate first line of error |
|---|
| 1448 | for dx in range(rows - offset - 2): |
|---|
| 1449 | ex[dx] = ex[dx + 1] + error * mat[0][offset + 1 + dx] |
|---|
| 1450 | ex[rows - offset - 2] = error * mat[0][rows - 1] |
|---|
| 1451 | # Propagate next lines |
|---|
| 1452 | if serpentine and y & 1: |
|---|
| 1453 | for dy in range(1, lines): |
|---|
| 1454 | for dx in range(rows): |
|---|
| 1455 | ey[dy][x + dx] += error * mat[dy][rows - 1 - dx] |
|---|
| 1456 | else: |
|---|
| 1457 | for dy in range(1, lines): |
|---|
| 1458 | for dx in range(rows): |
|---|
| 1459 | ey[dy][x + dx] += error * mat[dy][dx] |
|---|
| 1460 | for dy in range(lines - 1): |
|---|
| 1461 | ey[dy] = ey[dy + 1] |
|---|
| 1462 | ey[lines - 1] = [0.] * (w + rows - 1) |
|---|
| 1463 | return dest |
|---|
| 1464 | |
|---|
| 1465 | LINES22 = \ |
|---|
| 1466 | [[[0., 0.], [0., 0.]], |
|---|
| 1467 | [[0., 1.], [0., 1.]], |
|---|
| 1468 | [[1., 0.], [1., 0.]], |
|---|
| 1469 | [[1., 1.], [0., 0.]], |
|---|
| 1470 | [[0., 0.], [1., 1.]], |
|---|
| 1471 | [[1., 1.], [1., 1.]]] |
|---|
| 1472 | |
|---|
| 1473 | SQUARES33 = \ |
|---|
| 1474 | [[[0., 0., 0.], [0., 0., 0.], [0., 0., 0.]], |
|---|
| 1475 | [[1., 0., 0.], [0., 0., 0.], [0., 0., 0.]], |
|---|
| 1476 | [[0., 1., 0.], [0., 0., 0.], [0., 0., 0.]], |
|---|
| 1477 | [[0., 0., 1.], [0., 0., 0.], [0., 0., 0.]], |
|---|
| 1478 | [[0., 0., 0.], [1., 0., 0.], [0., 0., 0.]], |
|---|
| 1479 | [[0., 0., 0.], [0., 1., 0.], [0., 0., 0.]], |
|---|
| 1480 | [[0., 0., 0.], [0., 0., 1.], [0., 0., 0.]], |
|---|
| 1481 | [[0., 0., 0.], [0., 0., 0.], [1., 0., 0.]], |
|---|
| 1482 | [[0., 0., 0.], [0., 0., 0.], [0., 1., 0.]], |
|---|
| 1483 | [[0., 0., 0.], [0., 0., 0.], [0., 0., 1.]], |
|---|
| 1484 | [[1., 1., 0.], [1., 1., 0.], [0., 0., 0.]], |
|---|
| 1485 | [[0., 1., 1.], [0., 1., 1.], [0., 0., 0.]], |
|---|
| 1486 | [[0., 0., 0.], [1., 1., 0.], [1., 1., 0.]], |
|---|
| 1487 | [[0., 0., 0.], [0., 1., 1.], [0., 1., 1.]], |
|---|
| 1488 | [[1., 1., 1.], [1., 1., 1.], [1., 1., 1.]]] |
|---|
| 1489 | |
|---|
| 1490 | TILES22 = [] |
|---|
| 1491 | for n in range(1 << 4): |
|---|
| 1492 | mat = Matrix(2, 2) |
|---|
| 1493 | for y in range(2): |
|---|
| 1494 | for x in range(2): |
|---|
| 1495 | if n & (1 << (y * 2 + x)): |
|---|
| 1496 | mat[y][x] = 1. |
|---|
| 1497 | TILES22.append(mat) |
|---|
| 1498 | |
|---|
| 1499 | DIFF_EVEN22 = \ |
|---|
| 1500 | [[1./4, 1./4], |
|---|
| 1501 | [1./4, 1./4]] |
|---|
| 1502 | |
|---|
| 1503 | DIFF_EVEN33 = \ |
|---|
| 1504 | [[1./9, 1./9, 1./9], |
|---|
| 1505 | [1./9, 1./9, 1./9], |
|---|
| 1506 | [1./9, 1./9, 1./9]] |
|---|
| 1507 | |
|---|
| 1508 | if chapter(3): |
|---|
| 1509 | def colorise(val): |
|---|
| 1510 | if type(val) == str: |
|---|
| 1511 | return '#fbb' |
|---|
| 1512 | return '#fff' |
|---|
| 1513 | |
|---|
| 1514 | mat = [[.25, .25], |
|---|
| 1515 | [.25, .25]] |
|---|
| 1516 | Svg(mat).save("fig3-5-2b.png", 40) |
|---|
| 1517 | mat = [[0, 0, 'a', 'b', 7./64, 7./64], |
|---|
| 1518 | [0, 0, 'c', 'd', 7./64, 7./64], |
|---|
| 1519 | [3./64, 3./64, 5./64, 5./64, 1./64, 1./64], |
|---|
| 1520 | [3./64, 3./64, 5./64, 5./64, 1./64, 1./64]] |
|---|
| 1521 | Svg(mat, colorise).save("fig3-5-2.png", 40) |
|---|
| 1522 | |
|---|
| 1523 | test351(grad256bw, [[-1, 0]], |
|---|
| 1524 | LINES22, DIFF_EVEN22, True, True).save("grad3-5-1.png") |
|---|
| 1525 | test351(lenna256bw, [[-1, 0]], |
|---|
| 1526 | LINES22, DIFF_EVEN22, True, True).save("out3-5-1.png") |
|---|
| 1527 | test351(grad256bw, ERROR_FSTEIN, |
|---|
| 1528 | LINES22, DIFF_EVEN22, True, True).save("grad3-5-2.png") |
|---|
| 1529 | test351(lenna256bw, ERROR_FSTEIN, |
|---|
| 1530 | LINES22, DIFF_EVEN22, True, True).save("out3-5-2.png") |
|---|
| 1531 | |
|---|
| 1532 | test351(grad256bw, ERROR_FSTEIN, |
|---|
| 1533 | SQUARES33, DIFF_EVEN33, True, True).save("grad3-5-3.png") |
|---|
| 1534 | test351(lenna256bw, ERROR_FSTEIN, |
|---|
| 1535 | SQUARES33, DIFF_EVEN33, True, True).save("out3-5-3.png") |
|---|
| 1536 | |
|---|
| 1537 | test351(grad256bw, ERROR_FSTEIN, |
|---|
| 1538 | TILES22, DIFF_EVEN22, True, True).save("grad3-5-4.png") |
|---|
| 1539 | test351(lenna256bw, ERROR_FSTEIN, |
|---|
| 1540 | TILES22, DIFF_EVEN22, True, True).save("out3-5-4.png") |
|---|
| 1541 | |
|---|
| 1542 | DIFF_WEIGHED22 = \ |
|---|
| 1543 | [[51./128, 33./128], |
|---|
| 1544 | [25./128, 19./128]] |
|---|
| 1545 | |
|---|
| 1546 | if chapter(3): |
|---|
| 1547 | mat = [[6./16, 5./16], |
|---|
| 1548 | [3./16, 2./16]] |
|---|
| 1549 | Svg(mat).save("fig3-5-5b.png", 40) |
|---|
| 1550 | mat = [[0, 0, 'a', 'b', 6*7./256, 5*7./256], |
|---|
| 1551 | [0, 0, 'c', 'd', 3*7./256, 2*7./256], |
|---|
| 1552 | [6*3./256, 5*3./256, 6*5./256, 5*5./256, 6*1./256, 5*1./256], |
|---|
| 1553 | [3*3./256, 2*3./256, 3*5./256, 2*5./256, 3*1./256, 2*1./256]] |
|---|
| 1554 | Svg(mat, colorise).save("fig3-5-5.png", 40) |
|---|
| 1555 | |
|---|
| 1556 | test351(grad256bw, ERROR_FSTEIN, |
|---|
| 1557 | TILES22, DIFF_WEIGHED22, True, False).save("grad3-5-5.png") |
|---|
| 1558 | test351(lenna256bw, ERROR_FSTEIN, |
|---|
| 1559 | TILES22, DIFF_WEIGHED22, True, False).save("out3-5-5.png") |
|---|
| 1560 | |
|---|
| 1561 | # Output 3.6.1: sub-block error diffusion |
|---|
| 1562 | def test361(src, tiles, propagate, diff): |
|---|
| 1563 | (w, h) = src.size() |
|---|
| 1564 | # Propagating the error to a temporary buffer is becoming more and |
|---|
| 1565 | # more complicated. We decide to use an intermediate matrix instead. |
|---|
| 1566 | tmp = Matrix(w, h, 0.) |
|---|
| 1567 | for y in range(h): |
|---|
| 1568 | for x in range(w): |
|---|
| 1569 | tmp[y][x] = Gamma.CtoI(src.getGray(x, y)) |
|---|
| 1570 | dest = Image((w, h)) |
|---|
| 1571 | # Analyse tile list |
|---|
| 1572 | ntiles = len(tiles) |
|---|
| 1573 | ty = len(tiles[0]) |
|---|
| 1574 | tx = len(tiles[0][0]) |
|---|
| 1575 | cur = Matrix(tx, ty, 0.) |
|---|
| 1576 | w, h = w / tx, h / ty |
|---|
| 1577 | # Analyse error propagate list |
|---|
| 1578 | for y in range(h): |
|---|
| 1579 | for x in range(w): |
|---|
| 1580 | # Get block value |
|---|
| 1581 | for j in range(ty): |
|---|
| 1582 | for i in range(tx): |
|---|
| 1583 | cur[j][i] = Gamma.ItoC(tmp[y * ty + j][x * tx + i]) |
|---|
| 1584 | # Select closest block |
|---|
| 1585 | dist = tx * ty |
|---|
| 1586 | for n in range(ntiles): |
|---|
| 1587 | d = 0. |
|---|
| 1588 | e = 0. |
|---|
| 1589 | for j in range(ty): |
|---|
| 1590 | for i in range(tx): |
|---|
| 1591 | d += cur[j][i] - tiles[n][j][i] |
|---|
| 1592 | e += diff[j][i] * abs(cur[j][i] - tiles[n][j][i]) |
|---|
| 1593 | if abs(d) / (tx * ty) + e < dist: |
|---|
| 1594 | dist = abs(d) / (tx * ty) + e |
|---|
| 1595 | best = n |
|---|
| 1596 | # Set pixel |
|---|
| 1597 | for j in range(ty): |
|---|
| 1598 | for i in range(tx): |
|---|
| 1599 | dest.setGray(x * tx + i, y * ty + j, tiles[best][j][i]) |
|---|
| 1600 | # Propagate error |
|---|
| 1601 | for j in range(ty): |
|---|
| 1602 | for i in range(tx): |
|---|
| 1603 | e = Gamma.CtoI(cur[j][i]) - Gamma.CtoI(tiles[best][j][i]) |
|---|
| 1604 | m = propagate[j][i] |
|---|
| 1605 | for py in range(len(m)): |
|---|
| 1606 | for px in range(len(m[0])): |
|---|
| 1607 | if m[py][px] == 0: |
|---|
| 1608 | continue |
|---|
| 1609 | if m[py][px] == -1: |
|---|
| 1610 | cx, cy = px, py |
|---|
| 1611 | continue |
|---|
| 1612 | tmpx = x * tx + i + px - cx |
|---|
| 1613 | tmpy = y * ty + j + py - cy |
|---|
| 1614 | if tmpx > w * tx - 1 or tmpy > h * ty - 1: |
|---|
| 1615 | continue |
|---|
| 1616 | tmp[tmpy][tmpx] += m[py][px] * e |
|---|
| 1617 | return dest |
|---|
| 1618 | |
|---|
| 1619 | ERROR_SUBFS22 = \ |
|---|
| 1620 | [[[[0, -1, 0, 8./64], |
|---|
| 1621 | [0, 0, 0, 10./64], |
|---|
| 1622 | [7./64, 22./64, 15./64, 2./64]], |
|---|
| 1623 | [[0, 0, -1, 20./64], |
|---|
| 1624 | [0, 0, 0, 14./64], |
|---|
| 1625 | [2./64, 11./64, 15./64, 2./64]]], |
|---|
| 1626 | [[[0, 0, 0, 0./64], |
|---|
| 1627 | [0, -1, 0, 6./64], |
|---|
| 1628 | [12./64, 32./64, 13./64, 1./64]], |
|---|
| 1629 | [[0, 0, 0, 0./64], |
|---|
| 1630 | [0, 0, -1, 20./64], |
|---|
| 1631 | [0./64, 12./64, 28./64, 4./64]]]] |
|---|
| 1632 | |
|---|
| 1633 | if chapter(3): |
|---|
| 1634 | test361(grad256bw, TILES22, |
|---|
| 1635 | ERROR_SUBFS22, DIFF_WEIGHED22).save("grad3-6-1.png") |
|---|
| 1636 | test361(lenna256bw, TILES22, |
|---|
| 1637 | ERROR_SUBFS22, DIFF_WEIGHED22).save("out3-6-1.png") |
|---|
| 1638 | |
|---|
| 1639 | test361(grad256bw, LINES22, |
|---|
| 1640 | ERROR_SUBFS22, DIFF_WEIGHED22).save("grad3-6-2.png") |
|---|
| 1641 | test361(lenna256bw, LINES22, |
|---|
| 1642 | ERROR_SUBFS22, DIFF_WEIGHED22).save("out3-6-2.png") |
|---|
| 1643 | |
|---|
| 1644 | def colorise(val): |
|---|
| 1645 | if val == '': |
|---|
| 1646 | return '#ccc' |
|---|
| 1647 | if type(val) == str: |
|---|
| 1648 | return '#fbb' |
|---|
| 1649 | return '#fff' |
|---|
| 1650 | |
|---|
| 1651 | # XXX: hack, we modify ERROR_SUBFS22 because it's so much more convenient |
|---|
| 1652 | for y in range(2): |
|---|
| 1653 | for x in range(2): |
|---|
| 1654 | for j in range(2): |
|---|
| 1655 | for i in range(1, 3): |
|---|
| 1656 | ERROR_SUBFS22[y][x][j][i] = '' |
|---|
| 1657 | ERROR_SUBFS22[y][x][y][1 + x] = chr(ord('a') + 2 * y + x) |
|---|
| 1658 | Svg(ERROR_SUBFS22[0][0], colorise).save("fig3-6-1a.png", 40) |
|---|
| 1659 | Svg(ERROR_SUBFS22[0][1], colorise).save("fig3-6-1b.png", 40) |
|---|
| 1660 | Svg(ERROR_SUBFS22[1][0], colorise).save("fig3-6-1c.png", 40) |
|---|
| 1661 | Svg(ERROR_SUBFS22[1][1], colorise).save("fig3-6-1d.png", 40) |
|---|
| 1662 | for y in range(2): |
|---|
| 1663 | for x in range(2): |
|---|
| 1664 | for j in range(2): |
|---|
| 1665 | for i in range(1, 3): |
|---|
| 1666 | ERROR_SUBFS22[y][x][j][i] = 0 |
|---|
| 1667 | ERROR_SUBFS22[y][x][y][1 + x] = -1 |
|---|
| 1668 | |
|---|
| 1669 | ERROR_SUBFS33 = \ |
|---|
| 1670 | [[[[ 0, -1, 0, 0, 2./64], |
|---|
| 1671 | [ 0, 0, 0, 0, 5./64], |
|---|
| 1672 | [ 0, 0, 0, 0, 6./64], |
|---|
| 1673 | [ 5./64, 17./64, 17./64, 9./64, 1./64]], |
|---|
| 1674 | [[ 0, 0, -1, 0, 6./64], |
|---|
| 1675 | [ 0, 0, 0, 0, 9./64], |
|---|
| 1676 | [ 0, 0, 0, 0, 8./64], |
|---|
| 1677 | [ 2./64, 11./64, 16./64, 11./64, 1./64]], |
|---|
| 1678 | [[ 0, 0, 0, -1, 20./64], |
|---|
| 1679 | [ 0, 0, 0, 0, 14./64], |
|---|
| 1680 | [ 0, 0, 0, 0, 8./64], |
|---|
| 1681 | [ 0, 3./64, 9./64, 9./64, 1./64]]], |
|---|
| 1682 | [[[ 0, 0, 0, 0, 0], |
|---|
| 1683 | [ 0, -1, 0, 0, 2./64], |
|---|
| 1684 | [ 0, 0, 0, 0, 5./64], |
|---|
| 1685 | [ 7./64, 23./64, 18./64, 8./64, 1./64]], |
|---|
| 1686 | [[ 0, 0, 0, 0, 0], |
|---|
| 1687 | [ 0, 0, -1, 0, 6./64], |
|---|
| 1688 | [ 0, 0, 0, 0, 9./64], |
|---|
| 1689 | [ 2./64, 12./64, 21./64, 13./64, 1./64]], |
|---|
| 1690 | [[ 0, 0, 0, 0, 0], |
|---|
| 1691 | [ 0, 0, 0, -1, 20./64], |
|---|
| 1692 | [ 0, 0, 0, 0, 14./64], |
|---|
| 1693 | [ 0, 2./64, 11./64, 15./64, 2./64]]], |
|---|
| 1694 | [[[ 0, 0, 0, 0, 0], |
|---|
| 1695 | [ 0, 0, 0, 0, 0], |
|---|
| 1696 | [ 0, -1, 0, 0, 2./64], |
|---|
| 1697 | [12./64, 32./64, 14./64, 4./64, 0]], |
|---|
| 1698 | [[ 0, 0, 0, 0, 0], |
|---|
| 1699 | [ 0, 0, 0, 0, 0], |
|---|
| 1700 | [ 0, 0, -1, 0, 6./64], |
|---|
| 1701 | [ 0, 12./64, 32./64, 13./64, 1./64]], |
|---|
| 1702 | [[ 0, 0, 0, 0, 0], |
|---|
| 1703 | [ 0, 0, 0, 0, 0], |
|---|
| 1704 | [ 0, 0, 0, -1, 20./64], |
|---|
| 1705 | [ 0, 0, 12./64, 28./64, 4./64]]]] |
|---|
| 1706 | |
|---|
| 1707 | TILES33 = [] |
|---|
| 1708 | for n in range(1 << 9): |
|---|
| 1709 | mat = Matrix(3, 3) |
|---|
| 1710 | for y in range(3): |
|---|
| 1711 | for x in range(3): |
|---|
| 1712 | if n & (1 << (y * 3 + x)): |
|---|
| 1713 | mat[y][x] = 1. |
|---|
| 1714 | TILES33.append(mat) |
|---|
| 1715 | |
|---|
| 1716 | DIFF_WEIGHED33 = \ |
|---|
| 1717 | [[15./64, 10./64, 6./64], |
|---|
| 1718 | [10./64, 6./64, 4./64], |
|---|
| 1719 | [ 6./64, 4./64, 3./64]] |
|---|
| 1720 | |
|---|
| 1721 | if chapter(3): |
|---|
| 1722 | test361(grad256bw, TILES33, |
|---|
| 1723 | ERROR_SUBFS33, DIFF_WEIGHED33).save("grad3-6-3.png") |
|---|
| 1724 | test361(lenna256bw, TILES33, |
|---|
| 1725 | ERROR_SUBFS33, DIFF_WEIGHED33).save("out3-6-3.png") |
|---|
| 1726 | test361(grad256bw, SQUARES33, |
|---|
| 1727 | ERROR_SUBFS33, DIFF_WEIGHED33).save("grad3-6-4.png") |
|---|
| 1728 | test361(lenna256bw, SQUARES33, |
|---|
| 1729 | ERROR_SUBFS33, DIFF_WEIGHED33).save("out3-6-4.png") |
|---|
| 1730 | |
|---|
| 1731 | # XXX: hack, we modify ERROR_SUBFS33 because it's so much more convenient |
|---|
| 1732 | for y in range(3): |
|---|
| 1733 | for x in range(3): |
|---|
| 1734 | for j in range(3): |
|---|
| 1735 | for i in range(1, 4): |
|---|
| 1736 | ERROR_SUBFS33[y][x][j][i] = '' |
|---|
| 1737 | ERROR_SUBFS33[y][x][y][1 + x] = chr(ord('a') + 3 * y + x) |
|---|
| 1738 | Svg(ERROR_SUBFS33[0][0], colorise).save("fig3-6-3a.png", 30) |
|---|
| 1739 | Svg(ERROR_SUBFS33[0][1], colorise).save("fig3-6-3b.png", 30) |
|---|
| 1740 | Svg(ERROR_SUBFS33[0][2], colorise).save("fig3-6-3c.png", 30) |
|---|
| 1741 | Svg(ERROR_SUBFS33[1][0], colorise).save("fig3-6-3d.png", 30) |
|---|
| 1742 | Svg(ERROR_SUBFS33[1][1], colorise).save("fig3-6-3e.png", 30) |
|---|
| 1743 | Svg(ERROR_SUBFS33[1][2], colorise).save("fig3-6-3f.png", 30) |
|---|
| 1744 | Svg(ERROR_SUBFS33[2][0], colorise).save("fig3-6-3g.png", 30) |
|---|
| 1745 | Svg(ERROR_SUBFS33[2][1], colorise).save("fig3-6-3h.png", 30) |
|---|
| 1746 | Svg(ERROR_SUBFS33[2][2], colorise).save("fig3-6-3i.png", 30) |
|---|
| 1747 | for y in range(3): |
|---|
| 1748 | for x in range(3): |
|---|
| 1749 | for j in range(3): |
|---|
| 1750 | for i in range(1, 4): |
|---|
| 1751 | ERROR_SUBFS33[y][x][j][i] = 0 |
|---|
| 1752 | ERROR_SUBFS33[y][x][y][1 + x] = -1 |
|---|
| 1753 | |
|---|
| 1754 | # Output 3.7.1: direct binary search, iteration 0 |
|---|
| 1755 | # Output 3.7.2: direct binary search, iteration 1 |
|---|
| 1756 | # Output 3.7.3: direct binary search, iteration 2 |
|---|
| 1757 | # Output 3.7.4: direct binary search, iteration 5 |
|---|
| 1758 | def test37x(src): |
|---|
| 1759 | random.seed(0) |
|---|
| 1760 | (w, h) = src.size() |
|---|
| 1761 | dest = Image((w, h)) |
|---|
| 1762 | for y in range(h): |
|---|
| 1763 | for x in range(w): |
|---|
| 1764 | c = src.getGray(x, y) + random.random() - 0.5 |
|---|
| 1765 | d = c > 0.5 |
|---|
| 1766 | dest.setGray(x, y, d) |
|---|
| 1767 | return dest |
|---|
| 1768 | |
|---|
| 1769 | def test37y(src, dest): |
|---|
| 1770 | threshold = 0.4 |
|---|
| 1771 | kernel = Matrix(6, 6, 0.) # have a border of zeroes |
|---|
| 1772 | for j in range(4): |
|---|
| 1773 | for i in range(4): |
|---|
| 1774 | kernel[j][i] = math.pow(math.e, - math.sqrt(i * i + j * j)) |
|---|
| 1775 | (w, h) = src.size() |
|---|
| 1776 | # Build fast pixel lookup tables |
|---|
| 1777 | srcmat = Matrix(w, h, 0.) |
|---|
| 1778 | destmat = Matrix(w, h, 0.) |
|---|
| 1779 | for y in range(h): |
|---|
| 1780 | for x in range(w): |
|---|
| 1781 | srcmat[y][x] = src.getGray(x, y) |
|---|
| 1782 | destmat[y][x] = dest.getGray(x, y) |
|---|
| 1783 | # Build human perception model for both source and destination |
|---|
| 1784 | srchvs = Matrix(w, h, 0.) |
|---|
| 1785 | desthvs = Matrix(w, h, 0.) |
|---|
| 1786 | for y in range(h): |
|---|
| 1787 | for x in range(w): |
|---|
| 1788 | srcp = destp = 0. |
|---|
| 1789 | for j in range(-3, 4): |
|---|
| 1790 | if y + j < 0 or y + j >= h: |
|---|
| 1791 | continue |
|---|
| 1792 | for i in range(-3, 4): |
|---|
| 1793 | if x + i < 0 or x + i >= w: |
|---|
| 1794 | continue |
|---|
| 1795 | m = kernel[abs(j)][abs(i)] |
|---|
| 1796 | srcp += m * srcmat[y + j][x + i] |
|---|
| 1797 | destp += m * destmat[y + j][x + i] |
|---|
| 1798 | srchvs[y][x] = srcp |
|---|
| 1799 | desthvs[y][x] = destp |
|---|
| 1800 | swaps = toggles = 0 |
|---|
| 1801 | for y in range(h): |
|---|
| 1802 | for x in range(w): |
|---|
| 1803 | d = destmat[y][x] |
|---|
| 1804 | best = 0. |
|---|
| 1805 | # Compute the effect of a toggle |
|---|
| 1806 | e = 0. |
|---|
| 1807 | for j in range(-3, 4): |
|---|
| 1808 | if y + j < 0 or y + j >= h: |
|---|
| 1809 | continue |
|---|
| 1810 | for i in range(-3, 4): |
|---|
| 1811 | if x + i < 0 or x + i >= w: |
|---|
| 1812 | continue |
|---|
| 1813 | m = kernel[abs(j)][abs(i)] |
|---|
| 1814 | p = srchvs[y + j][x + i] |
|---|
| 1815 | q1 = desthvs[y + j][x + i] |
|---|
| 1816 | q2 = q1 - m * d + m * (1. - d) |
|---|
| 1817 | e += abs(q1 - p) - abs(q2 - p) |
|---|
| 1818 | if e > best: |
|---|
| 1819 | best = e |
|---|
| 1820 | op = False |
|---|
| 1821 | # Compute the effect of swaps |
|---|
| 1822 | for dx, dy in [(0, 1), (0, -1), (-1, 0), (1, 0)]: |
|---|
| 1823 | if y + dy < 0 or y + dy >= h or x + dx < 0 or x + dx >= w: |
|---|
| 1824 | continue |
|---|
| 1825 | d2 = destmat[y + dy][x + dx] |
|---|
| 1826 | if d2 == d: |
|---|
| 1827 | continue |
|---|
| 1828 | e = 0. |
|---|
| 1829 | for j in range(-4, 5): |
|---|
| 1830 | for i in range(-4, 5): |
|---|
| 1831 | if y + j < 0 or y + j >= h or x + i < 0 or x + i >= w: |
|---|
| 1832 | continue |
|---|
| 1833 | ma = kernel[abs(j)][abs(i)] |
|---|
| 1834 | mb = kernel[abs(j - dy)][abs(i - dx)] |
|---|
| 1835 | p = srchvs[y + j][x + i] |
|---|
| 1836 | q1 = desthvs[y + j][x + i] |
|---|
| 1837 | q2 = q1 - ma * d + ma * d2 - mb * d2 + mb * d |
|---|
| 1838 | e += abs(q1 - p) - abs(q2 - p) |
|---|
| 1839 | if e > best: |
|---|
| 1840 | best = e |
|---|
| 1841 | op = (dx, dy) |
|---|
| 1842 | # Apply the change if interesting |
|---|
| 1843 | if best <= 0.: |
|---|
| 1844 | continue |
|---|
| 1845 | if op: |
|---|
| 1846 | dx, dy = op |
|---|
| 1847 | d2 = destmat[y + dy][x + dx] |
|---|
| 1848 | destmat[y + dy][x + dx] = d |
|---|
| 1849 | else: |
|---|
| 1850 | d2 = 1. - d |
|---|
| 1851 | destmat[y][x] = d2 |
|---|
| 1852 | for j in range(-3, 4): |
|---|
| 1853 | for i in range(-3, 4): |
|---|
| 1854 | m = kernel[abs(j)][abs(i)] |
|---|
| 1855 | if y + j >= 0 and y + j < h and x + i >= 0 and x + i < w: |
|---|
| 1856 | desthvs[y + j][x + i] -= m * d |
|---|
| 1857 | desthvs[y + j][x + i] += m * d2 |
|---|
| 1858 | if op and y + dy + j >= 0 and y + dy + j < h \ |
|---|
| 1859 | and x + dx + i >= 0 and x + dx + i < w: |
|---|
| 1860 | desthvs[y + dy + j][x + dx + i] -= m * d2 |
|---|
| 1861 | desthvs[y + dy + j][x + dx + i] += m * d |
|---|
| 1862 | for y in range(h): |
|---|
| 1863 | for x in range(w): |
|---|
| 1864 | dest.setGray(x, y, destmat[y][x]) |
|---|
| 1865 | return dest |
|---|
| 1866 | |
|---|
| 1867 | if chapter(3): |
|---|
| 1868 | tmp = test37x(grad256bw) |
|---|
| 1869 | tmp.save("grad3-7-1.png") |
|---|
| 1870 | tmp = test37y(grad256bw, tmp) |
|---|
| 1871 | tmp.save("grad3-7-2.png") |
|---|
| 1872 | tmp = test37y(grad256bw, tmp) |
|---|
| 1873 | tmp.save("grad3-7-3.png") |
|---|
| 1874 | tmp = test37y(grad256bw, tmp) |
|---|
| 1875 | tmp = test37y(grad256bw, tmp) |
|---|
| 1876 | tmp = test37y(grad256bw, tmp) |
|---|
| 1877 | tmp.save("grad3-7-4.png") |
|---|
| 1878 | |
|---|
| 1879 | if chapter(3): |
|---|
| 1880 | tmp = test37x(lenna256bw) |
|---|
| 1881 | tmp.save("out3-7-1.png") |
|---|
| 1882 | tmp = test37y(lenna256bw, tmp) |
|---|
| 1883 | tmp.save("out3-7-2.png") |
|---|
| 1884 | tmp = test37y(lenna256bw, tmp) |
|---|
| 1885 | tmp.save("out3-7-3.png") |
|---|
| 1886 | tmp = test37y(lenna256bw, tmp) |
|---|
| 1887 | tmp = test37y(lenna256bw, tmp) |
|---|
| 1888 | tmp = test37y(lenna256bw, tmp) |
|---|
| 1889 | tmp.save("out3-7-4.png") |
|---|
| 1890 | |
|---|
| 1891 | ############################################################################## |
|---|
| 1892 | if chapter(4): |
|---|
| 1893 | print "Chapter 4. Greyscale dithering" |
|---|
| 1894 | |
|---|
| 1895 | # Output 4.0.1: 4x4 Bayer dithering, 3 colours |
|---|
| 1896 | def test401(src, mat): |
|---|
| 1897 | (w, h) = src.size() |
|---|
| 1898 | dest = Image((w, h)) |
|---|
| 1899 | dx = len(mat[0]) |
|---|
| 1900 | dy = len(mat) |
|---|
| 1901 | for y in range(h): |
|---|
| 1902 | for x in range(w): |
|---|
| 1903 | c = src.getGray(x, y) |
|---|
| 1904 | threshold = (1. + mat[y % dy][x % dx]) / (dx * dy + 1) |
|---|
| 1905 | if c < 0.5: |
|---|
| 1906 | c = 0.5 * (c > threshold / 2) |
|---|
| 1907 | else: |
|---|
| 1908 | c = 0.5 + 0.5 * (c > 0.5 + threshold / 2) |
|---|
| 1909 | dest.setGray(x, y, c) |
|---|
| 1910 | return dest |
|---|
| 1911 | |
|---|
| 1912 | if chapter(4): |
|---|
| 1913 | test401(grad256bw, DITHER_BAYER88).save("grad4-0-1.png") |
|---|
| 1914 | test401(lenna256bw, DITHER_BAYER88).save("out4-0-1.png") |
|---|
| 1915 | |
|---|
| 1916 | # Output 4.0.2: standard Floyd-Steinberg, 3 colours |
|---|
| 1917 | def test402(src, mat, serpentine): |
|---|
| 1918 | (w, h) = src.size() |
|---|
| 1919 | dest = Image((w, h)) |
|---|
| 1920 | lines = len(mat) |
|---|
| 1921 | rows = len(mat[0]) |
|---|
| 1922 | offset = mat[0].index(-1) |
|---|
| 1923 | ey = Matrix(w + rows - 1, lines, 0.) |
|---|
| 1924 | for y in range(h): |
|---|
| 1925 | ex = [0.] * (rows - offset) |
|---|
| 1926 | if serpentine and y & 1: |
|---|
| 1927 | xrange = range(w - 1, -1, -1) |
|---|
| 1928 | else: |
|---|
| 1929 | xrange = range(w) |
|---|
| 1930 | for x in xrange: |
|---|
| 1931 | # Set pixel |
|---|
| 1932 | c = src.getGray(x, y) + ex[0] + ey[0][x + offset] |
|---|
| 1933 | d = 0.5 * (c > 0.25) + 0.5 * (c > 0.75) |
|---|
| 1934 | dest.setGray(x, y, d) |
|---|
| 1935 | error = c - d |
|---|
| 1936 | # Propagate first line of error |
|---|
| 1937 | for dx in range(rows - offset - 2): |
|---|
| 1938 | ex[dx] = ex[dx + 1] + error * mat[0][offset + 1 + dx] |
|---|
| 1939 | ex[rows - offset - 2] = error * mat[0][rows - 1] |
|---|
| 1940 | # Propagate next lines |
|---|
| 1941 | if serpentine and y & 1: |
|---|
| 1942 | for dy in range(1, lines): |
|---|
| 1943 | for dx in range(rows): |
|---|
| 1944 | ey[dy][x + dx] += error * mat[dy][rows - 1 - dx] |
|---|
| 1945 | else: |
|---|
| 1946 | for dy in range(1, lines): |
|---|
| 1947 | for dx in range(rows): |
|---|
| 1948 | ey[dy][x + dx] += error * mat[dy][dx] |
|---|
| 1949 | for dy in range(lines - 1): |
|---|
| 1950 | ey[dy] = ey[dy + 1] |
|---|
| 1951 | ey[lines - 1] = [0.] * (w + rows - 1) |
|---|
| 1952 | return dest |
|---|
| 1953 | |
|---|
| 1954 | if chapter(4): |
|---|
| 1955 | test402(grad256bw, ERROR_FSTEIN, True).save("grad4-0-2.png") |
|---|
| 1956 | test402(lenna256bw, ERROR_FSTEIN, True).save("out4-0-2.png") |
|---|
| 1957 | |
|---|
| 1958 | # Pattern 4.1.1: gamma-corrected 50% grey, black-white halftone, 50% grey |
|---|
| 1959 | if chapter(4): |
|---|
| 1960 | dest = Image((240, 80)) |
|---|
| 1961 | for y in range(80): |
|---|
| 1962 | for x in range(80): |
|---|
| 1963 | dest.setGray(x, y, Gamma.ItoC(0.5)) |
|---|
| 1964 | for x in range(80, 160): |
|---|
| 1965 | c = (x + y) & 1 |
|---|
| 1966 | dest.setGray(x, y, c) |
|---|
| 1967 | for x in range(160, 240): |
|---|
| 1968 | dest.setGray(x, y, 0.5) |
|---|
| 1969 | dest.save("pat4-1-1.png") |
|---|
| 1970 | |
|---|
| 1971 | # Output 4.2.1: gamma-corrected 2-colour Floyd-Steinberg |
|---|
| 1972 | # Output 4.2.2: gamma-corrected 3-colour Floyd-Steinberg |
|---|
| 1973 | # Output 4.2.3: gamma-corrected 4-colour Floyd-Steinberg |
|---|
| 1974 | def test42x(src, mat, serpentine, threshold): |
|---|
| 1975 | (w, h) = src.size() |
|---|
| 1976 | dest = Image((w, h)) |
|---|
| 1977 | lines = len(mat) |
|---|
| 1978 | rows = len(mat[0]) |
|---|
| 1979 | offset = mat[0].index(-1) |
|---|
| 1980 | ey = Matrix(w + rows - 1, lines, 0.) |
|---|
| 1981 | for y in range(h): |
|---|
| 1982 | ex = [0.] * (rows - offset) |
|---|
| 1983 | if serpentine and y & 1: |
|---|
| 1984 | xrange = range(w - 1, -1, -1) |
|---|
| 1985 | else: |
|---|
| 1986 | xrange = range(w) |
|---|
| 1987 | for x in xrange: |
|---|
| 1988 | # Set pixel |
|---|
| 1989 | c = Gamma.CtoI(src.getGray(x, y)) + ex[0] + ey[0][x + offset] |
|---|
| 1990 | d = threshold(c) |
|---|
| 1991 | dest.setGray(x, y, Gamma.ItoC(d)) |
|---|
| 1992 | error = c - d |
|---|
| 1993 | # Propagate first line of error |
|---|
| 1994 | for dx in range(rows - offset - 2): |
|---|
| 1995 | ex[dx] = ex[dx + 1] + error * mat[0][offset + 1 + dx] |
|---|
| 1996 | ex[rows - offset - 2] = error * mat[0][rows - 1] |
|---|
| 1997 | # Propagate next lines |
|---|
| 1998 | if serpentine and y & 1: |
|---|
| 1999 | for dy in range(1, lines): |
|---|
| 2000 | for dx in range(rows): |
|---|
| 2001 | ey[dy][x + dx] += error * mat[dy][rows - 1 - dx] |
|---|
| 2002 | else: |
|---|
| 2003 | for dy in range(1, lines): |
|---|
| 2004 | for dx in range(rows): |
|---|
| 2005 | ey[dy][x + dx] += error * mat[dy][dx] |
|---|
| 2006 | for dy in range(lines - 1): |
|---|
| 2007 | ey[dy] = ey[dy + 1] |
|---|
| 2008 | ey[lines - 1] = [0.] * (w + rows - 1) |
|---|
| 2009 | return dest |
|---|
| 2010 | |
|---|
| 2011 | if chapter(4): |
|---|
| 2012 | test42x(grad256bw, ERROR_FSTEIN, True, Gamma.Cto2).save("grad4-2-1.png") |
|---|
| 2013 | test42x(lenna256bw, ERROR_FSTEIN, True, Gamma.Cto2).save("out4-2-1.png") |
|---|
| 2014 | test42x(grad256bw, ERROR_FSTEIN, True, Gamma.Cto3).save("grad4-2-2.png") |
|---|
| 2015 | test42x(lenna256bw, ERROR_FSTEIN, True, Gamma.Cto3).save("out4-2-2.png") |
|---|
| 2016 | test42x(grad256bw, ERROR_FSTEIN, True, Gamma.Cto4).save("grad4-2-3.png") |
|---|
| 2017 | test42x(lenna256bw, ERROR_FSTEIN, True, Gamma.Cto4).save("out4-2-3.png") |
|---|
| 2018 | |
|---|
| 2019 | # Output 4.3.1: full 4-colour block error diffusion |
|---|
| 2020 | GREY22 = [] |
|---|
| 2021 | for n in range(4*4*4*4): |
|---|
| 2022 | vals = [0., 0.333, 0.666, 1.] |
|---|
| 2023 | a, b, c, d = n & 3, (n >> 2) & 3, (n >> 4) & 3, (n >> 6) & 3 |
|---|
| 2024 | GREY22.append([[vals[a], vals[b]], [vals[c], vals[d]]]) |
|---|
| 2025 | |
|---|
| 2026 | if chapter(4): |
|---|
| 2027 | test361(grad256bw, GREY22, |
|---|
| 2028 | ERROR_SUBFS22, DIFF_WEIGHED22).save("grad4-3-1.png") |
|---|
| 2029 | test361(lenna256bw, GREY22, |
|---|
| 2030 | ERROR_SUBFS22, DIFF_WEIGHED22).save("out4-3-1.png") |
|---|
| 2031 | |
|---|
| 2032 | # Output 4.3.2: 4-colour block error diffusion with only line tiles |
|---|
| 2033 | GREYLINES22 = [] |
|---|
| 2034 | for n in range(4*4*4*4): |
|---|
| 2035 | vals = [0., 0.333, 0.666, 1.] |
|---|
| 2036 | a, b, c, d = n & 3, (n >> 2) & 3, (n >> 4) & 3, (n >> 6) & 3 |
|---|
| 2037 | if (a != b or c != d) and (a != c or b != d): |
|---|
| 2038 | continue |
|---|
| 2039 | GREYLINES22.append([[vals[a], vals[b]], [vals[c], vals[d]]]) |
|---|
| 2040 | |
|---|
| 2041 | if chapter(4): |
|---|
| 2042 | test361(grad256bw, GREYLINES22, |
|---|
| 2043 | ERROR_SUBFS22, DIFF_WEIGHED22).save("grad4-3-2.png") |
|---|
| 2044 | test361(lenna256bw, GREYLINES22, |
|---|
| 2045 | ERROR_SUBFS22, DIFF_WEIGHED22).save("out4-3-2.png") |
|---|
| 2046 | |
|---|
| 2047 | ############################################################################## |
|---|
| 2048 | if chapter(5): |
|---|
| 2049 | print "Chapter 5. Colour dithering" |
|---|
| 2050 | |
|---|
| 2051 | # Pattern 5.1.1: 8-colour palette |
|---|
| 2052 | if chapter(5): |
|---|
| 2053 | dest = Image((512, 64)) |
|---|
| 2054 | for x in range(512): |
|---|
| 2055 | d = x / 64 |
|---|
| 2056 | r = (d & 2) >> 1 |
|---|
| 2057 | g = (d & 4) >> 2 |
|---|
| 2058 | b = d & 1 |
|---|
| 2059 | for y in range(64): |
|---|
| 2060 | dest.setRgb(x, y, r, g, b) |
|---|
| 2061 | dest.save("pat5-1-1.png") |
|---|
| 2062 | |
|---|
| 2063 | # Output 5.1.1: 8-colour Floyd-Steinberg RGB dithering |
|---|
| 2064 | # Output 5.1.2: 8-colour gamma-corrected Floyd-Steinberg RGB dithering |
|---|
| 2065 | def test51x(src, mat, func): |
|---|
| 2066 | (w, h) = src.size() |
|---|
| 2067 | dest = Image((w, h)) |
|---|
| 2068 | tmp = [Image((w, h)), Image((w, h)), Image((w, h))] |
|---|
| 2069 | for y in range(h): |
|---|
| 2070 | for x in range(w): |
|---|
| 2071 | rgb = src.getRgb(x, y) |
|---|
| 2072 | for i in range(3): |
|---|
| 2073 | tmp[i].setGray(x, y, rgb[i]) |
|---|
| 2074 | for i in range(3): |
|---|
| 2075 | tmp[i] = func(tmp[i], mat, True, Gamma.Cto2) |
|---|
| 2076 | for y in range(h): |
|---|
| 2077 | for x in range(w): |
|---|
| 2078 | (r, g, b) = [tmp[i].getGray(x, y) for i in range(3)] |
|---|
| 2079 | dest.setRgb(x, y, r, g, b) |
|---|
| 2080 | return dest |
|---|
| 2081 | |
|---|
| 2082 | def test51y(src, mat, serpentine, threshold): |
|---|
| 2083 | return test3xx(src, mat, serpentine) |
|---|
| 2084 | |
|---|
| 2085 | if chapter(5): |
|---|
| 2086 | test51x(grad256, ERROR_FSTEIN, test51y).save("grad5-1-1.png") |
|---|
| 2087 | test51x(lenna256, ERROR_FSTEIN, test51y).save("out5-1-1.png") |
|---|
| 2088 | test51x(grad256, ERROR_FSTEIN, test42x).save("grad5-1-2.png") |
|---|
| 2089 | test51x(lenna256, ERROR_FSTEIN, test42x).save("out5-1-2.png") |
|---|
| 2090 | |
|---|
| 2091 | # Pattern 5.2.1: different colours give the same result |
|---|
| 2092 | if chapter(5): |
|---|
| 2093 | dest = Image((320, 160)) |
|---|
| 2094 | for x in range(80): |
|---|
| 2095 | for y in range(80): |
|---|
| 2096 | r = DITHER_BAYER44[(y / 8) % 4][(x / 8) % 4] > 7 |
|---|
| 2097 | g = DITHER_BAYER44[(y / 8) % 4][(x / 8) % 4] > 13 |
|---|
| 2098 | b = DITHER_BAYER44[(y / 8) % 4][(x / 8) % 4] > 13 |
|---|
| 2099 | dest.setRgb(x, y, b, g, r) |
|---|
| 2100 | for y in range(80, 160): |
|---|
| 2101 | r = DITHER_BAYER44[y % 4][x % 4] > 7 |
|---|
| 2102 | g = DITHER_BAYER44[y % 4][x % 4] > 13 |
|---|
| 2103 | b = DITHER_BAYER44[y % 4][x % 4] > 13 |
|---|
| 2104 | dest.setRgb(x, y, b, g, r) |
|---|
| 2105 | for x in range(80, 160): |
|---|
| 2106 | for y in range(80): |
|---|
| 2107 | r = DITHER_BAYER44[(y / 8) % 4][(x / 8 - 1) % 4] > 7 |
|---|
| 2108 | g = DITHER_BAYER44[(y / 8) % 4][(x / 8) % 4] > 13 |
|---|
| 2109 | b = DITHER_BAYER44[(y / 8) % 4][(x / 8) % 4] > 13 |
|---|
| 2110 | dest.setRgb(x, y, b, g, r) |
|---|
| 2111 | for y in range(80, 160): |
|---|
| 2112 | r = DITHER_BAYER44[y % 4][(x - 1) % 4] > 7 |
|---|
| 2113 | g = DITHER_BAYER44[y % 4][x % 4] > 13 |
|---|
| 2114 | b = DITHER_BAYER44[y % 4][x % 4] > 13 |
|---|
| 2115 | dest.setRgb(x, y, b, g, r) |
|---|
| 2116 | for x in range(160, 240): |
|---|
| 2117 | for y in range(80): |
|---|
| 2118 | r = DITHER_BAYER44[(y / 8 + 1) % 4][(x / 8 + 1) % 4] > 7 |
|---|
| 2119 | g = DITHER_BAYER44[(y / 8) % 4][(x / 8) % 4] > 13 |
|---|
| 2120 | b = DITHER_BAYER44[(y / 8 + 1) % 4][(x / 8) % 4] > 13 |
|---|
| 2121 | dest.setRgb(x, y, b, g, r) |
|---|
| 2122 | for y in range(80, 160): |
|---|
| 2123 | r = DITHER_BAYER44[(y + 1) % 4][(x + 1) % 4] > 7 |
|---|
| 2124 | g = DITHER_BAYER44[y % 4][x % 4] > 13 |
|---|
| 2125 | b = DITHER_BAYER44[(y + 1) % 4][x % 4] > 13 |
|---|
| 2126 | dest.setRgb(x, y, b, g, r) |
|---|
| 2127 | for x in range(240, 320): |
|---|
| 2128 | for y in range(80): |
|---|
| 2129 | r = DITHER_BAYER44[(y / 8 + 1) % 4][(x / 8) % 4] > 7 |
|---|
| 2130 | g = DITHER_BAYER44[(y / 8) % 4][(x / 8) % 4] > 13 |
|---|
| 2131 | b = DITHER_BAYER44[(y / 8) % 4][(x / 8 + 2) % 4] > 13 |
|---|
| 2132 | dest.setRgb(x, y, b, g, r) |
|---|
| 2133 | for y in range(80, 160): |
|---|
| 2134 | r = DITHER_BAYER44[(y + 1) % 4][x % 4] > 7 |
|---|
| 2135 | g = DITHER_BAYER44[y % 4][x % 4] > 13 |
|---|
| 2136 | b = DITHER_BAYER44[y % 4][(x + 2) % 4] > 13 |
|---|
| 2137 | dest.setRgb(x, y, b, g, r) |
|---|
| 2138 | dest.save("pat5-2-1.png") |
|---|
| 2139 | |
|---|
| 2140 | # Pattern 5.2.2: 16-colour palette |
|---|
| 2141 | if chapter(5): |
|---|
| 2142 | dest = Image((512, 128)) |
|---|
| 2143 | for x in range(64): |
|---|
| 2144 | for y in range(64): |
|---|
| 2145 | dest.setGray(x, y, 0) |
|---|
| 2146 | dest.setGray(448 + x, y, 0.7) |
|---|
| 2147 | dest.setGray(x, 64 + y, 0.3) |
|---|
| 2148 | dest.setGray(448 + x, 64 + y, 1.) |
|---|
| 2149 | for x in range(64, 448): |
|---|
| 2150 | d = x / 64 |
|---|
| 2151 | r = (d & 2) >> 1 |
|---|
| 2152 | g = (d & 4) >> 2 |
|---|
| 2153 | b = d & 1 |
|---|
| 2154 | for y in range(64, 128): |
|---|
| 2155 | dest.setRgb(x, y, r, g, b) |
|---|
| 2156 | r /= 2. |
|---|
| 2157 | g /= 2. |
|---|
| 2158 | b /= 2. |
|---|
| 2159 | for y in range(64): |
|---|
| 2160 | dest.setRgb(x, y, r, g, b) |
|---|
| 2161 | dest.save("pat5-2-2.png") |
|---|
| 2162 | |
|---|
| 2163 | # Output 5.2.1: gamma-corrected Floyd-Steinberg with ANSI palette (sigma-abs) |
|---|
| 2164 | # Output 5.2.2: gamma-corrected Floyd-Steinberg with ANSI palette (euclidian) |
|---|
| 2165 | def test52x(src, mat, cpal, distance, serpentine): |
|---|
| 2166 | (w, h) = src.size() |
|---|
| 2167 | ipal = [[Gamma.CtoI(c[i]) for i in range(3)] for c in cpal] |
|---|
| 2168 | dest = Image((w, h)) |
|---|
| 2169 | lines = len(mat) |
|---|
| 2170 | rows = len(mat[0]) |
|---|
| 2171 | offset = mat[0].index(-1) |
|---|
| 2172 | ey = [[[0.] * 3 for n in range(w + rows - 1)] for n in range(lines)] |
|---|
| 2173 | for y in range(h): |
|---|
| 2174 | ex = [[0.] * 3 for n in range(rows - offset)] |
|---|
| 2175 | if serpentine and y & 1: |
|---|
| 2176 | xrange = range(w - 1, -1, -1) |
|---|
| 2177 | else: |
|---|
| 2178 | xrange = range(w) |
|---|
| 2179 | for x in xrange: |
|---|
| 2180 | # Get pixel, add error, set pixel |
|---|
| 2181 | crgb = src.getRgb(x, y) |
|---|
| 2182 | irgb = [Gamma.CtoI(crgb[i]) + ex[0][i] + ey[0][x + offset][i] \ |
|---|
| 2183 | for i in range(3)] |
|---|
| 2184 | crgb = [Gamma.ItoC(irgb[i]) for i in range(3)] |
|---|
| 2185 | max = 999999. |
|---|
| 2186 | for n in range(len(cpal)): |
|---|
| 2187 | d = distance(crgb, cpal[n]) |
|---|
| 2188 | if d < max: |
|---|
| 2189 | max = d |
|---|
| 2190 | cbest = cpal[n] |
|---|
| 2191 | ibest = ipal[n] |
|---|
| 2192 | dest.setRgb(x, y, *cbest) |
|---|
| 2193 | # Compute error and propagate it |
|---|
| 2194 | for i in range(3): |
|---|
| 2195 | # Propagate first line of error |
|---|
| 2196 | error = irgb[i] - ibest[i] |
|---|
| 2197 | for dx in range(rows - offset - 2): |
|---|
| 2198 | ex[dx][i] = ex[dx + 1][i] + error * mat[0][offset + 1 + dx] |
|---|
| 2199 | ex[rows - offset - 2][i] = error * mat[0][rows - 1] |
|---|
| 2200 | # Propagate next lines |
|---|
| 2201 | if serpentine and y & 1: |
|---|
| 2202 | for dy in range(1, lines): |
|---|
| 2203 | for dx in range(rows): |
|---|
| 2204 | ey[dy][x + dx][i] += error * mat[dy][rows - 1 - dx] |
|---|
| 2205 | else: |
|---|
| 2206 | for dy in range(1, lines): |
|---|
| 2207 | for dx in range(rows): |
|---|
| 2208 | ey[dy][x + dx][i] += error * mat[dy][dx] |
|---|
| 2209 | for dy in range(lines - 1): |
|---|
| 2210 | ey[dy] = ey[dy + 1] |
|---|
| 2211 | ey[lines - 1] = [[0.] * 3 for n in range(w + rows - 1)] |
|---|
| 2212 | return dest |
|---|
| 2213 | |
|---|
| 2214 | RGB_PALETTE = \ |
|---|
| 2215 | [[0, 0, 0], |
|---|
| 2216 | [0, 0, 1], |
|---|
| 2217 | [0, 1, 0], |
|---|
| 2218 | [0, 1, 1], |
|---|
| 2219 | [1, 0, 0], |
|---|
| 2220 | [1, 0, 1], |
|---|
| 2221 | [1, 1, 0], |
|---|
| 2222 | [1, 1, 1]] |
|---|
| 2223 | |
|---|
| 2224 | ANSI_PALETTE = \ |
|---|
| 2225 | [[0, 0, 0], |
|---|
| 2226 | [0, 0, 0.5], |
|---|
| 2227 | [0, 0.5, 0], |
|---|
| 2228 | [0, 0.5, 0.5], |
|---|
| 2229 | [0.5, 0, 0], |
|---|
| 2230 | [0.5, 0, 0.5], |
|---|
| 2231 | [0.5, 0.5, 0], |
|---|
| 2232 | [0.7, 0.7, 0.7], |
|---|
| 2233 | [0.3, 0.3, 0.3], |
|---|
| 2234 | [0, 0, 1], |
|---|
| 2235 | [0, 1, 0], |
|---|
| 2236 | [0, 1, 1], |
|---|
| 2237 | [1, 0, 0], |
|---|
| 2238 | [1, 0, 1], |
|---|
| 2239 | [1, 1, 0], |
|---|
| 2240 | [1, 1, 1]] |
|---|
| 2241 | |
|---|
| 2242 | def distmax(u, v): |
|---|
| 2243 | r, g, b = [abs(u[i] - v[i]) for i in range(3)] |
|---|
| 2244 | return r + g + b |
|---|
| 2245 | |
|---|
| 2246 | def disteuclidian(u, v): |
|---|
| 2247 | r, g, b = [u[i] - v[i] for i in range(3)] |
|---|
| 2248 | return r*r + g*g + b*b |
|---|
| 2249 | |
|---|
| 2250 | if chapter(5): |
|---|
| 2251 | test52x(grad256, ERROR_FSTEIN, |
|---|
| 2252 | ANSI_PALETTE, distmax, True).save("grad5-2-1.png") |
|---|
| 2253 | test52x(lenna256, ERROR_FSTEIN, |
|---|
| 2254 | ANSI_PALETTE, distmax, True).save("out5-2-1.png") |
|---|
| 2255 | test52x(grad256, ERROR_FSTEIN, |
|---|
| 2256 | ANSI_PALETTE, disteuclidian, True).save("grad5-2-2.png") |
|---|
| 2257 | test52x(lenna256, ERROR_FSTEIN, |
|---|
| 2258 | ANSI_PALETTE, disteuclidian, True).save("out5-2-2.png") |
|---|
| 2259 | |
|---|
| 2260 | def rgb2hsv(r, g, b): |
|---|
| 2261 | m = (float)(min(r, g, b)) |
|---|
| 2262 | v = (float)(max(r, g, b)) |
|---|
| 2263 | if v == m or v == 0: |
|---|
| 2264 | return 0., 0., v |
|---|
| 2265 | s = (v - m) / v |
|---|
| 2266 | if r == v: |
|---|
| 2267 | h = (g - b) / (v - m) |
|---|
| 2268 | if h < 0.: |
|---|
| 2269 | h += 6 |
|---|
| 2270 | elif g == v: |
|---|
| 2271 | h = 2. + (b - r) / (v - m) |
|---|
| 2272 | elif b == v: |
|---|
| 2273 | h = 4. + (r - g) / (v - m) |
|---|
| 2274 | return math.pi * h / 3, s, v |
|---|
| 2275 | |
|---|
| 2276 | def disthsv(u, v): |
|---|
| 2277 | u = rgb2hsv(*u) |
|---|
| 2278 | v = rgb2hsv(*v) |
|---|
| 2279 | d1 = u[2] - v[2] |
|---|
| 2280 | d2 = u[2] * u[1] * math.cos(u[0]) - v[2] * v[1] * math.cos(v[0]) |
|---|
| 2281 | d3 = u[2] * u[1] * math.sin(u[0]) - v[2] * v[1] * math.sin(v[0]) |
|---|
| 2282 | return d1*d1 + d2*d2 + d3*d3 |
|---|
| 2283 | |
|---|
| 2284 | def disthsv3(u, v): |
|---|
| 2285 | u = rgb2hsv(*u) |
|---|
| 2286 | v = rgb2hsv(*v) |
|---|
| 2287 | d1 = u[2] - v[2] |
|---|
| 2288 | d2 = u[2] * u[1] * math.cos(u[0]) - v[2] * v[1] * math.cos(v[0]) |
|---|
| 2289 | d3 = u[2] * u[1] * math.sin(u[0]) - v[2] * v[1] * math.sin(v[0]) |
|---|
| 2290 | return 9*d1*d1 + d2*d2 + d3*d3 |
|---|
| 2291 | |
|---|
| 2292 | if chapter(5): |
|---|
| 2293 | test52x(grad256, ERROR_FSTEIN, |
|---|
| 2294 | ANSI_PALETTE, disthsv, True).save("grad5-2-3.png") |
|---|
| 2295 | test52x(lenna256, ERROR_FSTEIN, |
|---|
| 2296 | ANSI_PALETTE, disthsv, True).save("out5-2-3.png") |
|---|
| 2297 | test52x(grad256, ERROR_FSTEIN, |
|---|
| 2298 | ANSI_PALETTE, disthsv3, True).save("grad5-2-4.png") |
|---|
| 2299 | test52x(lenna256, ERROR_FSTEIN, |
|---|
| 2300 | ANSI_PALETTE, disthsv3, True).save("out5-2-4.png") |
|---|
| 2301 | |
|---|
| 2302 | ############################################################################## |
|---|
| 2303 | if chapter(6): |
|---|
| 2304 | print "Chapter 6. Photographic mosaics" |
|---|
| 2305 | |
|---|
| 2306 | # Output 6.0.1: create a mosaic from Lenna |
|---|
| 2307 | def mosaic_split(src, tnw, tnh): |
|---|
| 2308 | random.seed(0) |
|---|
| 2309 | thumbs = [] |
|---|
| 2310 | (w, h) = src.size() |
|---|
| 2311 | sw = w / tnw |
|---|
| 2312 | sh = h / tnh |
|---|
| 2313 | for y in range(sh): |
|---|
| 2314 | for x in range(sw): |
|---|
| 2315 | thumbs.append(src.getRegion(x * tnw, y * tnh, tnw, tnh)) |
|---|
| 2316 | random.shuffle(thumbs) |
|---|
| 2317 | return thumbs |
|---|
| 2318 | |
|---|
| 2319 | def mosaic_analyse(tnlist, dx, dy): |
|---|
| 2320 | coeffs = [] |
|---|
| 2321 | for (n, img) in enumerate(tnlist): |
|---|
| 2322 | tmp = [[[0] * 3 for x in range(dx)] for y in range(dy)] |
|---|
| 2323 | (w, h) = img.size() |
|---|
| 2324 | for y in range(h): |
|---|
| 2325 | my = y * dy / h |
|---|
| 2326 | for x in range(w): |
|---|
| 2327 | mx = x * dx / w |
|---|
| 2328 | (r, g, b) = img.getRgb(x, y) |
|---|
| 2329 | tmp[my][mx][0] += Gamma.CtoI(r) / (w / dx * h / dy) |
|---|
| 2330 | tmp[my][mx][1] += Gamma.CtoI(g) / (w / dx * h / dy) |
|---|
| 2331 | tmp[my][mx][2] += Gamma.CtoI(b) / (w / dx * h / dy) |
|---|
| 2332 | coeffs.append(tmp) |
|---|
| 2333 | return coeffs |
|---|
| 2334 | |
|---|
| 2335 | def test601(tnlist, cols): |
|---|
| 2336 | (tnw, tnh) = tnlist[0].size() |
|---|
| 2337 | dw = cols |
|---|
| 2338 | dh = (len(tnlist) + cols - 1) / cols |
|---|
| 2339 | dest = Image((dw * tnw + 8 * (dw + 1), dh * tnh + 8 * (dh + 1)), True) |
|---|
| 2340 | for (n, img) in enumerate(tnlist): |
|---|
| 2341 | di = 8 + (n % dw) * (tnw + 8) |
|---|
| 2342 | dj = 8 + (n / dw) * (tnh + 8) |
|---|
| 2343 | img.copyTo(dest, (di, dj)) |
|---|
| 2344 | return dest |
|---|
| 2345 | |
|---|
| 2346 | if chapter(6): |
|---|
| 2347 | tnlist = mosaic_split(lenna256, 32, 32) |
|---|
| 2348 | test601(tnlist, 10).save("out6-0-1.png") |
|---|
| 2349 | |
|---|
| 2350 | # Output 6.1.1: extract 1 colour feature from mosaic tiles |
|---|
| 2351 | # Output 6.1.2: crop Lenna |
|---|
| 2352 | # Output 6.1.3: generate a mosaic from the 1-feature database |
|---|
| 2353 | # Output 6.1.4: extract 4 colour features from mosaic tiles |
|---|
| 2354 | # Output 6.1.5: generate a mosaic from the 4-feature database |
|---|
| 2355 | def test61x(coeffs, cols, tnw, tnh): |
|---|
| 2356 | dx = len(coeffs[0][0]) |
|---|
| 2357 | dy = len(coeffs[0]) |
|---|
| 2358 | dw = cols |
|---|
| 2359 | dh = (len(coeffs) + cols - 1) / cols |
|---|
| 2360 | dest = Image((dw * tnw + 8 * (dw + 1), dh * tnh + 8 * (dh + 1)), True) |
|---|
| 2361 | for (n, tab) in enumerate(coeffs): |
|---|
| 2362 | di = 8 + (n % dw) * (tnw + 8) |
|---|
| 2363 | dj = 8 + (n / dw) * (tnh + 8) |
|---|
| 2364 | for y in range(tnh): |
|---|
| 2365 | for x in range(tnw): |
|---|
| 2366 | (r, g, b) = tab[y * dy / tnh][x * dx / tnw] |
|---|
| 2367 | dest.setRgb(di + x, dj + y, Gamma.ItoC(r), Gamma.ItoC(g), Gamma.ItoC(b)) |
|---|
| 2368 | return dest |
|---|
| 2369 | |
|---|
| 2370 | def test61y(src, sqw, sqh, tnlist, coeffs): |
|---|
| 2371 | (w, h) = src.size() |
|---|
| 2372 | (tnw, tnh) = tnlist[0].size() |
|---|
| 2373 | dx = len(coeffs[0][0]) |
|---|
| 2374 | dy = len(coeffs[0]) |
|---|
| 2375 | nx = w / sqw |
|---|
| 2376 | ny = h / sqh |
|---|
| 2377 | dest = Image((nx * tnw, ny * tnh), True) |
|---|
| 2378 | for Y in range(ny): |
|---|
| 2379 | for X in range(nx): |
|---|
| 2380 | # 1. create statistics about the current square |
|---|
| 2381 | cur = [[[0] * 3 for x in range(dx)] for y in range(dy)] |
|---|
| 2382 | for y in range(sqh): |
|---|
| 2383 | my = y * dy / sqh |
|---|
| 2384 | for x in range(sqw): |
|---|
| 2385 | mx = x * dx / sqw |
|---|
| 2386 | (r, g, b) = src.getRgb(X * sqw + x, Y * sqh + y) |
|---|
| 2387 | cur[my][mx][0] += Gamma.CtoI(r) / (sqw / dx * sqh / dy) |
|---|
| 2388 | cur[my][mx][1] += Gamma.CtoI(g) / (sqw / dx * sqh / dy) |
|---|
| 2389 | cur[my][mx][2] += Gamma.CtoI(b) / (sqw / dx * sqh / dy) |
|---|
| 2390 | # 2. find the best mosaic part |
|---|
| 2391 | best = -1 |
|---|
| 2392 | dist = 5. |
|---|
| 2393 | for (n, tmp) in enumerate(coeffs): |
|---|
| 2394 | d = 0 |
|---|
| 2395 | for j in range(dy): |
|---|
| 2396 | for i in range(dx): |
|---|
| 2397 | for c in range(3): |
|---|
| 2398 | t = cur[j][i][c] - tmp[j][i][c] |
|---|
| 2399 | d += t * t |
|---|
| 2400 | if d < dist: |
|---|
| 2401 | dist = d |
|---|
| 2402 | best = n |
|---|
| 2403 | # 3. blit mosaic chunk |
|---|
| 2404 | tnlist[best].copyTo(dest, (X * tnw, Y * tnh)) |
|---|
| 2405 | return dest |
|---|
| 2406 | |
|---|
| 2407 | if chapter(6): |
|---|
| 2408 | coeffs1x1 = mosaic_analyse(tnlist, 1, 1) |
|---|
| 2409 | test61x(coeffs1x1, 10, 8, 8).save("out6-1-1.png") |
|---|
| 2410 | out612 = lenna256.getRegion(100, 90, 80, 80) |
|---|
| 2411 | out612.save("out6-1-2.png") |
|---|
| 2412 | test61y(out612, 6, 6, tnlist, coeffs1x1).save("out6-1-3.png") |
|---|
| 2413 | |
|---|
| 2414 | coeffs2x2 = mosaic_analyse(tnlist, 2, 2) |
|---|
| 2415 | test61x(coeffs2x2, 10, 16, 16).save("out6-1-4.png") |
|---|
| 2416 | test61y(out612, 6, 6, tnlist, coeffs2x2).save("out6-1-5.png") |
|---|
| 2417 | |
|---|
| 2418 | ############################################################################## |
|---|
| 2419 | print "Finished" |
|---|
| 2420 | |
|---|
| 2421 | # Place temporary cruft below this |
|---|
| 2422 | sys.exit(0) |
|---|
| 2423 | |
|---|
| 2424 | # XXX: test -- ranked dither -- it SUCKS |
|---|
| 2425 | def test26x(src, mat): |
|---|
| 2426 | (w, h) = src.size() |
|---|
| 2427 | dest = Image((w, h)) |
|---|
| 2428 | dx = len(mat[0]) |
|---|
| 2429 | dy = len(mat) |
|---|
| 2430 | for y in range(h / dy): |
|---|
| 2431 | for x in range(w / dx): |
|---|
| 2432 | # Step 1: get the pixels and count groups |
|---|
| 2433 | groups = {} |
|---|
| 2434 | for j in range(dy): |
|---|
| 2435 | for i in range(dx): |
|---|
| 2436 | p = src.getGray(x * dx + i, y * dy + j) |
|---|
| 2437 | if groups.has_key(p): |
|---|
| 2438 | groups[p].append((i, j)) |
|---|
| 2439 | else: |
|---|
| 2440 | groups[p] = [(i, j)] |
|---|
| 2441 | # Step 2: create the ranked dither |
|---|
| 2442 | ranked = Matrix(dx, dy) |
|---|
| 2443 | for p, g in groups.items(): |
|---|
| 2444 | n = (int)(round(p * len(g))) |
|---|
| 2445 | if not n: |
|---|
| 2446 | continue |
|---|
| 2447 | v = [(mat[j][i], (i, j)) for (i, j) in g] |
|---|
| 2448 | v.sort() |
|---|
| 2449 | v = v[0 : n - 1] |
|---|
| 2450 | for (k, (i, j)) in v: |
|---|
| 2451 | ranked[j][i] = 1 |
|---|
| 2452 | # Step 3: blit the ranked dither |
|---|
| 2453 | for j in range(dy): |
|---|
| 2454 | for i in range(dx): |
|---|
| 2455 | dest.setGray(x * dx + i, y * dy + j, ranked[j][i]) |
|---|
| 2456 | return dest |
|---|
| 2457 | |
|---|
| 2458 | if chapter(2): |
|---|
| 2459 | #test26x(lenna256bw, DITHER_BAYER88).save("out2-6-1.png") |
|---|
| 2460 | #test26x(grad256bw, DITHER_BAYER88).save("grad2-6-1.png") |
|---|
| 2461 | test26x(lenna256bw, DITHER_CLUSTER88).save("out2-6-1.png") |
|---|
| 2462 | test26x(grad256bw, DITHER_CLUSTER88).save("grad2-6-1.png") |
|---|
| 2463 | |
|---|
| 2464 | ##################### |
|---|
| 2465 | #CIE-L*a*b* transformation -- euclidian distance doesn't seem to work great |
|---|
| 2466 | def rgb2lab(r, g, b): |
|---|
| 2467 | Xw50 = 0.9642; |
|---|
| 2468 | Yw50 = 1.0000; |
|---|
| 2469 | Zw50 = 0.8249; |
|---|
| 2470 | # RGB to sRGB |
|---|
| 2471 | r = Gamma.CtoI(r) |
|---|
| 2472 | g = Gamma.CtoI(g) |
|---|
| 2473 | b = Gamma.CtoI(b) |
|---|
| 2474 | # sRGB to XYZ(D65) |
|---|
| 2475 | x65 = 0.4124*r + 0.3576*g + 0.1805*b |
|---|
| 2476 | y65 = 0.2126*r + 0.7152*g + 0.0722*b |
|---|
| 2477 | z65 = 0.0193*r + 0.1192*g + 0.9505*b |
|---|
| 2478 | # XYZ(D65) to XYZ(D50) |
|---|
| 2479 | x50 = 1.0282015*x65 + 0.0500707*y65 - 0.0579688*z65 |
|---|
| 2480 | y50 = 0.0197032*x65 + 0.9871848*y65 - 0.0054285*z65 |
|---|
| 2481 | z50 = -0.0002329*x65 + 0.0006862*y65 + 0.7573070*z65 |
|---|
| 2482 | # XYZ(D50) to Lab(D50) |
|---|
| 2483 | if x50 / Xw50 > 0.008856: |
|---|
| 2484 | xx50 = math.pow(x50 / Xw50, 1. / 3.) |
|---|
| 2485 | else: |
|---|
| 2486 | xx50 = 7.78 * (x50 / Xw50) + 16. / 116. |
|---|
| 2487 | if y50 / Yw50 > 0.008856: |
|---|
| 2488 | yy50 = math.pow(y50 / Yw50, 1. / 3.) |
|---|
| 2489 | else: |
|---|
| 2490 | yy50 = 7.78 * (y50 / Yw50) + 16. / 116. |
|---|
| 2491 | if z50 / Zw50 > 0.008856: |
|---|
| 2492 | zz50 = math.pow(z50 / Zw50, 1. / 3.) |
|---|
| 2493 | else: |
|---|
| 2494 | zz50 = 7.78 * (z50 / Zw50) + 16. / 116. |
|---|
| 2495 | lo = 116. * yy50 - 16. |
|---|
| 2496 | ao = 500. * (xx50 - yy50) |
|---|
| 2497 | bo = 200. * (yy50 - zz50) |
|---|
| 2498 | return lo, ao, bo |
|---|
| 2499 | |
|---|
| 2500 | def distlab(u, v): |
|---|
| 2501 | u = rgb2lab(*u) |
|---|
| 2502 | v = rgb2lab(*v) |
|---|
| 2503 | l, a, b = u[0] - v[0], u[1] - v[1], u[2] - v[2] |
|---|
| 2504 | return l*l + a*a + b*b |
|---|
| 2505 | |
|---|
| 2506 | |
|---|