picture-frame/server/einkimage.go
2023-06-03 13:17:29 -06:00

82 lines
1.9 KiB
Go

package main
import (
"image"
"math"
"github.com/mattn/go-ciede2000"
)
// Maps RGB colors to the eink display's color values
type ColorSpace []struct {
Color Colorf
Code uint8
}
func ditherPixel(image [][]Colorf, x int, y int, diff Colorf) {
height := len(image)
width := len(image[0])
if x < 0 || y < 0 || x >= width || y >= height {
return
}
image[y][x] = clampColor(addColorf(image[y][x], diff), 0, 1)
}
func ditherFloydSteinberg(image [][]Colorf, x int, y int, color Colorf) {
ditherPixel(image, x+1, y, multColor(color, 7.0/16.0))
ditherPixel(image, x-1, y+1, multColor(color, 3.0/16.0))
ditherPixel(image, x, y+1, multColor(color, 5.0/16.0))
ditherPixel(image, x+1, y+1, multColor(color, 1.0/16.0))
}
func ConvertToEInkImage(src image.Image, colorSpace ColorSpace) []byte {
bounds := src.Bounds()
width, height := bounds.Max.X, bounds.Max.Y
image := convertImageToColors(src)
var einkImage []byte
for y := 0; y < height; y++ {
for x := 0; x < width; x++ {
// find closest color
minDist := math.MaxFloat64
bestColorCode := uint8(0x0)
bestColor := Colorf{0, 0, 0}
for _, def := range colorSpace {
dist := ciede2000.Diff(convertToRGBA(image[y][x]), convertToRGBA(def.Color))
if dist < minDist {
minDist = dist
bestColorCode = def.Code
bestColor = def.Color
}
}
einkImage = append(einkImage, byte(bestColorCode))
// dither the remaining color using Floyd-Steinberg to create the illusion of shading
diff := subtractColorf(image[y][x], bestColor)
ditherFloydSteinberg(image, x, y, diff)
}
}
// each byte holds two pixels for eink display
einkImage = packBytesIntoNibbles(einkImage)
return einkImage
}
func packBytesIntoNibbles(input []byte) []byte {
// input length must divisible by 2
var output []byte
for x := 0; x < len(input); x += 2 {
output = append(output, (input[x]<<4)|input[x+1])
}
return output
}