diff --git a/adjust.go b/adjust.go index 6980422..6d9df25 100644 --- a/adjust.go +++ b/adjust.go @@ -15,14 +15,15 @@ func Grayscale(img image.Image) *image.NRGBA { i := y * dst.Stride src.scan(0, y, src.w, y+1, dst.Pix[i:i+src.w*4]) for x := 0; x < src.w; x++ { - r := dst.Pix[i+0] - g := dst.Pix[i+1] - b := dst.Pix[i+2] + d := dst.Pix[i : i+3 : i+3] + r := d[0] + g := d[1] + b := d[2] f := 0.299*float64(r) + 0.587*float64(g) + 0.114*float64(b) y := uint8(f + 0.5) - dst.Pix[i+0] = y - dst.Pix[i+1] = y - dst.Pix[i+2] = y + d[0] = y + d[1] = y + d[2] = y i += 4 } } @@ -39,9 +40,10 @@ func Invert(img image.Image) *image.NRGBA { i := y * dst.Stride src.scan(0, y, src.w, y+1, dst.Pix[i:i+src.w*4]) for x := 0; x < src.w; x++ { - dst.Pix[i+0] = 255 - dst.Pix[i+0] - dst.Pix[i+1] = 255 - dst.Pix[i+1] - dst.Pix[i+2] = 255 - dst.Pix[i+2] + d := dst.Pix[i : i+3 : i+3] + d[0] = 255 - d[0] + d[1] = 255 - d[1] + d[2] = 255 - d[2] i += 4 } } @@ -191,14 +193,16 @@ func sigmoid(a, b, x float64) float64 { func adjustLUT(img image.Image, lut []uint8) *image.NRGBA { src := newScanner(img) dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h)) + lut = lut[0:256] parallel(0, src.h, func(ys <-chan int) { for y := range ys { i := y * dst.Stride src.scan(0, y, src.w, y+1, dst.Pix[i:i+src.w*4]) for x := 0; x < src.w; x++ { - dst.Pix[i+0] = lut[dst.Pix[i+0]] - dst.Pix[i+1] = lut[dst.Pix[i+1]] - dst.Pix[i+2] = lut[dst.Pix[i+2]] + d := dst.Pix[i : i+3 : i+3] + d[0] = lut[d[0]] + d[1] = lut[d[1]] + d[2] = lut[d[2]] i += 4 } } @@ -230,15 +234,16 @@ func AdjustFunc(img image.Image, fn func(c color.NRGBA) color.NRGBA) *image.NRGB i := y * dst.Stride src.scan(0, y, src.w, y+1, dst.Pix[i:i+src.w*4]) for x := 0; x < src.w; x++ { - r := dst.Pix[i+0] - g := dst.Pix[i+1] - b := dst.Pix[i+2] - a := dst.Pix[i+3] + d := dst.Pix[i : i+4 : i+4] + r := d[0] + g := d[1] + b := d[2] + a := d[3] c := fn(color.NRGBA{r, g, b, a}) - dst.Pix[i+0] = c.R - dst.Pix[i+1] = c.G - dst.Pix[i+2] = c.B - dst.Pix[i+3] = c.A + d[0] = c.R + d[1] = c.G + d[2] = c.B + d[3] = c.A i += 4 } } diff --git a/adjust_test.go b/adjust_test.go index a9c82f7..251cef5 100644 --- a/adjust_test.go +++ b/adjust_test.go @@ -820,3 +820,12 @@ func TestAdjustFunc(t *testing.T) { }) } } + +func BenchmarkAdjustFunc(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + AdjustFunc(testdataBranchesJPG, func(c color.NRGBA) color.NRGBA { + return color.NRGBA{c.B, c.G, c.R, c.A} + }) + } +} diff --git a/convolution.go b/convolution.go index 9e6404d..11eddc1 100644 --- a/convolution.go +++ b/convolution.go @@ -90,9 +90,10 @@ func convolve(img image.Image, kernel []float64, options *ConvolveOptions) *imag } off := iy*src.Stride + ix*4 - r += float64(src.Pix[off+0]) * c.k - g += float64(src.Pix[off+1]) * c.k - b += float64(src.Pix[off+2]) * c.k + s := src.Pix[off : off+3 : off+3] + r += float64(s[0]) * c.k + g += float64(s[1]) * c.k + b += float64(s[2]) * c.k } if options.Abs { @@ -115,10 +116,11 @@ func convolve(img image.Image, kernel []float64, options *ConvolveOptions) *imag srcOff := y*src.Stride + x*4 dstOff := y*dst.Stride + x*4 - dst.Pix[dstOff+0] = clamp(r) - dst.Pix[dstOff+1] = clamp(g) - dst.Pix[dstOff+2] = clamp(b) - dst.Pix[dstOff+3] = src.Pix[srcOff+3] + d := dst.Pix[dstOff : dstOff+4 : dstOff+4] + d[0] = clamp(r) + d[1] = clamp(g) + d[2] = clamp(b) + d[3] = src.Pix[srcOff+3] } } }) diff --git a/effects.go b/effects.go index 149cfeb..a4b17df 100644 --- a/effects.go +++ b/effects.go @@ -44,7 +44,7 @@ func blurHorizontal(img image.Image, kernel []float64) *image.NRGBA { for i, v := range scanLine { scanLineF[i] = float64(v) } - for x, idx := 0, 0; x < src.w; x, idx = x+1, idx+4 { + for x := 0; x < src.w; x++ { min := x - radius if min < 0 { min = 0 @@ -53,30 +53,28 @@ func blurHorizontal(img image.Image, kernel []float64) *image.NRGBA { if max > src.w-1 { max = src.w - 1 } - var r, g, b, a, wsum float64 for ix := min; ix <= max; ix++ { i := ix * 4 weight := kernel[absint(x-ix)] wsum += weight - wa := scanLineF[i+3] * weight - r += scanLineF[i+0] * wa - g += scanLineF[i+1] * wa - b += scanLineF[i+2] * wa + s := scanLineF[i : i+4 : i+4] + wa := s[3] * weight + r += s[0] * wa + g += s[1] * wa + b += s[2] * wa a += wa } if a != 0 { - r /= a - g /= a - b /= a + aInv := 1 / a + j := y*dst.Stride + x*4 + d := dst.Pix[j : j+4 : j+4] + d[0] = clamp(r * aInv) + d[1] = clamp(g * aInv) + d[2] = clamp(b * aInv) + d[3] = clamp(a / wsum) } - - scanLine[idx+0] = clamp(r) - scanLine[idx+1] = clamp(g) - scanLine[idx+2] = clamp(b) - scanLine[idx+3] = clamp(a / wsum) } - copy(dst.Pix[y*dst.Stride:], scanLine) } }) @@ -105,29 +103,27 @@ func blurVertical(img image.Image, kernel []float64) *image.NRGBA { if max > src.h-1 { max = src.h - 1 } - var r, g, b, a, wsum float64 for iy := min; iy <= max; iy++ { i := iy * 4 weight := kernel[absint(y-iy)] wsum += weight - wa := scanLineF[i+3] * weight - r += scanLineF[i+0] * wa - g += scanLineF[i+1] * wa - b += scanLineF[i+2] * wa + s := scanLineF[i : i+4 : i+4] + wa := s[3] * weight + r += s[0] * wa + g += s[1] * wa + b += s[2] * wa a += wa } if a != 0 { - r /= a - g /= a - b /= a + aInv := 1 / a + j := y*dst.Stride + x*4 + d := dst.Pix[j : j+4 : j+4] + d[0] = clamp(r * aInv) + d[1] = clamp(g * aInv) + d[2] = clamp(b * aInv) + d[3] = clamp(a / wsum) } - - j := y*dst.Stride + x*4 - dst.Pix[j+0] = clamp(r) - dst.Pix[j+1] = clamp(g) - dst.Pix[j+2] = clamp(b) - dst.Pix[j+3] = clamp(a / wsum) } } }) diff --git a/histogram.go b/histogram.go index 5bcb001..c547fe8 100644 --- a/histogram.go +++ b/histogram.go @@ -27,9 +27,10 @@ func Histogram(img image.Image) [256]float64 { src.scan(0, y, src.w, y+1, scanLine) i := 0 for x := 0; x < src.w; x++ { - r := scanLine[i+0] - g := scanLine[i+1] - b := scanLine[i+2] + s := scanLine[i : i+3 : i+3] + r := s[0] + g := s[1] + b := s[2] y := 0.299*float32(r) + 0.587*float32(g) + 0.114*float32(b) tmpHistogram[int(y+0.5)]++ tmpTotal++ diff --git a/tools.go b/tools.go index 7887946..e39f86f 100644 --- a/tools.go +++ b/tools.go @@ -198,15 +198,17 @@ func Overlay(background, img image.Image, pos image.Point, opacity float64) *ima i := y*dst.Stride + interRect.Min.X*4 j := 0 for x := interRect.Min.X; x < interRect.Max.X; x++ { - r1 := float64(dst.Pix[i+0]) - g1 := float64(dst.Pix[i+1]) - b1 := float64(dst.Pix[i+2]) - a1 := float64(dst.Pix[i+3]) + d := dst.Pix[i : i+4 : i+4] + r1 := float64(d[0]) + g1 := float64(d[1]) + b1 := float64(d[2]) + a1 := float64(d[3]) - r2 := float64(scanLine[j+0]) - g2 := float64(scanLine[j+1]) - b2 := float64(scanLine[j+2]) - a2 := float64(scanLine[j+3]) + s := scanLine[j : j+4 : j+4] + r2 := float64(s[0]) + g2 := float64(s[1]) + b2 := float64(s[2]) + a2 := float64(s[3]) coef2 := opacity * a2 / 255 coef1 := (1 - coef2) * a1 / 255 @@ -214,10 +216,10 @@ func Overlay(background, img image.Image, pos image.Point, opacity float64) *ima coef1 /= coefSum coef2 /= coefSum - dst.Pix[i+0] = uint8(r1*coef1 + r2*coef2) - dst.Pix[i+1] = uint8(g1*coef1 + g2*coef2) - dst.Pix[i+2] = uint8(b1*coef1 + b2*coef2) - dst.Pix[i+3] = uint8(math.Min(a1+a2*opacity*(255-a1)/255, 255)) + d[0] = uint8(r1*coef1 + r2*coef2) + d[1] = uint8(g1*coef1 + g2*coef2) + d[2] = uint8(b1*coef1 + b2*coef2) + d[3] = uint8(math.Min(a1+a2*opacity*(255-a1)/255, 255)) i += 4 j += 4 diff --git a/transform.go b/transform.go index d788d0d..fe4a92f 100644 --- a/transform.go +++ b/transform.go @@ -209,63 +209,60 @@ func rotatedSize(w, h int, angle float64) (int, int) { } func interpolatePoint(dst *image.NRGBA, dstX, dstY int, src *image.NRGBA, xf, yf float64, bgColor color.NRGBA) { - dstIndex := dstY*dst.Stride + dstX*4 + j := dstY*dst.Stride + dstX*4 + d := dst.Pix[j : j+4 : j+4] x0 := int(math.Floor(xf)) y0 := int(math.Floor(yf)) bounds := src.Bounds() if !image.Pt(x0, y0).In(image.Rect(bounds.Min.X-1, bounds.Min.Y-1, bounds.Max.X, bounds.Max.Y)) { - dst.Pix[dstIndex+0] = bgColor.R - dst.Pix[dstIndex+1] = bgColor.G - dst.Pix[dstIndex+2] = bgColor.B - dst.Pix[dstIndex+3] = bgColor.A + d[0] = bgColor.R + d[1] = bgColor.G + d[2] = bgColor.B + d[3] = bgColor.A return } xq := xf - float64(x0) yq := yf - float64(y0) - - var pxs [4]color.NRGBA - var cfs [4]float64 - - for i := 0; i < 2; i++ { - for j := 0; j < 2; j++ { - k := i*2 + j - pt := image.Pt(x0+j, y0+i) - if pt.In(bounds) { - l := pt.Y*src.Stride + pt.X*4 - pxs[k].R = src.Pix[l+0] - pxs[k].G = src.Pix[l+1] - pxs[k].B = src.Pix[l+2] - pxs[k].A = src.Pix[l+3] - } else { - pxs[k] = bgColor - } - } + points := [4]image.Point{ + {x0, y0}, + {x0 + 1, y0}, + {x0, y0 + 1}, + {x0 + 1, y0 + 1}, + } + weights := [4]float64{ + (1 - xq) * (1 - yq), + xq * (1 - yq), + (1 - xq) * yq, + xq * yq, } - - cfs[0] = (1 - xq) * (1 - yq) - cfs[1] = xq * (1 - yq) - cfs[2] = (1 - xq) * yq - cfs[3] = xq * yq var r, g, b, a float64 - for i := range pxs { - wa := float64(pxs[i].A) * cfs[i] - r += float64(pxs[i].R) * wa - g += float64(pxs[i].G) * wa - b += float64(pxs[i].B) * wa - a += wa + for i := 0; i < 4; i++ { + p := points[i] + w := weights[i] + if p.In(bounds) { + i := p.Y*src.Stride + p.X*4 + s := src.Pix[i : i+4 : i+4] + wa := float64(s[3]) * w + r += float64(s[0]) * wa + g += float64(s[1]) * wa + b += float64(s[2]) * wa + a += wa + } else { + wa := float64(bgColor.A) * w + r += float64(bgColor.R) * wa + g += float64(bgColor.G) * wa + b += float64(bgColor.B) * wa + a += wa + } } - if a != 0 { - r /= a - g /= a - b /= a + aInv := 1 / a + d[0] = clamp(r * aInv) + d[1] = clamp(g * aInv) + d[2] = clamp(b * aInv) + d[3] = clamp(a) } - - dst.Pix[dstIndex+0] = clamp(r) - dst.Pix[dstIndex+1] = clamp(g) - dst.Pix[dstIndex+2] = clamp(b) - dst.Pix[dstIndex+3] = clamp(a) } diff --git a/utils.go b/utils.go index aa3d260..6c7af1a 100644 --- a/utils.go +++ b/utils.go @@ -63,10 +63,12 @@ func reverse(pix []uint8) { i := 0 j := len(pix) - 4 for i < j { - pix[i+0], pix[j+0] = pix[j+0], pix[i+0] - pix[i+1], pix[j+1] = pix[j+1], pix[i+1] - pix[i+2], pix[j+2] = pix[j+2], pix[i+2] - pix[i+3], pix[j+3] = pix[j+3], pix[i+3] + pi := pix[i : i+4 : i+4] + pj := pix[j : j+4 : j+4] + pi[0], pj[0] = pj[0], pi[0] + pi[1], pj[1] = pj[1], pi[1] + pi[2], pj[2] = pj[2], pi[2] + pi[3], pj[3] = pj[3], pi[3] i += 4 j -= 4 }