// Transforms gray input image color depth to the palette defined in p // The algorithm simply assigns the nearest palette color to each pixel, // without distributing error in any way, so expect color banding func ditheringAverage(img *image.Gray, p *color.Palette) { size := img.Bounds() for y := size.Min.Y; y < size.Max.Y; y++ { for x := size.Min.X; x < size.Max.X; x++ { c1 := img.GrayAt(x, y) c2 := p.Convert(c1).(color.Gray) img.SetGray(x, y, c2) } } }
// Dithering algorithms // Dithering is the process of reducing the colorspace of an image to a more // restricted palette. Since the Game Boy Printer only has a 2bit colorspace // available, it's important that some dithering algorithms are implemented to // deal with the fact. Depending on the properties of the dithering algorithm // detail may or may not be preserved as well. Using simple techniques like // average dithering will yield results with lots of color banding, while employing // error diffusion techniques such as Floyd Steinberg will produce smoother // results (color banding is replaced by the introduction of noise) // The wikipedia article (http://en.wikipedia.org/wiki/Dither) is quite interesting // and talks about some dithering algorithms, some of which are implemented here. // Floyd Steinberg is an error diffusion dithering algorithm that adds the error // of each color conversion to the neighbours of each pixel. This way it's possible // to achieve a finer graded dithering func ditheringFloydSteinberg(img *image.Gray, p *color.Palette) { size := img.Bounds() for y := size.Min.Y; y < size.Max.Y; y++ { for x := size.Min.X; x < size.Max.X; x++ { c1 := img.GrayAt(x, y) c2 := p.Convert(c1).(color.Gray) delta := int(c1.Y) - int(c2.Y) switch { case x+1 < size.Max.X && y < size.Max.Y: img.SetGray(x+1, y, color.Gray{uint8(int(img.GrayAt(x+1, y).Y) + delta*7/16)}) fallthrough case x < size.Max.X && y+1 < size.Max.Y: img.SetGray(x, y+1, color.Gray{uint8(int(img.GrayAt(x, y+1).Y) + delta*5/16)}) fallthrough case x-1 >= size.Min.X && y+1 < size.Max.Y: img.SetGray(x-1, y+1, color.Gray{uint8(int(img.GrayAt(x-1, y+1).Y) + delta*3/16)}) fallthrough case x+1 < size.Max.X && y+1 < size.Max.Y: img.SetGray(x+1, y+1, color.Gray{uint8(int(img.GrayAt(x+1, y+1).Y) + delta*1/16)}) } img.Set(x, y, c2) } } }