Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.
 
 
 
 
 
 

2776 wiersze
96 KiB

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