source: www/study/study.py @ 1937

Last change on this file since 1937 was 1937, checked in by Sam Hocevar, 13 years ago
  • Burkes dithering.
  • Property svn:executable set to *
File size: 15.0 KB
Line 
1#!/usr/bin/env python
2
3import math, gd, random
4
5# Tiny image class to make examples short and readable
6class Image(gd.image):
7    def __new__(args, truecolor = False):
8        gd.gdMaxColors = 256 * 256 * 256
9        if truecolor:
10            return gd.image.__new__(args, True)
11        else:
12            return gd.image.__new__(args)
13    def getGray(self, x, y):
14        p = self.getPixel((x, y))
15        c = self.colorComponents(p)[0] / 255.0
16        return c
17    def getRgb(self, x, y):
18        p = self.getPixel((x, y))
19        rgb = self.colorComponents(p)
20        return [rgb[0] / 255.0, rgb[1] / 255.0, rgb[2] / 255.0]
21    def setGray(self, x, y, t):
22        p = (int)(t * 255.999)
23        c = self.colorResolve((p, p, p))
24        self.setPixel((x, y), c)
25    def setRgb(self, x, y, r, g, b):
26        r = (int)(r * 255.999)
27        g = (int)(g * 255.999)
28        b = (int)(b * 255.999)
29        c = self.colorResolve((r, g, b))
30        self.setPixel((x, y), c)
31
32# Manipulate gamma values
33class Gamma:
34    def CtoI(x):
35        return math.pow(x, 2.2)
36    def ItoC(x):
37        return math.pow(x, 1 / 2.2)
38    CtoI = staticmethod(CtoI)
39    ItoC = staticmethod(ItoC)
40
41# Load the 256x256 grayscale Lenna image
42lenna256bw = Image("lenna256bw.png")
43
44# Create a 32x256 grayscale gradient
45gradient256bw = Image((32, 256))
46for x in range(32):
47    for y in range(256):
48        gradient256bw.setGray(x, 255 - y, y / 255.)
49gradient256bw.writePng("gradient256bw.png")
50
51# Output 1.1.1: 50% threshold
52# Output 1.1.2: 40% threshold
53# Output 1.1.3: 60% threshold
54def test11x(src, threshold, name):
55    (w, h) = src.size()
56    dest = Image((w, h))
57    for y in range(h):
58        for x in range(w):
59            c = src.getGray(x, y) > threshold
60            dest.setGray(x, y, c)
61    dest.writePng(name)
62
63test11x(lenna256bw, 0.5, "out1-1-1.png")
64test11x(lenna256bw, 0.4, "out1-1-2.png")
65test11x(lenna256bw, 0.6, "out1-1-3.png")
66test11x(gradient256bw, 0.5, "grad1-1-1.png")
67test11x(gradient256bw, 0.4, "grad1-1-2.png")
68test11x(gradient256bw, 0.6, "grad1-1-3.png")
69
70# Output 1.2.1: 3-colour threshold
71# Output 1.2.2: 5-colour threshold
72def test12x(src, colors, name):
73    (w, h) = src.size()
74    dest = Image((w, h))
75    p = -0.0001 + colors
76    q = colors - 1
77    for y in range(h):
78        for x in range(w):
79            c = src.getGray(x, y)
80            c = math.floor(c * p) / q
81            dest.setGray(x, y, c)
82    dest.writePng(name)
83
84test12x(lenna256bw, 3, "out1-2-1.png")
85test12x(lenna256bw, 5, "out1-2-2.png")
86test12x(gradient256bw, 3, "grad1-2-1.png")
87test12x(gradient256bw, 5, "grad1-2-2.png")
88
89# Pattern 2.1.1: a 50% halftone pattern with various block sizes
90dest = Image((320, 80))
91for x in range(320):
92    d = 8 >> (x / 80)
93    for y in range(80):
94        c = (x / d + y / d) & 1
95        dest.setGray(x, y, c)
96dest.writePng("pat2-1-1.png")
97
98# Pattern 2.1.2: 25% and 75% halftone patterns with various block sizes
99dest = Image((320, 80))
100for x in range(320):
101    d = 8 >> (x / 80)
102    for y in range(40):
103        c = ((x / d + y / d) & 1) or (y / d & 1)
104        dest.setGray(x, y, c)
105    for y in range(40, 80):
106        c = ((x / d + y / d) & 1) and (y / d & 1)
107        dest.setGray(x, y, c)
108dest.writePng("pat2-1-2.png")
109
110# Output 2.1.1: 20/40/60/80% threshold with 25/50/75% patterns inbetween:
111def test211(src, name):
112    (w, h) = src.size()
113    dest = Image((w, h))
114    for y in range(h):
115        for x in range(w):
116            c = src.getGray(x, y)
117            if c < 0.2:
118                c = 0.
119            elif c < 0.4:
120                c = ((x + y) & 1) and (y & 1)
121            elif c < 0.6:
122                c = (x + y) & 1
123            elif c < 0.8:
124                c = ((x + y) & 1) or (y & 1)
125            else:
126                c = 1.
127            dest.setGray(x, y, c)
128    dest.writePng(name)
129
130test211(lenna256bw, "out2-1-1.png")
131test211(gradient256bw, "grad2-1-1.png")
132
133# Pattern 2.2.1: vertical, mixed and horizontal black-white halftones
134dest = Image((240, 80))
135for y in range(80):
136    for x in range(80):
137        c = x & 1
138        dest.setGray(x, y, c)
139    for x in range(80, 160):
140        c = (x / d + y / d) & 1
141        dest.setGray(x, y, c)
142    for x in range(160, 240):
143        c = y & 1
144        dest.setGray(x, y, c)
145dest.writePng("pat2-2-1.png")
146
147# Pattern 2.2.2: two different 25% patterns
148dest = Image((320, 80))
149for y in range(80):
150    for x in range(80):
151        c = (x / 2 & 1) and (y / 2 & 1)
152        dest.setGray(x, y, c)
153    for x in range(80, 160):
154        c = (x & 1) and (y & 1)
155        dest.setGray(x, y, c)
156    for x in range(160, 240):
157        c = (x & 1) and ((y + x / 2) & 1)
158        dest.setGray(x, y, c)
159    for x in range(240, 320):
160        c = (x / 2 & 1) and ((y / 2 + x / 4) & 1)
161        dest.setGray(x, y, c)
162dest.writePng("pat2-2-2.png")
163
164# Output 2.3.1: 4x4 Bayer dithering
165# Output 2.3.2: 4x4 cluster dot
166# Output 2.3.3: 5x3 line dithering
167def test23x(src, mat, name):
168    (w, h) = src.size()
169    dest = Image((w, h))
170    dx = len(mat[0])
171    dy = len(mat)
172    for y in range(h):
173        for x in range(w):
174            c = src.getGray(x, y)
175            threshold = (1. + mat[y % dy][x % dx]) / (dx * dy + 1)
176            c = c > threshold
177            dest.setGray(x, y, c)
178    dest.writePng(name)
179
180mat = [[  0,  8,  3, 11],
181       [ 15,  4, 12,  7],
182       [  2, 10,  1,  9],
183       [ 13,  6, 14,  5]]
184test23x(lenna256bw, mat, "out2-3-1.png")
185test23x(gradient256bw, mat, "grad2-3-1.png")
186
187mat = [[ 12,  5,  6, 13],
188       [  4,  0,  1,  7],
189       [ 11,  3,  2,  8],
190       [ 15, 10,  9, 14]]
191test23x(lenna256bw, mat, "out2-3-2.png")
192test23x(gradient256bw, mat, "grad2-3-2.png")
193
194mat = [[ 13,  7,  0,  4, 10],
195       [  9,  3,  1,  8, 14],
196       [ 11,  5,  2,  6, 12],]
197test23x(lenna256bw, mat, "out2-3-3.png")
198test23x(gradient256bw, mat, "grad2-3-3.png")
199
200# Output 2.4.1: uniform random dithering
201def test241(src, name):
202    random.seed(0)
203    (w, h) = src.size()
204    dest = Image((w, h))
205    for y in range(h):
206        for x in range(w):
207            c = src.getGray(x, y)
208            d = c > random.random()
209            dest.setGray(x, y, d)
210    dest.writePng(name)
211
212test241(lenna256bw, "out2-4-1.png")
213test241(gradient256bw, "grad2-4-1.png")
214
215# Output 2.4.2: random dithering
216def test242(src, name):
217    random.seed(0)
218    (w, h) = src.size()
219    dest = Image((w, h))
220    for y in range(h):
221        for x in range(w):
222            c = src.getGray(x, y)
223            d = c > random.gauss(0.5, 0.15)
224            dest.setGray(x, y, d)
225    dest.writePng(name)
226
227test242(lenna256bw, "out2-4-2.png")
228test242(gradient256bw, "grad2-4-2.png")
229
230# Output 3.1.1: standard Floyd-Steinberg
231def test311(src, name):
232    (w, h) = src.size()
233    dest = Image((w, h))
234    ep = [0.] * (w + 2)
235    for y in range(h):
236        ey = [0.] * (w + 2)
237        ex = 0
238        for x in range(w):
239            c = src.getGray(x, y) + ex + ep[x + 1]
240            d = c > 0.5
241            dest.setGray(x, y, d)
242            error = c - d
243            ex = error * 7. / 16.
244            ey[x] += error * 3. / 16.
245            ey[x + 1] += error * 5. / 16.
246            ey[x + 2] += error * 1. / 16.
247        ep = ey
248    dest.writePng(name)
249
250test311(lenna256bw, "out3-1-1.png")
251test311(gradient256bw, "grad3-1-1.png")
252
253# Output 3.1.2: serpentine Floyd-Steinberg
254def test312(src, name):
255    (w, h) = src.size()
256    dest = Image((w, h))
257    ep = [0.] * (w + 2)
258    for z in range(h / 2):
259        ey = [0.] * (w + 2)
260        ex = 0
261        y = z * 2
262        for x in range(w):
263            c = src.getGray(x, y) + ex + ep[x + 1]
264            d = c > 0.5
265            dest.setGray(x, y, d)
266            error = c - d
267            ex = error * 7. / 16.
268            ey[x] += error * 3. / 16.
269            ey[x + 1] += error * 5. / 16.
270            ey[x + 2] += error * 1. / 16.
271        ep = ey
272        ey = [0.] * (w + 2)
273        ex = 0
274        y = z * 2 + 1
275        for x in range(w - 1, -1, -1):
276            c = src.getGray(x, y) + ex + ep[x + 1]
277            d = c > 0.5
278            dest.setGray(x, y, d)
279            error = c - d
280            ex = error * 7. / 16.
281            ey[x] += error * 1. / 16.
282            ey[x + 1] += error * 5. / 16.
283            ey[x + 2] += error * 3. / 16.
284        ep = ey
285    dest.writePng(name)
286
287test312(lenna256bw, "out3-1-2.png")
288test312(gradient256bw, "grad3-1-2.png")
289
290# Output 3.2.1: Fan (modified Floyd-Steinberg)
291def test321(src, name):
292    (w, h) = src.size()
293    dest = Image((w, h))
294    ep = [0.] * (w + 3)
295    for y in range(h):
296        ey = [0.] * (w + 3)
297        ex = 0
298        for x in range(w):
299            c = src.getGray(x, y) + ex + ep[x + 2]
300            d = c > 0.5
301            dest.setGray(x, y, d)
302            error = c - d
303            ex = error * 7. / 16.
304            ey[x] += error * 1. / 16.
305            ey[x + 1] += error * 3. / 16.
306            ey[x + 2] += error * 5. / 16.
307        ep = ey
308    dest.writePng(name)
309
310test321(lenna256bw, "out3-2-1.png")
311test321(gradient256bw, "grad3-2-1.png")
312
313# Output 3.2.2: Jarvis, Judice and Ninke
314def test322(src, name):
315    (w, h) = src.size()
316    dest = Image((w, h))
317    ep = [0.] * (w + 4)
318    ey = [0.] * (w + 4)
319    for y in range(h):
320        ey2 = [0.] * (w + 4)
321        ex = 0
322        ex2 = 0
323        for x in range(w):
324            c = src.getGray(x, y) + ex + ep[x + 2]
325            d = c > 0.5
326            dest.setGray(x, y, d)
327            error = c - d
328            ex = ex2 + error * 7. / 48.
329            ex2 = error * 5. / 48.
330            ey[x] += error * 3. / 48.
331            ey[x + 1] += error * 5. / 48.
332            ey[x + 2] += error * 7. / 48.
333            ey[x + 3] += error * 5. / 48.
334            ey[x + 4] += error * 3. / 48.
335            ey2[x] += error * 1. / 48.
336            ey2[x + 1] += error * 3. / 48.
337            ey2[x + 2] += error * 5. / 48.
338            ey2[x + 3] += error * 3. / 48.
339            ey2[x + 4] += error * 1. / 48.
340        ep = ey
341        ey = ey2
342    dest.writePng(name)
343
344test322(lenna256bw, "out3-2-2.png")
345test322(gradient256bw, "grad3-2-2.png")
346
347# Output 3-2-3: Stucki
348# TODO: merge with Jarvis-Judice-Ninke
349def test323(src, name):
350    (w, h) = src.size()
351    dest = Image((w, h))
352    ep = [0.] * (w + 4)
353    ey = [0.] * (w + 4)
354    for y in range(h):
355        ey2 = [0.] * (w + 4)
356        ex = 0
357        ex2 = 0
358        for x in range(w):
359            c = src.getGray(x, y) + ex + ep[x + 2]
360            d = c > 0.5
361            dest.setGray(x, y, d)
362            error = c - d
363            ex = ex2 + error * 8. / 42.
364            ex2 = error * 4. / 42.
365            ey[x] += error * 2. / 42.
366            ey[x + 1] += error * 4. / 42.
367            ey[x + 2] += error * 8. / 42.
368            ey[x + 3] += error * 4. / 42.
369            ey[x + 4] += error * 2. / 42.
370            ey2[x] += error * 1. / 42.
371            ey2[x + 1] += error * 2. / 42.
372            ey2[x + 2] += error * 4. / 42.
373            ey2[x + 3] += error * 2. / 42.
374            ey2[x + 4] += error * 1. / 42.
375        ep = ey
376        ey = ey2
377    dest.writePng(name)
378
379test323(lenna256bw, "out3-2-3.png")
380test323(gradient256bw, "grad3-2-3.png")
381
382# Output 3-2-4: Burkes
383# TODO: merge with Jarvis-Judice-Ninke and Stucki
384def test324(src, name):
385    (w, h) = src.size()
386    dest = Image((w, h))
387    ep = [0.] * (w + 4)
388    for y in range(h):
389        ey = [0.] * (w + 4)
390        ex = 0
391        ex2 = 0
392        for x in range(w):
393            c = src.getGray(x, y) + ex + ep[x + 2]
394            d = c > 0.5
395            dest.setGray(x, y, d)
396            error = c - d
397            ex = ex2 + error * 8. / 32.
398            ex2 = error * 4. / 32.
399            ey[x] += error * 2. / 32.
400            ey[x + 1] += error * 4. / 32.
401            ey[x + 2] += error * 8. / 32.
402            ey[x + 3] += error * 4. / 32.
403            ey[x + 4] += error * 2. / 32.
404        ep = ey
405    dest.writePng(name)
406
407test324(lenna256bw, "out3-2-4.png")
408test324(gradient256bw, "grad3-2-4.png")
409
410##############################################################################
411# Only temporary cruft below this
412import sys
413sys.exit(0)
414
415
416
417
418# Pattern 4: gamma-corrected 50% gray, black-white halftone, 50% gray
419dest = Image((240, 80))
420for y in range(80):
421    for x in range(80):
422        dest.setGray(x, y, Gamma.ItoC(0.5))
423    for x in range(80, 160):
424        c = (x + y) & 1
425        dest.setGray(x, y, c)
426    for x in range(160, 240):
427        dest.setGray(x, y, 0.5)
428dest.writePng("pat004.png")
429
430# Pattern 5: gamma-corrected 50% gray, black-white halftone, 50% gray
431dest = Image((400, 240))
432for y in range(80):
433    for x in range(400):
434        if x < 80:
435            c = 0.
436        elif x < 160:
437            c = ((x + y) & 1) and (y & 1)
438        elif x < 240:
439            c = (x + y) & 1
440        elif x < 320:
441            c = ((x + y) & 1) or (y & 1)
442        else:
443            c = 1.
444        dest.setGray(x, y, c)
445for y in range(80, 160):
446    for x in range(400):
447        dest.setGray(x, y, x / 80 / 4.)
448for y in range(160, 240):
449    for x in range(400):
450        if x < 80:
451            c = 0.
452        elif x < 160:
453            c = (((x + y) & 3) == 1) and ((y & 3) == 1)
454        elif x < 240:
455            c = ((x + y) & 1) and (y & 1)
456        elif x < 320:
457            c = (x + y) & 1
458        else:
459            c = 1.
460        dest.setGray(x, y, c)
461dest.writePng("pat005.png")
462
463# Output 6: gamma-aware 20/40/60/80% threshold:
464def test4(src, name):
465    (w, h) = src.size()
466    dest = Image((w, h))
467    for y in range(h):
468        for x in range(w):
469            c = src.getGray(x, y)
470            if c < 0.2:
471                c = 0.
472            elif c < 0.4:
473                c = (((x + y) & 3) == 1) and ((y & 3) == 1)
474            elif c < 0.6:
475                c = ((x + y) & 1) and (y & 1)
476            elif c < 0.8:
477                c = (x + y) & 1
478            else:
479                c = 1.
480            dest.setGray(x, y, c)
481    dest.writePng(name)
482
483test4(lenna256bw, "out007.png")
484test4(gradient256bw, "grad007.png")
485
486
487src = lenna256bw
488src = gradient256bw
489(w, h) = src.size()
490
491mat = [[  0,  8,  3, 11],
492       [ 15,  4, 12,  7],
493       [  2, 10,  1,  9],
494       [ 13,  6, 14,  5]]
495#mat = [[  6,  7,  8,  9],
496#       [  5,  0,  1, 10],
497#       [  4,  3,  2, 11],
498#       [ 15, 14, 13, 12]]
499#mat = [[ 12,  5,  9, 13],
500#       [  8,  0,  1,  6],
501#       [  4,  3,  2, 10],
502#       [ 15, 11,  7, 14]]
503size = 4
504#mat = [[ 35, 24, 13, 14, 25, 32],
505#       [ 31, 12,  5,  6, 15, 26],
506#       [ 23,  4,  0,  1,  7, 16],
507#       [ 22, 11,  3,  2,  8, 17],
508#       [ 30, 21, 10,  9, 18, 27],
509#       [ 34, 29, 20, 19, 28, 33]]
510#mat = [[  0, 20, 30,  3, 23, 29],
511#       [ 12, 32, 18, 15, 35, 17],
512#       [ 27,  8,  4, 24, 11,  7],
513#       [  2, 22, 28,  1, 21, 31],
514#       [ 14, 34, 16, 13, 33, 19],
515#       [ 25, 10,  6, 26,  9,  5]]
516#size = 6
517dest = Image((w, h))
518for y in range(h):
519    for x in range(w):
520        c = src.getGray(x, y)
521        i = Gamma.CtoI(c)
522        threshold = mat[x % size][y % size]
523        d = math.floor(i * (size * size + .9999)) > threshold
524        if c > 0.95:
525            print c, i, i * (size * size + .9999)
526        c = d
527        dest.setGray(x, y, c)
528dest.writePng("out008.png")
529
530# Create a dot-matrix pattern
531mat = [[0, 2, 2, 3, 1],
532       [2, 2, 0, 2, 3],
533       [2, 0, 1, 1, 3],
534       [3, 2, 1, 0, 3],
535       [1, 3, 3, 3, 3]]
536dest = Image((320, 64))
537for x in range(320):
538    d = x / 64
539    for y in range(64):
540        c = mat[y % 5][x % 5] >= d
541        dest.setGray(x, y, c)
542dest.writePng("pat003.png")
543
Note: See TracBrowser for help on using the repository browser.