This example demonstrates dithering using a Bayer matrix. Dithering is most commonly used for displaying images on hardware with a low colour depth. The algorithm modifies the input values using an index matrix before quantising them.
+----+----+----+----+----+----+----+----+ | 0 | 32 | 8 | 40 | 2 | 34 | 10 | 42 | +----+----+----+----+----+----+----+----+ | 48 | 16 | 56 | 24 | 50 | 18 | 58 | 26 | +----+----+----+----+----+----+----+----+ | 12 | 44 | 4 | 36 | 14 | 46 | 6 | 38 | +----+----+----+----+----+----+----+----+ | 60 | 28 | 52 | 20 | 62 | 30 | 54 | 22 | +----+----+----+----+----+----+----+----+ | 3 | 35 | 11 | 43 | 1 | 33 | 9 | 41 | +----+----+----+----+----+----+----+----+ | 51 | 19 | 59 | 27 | 49 | 17 | 57 | 25 | +----+----+----+----+----+----+----+----+ | 15 | 47 | 7 | 39 | 13 | 45 | 5 | 37 | +----+----+----+----+----+----+----+----+ | 63 | 31 | 55 | 23 | 61 | 29 | 53 | 21 | +----+----+----+----+----+----+----+----+

#!/usr/bin/env ruby
require 'hornetseye'
include Hornetseye
class MultiArray
class << self
def ramp( w, h )
idx = int( w, h ).indgen!
retval = MultiArray.int 2, w, h
retval.roll[ 0 ] = idx % w
retval.roll[ 1 ] = idx / w
retval
end
def bayer( lsize )
n = 1 << lsize
idx = MultiArray.int( n, n ).indgen!
result = MultiArray.int( n, n ).fill!
m = Sequence[ 0, 2, 3, 1 ].to_int
for i in 0 ... lsize
q = idx.bit( i ) | idx.bit( i + lsize ) << 1
result |= q.map( m ) << ( ( lsize - i - 1 ) << 1 )
end
result
end
end
end
class Sequence_
def bit( i )
( self & ( 1 << i ) ) >> i
end
def dither( lsize = 4 )
if typecode < RGB_
result = self.class.float.new
result.r, result.g, result.b = [ r, g, b ].collect do |c|
c.dither lsize
end
result
else
bayer = MultiArray.bayer lsize
idx = MultiArray.ramp( *shape ) & ( ( 1 << lsize ) - 1 )
msk = ( 1 << lsize ) ** 2 - 1
( self & msk <= bayer.warp( idx ) ).conditional self & ~msk, self | msk
end
end
end
raise "Syntax: bayer.rb <input image> <output image>" if ARGV.size != 2
img = MultiArray.load_ubytergb ARGV[ 0 ]
img.dither( 3 ).save_ubytergb ARGV[1]