Changeset 2007


Ignore:
Timestamp:
Nov 17, 2007, 12:36:04 PM (13 years ago)
Author:
Sam Hocevar
Message:
  • Explain the problem with dimension separation.
Location:
www/study
Files:
5 added
2 edited
1 moved

Legend:

Unmodified
Added
Removed
  • www/study/part5.html

    r2005 r2007  
    6969
    7070<p> The following patterns show four ways to dither the same colour using
    71 our 8-colour palette. The first one mixes black, red and white pixels. The
    72 second one mixes black, red and cyan pixels. The third mixes black, red, green
    73 and magenta pixels. The last one mixes black, red, green and blue pixels. But
    74 all patterns visually blend to the same shade: </p>
     71our 8-colour palette. The first one mixes black, blue and white pixels. The
     72second one mixes black, blue and yellow pixels. The third mixes black, blue,
     73cyan and red pixels. The last one mixes black, blue, green and red pixels.
     74All patterns visually blend to the same shade, but the last one is the
     75most appealing visually: </p>
    7576
    7677<p style="text-align: center;">
    77   <img src="pat5-1-2.png" width="320" height="160"
     78  <img src="pat5-2-1.png" width="320" height="160"
    7879       class="inline" alt="3 ways to dither the same colour" />
     80</p>
     81
     82<p> It is therefore quite clear that the exact pixel values matter much less
     83than visual artifacts. Here is a close-up of the previous output’s top-left
     84corner. The slanted bright pixel lines that appear are typical Floyd-Steinberg
     85artifacts. They are rendered even worse by the fact that dithering is done on
     86three different dimensions that do not take the others into account: </p>
     87
     88<p style="text-align: center;">
     89  <img src="out5-2-1.png" width="32" height="32"
     90       class="inlinetop" alt="Floyd-Steinberg, 8 colours, gamma-corrected, cropped" />
     91  <img src="out5-2-2.png" width="192" height="192"
     92       class="inline" alt="Floyd-Steinberg, 8 colours, gamma-corrected, cropped, zoomed" />
     93</p>
     94
     95<p> Some algorithms perform a bit better in this area. This is Stucki
     96dithering. The Floyd-Steinberg artifacts are much less visible: </p>
     97
     98<p style="text-align: center;">
     99  <img src="out5-2-3.png" width="256" height="256"
     100       class="inline" alt="Stucki, 8 colours, gamma-corrected" />
     101</p>
     102
     103<p> And this is a close-up of the same area: </p>
     104
     105<p style="text-align: center;">
     106  <img src="out5-2-4.png" width="32" height="32"
     107       class="inlinetop" alt="Stucki, 8 colours, gamma-corrected, cropped" />
     108  <img src="out5-2-5.png" width="192" height="192"
     109       class="inline" alt="Stucki, 8 colours, gamma-corrected, cropped, zoomed" />
    79110</p>
    80111
  • www/study/study.py

    r2005 r2007  
    1111        else:
    1212            return gd.image.__new__(args)
     13    def save(self, name):
     14        print " * saving %s" % (name,)
     15        self.writePng(name)
    1316    def getGray(self, x, y):
    1417        p = self.getPixel((x, y))
     
    3033        self.setPixel((x, y), c)
    3134
     35##############################################################################
     36print "Initialisation"
     37
    3238# Manipulate gamma values
    3339class Gamma:
     
    4955    for y in range(256):
    5056        gradient256bw.setGray(x, 255 - y, y / 255.)
    51 gradient256bw.writePng("gradient256bw.png")
     57gradient256bw.save("gradient256bw.png")
     58
     59##############################################################################
     60print "Chapter 1. Colour quantisation"
    5261
    5362# Output 1.1.1: 50% threshold
     
    6372    return dest
    6473
    65 test11x(lenna256bw, 0.5).writePng("out1-1-1.png")
    66 test11x(lenna256bw, 0.4).writePng("out1-1-2.png")
    67 test11x(lenna256bw, 0.6).writePng("out1-1-3.png")
    68 test11x(gradient256bw, 0.5).writePng("grad1-1-1.png")
    69 test11x(gradient256bw, 0.4).writePng("grad1-1-2.png")
    70 test11x(gradient256bw, 0.6).writePng("grad1-1-3.png")
     74test11x(lenna256bw, 0.5).save("out1-1-1.png")
     75test11x(lenna256bw, 0.4).save("out1-1-2.png")
     76test11x(lenna256bw, 0.6).save("out1-1-3.png")
     77test11x(gradient256bw, 0.5).save("grad1-1-1.png")
     78test11x(gradient256bw, 0.4).save("grad1-1-2.png")
     79test11x(gradient256bw, 0.6).save("grad1-1-3.png")
    7180
    7281# Output 1.2.1: 3-colour threshold
     
    8493    return dest
    8594
    86 test12x(lenna256bw, 3).writePng("out1-2-1.png")
    87 test12x(lenna256bw, 5).writePng("out1-2-2.png")
    88 test12x(gradient256bw, 3).writePng("grad1-2-1.png")
    89 test12x(gradient256bw, 5).writePng("grad1-2-2.png")
     95test12x(lenna256bw, 3).save("out1-2-1.png")
     96test12x(lenna256bw, 5).save("out1-2-2.png")
     97test12x(gradient256bw, 3).save("grad1-2-1.png")
     98test12x(gradient256bw, 5).save("grad1-2-2.png")
     99
     100##############################################################################
     101print "Chapter 2. Halftoning patterns"
    90102
    91103# Pattern 2.1.1: a 50% halftone pattern with various block sizes
     
    96108        c = (x / d + y / d) & 1
    97109        dest.setGray(x, y, c)
    98 dest.writePng("pat2-1-1.png")
     110dest.save("pat2-1-1.png")
    99111
    100112# Pattern 2.1.2: 25% and 75% halftone patterns with various block sizes
     
    108120        c = ((x / d + y / d) & 1) and (y / d & 1)
    109121        dest.setGray(x, y, c)
    110 dest.writePng("pat2-1-2.png")
     122dest.save("pat2-1-2.png")
    111123
    112124# Output 2.1.1: 20/40/60/80% threshold with 25/50/75% patterns inbetween:
     
    130142    return dest
    131143
    132 test211(lenna256bw).writePng("out2-1-1.png")
    133 test211(gradient256bw).writePng("grad2-1-1.png")
     144test211(lenna256bw).save("out2-1-1.png")
     145test211(gradient256bw).save("grad2-1-1.png")
    134146
    135147# Pattern 2.2.1: vertical, mixed and horizontal black-white halftones
     
    145157        c = y & 1
    146158        dest.setGray(x, y, c)
    147 dest.writePng("pat2-2-1.png")
     159dest.save("pat2-2-1.png")
    148160
    149161# Pattern 2.2.2: two different 25% patterns
     
    162174        c = (x / 2 & 1) and ((y / 2 + x / 4) & 1)
    163175        dest.setGray(x, y, c)
    164 dest.writePng("pat2-2-2.png")
     176dest.save("pat2-2-2.png")
    165177
    166178# Output 2.3.1: 4x4 Bayer dithering
     
    195207    return dest
    196208
    197 test23x(lenna256bw, DITHER_BAYER44).writePng("out2-3-1.png")
    198 test23x(gradient256bw, DITHER_BAYER44).writePng("grad2-3-1.png")
    199 
    200 test23x(lenna256bw, DITHER_CLUSTER44).writePng("out2-3-2.png")
    201 test23x(gradient256bw, DITHER_CLUSTER44).writePng("grad2-3-2.png")
    202 
    203 test23x(lenna256bw, DITHER_LINE53).writePng("out2-3-3.png")
    204 test23x(gradient256bw, DITHER_LINE53).writePng("grad2-3-3.png")
     209test23x(lenna256bw, DITHER_BAYER44).save("out2-3-1.png")
     210test23x(gradient256bw, DITHER_BAYER44).save("grad2-3-1.png")
     211
     212test23x(lenna256bw, DITHER_CLUSTER44).save("out2-3-2.png")
     213test23x(gradient256bw, DITHER_CLUSTER44).save("grad2-3-2.png")
     214
     215test23x(lenna256bw, DITHER_LINE53).save("out2-3-3.png")
     216test23x(gradient256bw, DITHER_LINE53).save("grad2-3-3.png")
    205217
    206218# Output 2.4.1: uniform random dithering
     
    216228    return dest
    217229
    218 test241(lenna256bw).writePng("out2-4-1.png")
    219 test241(gradient256bw).writePng("grad2-4-1.png")
     230test241(lenna256bw).save("out2-4-1.png")
     231test241(gradient256bw).save("grad2-4-1.png")
    220232
    221233# Output 2.4.2: random dithering
     
    231243    return dest
    232244
    233 test242(lenna256bw).writePng("out2-4-2.png")
    234 test242(gradient256bw).writePng("grad2-4-2.png")
     245test242(lenna256bw).save("out2-4-2.png")
     246test242(gradient256bw).save("grad2-4-2.png")
     247
     248##############################################################################
     249print "Chapter 3. Error diffusion"
    235250
    236251# Output 3.0.1: naive error diffusion
     
    329344    return dest
    330345
    331 test3xx(lenna256bw, ERROR_NAIVE, False).writePng("out3-0-1.png")
    332 test3xx(gradient256bw, ERROR_NAIVE, False).writePng("grad3-0-1.png")
    333 
    334 test3xx(lenna256bw, ERROR_FSTEIN, False).writePng("out3-1-1.png")
    335 test3xx(gradient256bw, ERROR_FSTEIN, False).writePng("grad3-1-1.png")
    336 test3xx(lenna256bw, ERROR_FSTEIN, True).writePng("out3-1-2.png")
    337 test3xx(gradient256bw, ERROR_FSTEIN, True).writePng("grad3-1-2.png")
    338 
    339 test3xx(lenna256bw, ERROR_FAN, False).writePng("out3-2-1.png")
    340 test3xx(gradient256bw, ERROR_FAN, False).writePng("grad3-2-1.png")
    341 
    342 test3xx(lenna256bw, ERROR_JAJUNI, False).writePng("out3-2-2.png")
    343 test3xx(gradient256bw, ERROR_JAJUNI, False).writePng("grad3-2-2.png")
    344 
    345 test3xx(lenna256bw, ERROR_STUCKI, False).writePng("out3-2-3.png")
    346 test3xx(gradient256bw, ERROR_STUCKI, False).writePng("grad3-2-3.png")
    347 
    348 test3xx(lenna256bw, ERROR_BURKES, False).writePng("out3-2-4.png")
    349 test3xx(gradient256bw, ERROR_BURKES, False).writePng("grad3-2-4.png")
    350 
    351 test3xx(lenna256bw, ERROR_SIERRA, False).writePng("out3-2-5.png")
    352 test3xx(gradient256bw, ERROR_SIERRA, False).writePng("grad3-2-5.png")
    353 
    354 test3xx(lenna256bw, ERROR_SIERRA2, False).writePng("out3-2-6.png")
    355 test3xx(gradient256bw, ERROR_SIERRA2, False).writePng("grad3-2-6.png")
    356 
    357 test3xx(lenna256bw, ERROR_FILTERLITE, False).writePng("out3-2-7.png")
    358 test3xx(gradient256bw, ERROR_FILTERLITE, False).writePng("grad3-2-7.png")
    359 
    360 test3xx(lenna256bw, ERROR_ATKINSON, False).writePng("out3-2-8.png")
    361 test3xx(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")
     346test3xx(lenna256bw, ERROR_NAIVE, False).save("out3-0-1.png")
     347test3xx(gradient256bw, ERROR_NAIVE, False).save("grad3-0-1.png")
     348
     349test3xx(lenna256bw, ERROR_FSTEIN, False).save("out3-1-1.png")
     350test3xx(gradient256bw, ERROR_FSTEIN, False).save("grad3-1-1.png")
     351test3xx(lenna256bw, ERROR_FSTEIN, True).save("out3-1-2.png")
     352test3xx(gradient256bw, ERROR_FSTEIN, True).save("grad3-1-2.png")
     353
     354test3xx(lenna256bw, ERROR_FAN, False).save("out3-2-1.png")
     355test3xx(gradient256bw, ERROR_FAN, False).save("grad3-2-1.png")
     356
     357test3xx(lenna256bw, ERROR_JAJUNI, False).save("out3-2-2.png")
     358test3xx(gradient256bw, ERROR_JAJUNI, False).save("grad3-2-2.png")
     359
     360test3xx(lenna256bw, ERROR_STUCKI, False).save("out3-2-3.png")
     361test3xx(gradient256bw, ERROR_STUCKI, False).save("grad3-2-3.png")
     362
     363test3xx(lenna256bw, ERROR_BURKES, False).save("out3-2-4.png")
     364test3xx(gradient256bw, ERROR_BURKES, False).save("grad3-2-4.png")
     365
     366test3xx(lenna256bw, ERROR_SIERRA, False).save("out3-2-5.png")
     367test3xx(gradient256bw, ERROR_SIERRA, False).save("grad3-2-5.png")
     368
     369test3xx(lenna256bw, ERROR_SIERRA2, False).save("out3-2-6.png")
     370test3xx(gradient256bw, ERROR_SIERRA2, False).save("grad3-2-6.png")
     371
     372test3xx(lenna256bw, ERROR_FILTERLITE, False).save("out3-2-7.png")
     373test3xx(gradient256bw, ERROR_FILTERLITE, False).save("grad3-2-7.png")
     374
     375test3xx(lenna256bw, ERROR_ATKINSON, False).save("out3-2-8.png")
     376test3xx(gradient256bw, ERROR_ATKINSON, False).save("grad3-2-8.png")
     377
     378#test3xx(lenna256bw, ERROR_STAR, False).save("out3-2-9.png")
     379#test3xx(gradient256bw, ERROR_STAR, False).save("grad3-2-9.png")
     380
     381#test3xx(lenna256bw, ERROR_STAR, False).save("out3-2-9.png")
     382#test3xx(gradient256bw, ERROR_STAR, False).save("grad3-2-9.png")
     383
     384##############################################################################
     385print "Chapter 4. Grayscale dithering"
    368386
    369387# Output 4.0.1: 4x4 Bayer dithering, 3 colours
     
    384402    return dest
    385403
    386 test401(lenna256bw, DITHER_BAYER44).writePng("out4-0-1.png")
    387 test401(gradient256bw, DITHER_BAYER44).writePng("grad4-0-1.png")
     404test401(lenna256bw, DITHER_BAYER44).save("out4-0-1.png")
     405test401(gradient256bw, DITHER_BAYER44).save("grad4-0-1.png")
    388406
    389407# Output 4.0.2: standard Floyd-Steinberg, 3 colours
     
    425443    return dest
    426444
    427 test402(lenna256bw, ERROR_FSTEIN, False).writePng("out4-0-2.png")
    428 test402(gradient256bw, ERROR_FSTEIN, False).writePng("grad4-0-2.png")
     445test402(lenna256bw, ERROR_FSTEIN, False).save("out4-0-2.png")
     446test402(gradient256bw, ERROR_FSTEIN, False).save("grad4-0-2.png")
    429447
    430448# Pattern 4.1.1: gamma-corrected 50% gray, black-white halftone, 50% gray
     
    438456    for x in range(160, 240):
    439457        dest.setGray(x, y, 0.5)
    440 dest.writePng("pat4-1-1.png")
     458dest.save("pat4-1-1.png")
    441459
    442460# Output 4.2.1: gamma-corrected 2-colour Floyd-Steinberg
     
    492510    return 1.
    493511
    494 test42x(lenna256bw, ERROR_FSTEIN, threshold_2).writePng("out4-2-1.png")
    495 test42x(gradient256bw, ERROR_FSTEIN, threshold_2).writePng("grad4-2-1.png")
    496 test42x(lenna256bw, ERROR_FSTEIN, threshold_3).writePng("out4-2-2.png")
    497 test42x(gradient256bw, ERROR_FSTEIN, threshold_3).writePng("grad4-2-2.png")
    498 test42x(lenna256bw, ERROR_FSTEIN, threshold_4).writePng("out4-2-3.png")
    499 test42x(gradient256bw, ERROR_FSTEIN, threshold_4).writePng("grad4-2-3.png")
     512test42x(lenna256bw, ERROR_FSTEIN, threshold_2).save("out4-2-1.png")
     513test42x(gradient256bw, ERROR_FSTEIN, threshold_2).save("grad4-2-1.png")
     514test42x(lenna256bw, ERROR_FSTEIN, threshold_3).save("out4-2-2.png")
     515test42x(gradient256bw, ERROR_FSTEIN, threshold_3).save("grad4-2-2.png")
     516test42x(lenna256bw, ERROR_FSTEIN, threshold_4).save("out4-2-3.png")
     517test42x(gradient256bw, ERROR_FSTEIN, threshold_4).save("grad4-2-3.png")
     518
     519##############################################################################
     520print "Chapter 5. Colour dithering"
    500521
    501522# Pattern 5.1.1: 8-colour palette
     
    508529    for y in range(64):
    509530        dest.setRgb(x, y, r, g, b)
    510 dest.writePng("pat5-1-1.png")
     531dest.save("pat5-1-1.png")
    511532
    512533# Load the 256x256 colour Lenna image
     
    515536# Output 5.1.1: 8-colour Floyd-Steinberg RGB dithering
    516537# Output 5.1.2: 8-colour gamma-corrected Floyd-Steinberg RGB dithering
    517 def test501(src, mat, func):
     538def test51x(src, mat, func):
    518539    (w, h) = src.size()
    519540    dest = Image((w, h))
     
    532553    return dest
    533554
    534 test501(lenna256, ERROR_FSTEIN, test3xx).writePng("out5-1-1.png")
    535 test501(lenna256, ERROR_FSTEIN, test42x).writePng("out5-1-2.png")
    536 
    537 # Pattern 5.1.2: different colours give the same result
     555test51x(lenna256, ERROR_FSTEIN, test3xx).save("out5-1-1.png")
     556out512 = test51x(lenna256, ERROR_FSTEIN, test42x)
     557out512.save("out5-1-2.png")
     558
     559# Pattern 5.2.1: different colours give the same result
    538560dest = Image((320, 160))
    539561for x in range(80):
     
    542564        g = DITHER_BAYER44[(y / 8) % 4][(x / 8) % 4] > 13
    543565        b = DITHER_BAYER44[(y / 8) % 4][(x / 8) % 4] > 13
    544         dest.setRgb(x, y, r, b, g)
     566        dest.setRgb(x, y, b, g, r)
    545567    for y in range(80, 160):
    546568        r = DITHER_BAYER44[y % 4][x % 4] > 7
    547569        g = DITHER_BAYER44[y % 4][x % 4] > 13
    548570        b = DITHER_BAYER44[y % 4][x % 4] > 13
    549         dest.setRgb(x, y, r, b, g)
     571        dest.setRgb(x, y, b, g, r)
    550572for x in range(80, 160):
    551573    for y in range(80):
     
    553575        g = DITHER_BAYER44[(y / 8) % 4][(x / 8 + 1) % 4] > 13
    554576        b = DITHER_BAYER44[(y / 8) % 4][(x / 8 + 1) % 4] > 13
    555         dest.setRgb(x, y, r, b, g)
     577        dest.setRgb(x, y, b, g, r)
    556578    for y in range(80, 160):
    557579        r = DITHER_BAYER44[y % 4][x % 4] > 7
    558580        g = DITHER_BAYER44[y % 4][(x + 1) % 4] > 13
    559581        b = DITHER_BAYER44[y % 4][(x + 1) % 4] > 13
    560         dest.setRgb(x, y, r, b, g)
     582        dest.setRgb(x, y, b, g, r)
    561583for x in range(160, 240):
    562584    for y in range(80):
     
    564586        g = DITHER_BAYER44[(y / 8) % 4][(x / 8) % 4] > 13
    565587        b = DITHER_BAYER44[(y / 8 + 1) % 4][(x / 8) % 4] > 13
    566         dest.setRgb(x, y, r, b, g)
     588        dest.setRgb(x, y, b, g, r)
    567589    for y in range(80, 160):
    568590        r = DITHER_BAYER44[(y + 1) % 4][(x + 1) % 4] > 7
    569591        g = DITHER_BAYER44[y % 4][x % 4] > 13
    570592        b = DITHER_BAYER44[(y + 1) % 4][x % 4] > 13
    571         dest.setRgb(x, y, r, b, g)
     593        dest.setRgb(x, y, b, g, r)
    572594for x in range(240, 320):
    573595    for y in range(80):
     
    575597        g = DITHER_BAYER44[(y / 8) % 4][(x / 8) % 4] > 13
    576598        b = DITHER_BAYER44[(y / 8) % 4][(x / 8 + 2) % 4] > 13
    577         dest.setRgb(x, y, r, b, g)
     599        dest.setRgb(x, y, b, g, r)
    578600    for y in range(80, 160):
    579601        r = DITHER_BAYER44[(y + 1) % 4][x % 4] > 7
    580602        g = DITHER_BAYER44[y % 4][x % 4] > 13
    581603        b = DITHER_BAYER44[y % 4][(x + 2) % 4] > 13
    582         dest.setRgb(x, y, r, b, g)
    583 dest.writePng("pat5-1-2.png")
     604        dest.setRgb(x, y, b, g, r)
     605dest.save("pat5-2-1.png")
     606
     607# Output 5.2.1: cropped 5.1.2
     608# Output 5.2.2: close-up of cropped 5.1.2
     609def test52x(src, x, y, w, h, z):
     610    dest = Image((w * z, h * z))
     611    for j in range(h):
     612        for i in range(w):
     613            (r, g, b) = src.getRgb(x + i, y + j)
     614            for v in range(z):
     615                for u in range(z):
     616                    dest.setRgb(i * z + u, j * z + v, r, g, b)
     617    return dest
     618
     619test52x(out512, 20, 70, 32, 32, 1).save("out5-2-1.png")
     620test52x(out512, 20, 70, 32, 32, 6).save("out5-2-2.png")
     621
     622out523 = test51x(lenna256, ERROR_STUCKI, test42x)
     623out523.save("out5-2-3.png")
     624test52x(out523, 20, 70, 32, 32, 1).save("out5-2-4.png")
     625test52x(out523, 20, 70, 32, 32, 6).save("out5-2-5.png")
    584626
    585627##############################################################################
     628print "Finished"
     629
    586630# Place temporary cruft below this
    587631import sys; sys.exit(0)
Note: See TracChangeset for help on using the changeset viewer.