Browse Source

Alpha-premultiplied resizing and blurring

disintegration 8 years ago
parent
commit
7cbe056568
4 changed files with 52 additions and 48 deletions
  1. 16 14
      effects.go
  2. 7 7
      effects_test.go
  3. 20 18
      resize.go
  4. 9 9
      resize_test.go

+ 16 - 14
effects.go

@@ -67,15 +67,16 @@ func blurHorizontal(src *image.NRGBA, kernel []float64) *image.NRGBA {
 				for ix := start; ix <= end; ix++ {
 					weight := kernel[absint(x-ix)]
 					i := y*src.Stride + ix*4
-					r += float64(src.Pix[i+0]) * weight
-					g += float64(src.Pix[i+1]) * weight
-					b += float64(src.Pix[i+2]) * weight
-					a += float64(src.Pix[i+3]) * weight
+					wa := float64(src.Pix[i+3]) * weight
+					r += float64(src.Pix[i+0]) * wa
+					g += float64(src.Pix[i+1]) * wa
+					b += float64(src.Pix[i+2]) * wa
+					a += wa
 				}
 
-				r = math.Min(math.Max(r/weightSum, 0.0), 255.0)
-				g = math.Min(math.Max(g/weightSum, 0.0), 255.0)
-				b = math.Min(math.Max(b/weightSum, 0.0), 255.0)
+				r = math.Min(math.Max(r/a, 0.0), 255.0)
+				g = math.Min(math.Max(g/a, 0.0), 255.0)
+				b = math.Min(math.Max(b/a, 0.0), 255.0)
 				a = math.Min(math.Max(a/weightSum, 0.0), 255.0)
 
 				j := y*dst.Stride + x*4
@@ -121,15 +122,16 @@ func blurVertical(src *image.NRGBA, kernel []float64) *image.NRGBA {
 				for iy := start; iy <= end; iy++ {
 					weight := kernel[absint(y-iy)]
 					i := iy*src.Stride + x*4
-					r += float64(src.Pix[i+0]) * weight
-					g += float64(src.Pix[i+1]) * weight
-					b += float64(src.Pix[i+2]) * weight
-					a += float64(src.Pix[i+3]) * weight
+					wa := float64(src.Pix[i+3]) * weight
+					r += float64(src.Pix[i+0]) * wa
+					g += float64(src.Pix[i+1]) * wa
+					b += float64(src.Pix[i+2]) * wa
+					a += wa
 				}
 
-				r = math.Min(math.Max(r/weightSum, 0.0), 255.0)
-				g = math.Min(math.Max(g/weightSum, 0.0), 255.0)
-				b = math.Min(math.Max(b/weightSum, 0.0), 255.0)
+				r = math.Min(math.Max(r/a, 0.0), 255.0)
+				g = math.Min(math.Max(g/a, 0.0), 255.0)
+				b = math.Min(math.Max(b/a, 0.0), 255.0)
 				a = math.Min(math.Max(a/weightSum, 0.0), 255.0)
 
 				j := y*dst.Stride + x*4

+ 7 - 7
effects_test.go

@@ -50,9 +50,9 @@ func TestBlur(t *testing.T) {
 				Rect:   image.Rect(0, 0, 3, 3),
 				Stride: 3 * 4,
 				Pix: []uint8{
-					0x01, 0x02, 0x04, 0x04, 0x0a, 0x10, 0x18, 0x18, 0x01, 0x02, 0x04, 0x04,
-					0x09, 0x10, 0x18, 0x18, 0x3f, 0x69, 0x9e, 0x9e, 0x09, 0x10, 0x18, 0x18,
-					0x01, 0x02, 0x04, 0x04, 0x0a, 0x10, 0x18, 0x18, 0x01, 0x02, 0x04, 0x04,
+					0x66, 0xaa, 0xff, 0x04, 0x66, 0xaa, 0xff, 0x18, 0x66, 0xaa, 0xff, 0x04,
+					0x66, 0xaa, 0xff, 0x18, 0x66, 0xaa, 0xff, 0x9e, 0x66, 0xaa, 0xff, 0x18,
+					0x66, 0xaa, 0xff, 0x04, 0x66, 0xaa, 0xff, 0x18, 0x66, 0xaa, 0xff, 0x04,
 				},
 			},
 		},
@@ -72,9 +72,9 @@ func TestBlur(t *testing.T) {
 				Rect:   image.Rect(0, 0, 3, 3),
 				Stride: 3 * 4,
 				Pix: []uint8{
-					0x0b, 0x13, 0x1c, 0x1c, 0x0b, 0x13, 0x1c, 0x1c, 0x0b, 0x13, 0x1c, 0x1c,
-					0x0b, 0x13, 0x1c, 0x1c, 0x0b, 0x13, 0x1c, 0x1c, 0x0b, 0x13, 0x1c, 0x1c,
-					0x0b, 0x13, 0x1c, 0x1c, 0x0b, 0x13, 0x1c, 0x1c, 0x0b, 0x13, 0x1c, 0x1c,
+					0x66, 0xaa, 0xff, 0x1c, 0x66, 0xaa, 0xff, 0x1c, 0x66, 0xaa, 0xff, 0x1c,
+					0x66, 0xaa, 0xff, 0x1c, 0x66, 0xaa, 0xff, 0x1c, 0x66, 0xaa, 0xff, 0x1c,
+					0x66, 0xaa, 0xff, 0x1c, 0x66, 0xaa, 0xff, 0x1c, 0x66, 0xaa, 0xff, 0x1c,
 				},
 			},
 		},
@@ -134,7 +134,7 @@ func TestSharpen(t *testing.T) {
 				Stride: 3 * 4,
 				Pix: []uint8{
 					0x66, 0x66, 0x66, 0x66, 0x64, 0x64, 0x64, 0x64, 0x66, 0x66, 0x66, 0x66,
-					0x64, 0x64, 0x64, 0x64, 0x7e, 0x7e, 0x7e, 0x7e, 0x64, 0x64, 0x64, 0x64,
+					0x64, 0x64, 0x64, 0x64, 0x7d, 0x7d, 0x7d, 0x7e, 0x64, 0x64, 0x64, 0x64,
 					0x66, 0x66, 0x66, 0x66, 0x64, 0x64, 0x64, 0x64, 0x66, 0x66, 0x66, 0x66,
 				},
 			},

+ 20 - 18
resize.go

@@ -128,20 +128,21 @@ func resizeHorizontal(src *image.NRGBA, width int, filter ResampleFilter) *image
 	parallel(dstH, func(partStart, partEnd int) {
 		for dstY := partStart; dstY < partEnd; dstY++ {
 			for dstX := 0; dstX < dstW; dstX++ {
-				var c [4]int32
+				var c [4]int64
 				for _, iw := range weights[dstX].iwpairs {
 					i := dstY*src.Stride + iw.i*4
-					c[0] += int32(src.Pix[i+0]) * iw.w
-					c[1] += int32(src.Pix[i+1]) * iw.w
-					c[2] += int32(src.Pix[i+2]) * iw.w
-					c[3] += int32(src.Pix[i+3]) * iw.w
+					a := int64(src.Pix[i+3]) * int64(iw.w)
+					c[0] += int64(src.Pix[i+0]) * a
+					c[1] += int64(src.Pix[i+1]) * a
+					c[2] += int64(src.Pix[i+2]) * a
+					c[3] += a
 				}
 				j := dstY*dst.Stride + dstX*4
 				sum := weights[dstX].wsum
-				dst.Pix[j+0] = clampint32(int32(float32(c[0])/float32(sum) + 0.5))
-				dst.Pix[j+1] = clampint32(int32(float32(c[1])/float32(sum) + 0.5))
-				dst.Pix[j+2] = clampint32(int32(float32(c[2])/float32(sum) + 0.5))
-				dst.Pix[j+3] = clampint32(int32(float32(c[3])/float32(sum) + 0.5))
+				dst.Pix[j+0] = clampint32(int32(float64(c[0])/float64(c[3]) + 0.5))
+				dst.Pix[j+1] = clampint32(int32(float64(c[1])/float64(c[3]) + 0.5))
+				dst.Pix[j+2] = clampint32(int32(float64(c[2])/float64(c[3]) + 0.5))
+				dst.Pix[j+3] = clampint32(int32(float64(c[3])/float64(sum) + 0.5))
 			}
 		}
 	})
@@ -165,20 +166,21 @@ func resizeVertical(src *image.NRGBA, height int, filter ResampleFilter) *image.
 
 		for dstX := partStart; dstX < partEnd; dstX++ {
 			for dstY := 0; dstY < dstH; dstY++ {
-				var c [4]int32
+				var c [4]int64
 				for _, iw := range weights[dstY].iwpairs {
 					i := iw.i*src.Stride + dstX*4
-					c[0] += int32(src.Pix[i+0]) * iw.w
-					c[1] += int32(src.Pix[i+1]) * iw.w
-					c[2] += int32(src.Pix[i+2]) * iw.w
-					c[3] += int32(src.Pix[i+3]) * iw.w
+					a := int64(src.Pix[i+3]) * int64(iw.w)
+					c[0] += int64(src.Pix[i+0]) * a
+					c[1] += int64(src.Pix[i+1]) * a
+					c[2] += int64(src.Pix[i+2]) * a
+					c[3] += a
 				}
 				j := dstY*dst.Stride + dstX*4
 				sum := weights[dstY].wsum
-				dst.Pix[j+0] = clampint32(int32(float32(c[0])/float32(sum) + 0.5))
-				dst.Pix[j+1] = clampint32(int32(float32(c[1])/float32(sum) + 0.5))
-				dst.Pix[j+2] = clampint32(int32(float32(c[2])/float32(sum) + 0.5))
-				dst.Pix[j+3] = clampint32(int32(float32(c[3])/float32(sum) + 0.5))
+				dst.Pix[j+0] = clampint32(int32(float64(c[0])/float64(c[3]) + 0.5))
+				dst.Pix[j+1] = clampint32(int32(float64(c[1])/float64(c[3]) + 0.5))
+				dst.Pix[j+2] = clampint32(int32(float64(c[2])/float64(c[3]) + 0.5))
+				dst.Pix[j+3] = clampint32(int32(float64(c[3])/float64(sum) + 0.5))
 			}
 		}
 

+ 9 - 9
resize_test.go

@@ -28,7 +28,7 @@ func TestResize(t *testing.T) {
 			&image.NRGBA{
 				Rect:   image.Rect(0, 0, 1, 1),
 				Stride: 1 * 4,
-				Pix:    []uint8{0x40, 0x40, 0x40, 0xc0},
+				Pix:    []uint8{0x55, 0x55, 0x55, 0xc0},
 			},
 		},
 		{
@@ -108,10 +108,10 @@ func TestResize(t *testing.T) {
 				Rect:   image.Rect(0, 0, 4, 4),
 				Stride: 4 * 4,
 				Pix: []uint8{
-					0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x40, 0xbf, 0x00, 0x00, 0xbf, 0xff, 0x00, 0x00, 0xff,
-					0x00, 0x40, 0x00, 0x40, 0x30, 0x30, 0x10, 0x70, 0x8f, 0x10, 0x30, 0xcf, 0xbf, 0x00, 0x40, 0xff,
-					0x00, 0xbf, 0x00, 0xbf, 0x10, 0x8f, 0x30, 0xcf, 0x30, 0x30, 0x8f, 0xef, 0x40, 0x00, 0xbf, 0xff,
-					0x00, 0xff, 0x00, 0xff, 0x00, 0xbf, 0x40, 0xff, 0x00, 0x40, 0xbf, 0xff, 0x00, 0x00, 0xff, 0xff,
+					0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x3f, 0xff, 0x00, 0x00, 0xc0, 0xff, 0x00, 0x00, 0xff,
+					0x00, 0xff, 0x00, 0x3f, 0x6d, 0x6e, 0x24, 0x6f, 0xb1, 0x13, 0x3a, 0xd0, 0xc0, 0x00, 0x3f, 0xff,
+					0x00, 0xff, 0x00, 0xc0, 0x13, 0xb2, 0x3a, 0xcf, 0x33, 0x32, 0x9a, 0xef, 0x3f, 0x00, 0xc0, 0xff,
+					0x00, 0xff, 0x00, 0xff, 0x00, 0xc0, 0x3f, 0xff, 0x00, 0x3f, 0xc0, 0xff, 0x00, 0x00, 0xff, 0xff,
 				},
 			},
 		},
@@ -224,7 +224,7 @@ func TestFit(t *testing.T) {
 			&image.NRGBA{
 				Rect:   image.Rect(0, 0, 1, 1),
 				Stride: 1 * 4,
-				Pix:    []uint8{0x40, 0x40, 0x40, 0xc0},
+				Pix:    []uint8{0x55, 0x55, 0x55, 0xc0},
 			},
 		},
 		{
@@ -242,7 +242,7 @@ func TestFit(t *testing.T) {
 			&image.NRGBA{
 				Rect:   image.Rect(0, 0, 1, 1),
 				Stride: 1 * 4,
-				Pix:    []uint8{0x40, 0x40, 0x40, 0xc0},
+				Pix:    []uint8{0x55, 0x55, 0x55, 0xc0},
 			},
 		},
 		{
@@ -512,7 +512,7 @@ func TestThumbnail(t *testing.T) {
 			&image.NRGBA{
 				Rect:   image.Rect(0, 0, 1, 1),
 				Stride: 1 * 4,
-				Pix:    []uint8{0x40, 0x40, 0x40, 0xc0},
+				Pix:    []uint8{0x55, 0x55, 0x55, 0xc0},
 			},
 		},
 		{
@@ -534,7 +534,7 @@ func TestThumbnail(t *testing.T) {
 			&image.NRGBA{
 				Rect:   image.Rect(0, 0, 1, 1),
 				Stride: 1 * 4,
-				Pix:    []uint8{0x40, 0x40, 0x40, 0xc0},
+				Pix:    []uint8{0x55, 0x55, 0x55, 0xc0},
 			},
 		},
 		{