SandpointsGitHook/vendor/github.com/disintegration/gift/pixels.go
2021-03-20 23:21:23 +01:00

493 lines
10 KiB
Go

package gift
import (
"image"
"image/color"
"image/draw"
)
type pixel struct {
r, g, b, a float32
}
type imageType int
const (
itGeneric imageType = iota
itNRGBA
itNRGBA64
itRGBA
itRGBA64
itYCbCr
itGray
itGray16
itPaletted
)
type pixelGetter struct {
it imageType
bounds image.Rectangle
image image.Image
nrgba *image.NRGBA
nrgba64 *image.NRGBA64
rgba *image.RGBA
rgba64 *image.RGBA64
gray *image.Gray
gray16 *image.Gray16
ycbcr *image.YCbCr
paletted *image.Paletted
palette []pixel
}
func newPixelGetter(img image.Image) *pixelGetter {
switch img := img.(type) {
case *image.NRGBA:
return &pixelGetter{
it: itNRGBA,
bounds: img.Bounds(),
nrgba: img,
}
case *image.NRGBA64:
return &pixelGetter{
it: itNRGBA64,
bounds: img.Bounds(),
nrgba64: img,
}
case *image.RGBA:
return &pixelGetter{
it: itRGBA,
bounds: img.Bounds(),
rgba: img,
}
case *image.RGBA64:
return &pixelGetter{
it: itRGBA64,
bounds: img.Bounds(),
rgba64: img,
}
case *image.Gray:
return &pixelGetter{
it: itGray,
bounds: img.Bounds(),
gray: img,
}
case *image.Gray16:
return &pixelGetter{
it: itGray16,
bounds: img.Bounds(),
gray16: img,
}
case *image.YCbCr:
return &pixelGetter{
it: itYCbCr,
bounds: img.Bounds(),
ycbcr: img,
}
case *image.Paletted:
return &pixelGetter{
it: itPaletted,
bounds: img.Bounds(),
paletted: img,
palette: convertPalette(img.Palette),
}
default:
return &pixelGetter{
it: itGeneric,
bounds: img.Bounds(),
image: img,
}
}
}
const (
qf8 = 1.0 / 0xff
qf16 = 1.0 / 0xffff
epal = qf16 * qf16 / 2
)
func pixelFromColor(c color.Color) (px pixel) {
r16, g16, b16, a16 := c.RGBA()
switch a16 {
case 0:
px = pixel{0, 0, 0, 0}
case 0xffff:
r := float32(r16) * qf16
g := float32(g16) * qf16
b := float32(b16) * qf16
px = pixel{r, g, b, 1}
default:
q := float32(1) / float32(a16)
r := float32(r16) * q
g := float32(g16) * q
b := float32(b16) * q
a := float32(a16) * qf16
px = pixel{r, g, b, a}
}
return px
}
func convertPalette(p []color.Color) []pixel {
pal := make([]pixel, len(p))
for i := 0; i < len(p); i++ {
pal[i] = pixelFromColor(p[i])
}
return pal
}
func getPaletteIndex(pal []pixel, px pixel) int {
var k int
var dmin float32 = 4
for i, palpx := range pal {
d := px.r - palpx.r
dcur := d * d
d = px.g - palpx.g
dcur += d * d
d = px.b - palpx.b
dcur += d * d
d = px.a - palpx.a
dcur += d * d
if dcur < epal {
return i
}
if dcur < dmin {
dmin = dcur
k = i
}
}
return k
}
func (p *pixelGetter) getPixel(x, y int) pixel {
switch p.it {
case itNRGBA:
i := p.nrgba.PixOffset(x, y)
r := float32(p.nrgba.Pix[i+0]) * qf8
g := float32(p.nrgba.Pix[i+1]) * qf8
b := float32(p.nrgba.Pix[i+2]) * qf8
a := float32(p.nrgba.Pix[i+3]) * qf8
return pixel{r, g, b, a}
case itNRGBA64:
i := p.nrgba64.PixOffset(x, y)
r := float32(uint16(p.nrgba64.Pix[i+0])<<8|uint16(p.nrgba64.Pix[i+1])) * qf16
g := float32(uint16(p.nrgba64.Pix[i+2])<<8|uint16(p.nrgba64.Pix[i+3])) * qf16
b := float32(uint16(p.nrgba64.Pix[i+4])<<8|uint16(p.nrgba64.Pix[i+5])) * qf16
a := float32(uint16(p.nrgba64.Pix[i+6])<<8|uint16(p.nrgba64.Pix[i+7])) * qf16
return pixel{r, g, b, a}
case itRGBA:
i := p.rgba.PixOffset(x, y)
a8 := p.rgba.Pix[i+3]
switch a8 {
case 0xff:
r := float32(p.rgba.Pix[i+0]) * qf8
g := float32(p.rgba.Pix[i+1]) * qf8
b := float32(p.rgba.Pix[i+2]) * qf8
return pixel{r, g, b, 1}
case 0:
return pixel{0, 0, 0, 0}
default:
q := float32(1) / float32(a8)
r := float32(p.rgba.Pix[i+0]) * q
g := float32(p.rgba.Pix[i+1]) * q
b := float32(p.rgba.Pix[i+2]) * q
a := float32(a8) * qf8
return pixel{r, g, b, a}
}
case itRGBA64:
i := p.rgba64.PixOffset(x, y)
a16 := uint16(p.rgba64.Pix[i+6])<<8 | uint16(p.rgba64.Pix[i+7])
switch a16 {
case 0xffff:
r := float32(uint16(p.rgba64.Pix[i+0])<<8|uint16(p.rgba64.Pix[i+1])) * qf16
g := float32(uint16(p.rgba64.Pix[i+2])<<8|uint16(p.rgba64.Pix[i+3])) * qf16
b := float32(uint16(p.rgba64.Pix[i+4])<<8|uint16(p.rgba64.Pix[i+5])) * qf16
return pixel{r, g, b, 1}
case 0:
return pixel{0, 0, 0, 0}
default:
q := float32(1) / float32(a16)
r := float32(uint16(p.rgba64.Pix[i+0])<<8|uint16(p.rgba64.Pix[i+1])) * q
g := float32(uint16(p.rgba64.Pix[i+2])<<8|uint16(p.rgba64.Pix[i+3])) * q
b := float32(uint16(p.rgba64.Pix[i+4])<<8|uint16(p.rgba64.Pix[i+5])) * q
a := float32(a16) * qf16
return pixel{r, g, b, a}
}
case itGray:
i := p.gray.PixOffset(x, y)
v := float32(p.gray.Pix[i]) * qf8
return pixel{v, v, v, 1}
case itGray16:
i := p.gray16.PixOffset(x, y)
v := float32(uint16(p.gray16.Pix[i+0])<<8|uint16(p.gray16.Pix[i+1])) * qf16
return pixel{v, v, v, 1}
case itYCbCr:
iy := (y-p.ycbcr.Rect.Min.Y)*p.ycbcr.YStride + (x - p.ycbcr.Rect.Min.X)
var ic int
switch p.ycbcr.SubsampleRatio {
case image.YCbCrSubsampleRatio444:
ic = (y-p.ycbcr.Rect.Min.Y)*p.ycbcr.CStride + (x - p.ycbcr.Rect.Min.X)
case image.YCbCrSubsampleRatio422:
ic = (y-p.ycbcr.Rect.Min.Y)*p.ycbcr.CStride + (x/2 - p.ycbcr.Rect.Min.X/2)
case image.YCbCrSubsampleRatio420:
ic = (y/2-p.ycbcr.Rect.Min.Y/2)*p.ycbcr.CStride + (x/2 - p.ycbcr.Rect.Min.X/2)
case image.YCbCrSubsampleRatio440:
ic = (y/2-p.ycbcr.Rect.Min.Y/2)*p.ycbcr.CStride + (x - p.ycbcr.Rect.Min.X)
default:
ic = p.ycbcr.COffset(x, y)
}
const (
max = 255 * 1e5
inv = 1.0 / max
)
y1 := int32(p.ycbcr.Y[iy]) * 1e5
cb1 := int32(p.ycbcr.Cb[ic]) - 128
cr1 := int32(p.ycbcr.Cr[ic]) - 128
r1 := y1 + 140200*cr1
g1 := y1 - 34414*cb1 - 71414*cr1
b1 := y1 + 177200*cb1
r := float32(clampi32(r1, 0, max)) * inv
g := float32(clampi32(g1, 0, max)) * inv
b := float32(clampi32(b1, 0, max)) * inv
return pixel{r, g, b, 1}
case itPaletted:
i := p.paletted.PixOffset(x, y)
k := p.paletted.Pix[i]
return p.palette[k]
}
return pixelFromColor(p.image.At(x, y))
}
func (p *pixelGetter) getPixelRow(y int, buf *[]pixel) {
*buf = (*buf)[:0]
for x := p.bounds.Min.X; x != p.bounds.Max.X; x++ {
*buf = append(*buf, p.getPixel(x, y))
}
}
func (p *pixelGetter) getPixelColumn(x int, buf *[]pixel) {
*buf = (*buf)[:0]
for y := p.bounds.Min.Y; y != p.bounds.Max.Y; y++ {
*buf = append(*buf, p.getPixel(x, y))
}
}
func f32u8(val float32) uint8 {
x := int64(val + 0.5)
if x > 0xff {
return 0xff
}
if x > 0 {
return uint8(x)
}
return 0
}
func f32u16(val float32) uint16 {
x := int64(val + 0.5)
if x > 0xffff {
return 0xffff
}
if x > 0 {
return uint16(x)
}
return 0
}
func clampi32(val, min, max int32) int32 {
if val > max {
return max
}
if val > min {
return val
}
return 0
}
type pixelSetter struct {
it imageType
bounds image.Rectangle
image draw.Image
nrgba *image.NRGBA
nrgba64 *image.NRGBA64
rgba *image.RGBA
rgba64 *image.RGBA64
gray *image.Gray
gray16 *image.Gray16
paletted *image.Paletted
palette []pixel
}
func newPixelSetter(img draw.Image) *pixelSetter {
switch img := img.(type) {
case *image.NRGBA:
return &pixelSetter{
it: itNRGBA,
bounds: img.Bounds(),
nrgba: img,
}
case *image.NRGBA64:
return &pixelSetter{
it: itNRGBA64,
bounds: img.Bounds(),
nrgba64: img,
}
case *image.RGBA:
return &pixelSetter{
it: itRGBA,
bounds: img.Bounds(),
rgba: img,
}
case *image.RGBA64:
return &pixelSetter{
it: itRGBA64,
bounds: img.Bounds(),
rgba64: img,
}
case *image.Gray:
return &pixelSetter{
it: itGray,
bounds: img.Bounds(),
gray: img,
}
case *image.Gray16:
return &pixelSetter{
it: itGray16,
bounds: img.Bounds(),
gray16: img,
}
case *image.Paletted:
return &pixelSetter{
it: itPaletted,
bounds: img.Bounds(),
paletted: img,
palette: convertPalette(img.Palette),
}
default:
return &pixelSetter{
it: itGeneric,
bounds: img.Bounds(),
image: img,
}
}
}
func (p *pixelSetter) setPixel(x, y int, px pixel) {
if !image.Pt(x, y).In(p.bounds) {
return
}
switch p.it {
case itNRGBA:
i := p.nrgba.PixOffset(x, y)
p.nrgba.Pix[i+0] = f32u8(px.r * 0xff)
p.nrgba.Pix[i+1] = f32u8(px.g * 0xff)
p.nrgba.Pix[i+2] = f32u8(px.b * 0xff)
p.nrgba.Pix[i+3] = f32u8(px.a * 0xff)
case itNRGBA64:
r16 := f32u16(px.r * 0xffff)
g16 := f32u16(px.g * 0xffff)
b16 := f32u16(px.b * 0xffff)
a16 := f32u16(px.a * 0xffff)
i := p.nrgba64.PixOffset(x, y)
p.nrgba64.Pix[i+0] = uint8(r16 >> 8)
p.nrgba64.Pix[i+1] = uint8(r16 & 0xff)
p.nrgba64.Pix[i+2] = uint8(g16 >> 8)
p.nrgba64.Pix[i+3] = uint8(g16 & 0xff)
p.nrgba64.Pix[i+4] = uint8(b16 >> 8)
p.nrgba64.Pix[i+5] = uint8(b16 & 0xff)
p.nrgba64.Pix[i+6] = uint8(a16 >> 8)
p.nrgba64.Pix[i+7] = uint8(a16 & 0xff)
case itRGBA:
fa := px.a * 0xff
i := p.rgba.PixOffset(x, y)
p.rgba.Pix[i+0] = f32u8(px.r * fa)
p.rgba.Pix[i+1] = f32u8(px.g * fa)
p.rgba.Pix[i+2] = f32u8(px.b * fa)
p.rgba.Pix[i+3] = f32u8(fa)
case itRGBA64:
fa := px.a * 0xffff
r16 := f32u16(px.r * fa)
g16 := f32u16(px.g * fa)
b16 := f32u16(px.b * fa)
a16 := f32u16(fa)
i := p.rgba64.PixOffset(x, y)
p.rgba64.Pix[i+0] = uint8(r16 >> 8)
p.rgba64.Pix[i+1] = uint8(r16 & 0xff)
p.rgba64.Pix[i+2] = uint8(g16 >> 8)
p.rgba64.Pix[i+3] = uint8(g16 & 0xff)
p.rgba64.Pix[i+4] = uint8(b16 >> 8)
p.rgba64.Pix[i+5] = uint8(b16 & 0xff)
p.rgba64.Pix[i+6] = uint8(a16 >> 8)
p.rgba64.Pix[i+7] = uint8(a16 & 0xff)
case itGray:
i := p.gray.PixOffset(x, y)
p.gray.Pix[i] = f32u8((0.299*px.r + 0.587*px.g + 0.114*px.b) * px.a * 0xff)
case itGray16:
i := p.gray16.PixOffset(x, y)
y16 := f32u16((0.299*px.r + 0.587*px.g + 0.114*px.b) * px.a * 0xffff)
p.gray16.Pix[i+0] = uint8(y16 >> 8)
p.gray16.Pix[i+1] = uint8(y16 & 0xff)
case itPaletted:
px1 := pixel{
minf32(maxf32(px.r, 0), 1),
minf32(maxf32(px.g, 0), 1),
minf32(maxf32(px.b, 0), 1),
minf32(maxf32(px.a, 0), 1),
}
i := p.paletted.PixOffset(x, y)
k := getPaletteIndex(p.palette, px1)
p.paletted.Pix[i] = uint8(k)
case itGeneric:
r16 := f32u16(px.r * 0xffff)
g16 := f32u16(px.g * 0xffff)
b16 := f32u16(px.b * 0xffff)
a16 := f32u16(px.a * 0xffff)
p.image.Set(x, y, color.NRGBA64{r16, g16, b16, a16})
}
}
func (p *pixelSetter) setPixelRow(y int, buf []pixel) {
for i, x := 0, p.bounds.Min.X; i < len(buf); i, x = i+1, x+1 {
p.setPixel(x, y, buf[i])
}
}
func (p *pixelSetter) setPixelColumn(x int, buf []pixel) {
for i, y := 0, p.bounds.Min.Y; i < len(buf); i, y = i+1, y+1 {
p.setPixel(x, y, buf[i])
}
}