@@ -85,7 +85,15 @@ function dithering(ctx, width, height, threshold, typeIndex) {
8585 ctx . putImageData ( imageData , 0 , 0 ) ;
8686}
8787
88- function toGrayscale ( ctx , width , height ) {
88+ function grayscale_dithering ( ctx , width , height , threshold , typeIndex ) {
89+ const type = [ 'binary' , 'bayer' , 'floydsteinberg' , 'atkinson' ] [ typeIndex ] ;
90+ const bayerThresholdMap = [
91+ [ 15 , 135 , 45 , 165 ] ,
92+ [ 195 , 75 , 225 , 105 ] ,
93+ [ 60 , 180 , 30 , 150 ] ,
94+ [ 240 , 120 , 210 , 90 ] ,
95+ ] ;
96+
8997 const lumR = [ ] ;
9098 const lumG = [ ] ;
9199 const lumB = [ ] ;
@@ -98,10 +106,58 @@ function toGrayscale(ctx, width, height) {
98106
99107 const imageDataLength = imageData . data . length ;
100108
101- // Greyscale luminance (sets r pixels to luminance of rgb)
109+ const w = imageData . width ;
110+ let currentPixel ;
111+ let newPixel ;
112+ let err ;
113+
114+ // Grayscale luminance (sets r pixels to luminance of rgb)
102115 for ( let i = 0 ; i <= imageDataLength ; i += 4 ) {
103- const grayscale_pixel = Math . ceil ( Math . round ( lumR [ imageData . data [ i ] ] + lumG [ imageData . data [ i + 1 ] ] + lumB [ imageData . data [ i + 2 ] ] ) / 16 ) ;
104- imageData . data [ i ] = imageData . data [ i + 1 ] = imageData . data [ i + 2 ] = grayscale_pixel * 16 ;
116+ imageData . data [ i ] =
117+ Math . round ( lumR [ imageData . data [ i ] ] + lumG [ imageData . data [ i + 1 ] ] + lumB [ imageData . data [ i + 2 ] ] ) ;
118+ }
119+
120+ for ( let currentPixelIndex = 0 ; currentPixelIndex <= imageDataLength ; currentPixelIndex += 4 ) {
121+ newPixel = Math . round ( ( imageData . data [ currentPixelIndex ] ) / 16 ) * 16 ;
122+
123+ if ( type === 'binary' ) {
124+ // No dithering
125+ imageData . data [ currentPixelIndex ] = newPixel ;
126+ } else if ( type === 'bayer' ) {
127+ // 4x4 Bayer ordered dithering algorithm
128+ // eslint-disable-next-line no-mixed-operators
129+ const x = currentPixelIndex / 4 % w ;
130+ const y = Math . floor ( currentPixelIndex / 4 / w ) ;
131+ const map = Math . floor ( ( newPixel + bayerThresholdMap [ x % 4 ] [ y % 4 ] ) / 2 ) ;
132+ //console.log(map);
133+ imageData . data [ currentPixelIndex ] = Math . round ( map / 16 ) * 16 ;
134+ } else if ( type === 'floydsteinberg' ) {
135+ // Floyda€"Steinberg dithering algorithm
136+ currentPixel = imageData . data [ currentPixelIndex ]
137+ err = Math . floor ( ( currentPixel - newPixel ) / 16 ) ;
138+ imageData . data [ currentPixelIndex ] = newPixel ;
139+
140+ imageData . data [ currentPixelIndex + 4 ] += err * 7 ;
141+ imageData . data [ currentPixelIndex + 4 * w - 4 ] += err * 3 ;
142+ imageData . data [ currentPixelIndex + 4 * w ] += err * 5 ;
143+ imageData . data [ currentPixelIndex + 4 * w + 4 ] += err * 1 ;
144+ } else if ( type === 'atkinson' ) {
145+ // Bill Atkinson's dithering algorithm
146+ err = Math . floor ( ( imageData . data [ currentPixelIndex ] - newPixel ) / 8 ) ;
147+ imageData . data [ currentPixelIndex ] = newPixel ;
148+
149+ imageData . data [ currentPixelIndex + 4 ] += err ;
150+ imageData . data [ currentPixelIndex + 8 ] += err ;
151+ imageData . data [ currentPixelIndex + 4 * w - 4 ] += err ;
152+ imageData . data [ currentPixelIndex + 4 * w ] += err ;
153+ imageData . data [ currentPixelIndex + 4 * w + 4 ] += err ;
154+ imageData . data [ currentPixelIndex + 8 * w ] += err ;
155+ } else {
156+ console . error ( `unknown dithering type requested: ${ type } ` ) ;
157+ }
158+
159+ // Set g and b pixels equal to r
160+ imageData . data [ currentPixelIndex + 1 ] = imageData . data [ currentPixelIndex + 2 ] = imageData . data [ currentPixelIndex ] ;
105161 }
106162
107163 ctx . putImageData ( imageData , 0 , 0 ) ;
0 commit comments