source: www/study/study.py @ 1978

Last change on this file since 1978 was 1978, checked in by Sam Hocevar, 13 years ago
  • No longer pass the idiotic "name" parameter to tests.
  • Property svn:executable set to *
File size: 16.2 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        if x < 0:
38            return -math.pow(-x, 1 / 2.2)
39        return math.pow(x, 1 / 2.2)
40    CtoI = staticmethod(CtoI)
41    ItoC = staticmethod(ItoC)
42
43# Load the 256x256 grayscale Lenna image
44lenna256bw = Image("lenna256bw.png")
45
46# Create a 32x256 grayscale gradient
47gradient256bw = Image((32, 256))
48for x in range(32):
49    for y in range(256):
50        gradient256bw.setGray(x, 255 - y, y / 255.)
51gradient256bw.writePng("gradient256bw.png")
52
53# Output 1.1.1: 50% threshold
54# Output 1.1.2: 40% threshold
55# Output 1.1.3: 60% threshold
56def test11x(src, threshold):
57    (w, h) = src.size()
58    dest = Image((w, h))
59    for y in range(h):
60        for x in range(w):
61            c = src.getGray(x, y) > threshold
62            dest.setGray(x, y, c)
63    return dest
64
65test11x(lenna256bw, 0.5).writePng("out1-1-1.png")
66test11x(lenna256bw, 0.4).writePng("out1-1-2.png")
67test11x(lenna256bw, 0.6).writePng("out1-1-3.png")
68test11x(gradient256bw, 0.5).writePng("grad1-1-1.png")
69test11x(gradient256bw, 0.4).writePng("grad1-1-2.png")
70test11x(gradient256bw, 0.6).writePng("grad1-1-3.png")
71
72# Output 1.2.1: 3-colour threshold
73# Output 1.2.2: 5-colour threshold
74def test12x(src, colors):
75    (w, h) = src.size()
76    dest = Image((w, h))
77    p = -0.0001 + colors
78    q = colors - 1
79    for y in range(h):
80        for x in range(w):
81            c = src.getGray(x, y)
82            c = math.floor(c * p) / q
83            dest.setGray(x, y, c)
84    return dest
85
86test12x(lenna256bw, 3).writePng("out1-2-1.png")
87test12x(lenna256bw, 5).writePng("out1-2-2.png")
88test12x(gradient256bw, 3).writePng("grad1-2-1.png")
89test12x(gradient256bw, 5).writePng("grad1-2-2.png")
90
91# Pattern 2.1.1: a 50% halftone pattern with various block sizes
92dest = Image((320, 80))
93for x in range(320):
94    d = 8 >> (x / 80)
95    for y in range(80):
96        c = (x / d + y / d) & 1
97        dest.setGray(x, y, c)
98dest.writePng("pat2-1-1.png")
99
100# Pattern 2.1.2: 25% and 75% halftone patterns with various block sizes
101dest = Image((320, 80))
102for x in range(320):
103    d = 8 >> (x / 80)
104    for y in range(40):
105        c = ((x / d + y / d) & 1) or (y / d & 1)
106        dest.setGray(x, y, c)
107    for y in range(40, 80):
108        c = ((x / d + y / d) & 1) and (y / d & 1)
109        dest.setGray(x, y, c)
110dest.writePng("pat2-1-2.png")
111
112# Output 2.1.1: 20/40/60/80% threshold with 25/50/75% patterns inbetween:
113def test211(src):
114    (w, h) = src.size()
115    dest = Image((w, h))
116    for y in range(h):
117        for x in range(w):
118            c = src.getGray(x, y)
119            if c < 0.2:
120                c = 0.
121            elif c < 0.4:
122                c = ((x + y) & 1) and (y & 1)
123            elif c < 0.6:
124                c = (x + y) & 1
125            elif c < 0.8:
126                c = ((x + y) & 1) or (y & 1)
127            else:
128                c = 1.
129            dest.setGray(x, y, c)
130    return dest
131
132test211(lenna256bw).writePng("out2-1-1.png")
133test211(gradient256bw).writePng("grad2-1-1.png")
134
135# Pattern 2.2.1: vertical, mixed and horizontal black-white halftones
136dest = Image((240, 80))
137for y in range(80):
138    for x in range(80):
139        c = x & 1
140        dest.setGray(x, y, c)
141    for x in range(80, 160):
142        c = (x / d + y / d) & 1
143        dest.setGray(x, y, c)
144    for x in range(160, 240):
145        c = y & 1
146        dest.setGray(x, y, c)
147dest.writePng("pat2-2-1.png")
148
149# Pattern 2.2.2: two different 25% patterns
150dest = Image((320, 80))
151for y in range(80):
152    for x in range(80):
153        c = (x / 2 & 1) and (y / 2 & 1)
154        dest.setGray(x, y, c)
155    for x in range(80, 160):
156        c = (x & 1) and (y & 1)
157        dest.setGray(x, y, c)
158    for x in range(160, 240):
159        c = (x & 1) and ((y + x / 2) & 1)
160        dest.setGray(x, y, c)
161    for x in range(240, 320):
162        c = (x / 2 & 1) and ((y / 2 + x / 4) & 1)
163        dest.setGray(x, y, c)
164dest.writePng("pat2-2-2.png")
165
166# Output 2.3.1: 4x4 Bayer dithering
167# Output 2.3.2: 4x4 cluster dot
168# Output 2.3.3: 5x3 line dithering
169DITHER_BAYER44 = \
170    [[  0,  8,  3, 11],
171     [ 15,  4, 12,  7],
172     [  2, 10,  1,  9],
173     [ 13,  6, 14,  5]]
174DITHER_CLUSTER44 = \
175    [[ 12,  5,  6, 13],
176     [  4,  0,  1,  7],
177     [ 11,  3,  2,  8],
178     [ 15, 10,  9, 14]]
179DITHER_LINE53 = \
180    [[ 13,  7,  0,  4, 10],
181     [  9,  3,  1,  8, 14],
182     [ 11,  5,  2,  6, 12],]
183
184def test23x(src, mat):
185    (w, h) = src.size()
186    dest = Image((w, h))
187    dx = len(mat[0])
188    dy = len(mat)
189    for y in range(h):
190        for x in range(w):
191            c = src.getGray(x, y)
192            threshold = (1. + mat[y % dy][x % dx]) / (dx * dy + 1)
193            c = c > threshold
194            dest.setGray(x, y, c)
195    return dest
196
197test23x(lenna256bw, DITHER_BAYER44).writePng("out2-3-1.png")
198test23x(gradient256bw, DITHER_BAYER44).writePng("grad2-3-1.png")
199
200test23x(lenna256bw, DITHER_CLUSTER44).writePng("out2-3-2.png")
201test23x(gradient256bw, DITHER_CLUSTER44).writePng("grad2-3-2.png")
202
203test23x(lenna256bw, DITHER_LINE53).writePng("out2-3-3.png")
204test23x(gradient256bw, DITHER_LINE53).writePng("grad2-3-3.png")
205
206# Output 2.4.1: uniform random dithering
207def test241(src):
208    random.seed(0)
209    (w, h) = src.size()
210    dest = Image((w, h))
211    for y in range(h):
212        for x in range(w):
213            c = src.getGray(x, y)
214            d = c > random.random()
215            dest.setGray(x, y, d)
216    return dest
217
218test241(lenna256bw).writePng("out2-4-1.png")
219test241(gradient256bw).writePng("grad2-4-1.png")
220
221# Output 2.4.2: random dithering
222def test242(src):
223    random.seed(0)
224    (w, h) = src.size()
225    dest = Image((w, h))
226    for y in range(h):
227        for x in range(w):
228            c = src.getGray(x, y)
229            d = c > random.gauss(0.5, 0.15)
230            dest.setGray(x, y, d)
231    return dest
232
233test242(lenna256bw).writePng("out2-4-2.png")
234test242(gradient256bw).writePng("grad2-4-2.png")
235
236# Output 3.0.1: naive error diffusion
237# Output 3.1.1: standard Floyd-Steinberg
238# Output 3.1.2: serpentine Floyd-Steinberg
239# FIXME: serpentine only works if rows == offset * 2 + 1
240# Output 3.2.1: Fan (modified Floyd-Steinberg)
241# Output 3.2.2: Jarvis, Judice and Ninke
242# Output 3-2-3: Stucki
243# Output 3-2-4: Burkes
244# Output 3-2-5: Sierra
245# Output 3.2.6: Two-line Sierra
246# Output 3.2.7: Sierra's Filter Lite
247# Output 3-2-8: Atkinson
248ERROR_NAIVE = \
249    [[ -1, 1]]
250ERROR_FSTEIN = \
251    [[    0.,    -1, 7./16],
252     [ 3./16, 5./16, 1./16]]
253ERROR_FAN = \
254    [[    0.,    0.,    -1, 7./16],
255     [ 1./16, 3./16, 5./16,    0.]]
256ERROR_JAJUNI = \
257    [[    0.,    0.,    -1, 7./48, 5./48],
258     [ 3./48, 5./48, 7./48, 5./48, 3./48],
259     [ 1./48, 3./48, 5./48, 3./48, 1./48]]
260ERROR_STUCKI = \
261    [[    0.,    0.,    -1, 8./42, 4./42],
262     [ 2./42, 4./42, 8./42, 4./42, 2./42],
263     [ 1./42, 2./42, 4./42, 2./42, 1./42]]
264ERROR_BURKES = \
265    [[    0.,    0.,    -1, 8./32, 4./32],
266     [ 2./32, 4./32, 8./32, 4./32, 2./32]]
267ERROR_SIERRA = \
268    [[    0.,    0.,    -1, 5./32, 3./32],
269     [ 2./32, 4./32, 5./32, 4./32, 2./32],
270     [    0., 2./32, 3./32, 2./32,    0.]]
271ERROR_SIERRA2 = \
272    [[    0.,    0.,    -1, 4./16, 3./16],
273     [ 1./16, 2./16, 3./16, 2./16, 1./16]]
274ERROR_FILTERLITE = \
275    [[   0.,   -1, 2./4],
276     [ 1./4, 1./4,   0.]]
277ERROR_ATKINSON = \
278    [[   0.,   -1, 1./8, 1./8],
279     [ 1./8, 1./8, 1./8,   0.],
280     [   0., 1./8,   0.,   0.]]
281## This is Stevenson-Arce in hex lattice
282#ERROR_STAR = \
283#    [[      0.,      0.,      0.,      -1,      0.,  32./200,      0.],
284#     [ 12./200,      0., 26./200,      0., 30./200,       0., 16./200],
285#     [      0., 12./200,      0., 26./200,      0.,  12./200,      0.],
286#     [  5./200,      0., 12./200,      0., 12./200,       0.,  5./200]]
287## This is an attempt at implementing Stevenson-Arce in square lattice
288#ERROR_STAR = \
289#    [[      0.,      0.,      -1, 32./200,      0.],
290#     [  6./200, 19./200, 28./200, 23./200,  8./200],
291#     [      0., 12./200, 26./200, 12./200,      0.],
292#     [  2./200,  9./200, 12./200,  9./200,  2./200]]
293
294def test3xx(src, mat, serpentine):
295    (w, h) = src.size()
296    dest = Image((w, h))
297    lines = len(mat)
298    rows = len(mat[0])
299    offset = mat[0].index(-1)
300    ey = [[0.] * (w + rows - 1) for x in range(lines)]
301    for y in range(h):
302        ex = [0.] * (rows - offset)
303        if serpentine and y & 1:
304            xrange = range(w - 1, -1, -1)
305        else:
306            xrange = range(w)
307        for x in xrange:
308            # Set pixel
309            c = src.getGray(x, y) + ex[0] + ey[0][x + offset]
310            d = c > 0.5
311            dest.setGray(x, y, d)
312            error = c - d
313            # Propagate first line of error
314            for dx in range(rows - offset - 2):
315                ex[dx] = ex[dx + 1] + error * mat[0][offset + 1 + dx]
316            ex[rows - offset - 2] = error * mat[0][rows - 1]
317            # Propagate next lines
318            if serpentine and y & 1:
319                for dy in range(1, lines):
320                    for dx in range(rows):
321                        ey[dy][x + dx] += error * mat[dy][rows - 1 - dx]
322            else:
323                for dy in range(1, lines):
324                    for dx in range(rows):
325                        ey[dy][x + dx] += error * mat[dy][dx]
326        for dy in range(lines - 1):
327            ey[dy] = ey[dy + 1]
328        ey[lines - 1] = [0.] * (w + rows - 1)
329    return dest
330
331test3xx(lenna256bw, ERROR_NAIVE, False).writePng("out3-0-1.png")
332test3xx(gradient256bw, ERROR_NAIVE, False).writePng("grad3-0-1.png")
333
334test3xx(lenna256bw, ERROR_FSTEIN, False).writePng("out3-1-1.png")
335test3xx(gradient256bw, ERROR_FSTEIN, False).writePng("grad3-1-1.png")
336test3xx(lenna256bw, ERROR_FSTEIN, True).writePng("out3-1-2.png")
337test3xx(gradient256bw, ERROR_FSTEIN, True).writePng("grad3-1-2.png")
338
339test3xx(lenna256bw, ERROR_FAN, False).writePng("out3-2-1.png")
340test3xx(gradient256bw, ERROR_FAN, False).writePng("grad3-2-1.png")
341
342test3xx(lenna256bw, ERROR_JAJUNI, False).writePng("out3-2-2.png")
343test3xx(gradient256bw, ERROR_JAJUNI, False).writePng("grad3-2-2.png")
344
345test3xx(lenna256bw, ERROR_STUCKI, False).writePng("out3-2-3.png")
346test3xx(gradient256bw, ERROR_STUCKI, False).writePng("grad3-2-3.png")
347
348test3xx(lenna256bw, ERROR_BURKES, False).writePng("out3-2-4.png")
349test3xx(gradient256bw, ERROR_BURKES, False).writePng("grad3-2-4.png")
350
351test3xx(lenna256bw, ERROR_SIERRA, False).writePng("out3-2-5.png")
352test3xx(gradient256bw, ERROR_SIERRA, False).writePng("grad3-2-5.png")
353
354test3xx(lenna256bw, ERROR_SIERRA2, False).writePng("out3-2-6.png")
355test3xx(gradient256bw, ERROR_SIERRA2, False).writePng("grad3-2-6.png")
356
357test3xx(lenna256bw, ERROR_FILTERLITE, False).writePng("out3-2-7.png")
358test3xx(gradient256bw, ERROR_FILTERLITE, False).writePng("grad3-2-7.png")
359
360test3xx(lenna256bw, ERROR_ATKINSON, False).writePng("out3-2-8.png")
361test3xx(gradient256bw, ERROR_ATKINSON, False).writePng("grad3-2-8.png")
362
363#test3xx(lenna256bw, ERROR_STAR, False).writePng("out3-2-9.png")
364#test3xx(gradient256bw, ERROR_STAR, False).writePng("grad3-2-9.png")
365
366#test3xx(lenna256bw, ERROR_STAR, False).writePng("out3-2-9.png")
367#test3xx(gradient256bw, ERROR_STAR, False).writePng("grad3-2-9.png")
368
369# Output 4.0.1: 4x4 Bayer dithering, 3 colours
370def test401(src, mat):
371    (w, h) = src.size()
372    dest = Image((w, h))
373    dx = len(mat[0])
374    dy = len(mat)
375    for y in range(h):
376        for x in range(w):
377            c = src.getGray(x, y)
378            threshold = (1. + mat[y % dy][x % dx]) / (dx * dy + 1)
379            if c < 0.5:
380                c = 0.5 * (c > threshold / 2)
381            else:
382                c = 0.5 + 0.5 * (c > 0.5 + threshold / 2)
383            dest.setGray(x, y, c)
384    return dest
385
386test401(lenna256bw, DITHER_BAYER44).writePng("out4-0-1.png")
387test401(gradient256bw, DITHER_BAYER44).writePng("grad4-0-1.png")
388
389# Output 4.0.2: standard Floyd-Steinberg, 3 colours
390def test402(src, mat, serpentine):
391    (w, h) = src.size()
392    dest = Image((w, h))
393    lines = len(mat)
394    rows = len(mat[0])
395    offset = mat[0].index(-1)
396    ey = [[0.] * (w + rows - 1) for x in range(lines)]
397    for y in range(h):
398        ex = [0.] * (rows - offset)
399        if serpentine and y & 1:
400            xrange = range(w - 1, -1, -1)
401        else:
402            xrange = range(w)
403        for x in xrange:
404            # Set pixel
405            c = src.getGray(x, y) + ex[0] + ey[0][x + offset]
406            d = 0.5 * (c > 0.25) + 0.5 * (c > 0.75)
407            dest.setGray(x, y, d)
408            error = c - d
409            # Propagate first line of error
410            for dx in range(rows - offset - 2):
411                ex[dx] = ex[dx + 1] + error * mat[0][offset + 1 + dx]
412            ex[rows - offset - 2] = error * mat[0][rows - 1]
413            # Propagate next lines
414            if serpentine and y & 1:
415                for dy in range(1, lines):
416                    for dx in range(rows):
417                        ey[dy][x + dx] += error * mat[dy][rows - 1 - dx]
418            else:
419                for dy in range(1, lines):
420                    for dx in range(rows):
421                        ey[dy][x + dx] += error * mat[dy][dx]
422        for dy in range(lines - 1):
423            ey[dy] = ey[dy + 1]
424        ey[lines - 1] = [0.] * (w + rows - 1)
425    return dest
426
427test402(lenna256bw, ERROR_FSTEIN, False).writePng("out4-0-2.png")
428test402(gradient256bw, ERROR_FSTEIN, False).writePng("grad4-0-2.png")
429
430# Pattern 4.1.1: gamma-corrected 50% gray, black-white halftone, 50% gray
431dest = Image((240, 80))
432for y in range(80):
433    for x in range(80):
434        dest.setGray(x, y, Gamma.ItoC(0.5))
435    for x in range(80, 160):
436        c = (x + y) & 1
437        dest.setGray(x, y, c)
438    for x in range(160, 240):
439        dest.setGray(x, y, 0.5)
440dest.writePng("pat4-1-1.png")
441
442# Output 4.2.1: gamma-corrected 2-colour Floyd-Steinberg
443# Output 4.2.2: gamma-corrected 3-colour Floyd-Steinberg
444def test42x(src, mat, threshold):
445    (w, h) = src.size()
446    dest = Image((w, h))
447    lines = len(mat)
448    rows = len(mat[0])
449    offset = mat[0].index(-1)
450    ey = [[0.] * (w + rows - 1) for x in range(lines)]
451    for y in range(h):
452        ex = [0.] * (rows - offset)
453        for x in range(w):
454            # Set pixel
455            c = Gamma.CtoI(src.getGray(x, y)) + ex[0] + ey[0][x + offset]
456            d = threshold(c)
457            dest.setGray(x, y, Gamma.ItoC(d))
458            error = c - d
459            # Propagate first line of error
460            for dx in range(rows - offset - 2):
461                ex[dx] = ex[dx + 1] + error * mat[0][offset + 1 + dx]
462            ex[rows - offset - 2] = error * mat[0][rows - 1]
463            # Propagate next lines
464            for dy in range(1, lines):
465                for dx in range(rows):
466                    ey[dy][x + dx] += error * mat[dy][dx]
467        for dy in range(lines - 1):
468            ey[dy] = ey[dy + 1]
469        ey[lines - 1] = [0.] * (w + rows - 1)
470    return dest
471
472def threshold_2(x):
473    if x < Gamma.CtoI(0.50):
474        return 0.
475    return 1.
476
477def threshold_3(x):
478    if x < Gamma.CtoI(0.25):
479        return 0.
480    elif x < Gamma.CtoI(0.75):
481        return Gamma.CtoI(0.5)
482    return 1.
483
484def threshold_4(x):
485    if x < Gamma.CtoI(0.17):
486        return 0.
487    elif x < Gamma.CtoI(0.50):
488        return Gamma.CtoI(0.3333)
489    elif x < Gamma.CtoI(0.83):
490        return Gamma.CtoI(0.6666)
491    return 1.
492
493test42x(lenna256bw, ERROR_FSTEIN, threshold_2).writePng("out4-2-1.png")
494test42x(gradient256bw, ERROR_FSTEIN, threshold_2).writePng("grad4-2-1.png")
495test42x(lenna256bw, ERROR_FSTEIN, threshold_3).writePng("out4-2-2.png")
496test42x(gradient256bw, ERROR_FSTEIN, threshold_3).writePng("grad4-2-2.png")
497
498##############################################################################
499# Place temporary cruft below this
500import sys; sys.exit(0)
501
Note: See TracBrowser for help on using the repository browser.