package main import ( "image/color" "math/rand" ) var bwpallette = color.Palette{ color.Black, color.White, } var sixteencolors = color.Palette{ color.RGBA{0, 0, 0, 255}, // black color.RGBA{0, 0, 127, 255}, // navy color.RGBA{0, 0, 255, 255}, // blue color.RGBA{0, 127, 0, 255}, // green color.RGBA{0, 255, 0, 255}, // lime color.RGBA{127, 0, 0, 255}, // maroon color.RGBA{255, 0, 0, 255}, // red color.RGBA{0, 127, 127, 255}, // teal color.RGBA{127, 0, 127, 255}, // purple color.RGBA{127, 127, 0, 255}, // olive color.RGBA{0, 255, 255, 255}, // aqua color.RGBA{255, 0, 255, 255}, // fuchsia color.RGBA{255, 255, 0, 255}, // yellow color.RGBA{127, 127, 127, 255}, // gray color.RGBA{192, 192, 192, 255}, // silver color.RGBA{255, 255, 255, 255}, // white } func permuteColor(c color.Color, i uint8) color.Color { r, g, b, a := c.RGBA() return color.RGBA{ uint8(r>>8) + i, uint8(g>>8) + i, uint8(b>>8) + i, uint8(a >> 8), } } // naivePalette smashes each pixel to its closest color in the pallette. func naivePalette(p color.Palette) quantizerFunction { return func(_, _ int, c color.Color) color.Color { return p[p.Index(c)] } } // randomNoisePalette injects random noise into the quantization step func randomNoisePalette(p color.Palette) quantizerFunction { r := len(p) return func(_, _ int, c color.Color) color.Color { // I think the proper theory here is probably "only try and randomize within one palette swatch in either direction". // it might be possible to instead permute the color selected _from the palette_ (i.e. modify the result of p.Index(c))... // ...but I think for that to work you'd need a proper "ordering" for the colors. noise := rand.Intn(256) / r rc := permuteColor(c, uint8(noise)) return p[p.Index(rc)] } } // color permutation algo: https://en.wikipedia.org/wiki/Ordered_dithering#Algorithm func colorBayer(level int, p color.Palette) quantizerFunction { b := newBayer(level) r := len(p) return func(x, y int, c color.Color) color.Color { v := float64(r) * (b.valueAt(x, y) - 0.5) rc := permuteColor(c, uint8(v)) return p[p.Index(rc)] } } func colorError(errMap map[coord]float64, d diffusion, palette color.Palette) quantizerFunction { r := float64(len(palette)) return func(x int, y int, c color.Color) color.Color { p := coord{x: x, y: y} rc := permuteColor(c, uint8(r*errMap[p])) delete(errMap, p) // don't let the error map grow too big nc := palette[palette.Index(rc)] l := luminence(c) - luminence(nc) applyError(d, l, p, errMap) return nc } } func simpleColorErrorDiffusion() quantizerFunction { errMap := make(map[coord]float64) d := diffusion{ divisor: 2.0, matrix: map[coord]float64{ {x: 1, y: 0}: 1.0, {x: 0, y: 1}: 1.0, }, } return colorError(errMap, d, sixteencolors) } func colorJarvisJudiceNinke() quantizerFunction { errMap := make(map[coord]float64) d := diffusion{ divisor: 48.0, matrix: map[coord]float64{ {x: 1, y: 0}: 7.0, {x: 2, y: 0}: 5.0, {x: -2, y: 1}: 3.0, {x: -1, y: 1}: 5.0, {x: 0, y: 1}: 7.0, {x: 1, y: 1}: 5.0, {x: 2, y: 1}: 3.0, {x: -2, y: 2}: 1.0, {x: -1, y: 2}: 3.0, {x: 0, y: 2}: 5.0, {x: 1, y: 2}: 3.0, {x: 2, y: 2}: 1.0, }, } return colorError(errMap, d, sixteencolors) }