Files
questshock/modding/hacked/ss1/content/bitmap/Bitmapper.go
T

106 lines
2.8 KiB
Go

package bitmap
import (
"image"
"image/color"
"math"
)
// d64 is the reference white point.
var d65 = [3]float64{0.95047, 1.00000, 1.08883}
func labF(t float64) float64 {
if t > 6.0/29.0*6.0/29.0*6.0/29.0 {
return math.Cbrt(t)
}
return t/3.0*29.0/6.0*29.0/6.0 + 4.0/29.0
}
func square(value float64) float64 {
return value * value
}
type labEntry struct {
l float64
a float64
b float64
}
func labEntryFromColor(clr color.Color) labEntry {
rLinear, gLinear, bLinear, _ := clr.RGBA()
r, g, b := float64(rLinear)/float64(0xFFFF), float64(gLinear)/float64(0xFFFF), float64(bLinear)/float64(0xFFFF)
x := 0.4124564*r + 0.3575761*g + 0.1804375*b
y := 0.2126729*r + 0.7151522*g + 0.0721750*b
z := 0.0193339*r + 0.1191920*g + 0.9503041*b
whiteRef := d65
fy := labF(y / whiteRef[1])
entry := labEntry{
l: 1.16*fy - 0.16,
a: 5.0 * (labF(x/whiteRef[0]) - fy),
b: 2.0 * (fy - labF(z/whiteRef[2]))}
return entry
}
func (entry labEntry) distanceTo(other labEntry) float64 {
return math.Sqrt(square(entry.l-other.l) + square(entry.a-other.a) + square(entry.b-other.b))
}
// Bitmapper creates bitmap images from generic images.
type Bitmapper struct {
pal []labEntry
}
// NewBitmapper returns a new bitmapper instance based on the given palette.
func NewBitmapper(palette *Palette) *Bitmapper {
bitmapper := &Bitmapper{}
for _, clr := range palette {
bitmapper.pal = append(bitmapper.pal, labEntryFromColor(clr.Color(0xFF)))
}
return bitmapper
}
// Map maps the provided image to a bitmap based on the internal palette.
func (bitmapper *Bitmapper) Map(img image.Image) Bitmap {
var bmp Bitmap
bounds := img.Bounds()
bmp.Header.Width = int16(math.Max(0, math.Min(float64(bounds.Dx()), math.MaxInt16)))
bmp.Header.Height = int16(math.Max(0, math.Min(float64(bounds.Dy()), math.MaxInt16)))
bmp.Pixels = make([]byte, int(bmp.Header.Width)*int(bmp.Header.Height))
for row := 0; row < int(bmp.Header.Height); row++ {
for column := 0; column < int(bmp.Header.Width); column++ {
bmp.Pixels[row*int(bmp.Header.Width)+column] = bitmapper.MapColor(img.At(column, row))
}
}
return bmp
}
// MapColor maps the provided color to the nearest index in the palette.
func (bitmapper *Bitmapper) MapColor(clr color.Color) (palIndex byte) {
_, _, _, a := clr.RGBA() // nolint:dogsled
indexWithin := func(index, from, to int) bool {
return (index >= from) && (index <= to)
}
if a > 0 {
clrEntry := labEntryFromColor(clr)
palDistance := 1000.0
for colorIndex, palEntry := range bitmapper.pal {
isRegularColor := indexWithin(colorIndex, 0x01, 0x02) || indexWithin(colorIndex, 0x08, 0x0A) || indexWithin(colorIndex, 0x20, 0xFF)
if isRegularColor {
distance := palEntry.distanceTo(clrEntry)
if distance < palDistance {
palDistance = distance
palIndex = byte(colorIndex)
}
}
}
}
return
}