source: www/study/study.py @ 1972

Last change on this file since 1972 was 1972, checked in by Sam Hocevar, 13 years ago
  • More about gamma correction. Fixed graphs.
  • Property svn:executable set to *
File size: 18.1 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
231# Output 3.1.2: serpentine Floyd-Steinberg
232# FIXME: serpentine only works if rows == offset * 2 + 1
233# Output 3.2.1: Fan (modified Floyd-Steinberg)
234# Output 3.2.2: Jarvis, Judice and Ninke
235# Output 3-2-3: Stucki
236# Output 3-2-4: Burkes
237# Output 3-2-5: Sierra
238# Output 3.2.6: Two-line Sierra
239# Output 3.2.7: Sierra's Filter Lite
240# Output 3-2-8: Atkinson
241# Output 3-2-9: Stevenson-Arce
242def test3xx(src, mat, serpentine, name):
243    (w, h) = src.size()
244    dest = Image((w, h))
245    lines = len(mat)
246    rows = len(mat[0])
247    offset = mat[0].index(-1)
248    ey = [[0.] * (w + rows - 1) for x in range(lines)]
249    for y in range(h):
250        ex = [0.] * (rows - offset)
251        if serpentine and y & 1:
252            xrange = range(w - 1, -1, -1)
253        else:
254            xrange = range(w)
255        for x in xrange:
256            # Set pixel
257            c = src.getGray(x, y) + ex[0] + ey[0][x + offset]
258            d = c > 0.5
259            dest.setGray(x, y, d)
260            error = c - d
261            # Propagate first line of error
262            for dx in range(rows - offset - 2):
263                ex[dx] = ex[dx + 1] + error * mat[0][offset + 1 + dx]
264            ex[rows - offset - 2] = error * mat[0][rows - 1]
265            # Propagate next lines
266            if serpentine and y & 1:
267                for dy in range(1, lines):
268                    for dx in range(rows):
269                        ey[dy][x + dx] += error * mat[dy][rows - 1 - dx]
270            else:
271                for dy in range(1, lines):
272                    for dx in range(rows):
273                        ey[dy][x + dx] += error * mat[dy][dx]
274        for dy in range(lines - 1):
275            ey[dy] = ey[dy + 1]
276        ey[lines - 1] = [0.] * (w + rows - 1)
277    dest.writePng(name)
278
279mat = [[ -1, 1]]
280test3xx(lenna256bw, mat, False, "out3-0-1.png")
281test3xx(gradient256bw, mat, False, "grad3-0-1.png")
282
283mat = [[    0.,    -1, 7./16],
284       [ 3./16, 5./16, 1./16]]
285test3xx(lenna256bw, mat, False, "out3-1-1.png")
286test3xx(gradient256bw, mat, False, "grad3-1-1.png")
287test3xx(lenna256bw, mat, True, "out3-1-2.png")
288test3xx(gradient256bw, mat, True, "grad3-1-2.png")
289
290mat = [[    0.,    0.,    -1, 7./16],
291       [ 1./16, 3./16, 5./16,    0.]]
292test3xx(lenna256bw, mat, False, "out3-2-1.png")
293test3xx(gradient256bw, mat, False, "grad3-2-1.png")
294
295mat = [[    0.,    0.,    -1, 7./48, 5./48],
296       [ 3./48, 5./48, 7./48, 5./48, 3./48],
297       [ 1./48, 3./48, 5./48, 3./48, 1./48]]
298test3xx(lenna256bw, mat, False, "out3-2-2.png")
299test3xx(gradient256bw, mat, False, "grad3-2-2.png")
300
301mat = [[    0.,    0.,    -1, 8./42, 4./42],
302       [ 2./42, 4./42, 8./42, 4./42, 2./42],
303       [ 1./42, 2./42, 4./42, 2./42, 1./42]]
304test3xx(lenna256bw, mat, False, "out3-2-3.png")
305test3xx(gradient256bw, mat, False, "grad3-2-3.png")
306
307mat = [[    0.,    0.,    -1, 8./32, 4./32],
308       [ 2./32, 4./32, 8./32, 4./32, 2./32]]
309test3xx(lenna256bw, mat, False, "out3-2-4.png")
310test3xx(gradient256bw, mat, False, "grad3-2-4.png")
311
312mat = [[    0.,    0.,    -1, 5./32, 3./32],
313       [ 2./32, 4./32, 5./32, 4./32, 2./32],
314       [    0., 2./32, 3./32, 2./32,    0.]]
315test3xx(lenna256bw, mat, False, "out3-2-5.png")
316test3xx(gradient256bw, mat, False, "grad3-2-5.png")
317
318mat = [[    0.,    0.,    -1, 4./16, 3./16],
319       [ 1./16, 2./16, 3./16, 2./16, 1./16]]
320test3xx(lenna256bw, mat, False, "out3-2-6.png")
321test3xx(gradient256bw, mat, False, "grad3-2-6.png")
322
323mat = [[   0.,   -1, 2./4],
324       [ 1./4, 1./4,   0.]]
325test3xx(lenna256bw, mat, False, "out3-2-7.png")
326test3xx(gradient256bw, mat, False, "grad3-2-7.png")
327
328mat = [[   0.,   -1, 1./8, 1./8],
329       [ 1./8, 1./8, 1./8,   0.],
330       [   0., 1./8,   0.,   0.]]
331test3xx(lenna256bw, mat, False, "out3-2-8.png")
332test3xx(gradient256bw, mat, False, "grad3-2-8.png")
333
334# This is Stevenson-Arce in hex lattice
335#mat = [[      0.,      0.,      0.,      -1,      0.,  32./200,      0.],
336#       [ 12./200,      0., 26./200,      0., 30./200,       0., 16./200],
337#       [      0., 12./200,      0., 26./200,      0.,  12./200,      0.],
338#       [  5./200,      0., 12./200,      0., 12./200,       0.,  5./200]]
339#test3xx(lenna256bw, mat, False, "out3-2-9.png")
340#test3xx(gradient256bw, mat, False, "grad3-2-9.png")
341
342# This is an attempt at implementing Stevenson-Arce in square lattice
343#mat = [[      0.,      0.,      -1, 32./200,      0.],
344#       [  6./200, 19./200, 28./200, 23./200,  8./200],
345#       [      0., 12./200, 26./200, 12./200,      0.],
346#       [  2./200,  9./200, 12./200,  9./200,  2./200]]
347#test3xx(lenna256bw, mat, False, "out3-2-9.png")
348#test3xx(gradient256bw, mat, False, "grad3-2-9.png")
349
350# Output 4.0.1: 4x4 Bayer dithering, 3 colours
351def test401(src, mat, name):
352    (w, h) = src.size()
353    dest = Image((w, h))
354    dx = len(mat[0])
355    dy = len(mat)
356    for y in range(h):
357        for x in range(w):
358            c = src.getGray(x, y)
359            threshold = (1. + mat[y % dy][x % dx]) / (dx * dy + 1)
360            if c < 0.5:
361                c = 0.5 * (c > threshold / 2)
362            else:
363                c = 0.5 + 0.5 * (c > 0.5 + threshold / 2)
364            dest.setGray(x, y, c)
365    dest.writePng(name)
366
367mat = [[  0,  8,  3, 11],
368       [ 15,  4, 12,  7],
369       [  2, 10,  1,  9],
370       [ 13,  6, 14,  5]]
371test401(lenna256bw, mat, "out4-0-1.png")
372test401(gradient256bw, mat, "grad4-0-1.png")
373
374# Output 4.0.2: standard Floyd-Steinberg, 3 colours
375def test402(src, mat, serpentine, name):
376    (w, h) = src.size()
377    dest = Image((w, h))
378    lines = len(mat)
379    rows = len(mat[0])
380    offset = mat[0].index(-1)
381    ey = [[0.] * (w + rows - 1) for x in range(lines)]
382    for y in range(h):
383        ex = [0.] * (rows - offset)
384        if serpentine and y & 1:
385            xrange = range(w - 1, -1, -1)
386        else:
387            xrange = range(w)
388        for x in xrange:
389            # Set pixel
390            c = src.getGray(x, y) + ex[0] + ey[0][x + offset]
391            d = 0.5 * (c > 0.25) + 0.5 * (c > 0.75)
392            dest.setGray(x, y, d)
393            error = c - d
394            # Propagate first line of error
395            for dx in range(rows - offset - 2):
396                ex[dx] = ex[dx + 1] + error * mat[0][offset + 1 + dx]
397            ex[rows - offset - 2] = error * mat[0][rows - 1]
398            # Propagate next lines
399            if serpentine and y & 1:
400                for dy in range(1, lines):
401                    for dx in range(rows):
402                        ey[dy][x + dx] += error * mat[dy][rows - 1 - dx]
403            else:
404                for dy in range(1, lines):
405                    for dx in range(rows):
406                        ey[dy][x + dx] += error * mat[dy][dx]
407        for dy in range(lines - 1):
408            ey[dy] = ey[dy + 1]
409        ey[lines - 1] = [0.] * (w + rows - 1)
410    dest.writePng(name)
411
412mat = [[    0.,    -1, 7./16],
413       [ 3./16, 5./16, 1./16]]
414test402(lenna256bw, mat, False, "out4-0-2.png")
415test402(gradient256bw, mat, False, "grad4-0-2.png")
416
417# Pattern 4.1.1: gamma-corrected 50% gray, black-white halftone, 50% gray
418dest = Image((240, 80))
419for y in range(80):
420    for x in range(80):
421        dest.setGray(x, y, Gamma.ItoC(0.5))
422    for x in range(80, 160):
423        c = (x + y) & 1
424        dest.setGray(x, y, c)
425    for x in range(160, 240):
426        dest.setGray(x, y, 0.5)
427dest.writePng("pat4-1-1.png")
428
429# Output 4.2.1: gamma-corrected 2-colour Floyd-Steinberg
430# Output 4.2.2: gamma-corrected 3-colour Floyd-Steinberg
431def test42x(src, mat, threshold, name):
432    (w, h) = src.size()
433    dest = Image((w, h))
434    lines = len(mat)
435    rows = len(mat[0])
436    offset = mat[0].index(-1)
437    ey = [[0.] * (w + rows - 1) for x in range(lines)]
438    for y in range(h):
439        ex = [0.] * (rows - offset)
440        for x in range(w):
441            # Set pixel
442            c = Gamma.CtoI(src.getGray(x, y)) + ex[0] + ey[0][x + offset]
443            d = threshold(c)
444            dest.setGray(x, y, Gamma.ItoC(d))
445            error = c - d
446            # Propagate first line of error
447            for dx in range(rows - offset - 2):
448                ex[dx] = ex[dx + 1] + error * mat[0][offset + 1 + dx]
449            ex[rows - offset - 2] = error * mat[0][rows - 1]
450            # Propagate next lines
451            for dy in range(1, lines):
452                for dx in range(rows):
453                    ey[dy][x + dx] += error * mat[dy][dx]
454        for dy in range(lines - 1):
455            ey[dy] = ey[dy + 1]
456        ey[lines - 1] = [0.] * (w + rows - 1)
457    dest.writePng(name)
458
459mat = [[    0.,    -1, 7./16],
460       [ 3./16, 5./16, 1./16]]
461test42x(lenna256bw, mat, lambda x: x > 0.5, "out4-2-1.png")
462test42x(gradient256bw, mat, lambda x: x > 0.5, "grad4-2-1.png")
463test42x(lenna256bw, mat, lambda x: 0.25 * (x > 0.25) + 0.75 * (x > 0.75), "out4-2-2.png")
464test42x(gradient256bw, mat, lambda x: 0.25 * (x > 0.25) + 0.75 * (x > 0.75), "grad4-2-2.png")
465
466##############################################################################
467# Only temporary cruft below this
468import sys
469sys.exit(0)
470
471
472
473
474# Pattern 5: gamma-corrected 50% gray, black-white halftone, 50% gray
475dest = Image((400, 240))
476for y in range(80):
477    for x in range(400):
478        if x < 80:
479            c = 0.
480        elif x < 160:
481            c = ((x + y) & 1) and (y & 1)
482        elif x < 240:
483            c = (x + y) & 1
484        elif x < 320:
485            c = ((x + y) & 1) or (y & 1)
486        else:
487            c = 1.
488        dest.setGray(x, y, c)
489for y in range(80, 160):
490    for x in range(400):
491        dest.setGray(x, y, x / 80 / 4.)
492for y in range(160, 240):
493    for x in range(400):
494        if x < 80:
495            c = 0.
496        elif x < 160:
497            c = (((x + y) & 3) == 1) and ((y & 3) == 1)
498        elif x < 240:
499            c = ((x + y) & 1) and (y & 1)
500        elif x < 320:
501            c = (x + y) & 1
502        else:
503            c = 1.
504        dest.setGray(x, y, c)
505dest.writePng("pat005.png")
506
507# Output 6: gamma-aware 20/40/60/80% threshold:
508def test4(src, name):
509    (w, h) = src.size()
510    dest = Image((w, h))
511    for y in range(h):
512        for x in range(w):
513            c = src.getGray(x, y)
514            if c < 0.2:
515                c = 0.
516            elif c < 0.4:
517                c = (((x + y) & 3) == 1) and ((y & 3) == 1)
518            elif c < 0.6:
519                c = ((x + y) & 1) and (y & 1)
520            elif c < 0.8:
521                c = (x + y) & 1
522            else:
523                c = 1.
524            dest.setGray(x, y, c)
525    dest.writePng(name)
526
527test4(lenna256bw, "out007.png")
528test4(gradient256bw, "grad007.png")
529
530
531src = lenna256bw
532src = gradient256bw
533(w, h) = src.size()
534
535mat = [[  0,  8,  3, 11],
536       [ 15,  4, 12,  7],
537       [  2, 10,  1,  9],
538       [ 13,  6, 14,  5]]
539#mat = [[  6,  7,  8,  9],
540#       [  5,  0,  1, 10],
541#       [  4,  3,  2, 11],
542#       [ 15, 14, 13, 12]]
543#mat = [[ 12,  5,  9, 13],
544#       [  8,  0,  1,  6],
545#       [  4,  3,  2, 10],
546#       [ 15, 11,  7, 14]]
547size = 4
548#mat = [[ 35, 24, 13, 14, 25, 32],
549#       [ 31, 12,  5,  6, 15, 26],
550#       [ 23,  4,  0,  1,  7, 16],
551#       [ 22, 11,  3,  2,  8, 17],
552#       [ 30, 21, 10,  9, 18, 27],
553#       [ 34, 29, 20, 19, 28, 33]]
554#mat = [[  0, 20, 30,  3, 23, 29],
555#       [ 12, 32, 18, 15, 35, 17],
556#       [ 27,  8,  4, 24, 11,  7],
557#       [  2, 22, 28,  1, 21, 31],
558#       [ 14, 34, 16, 13, 33, 19],
559#       [ 25, 10,  6, 26,  9,  5]]
560#size = 6
561dest = Image((w, h))
562for y in range(h):
563    for x in range(w):
564        c = src.getGray(x, y)
565        i = Gamma.CtoI(c)
566        threshold = mat[x % size][y % size]
567        d = math.floor(i * (size * size + .9999)) > threshold
568        if c > 0.95:
569            print c, i, i * (size * size + .9999)
570        c = d
571        dest.setGray(x, y, c)
572dest.writePng("out008.png")
573
574# Create a dot-matrix pattern
575mat = [[0, 2, 2, 3, 1],
576       [2, 2, 0, 2, 3],
577       [2, 0, 1, 1, 3],
578       [3, 2, 1, 0, 3],
579       [1, 3, 3, 3, 3]]
580dest = Image((320, 64))
581for x in range(320):
582    d = x / 64
583    for y in range(64):
584        c = mat[y % 5][x % 5] >= d
585        dest.setGray(x, y, c)
586dest.writePng("pat003.png")
587
Note: See TracBrowser for help on using the repository browser.