draw: clamp kernel output so red, green and blue <= alpha.
The raw computation can produce red > alpha when some weights are negative. Change-Id: Ic6701354770f012d3ef21a390a8400e14e9d1e25 Reviewed-on: https://go-review.googlesource.com/8740 Reviewed-by: Rob Pike <r@golang.org>
This commit is contained in:
parent
67c770d218
commit
697863cec6
20
draw/gen.go
20
draw/gen.go
|
@ -286,6 +286,24 @@ func expnDollar(prefix, dollar, suffix string, d *data) string {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case "clampToAlpha":
|
||||||
|
if alwaysOpaque[d.sType] {
|
||||||
|
return ";"
|
||||||
|
}
|
||||||
|
// Go uses alpha-premultiplied color. The naive computation can lead to
|
||||||
|
// invalid colors, e.g. red > alpha, when some weights are negative.
|
||||||
|
return `
|
||||||
|
if pr > pa {
|
||||||
|
pr = pa
|
||||||
|
}
|
||||||
|
if pg > pa {
|
||||||
|
pg = pa
|
||||||
|
}
|
||||||
|
if pb > pa {
|
||||||
|
pb = pa
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
case "outputu":
|
case "outputu":
|
||||||
// TODO: handle op==Over, not just op==Src.
|
// TODO: handle op==Over, not just op==Src.
|
||||||
args, _ := splitArgs(suffix)
|
args, _ := splitArgs(suffix)
|
||||||
|
@ -1118,6 +1136,7 @@ const (
|
||||||
pb += p[2] * c.weight
|
pb += p[2] * c.weight
|
||||||
pa += p[3] * c.weight
|
pa += p[3] * c.weight
|
||||||
}
|
}
|
||||||
|
$clampToAlpha
|
||||||
$outputf[dr.Min.X + int(dx), dr.Min.Y + int(adr.Min.Y + dy), ftou, p, s.invTotalWeight]
|
$outputf[dr.Min.X + int(dx), dr.Min.Y + int(adr.Min.Y + dy), ftou, p, s.invTotalWeight]
|
||||||
$tweakD
|
$tweakD
|
||||||
}
|
}
|
||||||
|
@ -1215,6 +1234,7 @@ const (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
$clampToAlpha
|
||||||
$outputf[dr.Min.X + int(dx), dr.Min.Y + int(dy), fffftou, p, 1]
|
$outputf[dr.Min.X + int(dx), dr.Min.Y + int(dy), fffftou, p, 1]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
132
draw/impl.go
132
draw/impl.go
|
@ -4494,6 +4494,17 @@ func (z *kernelScaler) scaleY_RGBA_Over(dst *image.RGBA, dr, adr image.Rectangle
|
||||||
pb += p[2] * c.weight
|
pb += p[2] * c.weight
|
||||||
pa += p[3] * c.weight
|
pa += p[3] * c.weight
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if pr > pa {
|
||||||
|
pr = pa
|
||||||
|
}
|
||||||
|
if pg > pa {
|
||||||
|
pg = pa
|
||||||
|
}
|
||||||
|
if pb > pa {
|
||||||
|
pb = pa
|
||||||
|
}
|
||||||
|
|
||||||
dst.Pix[d+0] = uint8(ftou(pr*s.invTotalWeight) >> 8)
|
dst.Pix[d+0] = uint8(ftou(pr*s.invTotalWeight) >> 8)
|
||||||
dst.Pix[d+1] = uint8(ftou(pg*s.invTotalWeight) >> 8)
|
dst.Pix[d+1] = uint8(ftou(pg*s.invTotalWeight) >> 8)
|
||||||
dst.Pix[d+2] = uint8(ftou(pb*s.invTotalWeight) >> 8)
|
dst.Pix[d+2] = uint8(ftou(pb*s.invTotalWeight) >> 8)
|
||||||
|
@ -4515,6 +4526,17 @@ func (z *kernelScaler) scaleY_RGBA_Src(dst *image.RGBA, dr, adr image.Rectangle,
|
||||||
pb += p[2] * c.weight
|
pb += p[2] * c.weight
|
||||||
pa += p[3] * c.weight
|
pa += p[3] * c.weight
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if pr > pa {
|
||||||
|
pr = pa
|
||||||
|
}
|
||||||
|
if pg > pa {
|
||||||
|
pg = pa
|
||||||
|
}
|
||||||
|
if pb > pa {
|
||||||
|
pb = pa
|
||||||
|
}
|
||||||
|
|
||||||
dst.Pix[d+0] = uint8(ftou(pr*s.invTotalWeight) >> 8)
|
dst.Pix[d+0] = uint8(ftou(pr*s.invTotalWeight) >> 8)
|
||||||
dst.Pix[d+1] = uint8(ftou(pg*s.invTotalWeight) >> 8)
|
dst.Pix[d+1] = uint8(ftou(pg*s.invTotalWeight) >> 8)
|
||||||
dst.Pix[d+2] = uint8(ftou(pb*s.invTotalWeight) >> 8)
|
dst.Pix[d+2] = uint8(ftou(pb*s.invTotalWeight) >> 8)
|
||||||
|
@ -4537,6 +4559,17 @@ func (z *kernelScaler) scaleY_Image_Over(dst Image, dr, adr image.Rectangle, tmp
|
||||||
pb += p[2] * c.weight
|
pb += p[2] * c.weight
|
||||||
pa += p[3] * c.weight
|
pa += p[3] * c.weight
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if pr > pa {
|
||||||
|
pr = pa
|
||||||
|
}
|
||||||
|
if pg > pa {
|
||||||
|
pg = pa
|
||||||
|
}
|
||||||
|
if pb > pa {
|
||||||
|
pb = pa
|
||||||
|
}
|
||||||
|
|
||||||
dstColorRGBA64.R = ftou(pr * s.invTotalWeight)
|
dstColorRGBA64.R = ftou(pr * s.invTotalWeight)
|
||||||
dstColorRGBA64.G = ftou(pg * s.invTotalWeight)
|
dstColorRGBA64.G = ftou(pg * s.invTotalWeight)
|
||||||
dstColorRGBA64.B = ftou(pb * s.invTotalWeight)
|
dstColorRGBA64.B = ftou(pb * s.invTotalWeight)
|
||||||
|
@ -4559,6 +4592,17 @@ func (z *kernelScaler) scaleY_Image_Src(dst Image, dr, adr image.Rectangle, tmp
|
||||||
pb += p[2] * c.weight
|
pb += p[2] * c.weight
|
||||||
pa += p[3] * c.weight
|
pa += p[3] * c.weight
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if pr > pa {
|
||||||
|
pr = pa
|
||||||
|
}
|
||||||
|
if pg > pa {
|
||||||
|
pg = pa
|
||||||
|
}
|
||||||
|
if pb > pa {
|
||||||
|
pb = pa
|
||||||
|
}
|
||||||
|
|
||||||
dstColorRGBA64.R = ftou(pr * s.invTotalWeight)
|
dstColorRGBA64.R = ftou(pr * s.invTotalWeight)
|
||||||
dstColorRGBA64.G = ftou(pg * s.invTotalWeight)
|
dstColorRGBA64.G = ftou(pg * s.invTotalWeight)
|
||||||
dstColorRGBA64.B = ftou(pb * s.invTotalWeight)
|
dstColorRGBA64.B = ftou(pb * s.invTotalWeight)
|
||||||
|
@ -4763,6 +4807,17 @@ func (q *Kernel) transform_RGBA_NRGBA_Over(dst *image.RGBA, dr, adr image.Rectan
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if pr > pa {
|
||||||
|
pr = pa
|
||||||
|
}
|
||||||
|
if pg > pa {
|
||||||
|
pg = pa
|
||||||
|
}
|
||||||
|
if pb > pa {
|
||||||
|
pb = pa
|
||||||
|
}
|
||||||
|
|
||||||
dst.Pix[d+0] = uint8(fffftou(pr) >> 8)
|
dst.Pix[d+0] = uint8(fffftou(pr) >> 8)
|
||||||
dst.Pix[d+1] = uint8(fffftou(pg) >> 8)
|
dst.Pix[d+1] = uint8(fffftou(pg) >> 8)
|
||||||
dst.Pix[d+2] = uint8(fffftou(pb) >> 8)
|
dst.Pix[d+2] = uint8(fffftou(pb) >> 8)
|
||||||
|
@ -4867,6 +4922,17 @@ func (q *Kernel) transform_RGBA_NRGBA_Src(dst *image.RGBA, dr, adr image.Rectang
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if pr > pa {
|
||||||
|
pr = pa
|
||||||
|
}
|
||||||
|
if pg > pa {
|
||||||
|
pg = pa
|
||||||
|
}
|
||||||
|
if pb > pa {
|
||||||
|
pb = pa
|
||||||
|
}
|
||||||
|
|
||||||
dst.Pix[d+0] = uint8(fffftou(pr) >> 8)
|
dst.Pix[d+0] = uint8(fffftou(pr) >> 8)
|
||||||
dst.Pix[d+1] = uint8(fffftou(pg) >> 8)
|
dst.Pix[d+1] = uint8(fffftou(pg) >> 8)
|
||||||
dst.Pix[d+2] = uint8(fffftou(pb) >> 8)
|
dst.Pix[d+2] = uint8(fffftou(pb) >> 8)
|
||||||
|
@ -4971,6 +5037,17 @@ func (q *Kernel) transform_RGBA_RGBA_Over(dst *image.RGBA, dr, adr image.Rectang
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if pr > pa {
|
||||||
|
pr = pa
|
||||||
|
}
|
||||||
|
if pg > pa {
|
||||||
|
pg = pa
|
||||||
|
}
|
||||||
|
if pb > pa {
|
||||||
|
pb = pa
|
||||||
|
}
|
||||||
|
|
||||||
dst.Pix[d+0] = uint8(fffftou(pr) >> 8)
|
dst.Pix[d+0] = uint8(fffftou(pr) >> 8)
|
||||||
dst.Pix[d+1] = uint8(fffftou(pg) >> 8)
|
dst.Pix[d+1] = uint8(fffftou(pg) >> 8)
|
||||||
dst.Pix[d+2] = uint8(fffftou(pb) >> 8)
|
dst.Pix[d+2] = uint8(fffftou(pb) >> 8)
|
||||||
|
@ -5075,6 +5152,17 @@ func (q *Kernel) transform_RGBA_RGBA_Src(dst *image.RGBA, dr, adr image.Rectangl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if pr > pa {
|
||||||
|
pr = pa
|
||||||
|
}
|
||||||
|
if pg > pa {
|
||||||
|
pg = pa
|
||||||
|
}
|
||||||
|
if pb > pa {
|
||||||
|
pb = pa
|
||||||
|
}
|
||||||
|
|
||||||
dst.Pix[d+0] = uint8(fffftou(pr) >> 8)
|
dst.Pix[d+0] = uint8(fffftou(pr) >> 8)
|
||||||
dst.Pix[d+1] = uint8(fffftou(pg) >> 8)
|
dst.Pix[d+1] = uint8(fffftou(pg) >> 8)
|
||||||
dst.Pix[d+2] = uint8(fffftou(pb) >> 8)
|
dst.Pix[d+2] = uint8(fffftou(pb) >> 8)
|
||||||
|
@ -5671,6 +5759,17 @@ func (q *Kernel) transform_RGBA_Image_Over(dst *image.RGBA, dr, adr image.Rectan
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if pr > pa {
|
||||||
|
pr = pa
|
||||||
|
}
|
||||||
|
if pg > pa {
|
||||||
|
pg = pa
|
||||||
|
}
|
||||||
|
if pb > pa {
|
||||||
|
pb = pa
|
||||||
|
}
|
||||||
|
|
||||||
dst.Pix[d+0] = uint8(fffftou(pr) >> 8)
|
dst.Pix[d+0] = uint8(fffftou(pr) >> 8)
|
||||||
dst.Pix[d+1] = uint8(fffftou(pg) >> 8)
|
dst.Pix[d+1] = uint8(fffftou(pg) >> 8)
|
||||||
dst.Pix[d+2] = uint8(fffftou(pb) >> 8)
|
dst.Pix[d+2] = uint8(fffftou(pb) >> 8)
|
||||||
|
@ -5771,6 +5870,17 @@ func (q *Kernel) transform_RGBA_Image_Src(dst *image.RGBA, dr, adr image.Rectang
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if pr > pa {
|
||||||
|
pr = pa
|
||||||
|
}
|
||||||
|
if pg > pa {
|
||||||
|
pg = pa
|
||||||
|
}
|
||||||
|
if pb > pa {
|
||||||
|
pb = pa
|
||||||
|
}
|
||||||
|
|
||||||
dst.Pix[d+0] = uint8(fffftou(pr) >> 8)
|
dst.Pix[d+0] = uint8(fffftou(pr) >> 8)
|
||||||
dst.Pix[d+1] = uint8(fffftou(pg) >> 8)
|
dst.Pix[d+1] = uint8(fffftou(pg) >> 8)
|
||||||
dst.Pix[d+2] = uint8(fffftou(pb) >> 8)
|
dst.Pix[d+2] = uint8(fffftou(pb) >> 8)
|
||||||
|
@ -5872,6 +5982,17 @@ func (q *Kernel) transform_Image_Image_Over(dst Image, dr, adr image.Rectangle,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if pr > pa {
|
||||||
|
pr = pa
|
||||||
|
}
|
||||||
|
if pg > pa {
|
||||||
|
pg = pa
|
||||||
|
}
|
||||||
|
if pb > pa {
|
||||||
|
pb = pa
|
||||||
|
}
|
||||||
|
|
||||||
dstColorRGBA64.R = fffftou(pr)
|
dstColorRGBA64.R = fffftou(pr)
|
||||||
dstColorRGBA64.G = fffftou(pg)
|
dstColorRGBA64.G = fffftou(pg)
|
||||||
dstColorRGBA64.B = fffftou(pb)
|
dstColorRGBA64.B = fffftou(pb)
|
||||||
|
@ -5974,6 +6095,17 @@ func (q *Kernel) transform_Image_Image_Src(dst Image, dr, adr image.Rectangle, d
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if pr > pa {
|
||||||
|
pr = pa
|
||||||
|
}
|
||||||
|
if pg > pa {
|
||||||
|
pg = pa
|
||||||
|
}
|
||||||
|
if pb > pa {
|
||||||
|
pb = pa
|
||||||
|
}
|
||||||
|
|
||||||
dstColorRGBA64.R = fffftou(pr)
|
dstColorRGBA64.R = fffftou(pr)
|
||||||
dstColorRGBA64.G = fffftou(pg)
|
dstColorRGBA64.G = fffftou(pg)
|
||||||
dstColorRGBA64.B = fffftou(pb)
|
dstColorRGBA64.B = fffftou(pb)
|
||||||
|
|
|
@ -115,6 +115,43 @@ func TestScaleDown(t *testing.T) { testInterp(t, 100, 100, "down", "280x360.jpeg
|
||||||
func TestScaleUp(t *testing.T) { testInterp(t, 75, 100, "up", "14x18.png") }
|
func TestScaleUp(t *testing.T) { testInterp(t, 75, 100, "up", "14x18.png") }
|
||||||
func TestTransform(t *testing.T) { testInterp(t, 100, 100, "rotate", "14x18.png") }
|
func TestTransform(t *testing.T) { testInterp(t, 100, 100, "rotate", "14x18.png") }
|
||||||
|
|
||||||
|
// TestNegativeWeights tests that scaling by a kernel that produces negative
|
||||||
|
// weights, such as the Catmull-Rom kernel, doesn't produce an invalid color
|
||||||
|
// according to Go's alpha-premultiplied model.
|
||||||
|
func TestNegativeWeights(t *testing.T) {
|
||||||
|
check := func(m *image.RGBA) error {
|
||||||
|
b := m.Bounds()
|
||||||
|
for y := b.Min.Y; y < b.Max.Y; y++ {
|
||||||
|
for x := b.Min.X; x < b.Max.X; x++ {
|
||||||
|
if c := m.RGBAAt(x, y); c.R > c.A || c.G > c.A || c.B > c.A {
|
||||||
|
return fmt.Errorf("invalid color.RGBA at (%d, %d): %v", x, y, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
src := image.NewRGBA(image.Rect(0, 0, 16, 16))
|
||||||
|
for y := 0; y < 16; y++ {
|
||||||
|
for x := 0; x < 16; x++ {
|
||||||
|
a := y * 0x11
|
||||||
|
src.Set(x, y, color.RGBA{
|
||||||
|
R: uint8(x * 0x11 * a / 0xff),
|
||||||
|
A: uint8(a),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := check(src); err != nil {
|
||||||
|
t.Fatalf("src image: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dst := image.NewRGBA(image.Rect(0, 0, 32, 32))
|
||||||
|
CatmullRom.Scale(dst, dst.Bounds(), src, src.Bounds(), nil)
|
||||||
|
if err := check(dst); err != nil {
|
||||||
|
t.Fatalf("dst image: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func fillPix(r *rand.Rand, pixs ...[]byte) {
|
func fillPix(r *rand.Rand, pixs ...[]byte) {
|
||||||
for _, pix := range pixs {
|
for _, pix := range pixs {
|
||||||
for i := range pix {
|
for i := range pix {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user