Browse Source

helpers tests

disintegration 10 years ago
parent
commit
9f29595049
14 changed files with 890 additions and 733 deletions
  1. 23 7
      adjust.go
  2. 0 194
      clone.go
  3. 0 36
      crop.go
  4. 0 60
      doc.go
  5. 41 7
      effects.go
  6. 201 6
      helpers.go
  7. 215 1
      helpers_test.go
  8. 0 58
      overlay.go
  9. 0 55
      paste.go
  10. 0 263
      resample_filters.go
  11. 258 0
      resize.go
  12. 0 46
      sharpen.go
  13. 139 0
      tools.go
  14. 13 0
      utils.go

+ 23 - 7
adjust.go

@@ -6,7 +6,23 @@ import (
 	"math"
 )
 
-func applyColorMapping(img image.Image, fn func(c color.NRGBA) color.NRGBA) *image.NRGBA {
+// AdjustFunc applies the fn function to each pixel of the img image and returns the adjusted image.
+//
+// Example:
+//
+// 	dstImage = imaging.AdjustFunc(
+// 		srcImage,
+// 		func(c color.NRGBA) color.NRGBA {
+// 			// shift the red channel by 16
+//			r := int(c.R) + 16
+//			if r > 255 {
+// 				r = 255
+// 			}
+// 			return color.NRGBA{uint8(r), c.G, c.B, c.A}
+// 		}
+// 	)
+//
+func AdjustFunc(img image.Image, fn func(c color.NRGBA) color.NRGBA) *image.NRGBA {
 	src := toNRGBA(img)
 	width := src.Bounds().Max.X
 	height := src.Bounds().Max.Y
@@ -56,7 +72,7 @@ func AdjustGamma(img image.Image, gamma float64) *image.NRGBA {
 		return color.NRGBA{lut[c.R], lut[c.G], lut[c.B], c.A}
 	}
 
-	return applyColorMapping(img, fn)
+	return AdjustFunc(img, fn)
 }
 
 func sigmoid(a, b, x float64) float64 {
@@ -106,7 +122,7 @@ func AdjustSigmoid(img image.Image, midpoint, factor float64) *image.NRGBA {
 		return color.NRGBA{lut[c.R], lut[c.G], lut[c.B], c.A}
 	}
 
-	return applyColorMapping(img, fn)
+	return AdjustFunc(img, fn)
 }
 
 // AdjustContrast changes the contrast of the image using the percentage parameter and returns the adjusted image.
@@ -137,7 +153,7 @@ func AdjustContrast(img image.Image, percentage float64) *image.NRGBA {
 		return color.NRGBA{lut[c.R], lut[c.G], lut[c.B], c.A}
 	}
 
-	return applyColorMapping(img, fn)
+	return AdjustFunc(img, fn)
 }
 
 // AdjustBrightness changes the brightness of the image using the percentage parameter and returns the adjusted image.
@@ -162,7 +178,7 @@ func AdjustBrightness(img image.Image, percentage float64) *image.NRGBA {
 		return color.NRGBA{lut[c.R], lut[c.G], lut[c.B], c.A}
 	}
 
-	return applyColorMapping(img, fn)
+	return AdjustFunc(img, fn)
 }
 
 // Grayscale produces grayscale version of the image.
@@ -172,7 +188,7 @@ func Grayscale(img image.Image) *image.NRGBA {
 		y := uint8(f + 0.5)
 		return color.NRGBA{y, y, y, c.A}
 	}
-	return applyColorMapping(img, fn)
+	return AdjustFunc(img, fn)
 }
 
 // Invert produces inverted (negated) version of the image.
@@ -180,5 +196,5 @@ func Invert(img image.Image) *image.NRGBA {
 	fn := func(c color.NRGBA) color.NRGBA {
 		return color.NRGBA{255 - c.R, 255 - c.G, 255 - c.B, c.A}
 	}
-	return applyColorMapping(img, fn)
+	return AdjustFunc(img, fn)
 }

+ 0 - 194
clone.go

@@ -1,194 +0,0 @@
-package imaging
-
-import (
-	"image"
-	"image/color"
-)
-
-// Clone returns a copy of the given image.
-func Clone(img image.Image) *image.NRGBA {
-	srcBounds := img.Bounds()
-	dstBounds := srcBounds.Sub(srcBounds.Min)
-
-	dst := image.NewNRGBA(dstBounds)
-
-	dstMinX := dstBounds.Min.X
-	dstMinY := dstBounds.Min.Y
-
-	srcMinX := srcBounds.Min.X
-	srcMinY := srcBounds.Min.Y
-	srcMaxX := srcBounds.Max.X
-	srcMaxY := srcBounds.Max.Y
-
-	switch src0 := img.(type) {
-
-	case *image.NRGBA:
-		rowSize := srcBounds.Dx() * 4
-		numRows := srcBounds.Dy()
-
-		i0 := dst.PixOffset(dstMinX, dstMinY)
-		j0 := src0.PixOffset(srcMinX, srcMinY)
-
-		di := dst.Stride
-		dj := src0.Stride
-
-		for row := 0; row < numRows; row++ {
-			copy(dst.Pix[i0:i0+rowSize], src0.Pix[j0:j0+rowSize])
-			i0 += di
-			j0 += dj
-		}
-
-	case *image.NRGBA64:
-		i0 := dst.PixOffset(dstMinX, dstMinY)
-		for y := srcMinY; y < srcMaxY; y, i0 = y+1, i0+dst.Stride {
-			for x, i := srcMinX, i0; x < srcMaxX; x, i = x+1, i+4 {
-
-				j := src0.PixOffset(x, y)
-
-				dst.Pix[i+0] = src0.Pix[j+0]
-				dst.Pix[i+1] = src0.Pix[j+2]
-				dst.Pix[i+2] = src0.Pix[j+4]
-				dst.Pix[i+3] = src0.Pix[j+6]
-
-			}
-		}
-
-	case *image.RGBA:
-		i0 := dst.PixOffset(dstMinX, dstMinY)
-		for y := srcMinY; y < srcMaxY; y, i0 = y+1, i0+dst.Stride {
-			for x, i := srcMinX, i0; x < srcMaxX; x, i = x+1, i+4 {
-
-				j := src0.PixOffset(x, y)
-				a := src0.Pix[j+3]
-				dst.Pix[i+3] = a
-
-				switch a {
-				case 0:
-					dst.Pix[i+0] = 0
-					dst.Pix[i+1] = 0
-					dst.Pix[i+2] = 0
-				case 0xff:
-					dst.Pix[i+0] = src0.Pix[j+0]
-					dst.Pix[i+1] = src0.Pix[j+1]
-					dst.Pix[i+2] = src0.Pix[j+2]
-				default:
-					dst.Pix[i+0] = uint8(uint16(src0.Pix[j+0]) * 0xff / uint16(a))
-					dst.Pix[i+1] = uint8(uint16(src0.Pix[j+1]) * 0xff / uint16(a))
-					dst.Pix[i+2] = uint8(uint16(src0.Pix[j+2]) * 0xff / uint16(a))
-				}
-			}
-		}
-
-	case *image.RGBA64:
-		i0 := dst.PixOffset(dstMinX, dstMinY)
-		for y := srcMinY; y < srcMaxY; y, i0 = y+1, i0+dst.Stride {
-			for x, i := srcMinX, i0; x < srcMaxX; x, i = x+1, i+4 {
-
-				j := src0.PixOffset(x, y)
-				a := src0.Pix[j+6]
-				dst.Pix[i+3] = a
-
-				switch a {
-				case 0:
-					dst.Pix[i+0] = 0
-					dst.Pix[i+1] = 0
-					dst.Pix[i+2] = 0
-				case 0xff:
-					dst.Pix[i+0] = src0.Pix[j+0]
-					dst.Pix[i+1] = src0.Pix[j+2]
-					dst.Pix[i+2] = src0.Pix[j+4]
-				default:
-					dst.Pix[i+0] = uint8(uint16(src0.Pix[j+0]) * 0xff / uint16(a))
-					dst.Pix[i+1] = uint8(uint16(src0.Pix[j+2]) * 0xff / uint16(a))
-					dst.Pix[i+2] = uint8(uint16(src0.Pix[j+4]) * 0xff / uint16(a))
-				}
-			}
-		}
-
-	case *image.Gray:
-		i0 := dst.PixOffset(dstMinX, dstMinY)
-		for y := srcMinY; y < srcMaxY; y, i0 = y+1, i0+dst.Stride {
-			for x, i := srcMinX, i0; x < srcMaxX; x, i = x+1, i+4 {
-
-				j := src0.PixOffset(x, y)
-				c := src0.Pix[j]
-				dst.Pix[i+0] = c
-				dst.Pix[i+1] = c
-				dst.Pix[i+2] = c
-				dst.Pix[i+3] = 0xff
-
-			}
-		}
-
-	case *image.Gray16:
-		i0 := dst.PixOffset(dstMinX, dstMinY)
-		for y := srcMinY; y < srcMaxY; y, i0 = y+1, i0+dst.Stride {
-			for x, i := srcMinX, i0; x < srcMaxX; x, i = x+1, i+4 {
-
-				j := src0.PixOffset(x, y)
-				c := src0.Pix[j]
-				dst.Pix[i+0] = c
-				dst.Pix[i+1] = c
-				dst.Pix[i+2] = c
-				dst.Pix[i+3] = 0xff
-
-			}
-		}
-
-	case *image.YCbCr:
-		i0 := dst.PixOffset(dstMinX, dstMinY)
-		for y := srcMinY; y < srcMaxY; y, i0 = y+1, i0+dst.Stride {
-			for x, i := srcMinX, i0; x < srcMaxX; x, i = x+1, i+4 {
-
-				yj := src0.YOffset(x, y)
-				cj := src0.COffset(x, y)
-				r, g, b := color.YCbCrToRGB(src0.Y[yj], src0.Cb[cj], src0.Cr[cj])
-
-				dst.Pix[i+0] = r
-				dst.Pix[i+1] = g
-				dst.Pix[i+2] = b
-				dst.Pix[i+3] = 0xff
-
-			}
-		}
-
-	case *image.Paletted:
-		plen := len(src0.Palette)
-		pnew := make([]color.NRGBA, plen)
-		for i := 0; i < plen; i++ {
-			pnew[i] = color.NRGBAModel.Convert(src0.Palette[i]).(color.NRGBA)
-		}
-
-		i0 := dst.PixOffset(dstMinX, dstMinY)
-		for y := srcMinY; y < srcMaxY; y, i0 = y+1, i0+dst.Stride {
-			for x, i := srcMinX, i0; x < srcMaxX; x, i = x+1, i+4 {
-
-				j := src0.PixOffset(x, y)
-				c := pnew[src0.Pix[j]]
-
-				dst.Pix[i+0] = c.R
-				dst.Pix[i+1] = c.G
-				dst.Pix[i+2] = c.B
-				dst.Pix[i+3] = c.A
-
-			}
-		}
-
-	default:
-		i0 := dst.PixOffset(dstMinX, dstMinY)
-		for y := srcMinY; y < srcMaxY; y, i0 = y+1, i0+dst.Stride {
-			for x, i := srcMinX, i0; x < srcMaxX; x, i = x+1, i+4 {
-
-				c := color.NRGBAModel.Convert(img.At(x, y)).(color.NRGBA)
-
-				dst.Pix[i+0] = c.R
-				dst.Pix[i+1] = c.G
-				dst.Pix[i+2] = c.B
-				dst.Pix[i+3] = c.A
-
-			}
-		}
-	}
-
-	return dst
-}

+ 0 - 36
crop.go

@@ -1,36 +0,0 @@
-package imaging
-
-import (
-	"image"
-)
-
-// Crop cuts out a rectangular region with the specified bounds
-// from the image and returns the cropped image.
-func Crop(img image.Image, rect image.Rectangle) *image.NRGBA {
-	src := toNRGBA(img)
-	srcRect := rect.Sub(img.Bounds().Min)
-	sub := src.SubImage(srcRect)
-	return Clone(sub) // New image Bounds().Min point will be (0, 0)
-}
-
-// CropCenter cuts out a rectangular region with the specified size
-// from the center of the image and returns the cropped image.
-func CropCenter(img image.Image, width, height int) *image.NRGBA {
-	cropW, cropH := width, height
-
-	srcBounds := img.Bounds()
-	srcW := srcBounds.Dx()
-	srcH := srcBounds.Dy()
-	srcMinX := srcBounds.Min.X
-	srcMinY := srcBounds.Min.Y
-
-	centerX := srcMinX + srcW/2
-	centerY := srcMinY + srcH/2
-
-	x0 := centerX - cropW/2
-	y0 := centerY - cropH/2
-	x1 := x0 + cropW
-	y1 := y0 + cropH
-
-	return Crop(img, image.Rect(x0, y0, x1, y1))
-}

+ 0 - 60
doc.go

@@ -1,60 +0,0 @@
-/*
-Package imaging provides basic image manipulation functions (resize, rotate, flip, crop, etc.).
-This package is based on the standard Go image package and works best along with it.
-
-Image manipulation functions provided by the package take any image type
-that implements `image.Image` interface as an input, and return a new image of
-`*image.NRGBA` type (32bit RGBA colors, not premultiplied by alpha).
-
-Imaging package uses parallel goroutines for faster image processing.
-To achieve maximum performance, make sure to allow Go to utilize all CPU cores:
-
-	runtime.GOMAXPROCS(runtime.NumCPU())
-
-Here is the complete example that loads several images, makes thumbnails of them
-and joins them together.
-
-	package main
-
-	import (
-		"image"
-		"image/color"
-		"runtime"
-
-		"github.com/disintegration/imaging"
-	)
-
-	func main() {
-		// use all CPU cores for maximum performance
-		runtime.GOMAXPROCS(runtime.NumCPU())
-
-		// input files
-		files := []string{"01.jpg", "02.jpg", "03.jpg"}
-
-		// load images and make 100x100 thumbnails of them
-		var thumbnails []image.Image
-		for _, file := range files {
-			img, err := imaging.Open(file)
-			if err != nil {
-				panic(err)
-			}
-			thumb := imaging.Thumbnail(img, 100, 100, imaging.CatmullRom)
-			thumbnails = append(thumbnails, thumb)
-		}
-
-		// create a new blank image
-		dst := imaging.New(100*len(thumbnails), 100, color.NRGBA{0, 0, 0, 0})
-
-		// paste thumbnails into the new image side by side
-		for i, thumb := range thumbnails {
-			dst = imaging.Paste(dst, thumb, image.Pt(i*100, 0))
-		}
-
-		// save the combined image to file
-		err := imaging.Save(dst, "dst.jpg")
-		if err != nil {
-			panic(err)
-		}
-	}
-*/
-package imaging

+ 41 - 7
blur.go → effects.go

@@ -9,13 +9,6 @@ func gaussianBlurKernel(x, sigma float64) float64 {
 	return math.Exp(-(x*x)/(2*sigma*sigma)) / (sigma * math.Sqrt(2*math.Pi))
 }
 
-func absint(i int) int {
-	if i < 0 {
-		return -i
-	}
-	return i
-}
-
 // Blur produces a blurred version of the image using a Gaussian function.
 // Sigma parameter must be positive and indicates how much the image will be blurred.
 //
@@ -151,3 +144,44 @@ func blurVertical(src *image.NRGBA, kernel []float64) *image.NRGBA {
 
 	return dst
 }
+
+// Sharpen produces a sharpened version of the image.
+// Sigma parameter must be positive and indicates how much the image will be sharpened.
+//
+// Usage example:
+//
+//		dstImage := imaging.Sharpen(srcImage, 3.5)
+//
+func Sharpen(img image.Image, sigma float64) *image.NRGBA {
+	if sigma <= 0 {
+		// sigma parameter must be positive!
+		return Clone(img)
+	}
+
+	src := toNRGBA(img)
+	blurred := Blur(img, sigma)
+
+	width := src.Bounds().Max.X
+	height := src.Bounds().Max.Y
+	dst := image.NewNRGBA(image.Rect(0, 0, width, height))
+
+	parallel(height, func(partStart, partEnd int) {
+		for y := partStart; y < partEnd; y++ {
+			for x := 0; x < width; x++ {
+				i := y*src.Stride + x*4
+				for j := 0; j < 4; j++ {
+					k := i + j
+					val := int(src.Pix[k]) + (int(src.Pix[k]) - int(blurred.Pix[k]))
+					if val < 0 {
+						val = 0
+					} else if val > 255 {
+						val = 255
+					}
+					dst.Pix[k] = uint8(val)
+				}
+			}
+		}
+	})
+
+	return dst
+}

+ 201 - 6
helpers.go

@@ -1,3 +1,16 @@
+/*
+Package imaging provides basic image manipulation functions (resize, rotate, flip, crop, etc.).
+This package is based on the standard Go image package and works best along with it.
+
+Image manipulation functions provided by the package take any image type
+that implements `image.Image` interface as an input, and return a new image of
+`*image.NRGBA` type (32bit RGBA colors, not premultiplied by alpha).
+
+Imaging package uses parallel goroutines for faster image processing.
+To achieve maximum performance, make sure to allow Go to utilize all CPU cores:
+
+	runtime.GOMAXPROCS(runtime.NumCPU())
+*/
 package imaging
 
 import (
@@ -8,7 +21,6 @@ import (
 	"image/jpeg"
 	"image/png"
 	"io"
-	"math"
 	"os"
 	"path/filepath"
 	"strings"
@@ -158,6 +170,194 @@ func New(width, height int, fillColor color.Color) *image.NRGBA {
 	return dst
 }
 
+// Clone returns a copy of the given image.
+func Clone(img image.Image) *image.NRGBA {
+	srcBounds := img.Bounds()
+	dstBounds := srcBounds.Sub(srcBounds.Min)
+
+	dst := image.NewNRGBA(dstBounds)
+
+	dstMinX := dstBounds.Min.X
+	dstMinY := dstBounds.Min.Y
+
+	srcMinX := srcBounds.Min.X
+	srcMinY := srcBounds.Min.Y
+	srcMaxX := srcBounds.Max.X
+	srcMaxY := srcBounds.Max.Y
+
+	switch src0 := img.(type) {
+
+	case *image.NRGBA:
+		rowSize := srcBounds.Dx() * 4
+		numRows := srcBounds.Dy()
+
+		i0 := dst.PixOffset(dstMinX, dstMinY)
+		j0 := src0.PixOffset(srcMinX, srcMinY)
+
+		di := dst.Stride
+		dj := src0.Stride
+
+		for row := 0; row < numRows; row++ {
+			copy(dst.Pix[i0:i0+rowSize], src0.Pix[j0:j0+rowSize])
+			i0 += di
+			j0 += dj
+		}
+
+	case *image.NRGBA64:
+		i0 := dst.PixOffset(dstMinX, dstMinY)
+		for y := srcMinY; y < srcMaxY; y, i0 = y+1, i0+dst.Stride {
+			for x, i := srcMinX, i0; x < srcMaxX; x, i = x+1, i+4 {
+
+				j := src0.PixOffset(x, y)
+
+				dst.Pix[i+0] = src0.Pix[j+0]
+				dst.Pix[i+1] = src0.Pix[j+2]
+				dst.Pix[i+2] = src0.Pix[j+4]
+				dst.Pix[i+3] = src0.Pix[j+6]
+
+			}
+		}
+
+	case *image.RGBA:
+		i0 := dst.PixOffset(dstMinX, dstMinY)
+		for y := srcMinY; y < srcMaxY; y, i0 = y+1, i0+dst.Stride {
+			for x, i := srcMinX, i0; x < srcMaxX; x, i = x+1, i+4 {
+
+				j := src0.PixOffset(x, y)
+				a := src0.Pix[j+3]
+				dst.Pix[i+3] = a
+
+				switch a {
+				case 0:
+					dst.Pix[i+0] = 0
+					dst.Pix[i+1] = 0
+					dst.Pix[i+2] = 0
+				case 0xff:
+					dst.Pix[i+0] = src0.Pix[j+0]
+					dst.Pix[i+1] = src0.Pix[j+1]
+					dst.Pix[i+2] = src0.Pix[j+2]
+				default:
+					dst.Pix[i+0] = uint8(uint16(src0.Pix[j+0]) * 0xff / uint16(a))
+					dst.Pix[i+1] = uint8(uint16(src0.Pix[j+1]) * 0xff / uint16(a))
+					dst.Pix[i+2] = uint8(uint16(src0.Pix[j+2]) * 0xff / uint16(a))
+				}
+			}
+		}
+
+	case *image.RGBA64:
+		i0 := dst.PixOffset(dstMinX, dstMinY)
+		for y := srcMinY; y < srcMaxY; y, i0 = y+1, i0+dst.Stride {
+			for x, i := srcMinX, i0; x < srcMaxX; x, i = x+1, i+4 {
+
+				j := src0.PixOffset(x, y)
+				a := src0.Pix[j+6]
+				dst.Pix[i+3] = a
+
+				switch a {
+				case 0:
+					dst.Pix[i+0] = 0
+					dst.Pix[i+1] = 0
+					dst.Pix[i+2] = 0
+				case 0xff:
+					dst.Pix[i+0] = src0.Pix[j+0]
+					dst.Pix[i+1] = src0.Pix[j+2]
+					dst.Pix[i+2] = src0.Pix[j+4]
+				default:
+					dst.Pix[i+0] = uint8(uint16(src0.Pix[j+0]) * 0xff / uint16(a))
+					dst.Pix[i+1] = uint8(uint16(src0.Pix[j+2]) * 0xff / uint16(a))
+					dst.Pix[i+2] = uint8(uint16(src0.Pix[j+4]) * 0xff / uint16(a))
+				}
+			}
+		}
+
+	case *image.Gray:
+		i0 := dst.PixOffset(dstMinX, dstMinY)
+		for y := srcMinY; y < srcMaxY; y, i0 = y+1, i0+dst.Stride {
+			for x, i := srcMinX, i0; x < srcMaxX; x, i = x+1, i+4 {
+
+				j := src0.PixOffset(x, y)
+				c := src0.Pix[j]
+				dst.Pix[i+0] = c
+				dst.Pix[i+1] = c
+				dst.Pix[i+2] = c
+				dst.Pix[i+3] = 0xff
+
+			}
+		}
+
+	case *image.Gray16:
+		i0 := dst.PixOffset(dstMinX, dstMinY)
+		for y := srcMinY; y < srcMaxY; y, i0 = y+1, i0+dst.Stride {
+			for x, i := srcMinX, i0; x < srcMaxX; x, i = x+1, i+4 {
+
+				j := src0.PixOffset(x, y)
+				c := src0.Pix[j]
+				dst.Pix[i+0] = c
+				dst.Pix[i+1] = c
+				dst.Pix[i+2] = c
+				dst.Pix[i+3] = 0xff
+
+			}
+		}
+
+	case *image.YCbCr:
+		i0 := dst.PixOffset(dstMinX, dstMinY)
+		for y := srcMinY; y < srcMaxY; y, i0 = y+1, i0+dst.Stride {
+			for x, i := srcMinX, i0; x < srcMaxX; x, i = x+1, i+4 {
+
+				yj := src0.YOffset(x, y)
+				cj := src0.COffset(x, y)
+				r, g, b := color.YCbCrToRGB(src0.Y[yj], src0.Cb[cj], src0.Cr[cj])
+
+				dst.Pix[i+0] = r
+				dst.Pix[i+1] = g
+				dst.Pix[i+2] = b
+				dst.Pix[i+3] = 0xff
+
+			}
+		}
+
+	case *image.Paletted:
+		plen := len(src0.Palette)
+		pnew := make([]color.NRGBA, plen)
+		for i := 0; i < plen; i++ {
+			pnew[i] = color.NRGBAModel.Convert(src0.Palette[i]).(color.NRGBA)
+		}
+
+		i0 := dst.PixOffset(dstMinX, dstMinY)
+		for y := srcMinY; y < srcMaxY; y, i0 = y+1, i0+dst.Stride {
+			for x, i := srcMinX, i0; x < srcMaxX; x, i = x+1, i+4 {
+
+				j := src0.PixOffset(x, y)
+				c := pnew[src0.Pix[j]]
+
+				dst.Pix[i+0] = c.R
+				dst.Pix[i+1] = c.G
+				dst.Pix[i+2] = c.B
+				dst.Pix[i+3] = c.A
+
+			}
+		}
+
+	default:
+		i0 := dst.PixOffset(dstMinX, dstMinY)
+		for y := srcMinY; y < srcMaxY; y, i0 = y+1, i0+dst.Stride {
+			for x, i := srcMinX, i0; x < srcMaxX; x, i = x+1, i+4 {
+
+				c := color.NRGBAModel.Convert(img.At(x, y)).(color.NRGBA)
+
+				dst.Pix[i+0] = c.R
+				dst.Pix[i+1] = c.G
+				dst.Pix[i+2] = c.B
+				dst.Pix[i+3] = c.A
+
+			}
+		}
+	}
+
+	return dst
+}
+
 // This function used internally to convert any image type to NRGBA if needed.
 func toNRGBA(img image.Image) *image.NRGBA {
 	srcBounds := img.Bounds()
@@ -168,8 +368,3 @@ func toNRGBA(img image.Image) *image.NRGBA {
 	}
 	return Clone(img)
 }
-
-// clamp & round float64 to uint8 (0..255)
-func clamp(v float64) uint8 {
-	return uint8(math.Min(math.Max(v, 0.0), 255.0) + 0.5)
-}

+ 215 - 1
helpers_test.go

@@ -3,6 +3,7 @@ package imaging
 import (
 	"bytes"
 	"image"
+	"image/color"
 	"testing"
 )
 
@@ -67,7 +68,7 @@ func TestEncodeDecode(t *testing.T) {
 		}
 
 		if !compareNRGBA(img, img2cloned, delta) {
-			t.Errorf("fail comparing images format=%s %#v %#v", format, img, img2cloned)
+			t.Errorf("test [DecodeEncode %s] failed: %#v %#v", format, img, img2cloned)
 			continue
 		}
 	}
@@ -78,3 +79,216 @@ func TestEncodeDecode(t *testing.T) {
 		t.Errorf("expected ErrUnsupportedFormat")
 	}
 }
+
+func TestNew(t *testing.T) {
+	td := []struct {
+		desc      string
+		w, h      int
+		c         color.Color
+		dstBounds image.Rectangle
+		dstPix    []uint8
+	}{
+		{
+			"New 1x1 black",
+			1, 1,
+			color.NRGBA{0, 0, 0, 0},
+			image.Rect(0, 0, 1, 1),
+			[]uint8{0x00, 0x00, 0x00, 0x00},
+		},
+		{
+			"New 1x2 red",
+			1, 2,
+			color.NRGBA{255, 0, 0, 255},
+			image.Rect(0, 0, 1, 2),
+			[]uint8{0xff, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff},
+		},
+		{
+			"New 2x1 white",
+			2, 1,
+			color.NRGBA{255, 255, 255, 255},
+			image.Rect(0, 0, 2, 1),
+			[]uint8{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
+		},
+	}
+
+	for _, d := range td {
+		got := New(d.w, d.h, d.c)
+		want := image.NewNRGBA(d.dstBounds)
+		want.Pix = d.dstPix
+		if !compareNRGBA(got, want, 0) {
+			t.Errorf("test [%s] failed: %#v", d.desc, got)
+		}
+	}
+}
+
+func TestClone(t *testing.T) {
+	td := []struct {
+		desc string
+		src  image.Image
+		want *image.NRGBA
+	}{
+		{
+			"Clone NRGBA",
+			&image.NRGBA{
+				Rect:   image.Rect(-1, -1, 0, 1),
+				Stride: 1 * 4,
+				Pix:    []uint8{0x00, 0x11, 0x22, 0x33, 0xcc, 0xdd, 0xee, 0xff},
+			},
+			&image.NRGBA{
+				Rect:   image.Rect(0, 0, 1, 2),
+				Stride: 1 * 4,
+				Pix:    []uint8{0x00, 0x11, 0x22, 0x33, 0xcc, 0xdd, 0xee, 0xff},
+			},
+		},
+		{
+			"Clone NRGBA64",
+			&image.NRGBA64{
+				Rect:   image.Rect(-1, -1, 0, 1),
+				Stride: 1 * 8,
+				Pix: []uint8{
+					0x00, 0x00, 0x11, 0x11, 0x22, 0x22, 0x33, 0x33,
+					0xcc, 0xcc, 0xdd, 0xdd, 0xee, 0xee, 0xff, 0xff,
+				},
+			},
+			&image.NRGBA{
+				Rect:   image.Rect(0, 0, 1, 2),
+				Stride: 1 * 4,
+				Pix:    []uint8{0x00, 0x11, 0x22, 0x33, 0xcc, 0xdd, 0xee, 0xff},
+			},
+		},
+		{
+			"Clone RGBA",
+			&image.RGBA{
+				Rect:   image.Rect(-1, -1, 0, 1),
+				Stride: 1 * 4,
+				Pix:    []uint8{0x00, 0x11, 0x22, 0x33, 0xcc, 0xdd, 0xee, 0xff},
+			},
+			&image.NRGBA{
+				Rect:   image.Rect(0, 0, 1, 2),
+				Stride: 1 * 4,
+				Pix:    []uint8{0x00, 0x55, 0xaa, 0x33, 0xcc, 0xdd, 0xee, 0xff},
+			},
+		},
+		{
+			"Clone RGBA64",
+			&image.RGBA64{
+				Rect:   image.Rect(-1, -1, 0, 1),
+				Stride: 1 * 8,
+				Pix: []uint8{
+					0x00, 0x00, 0x11, 0x11, 0x22, 0x22, 0x33, 0x33,
+					0xcc, 0xcc, 0xdd, 0xdd, 0xee, 0xee, 0xff, 0xff,
+				},
+			},
+			&image.NRGBA{
+				Rect:   image.Rect(0, 0, 1, 2),
+				Stride: 1 * 4,
+				Pix:    []uint8{0x00, 0x55, 0xaa, 0x33, 0xcc, 0xdd, 0xee, 0xff},
+			},
+		},
+		{
+			"Clone Gray",
+			&image.Gray{
+				Rect:   image.Rect(-1, -1, 0, 1),
+				Stride: 1 * 1,
+				Pix:    []uint8{0x11, 0xee},
+			},
+			&image.NRGBA{
+				Rect:   image.Rect(0, 0, 1, 2),
+				Stride: 1 * 4,
+				Pix:    []uint8{0x11, 0x11, 0x11, 0xff, 0xee, 0xee, 0xee, 0xff},
+			},
+		},
+		{
+			"Clone Gray16",
+			&image.Gray16{
+				Rect:   image.Rect(-1, -1, 0, 1),
+				Stride: 1 * 2,
+				Pix:    []uint8{0x11, 0x11, 0xee, 0xee},
+			},
+			&image.NRGBA{
+				Rect:   image.Rect(0, 0, 1, 2),
+				Stride: 1 * 4,
+				Pix:    []uint8{0x11, 0x11, 0x11, 0xff, 0xee, 0xee, 0xee, 0xff},
+			},
+		},
+		{
+			"Clone Alpha",
+			&image.Alpha{
+				Rect:   image.Rect(-1, -1, 0, 1),
+				Stride: 1 * 1,
+				Pix:    []uint8{0x11, 0xee},
+			},
+			&image.NRGBA{
+				Rect:   image.Rect(0, 0, 1, 2),
+				Stride: 1 * 4,
+				Pix:    []uint8{0xff, 0xff, 0xff, 0x11, 0xff, 0xff, 0xff, 0xee},
+			},
+		},
+		{
+			"Clone YCbCr",
+			&image.YCbCr{
+				Rect:           image.Rect(-1, -1, 5, 0),
+				SubsampleRatio: image.YCbCrSubsampleRatio444,
+				YStride:        6,
+				CStride:        6,
+				Y:              []uint8{0x00, 0xff, 0x7f, 0x26, 0x4b, 0x0e},
+				Cb:             []uint8{0x80, 0x80, 0x80, 0x6b, 0x56, 0xc0},
+				Cr:             []uint8{0x80, 0x80, 0x80, 0xc0, 0x4b, 0x76},
+			},
+			&image.NRGBA{
+				Rect:   image.Rect(0, 0, 6, 1),
+				Stride: 6 * 4,
+				Pix: []uint8{
+					0x00, 0x00, 0x00, 0xff,
+					0xff, 0xff, 0xff, 0xff,
+					0x7f, 0x7f, 0x7f, 0xff,
+					0x7f, 0x00, 0x00, 0xff,
+					0x00, 0x7f, 0x00, 0xff,
+					0x00, 0x00, 0x7f, 0xff,
+				},
+			},
+		},
+		{
+			"Clone Paletted",
+			&image.Paletted{
+				Rect:   image.Rect(-1, -1, 5, 0),
+				Stride: 6 * 1,
+				Palette: color.Palette{
+					color.NRGBA{R: 0x00, G: 0x00, B: 0x00, A: 0xff},
+					color.NRGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff},
+					color.NRGBA{R: 0x7f, G: 0x7f, B: 0x7f, A: 0xff},
+					color.NRGBA{R: 0x7f, G: 0x00, B: 0x00, A: 0xff},
+					color.NRGBA{R: 0x00, G: 0x7f, B: 0x00, A: 0xff},
+					color.NRGBA{R: 0x00, G: 0x00, B: 0x7f, A: 0xff},
+				},
+				Pix: []uint8{0x0, 0x1, 0x2, 0x3, 0x4, 0x5},
+			},
+			&image.NRGBA{
+				Rect:   image.Rect(0, 0, 6, 1),
+				Stride: 6 * 4,
+				Pix: []uint8{
+					0x00, 0x00, 0x00, 0xff,
+					0xff, 0xff, 0xff, 0xff,
+					0x7f, 0x7f, 0x7f, 0xff,
+					0x7f, 0x00, 0x00, 0xff,
+					0x00, 0x7f, 0x00, 0xff,
+					0x00, 0x00, 0x7f, 0xff,
+				},
+			},
+		},
+	}
+
+	for _, d := range td {
+		got := Clone(d.src)
+		want := d.want
+
+		delta := 0
+		if _, ok := d.src.(*image.YCbCr); ok {
+			delta = 1
+		}
+
+		if !compareNRGBA(got, want, delta) {
+			t.Errorf("test [%s] failed: %#v", d.desc, got)
+		}
+	}
+}

+ 0 - 58
overlay.go

@@ -1,58 +0,0 @@
-package imaging
-
-import (
-	"image"
-	"math"
-)
-
-// Overlay draws the img image over the background image at given position
-// and returns the combined image. Opacity parameter is the opacity of the img
-// image layer, used to compose the images, it must be from 0.0 to 1.0.
-//
-// Usage examples:
-//
-//		// draw the sprite over the background at position (50, 50)
-//		dstImage := imaging.Overlay(backgroundImage, spriteImage, image.Pt(50, 50), 1.0)
-//
-//		// blend two opaque images of the same size
-//		dstImage := imaging.Overlay(imageOne, imageTwo, image.Pt(0, 0), 0.5)
-//
-func Overlay(background, img image.Image, pos image.Point, opacity float64) *image.NRGBA {
-	opacity = math.Min(math.Max(opacity, 0.0), 1.0) // check: 0.0 <= opacity <= 1.0
-
-	src := toNRGBA(img)
-	dst := Clone(background)                    // cloned image bounds start at (0, 0)
-	startPt := pos.Sub(background.Bounds().Min) // so we should translate start point
-	endPt := startPt.Add(src.Bounds().Size())
-	pasteBounds := image.Rectangle{startPt, endPt}
-
-	if dst.Bounds().Overlaps(pasteBounds) {
-		intersectBounds := dst.Bounds().Intersect(pasteBounds)
-
-		for y := intersectBounds.Min.Y; y < intersectBounds.Max.Y; y++ {
-			for x := intersectBounds.Min.X; x < intersectBounds.Max.X; x++ {
-				i := y*dst.Stride + x*4
-
-				srcX := x - pasteBounds.Min.X
-				srcY := y - pasteBounds.Min.Y
-				j := srcY*src.Stride + srcX*4
-
-				a1 := float64(dst.Pix[i+3])
-				a2 := float64(src.Pix[j+3])
-
-				coef2 := opacity * a2 / 255.0
-				coef1 := (1 - coef2) * a1 / 255.0
-				coefSum := coef1 + coef2
-				coef1 /= coefSum
-				coef2 /= coefSum
-
-				dst.Pix[i+0] = uint8(float64(dst.Pix[i+0])*coef1 + float64(src.Pix[j+0])*coef2)
-				dst.Pix[i+1] = uint8(float64(dst.Pix[i+1])*coef1 + float64(src.Pix[j+1])*coef2)
-				dst.Pix[i+2] = uint8(float64(dst.Pix[i+2])*coef1 + float64(src.Pix[j+2])*coef2)
-				dst.Pix[i+3] = uint8(math.Min(a1+a2*opacity*(255.0-a1)/255.0, 255.0))
-			}
-		}
-	}
-
-	return dst
-}

+ 0 - 55
paste.go

@@ -1,55 +0,0 @@
-package imaging
-
-import (
-	"image"
-)
-
-// Paste pastes the img image to the background image at the specified position and returns the combined image.
-func Paste(background, img image.Image, pos image.Point) *image.NRGBA {
-	src := toNRGBA(img)
-	dst := Clone(background)                    // cloned image bounds start at (0, 0)
-	startPt := pos.Sub(background.Bounds().Min) // so we should translate start point
-	endPt := startPt.Add(src.Bounds().Size())
-	pasteBounds := image.Rectangle{startPt, endPt}
-
-	if dst.Bounds().Overlaps(pasteBounds) {
-		intersectBounds := dst.Bounds().Intersect(pasteBounds)
-
-		rowSize := intersectBounds.Dx() * 4
-		numRows := intersectBounds.Dy()
-
-		srcStartX := intersectBounds.Min.X - pasteBounds.Min.X
-		srcStartY := intersectBounds.Min.Y - pasteBounds.Min.Y
-
-		i0 := dst.PixOffset(intersectBounds.Min.X, intersectBounds.Min.Y)
-		j0 := src.PixOffset(srcStartX, srcStartY)
-
-		di := dst.Stride
-		dj := src.Stride
-
-		for row := 0; row < numRows; row++ {
-			copy(dst.Pix[i0:i0+rowSize], src.Pix[j0:j0+rowSize])
-			i0 += di
-			j0 += dj
-		}
-	}
-
-	return dst
-}
-
-// PasteCenter pastes the img image to the center of the background image and returns the combined image.
-func PasteCenter(background, img image.Image) *image.NRGBA {
-	bgBounds := background.Bounds()
-	bgW := bgBounds.Dx()
-	bgH := bgBounds.Dy()
-	bgMinX := bgBounds.Min.X
-	bgMinY := bgBounds.Min.Y
-
-	centerX := bgMinX + bgW/2
-	centerY := bgMinY + bgH/2
-
-	x0 := centerX - img.Bounds().Dx()/2
-	y0 := centerY - img.Bounds().Dy()/2
-
-	return Paste(background, img, image.Pt(x0, y0))
-}

+ 0 - 263
resample_filters.go

@@ -1,263 +0,0 @@
-package imaging
-
-import (
-	"math"
-)
-
-// Resample filter struct. It can be used to make custom filters.
-//
-// Supported resample filters: NearestNeighbor, Box, Linear, Hermite, MitchellNetravali,
-// CatmullRom, BSpline, Gaussian, Lanczos, Hann, Hamming, Blackman, Bartlett, Welch, Cosine.
-//
-//	General filter recommendations:
-//
-//	- Lanczos
-//		Probably the best resampling filter for photographic images yielding sharp results, 
-//		but it's slower than cubic filters (see below).
-//	
-//	- CatmullRom
-//		A sharp cubic filter. It's a good filter for both upscaling and downscaling if sharp results are needed.
-//	
-//	- MitchellNetravali 
-//		A high quality cubic filter that produces smoother results with less ringing than CatmullRom.
-//	
-//	- BSpline
-//		A good filter if a very smooth output is needed.
-//	
-//	- Linear
-//		Bilinear interpolation filter, produces reasonably good, smooth output. It's faster than cubic filters.
-//
-//	- Box
-//		Simple and fast resampling filter appropriate for downscaling.
-//		When upscaling it's similar to NearestNeighbor.
-//
-//	- NearestNeighbor
-//		Fastest resample filter, no antialiasing at all. Rarely used.
-//
-type ResampleFilter struct {
-	Support float64
-	Kernel  func(float64) float64
-}
-
-// Nearest-neighbor filter, no anti-aliasing.
-var NearestNeighbor ResampleFilter
-
-// Box filter (averaging pixels).
-var Box ResampleFilter
-
-// Linear filter.
-var Linear ResampleFilter
-
-// Hermite cubic spline filter (BC-spline; B=0; C=0).
-var Hermite ResampleFilter
-
-// Mitchell-Netravali cubic filter (BC-spline; B=1/3; C=1/3).
-var MitchellNetravali ResampleFilter
-
-// Catmull-Rom - sharp cubic filter (BC-spline; B=0; C=0.5).
-var CatmullRom ResampleFilter
-
-// Cubic B-spline - smooth cubic filter (BC-spline; B=1; C=0).
-var BSpline ResampleFilter
-
-// Gaussian Blurring Filter.
-var Gaussian ResampleFilter
-
-// Bartlett-windowed sinc filter (3 lobes).
-var Bartlett ResampleFilter
-
-// Lanczos filter (3 lobes).
-var Lanczos ResampleFilter
-
-// Hann-windowed sinc filter (3 lobes).
-var Hann ResampleFilter
-
-// Hamming-windowed sinc filter (3 lobes).
-var Hamming ResampleFilter
-
-// Blackman-windowed sinc filter (3 lobes).
-var Blackman ResampleFilter
-
-// Welch-windowed sinc filter (parabolic window, 3 lobes).
-var Welch ResampleFilter
-
-// Cosine-windowed sinc filter (3 lobes).
-var Cosine ResampleFilter
-
-func bcspline(x, b, c float64) float64 {
-	x = math.Abs(x)
-	if x < 1.0 {
-		return ((12-9*b-6*c)*x*x*x + (-18+12*b+6*c)*x*x + (6 - 2*b)) / 6
-	}
-	if x < 2.0 {
-		return ((-b-6*c)*x*x*x + (6*b+30*c)*x*x + (-12*b-48*c)*x + (8*b + 24*c)) / 6
-	}
-	return 0
-}
-
-func sinc(x float64) float64 {
-	if x == 0 {
-		return 1
-	}
-	return math.Sin(math.Pi*x) / (math.Pi * x)
-}
-
-func init() {
-	NearestNeighbor = ResampleFilter{
-		Support: 0.0, // special case - not applying the filter
-	}
-
-	Box = ResampleFilter{
-		Support: 0.5,
-		Kernel: func(x float64) float64 {
-			x = math.Abs(x)
-			if x <= 0.5 {
-				return 1.0
-			}
-			return 0
-		},
-	}
-
-	Linear = ResampleFilter{
-		Support: 1.0,
-		Kernel: func(x float64) float64 {
-			x = math.Abs(x)
-			if x < 1.0 {
-				return 1.0 - x
-			}
-			return 0
-		},
-	}
-
-	Hermite = ResampleFilter{
-		Support: 1.0,
-		Kernel: func(x float64) float64 {
-			x = math.Abs(x)
-			if x < 1.0 {
-				return bcspline(x, 0.0, 0.0)
-			}
-			return 0
-		},
-	}
-
-	MitchellNetravali = ResampleFilter{
-		Support: 2.0,
-		Kernel: func(x float64) float64 {
-			x = math.Abs(x)
-			if x < 2.0 {
-				return bcspline(x, 1.0/3.0, 1.0/3.0)
-			}
-			return 0
-		},
-	}
-
-	CatmullRom = ResampleFilter{
-		Support: 2.0,
-		Kernel: func(x float64) float64 {
-			x = math.Abs(x)
-			if x < 2.0 {
-				return bcspline(x, 0.0, 0.5)
-			}
-			return 0
-		},
-	}
-
-	BSpline = ResampleFilter{
-		Support: 2.0,
-		Kernel: func(x float64) float64 {
-			x = math.Abs(x)
-			if x < 2.0 {
-				return bcspline(x, 1.0, 0.0)
-			}
-			return 0
-		},
-	}
-
-	Gaussian = ResampleFilter{
-		Support: 2.0,
-		Kernel: func(x float64) float64 {
-			x = math.Abs(x)
-			if x < 2.0 {
-				return math.Exp(-2 * x * x)
-			}
-			return 0
-		},
-	}
-
-	Bartlett = ResampleFilter{
-		Support: 3.0,
-		Kernel: func(x float64) float64 {
-			x = math.Abs(x)
-			if x < 3.0 {
-				return sinc(x) * (3.0 - x) / 3.0
-			}
-			return 0
-		},
-	}
-
-	Lanczos = ResampleFilter{
-		Support: 3.0,
-		Kernel: func(x float64) float64 {
-			x = math.Abs(x)
-			if x < 3.0 {
-				return sinc(x) * sinc(x/3.0)
-			}
-			return 0
-		},
-	}
-
-	Hann = ResampleFilter{
-		Support: 3.0,
-		Kernel: func(x float64) float64 {
-			x = math.Abs(x)
-			if x < 3.0 {
-				return sinc(x) * (0.5 + 0.5*math.Cos(math.Pi*x/3.0))
-			}
-			return 0
-		},
-	}
-
-	Hamming = ResampleFilter{
-		Support: 3.0,
-		Kernel: func(x float64) float64 {
-			x = math.Abs(x)
-			if x < 3.0 {
-				return sinc(x) * (0.54 + 0.46*math.Cos(math.Pi*x/3.0))
-			}
-			return 0
-		},
-	}
-
-	Blackman = ResampleFilter{
-		Support: 3.0,
-		Kernel: func(x float64) float64 {
-			x = math.Abs(x)
-			if x < 3.0 {
-				return sinc(x) * (0.42 - 0.5*math.Cos(math.Pi*x/3.0+math.Pi) + 0.08*math.Cos(2.0*math.Pi*x/3.0))
-			}
-			return 0
-		},
-	}
-
-	Welch = ResampleFilter{
-		Support: 3.0,
-		Kernel: func(x float64) float64 {
-			x = math.Abs(x)
-			if x < 3.0 {
-				return sinc(x) * (1.0 - (x * x / 9.0))
-			}
-			return 0
-		},
-	}
-
-	Cosine = ResampleFilter{
-		Support: 3.0,
-		Kernel: func(x float64) float64 {
-			x = math.Abs(x)
-			if x < 3.0 {
-				return sinc(x) * math.Cos((math.Pi/2.0)*(x/3.0))
-			}
-			return 0
-		},
-	}
-}

+ 258 - 0
resample.go → resize.go

@@ -319,3 +319,261 @@ func Thumbnail(img image.Image, width, height int, filter ResampleFilter) *image
 
 	return CropCenter(tmp, thumbW, thumbH)
 }
+
+// Resample filter struct. It can be used to make custom filters.
+//
+// Supported resample filters: NearestNeighbor, Box, Linear, Hermite, MitchellNetravali,
+// CatmullRom, BSpline, Gaussian, Lanczos, Hann, Hamming, Blackman, Bartlett, Welch, Cosine.
+//
+//	General filter recommendations:
+//
+//	- Lanczos
+//		Probably the best resampling filter for photographic images yielding sharp results,
+//		but it's slower than cubic filters (see below).
+//
+//	- CatmullRom
+//		A sharp cubic filter. It's a good filter for both upscaling and downscaling if sharp results are needed.
+//
+//	- MitchellNetravali
+//		A high quality cubic filter that produces smoother results with less ringing than CatmullRom.
+//
+//	- BSpline
+//		A good filter if a very smooth output is needed.
+//
+//	- Linear
+//		Bilinear interpolation filter, produces reasonably good, smooth output. It's faster than cubic filters.
+//
+//	- Box
+//		Simple and fast resampling filter appropriate for downscaling.
+//		When upscaling it's similar to NearestNeighbor.
+//
+//	- NearestNeighbor
+//		Fastest resample filter, no antialiasing at all. Rarely used.
+//
+type ResampleFilter struct {
+	Support float64
+	Kernel  func(float64) float64
+}
+
+// Nearest-neighbor filter, no anti-aliasing.
+var NearestNeighbor ResampleFilter
+
+// Box filter (averaging pixels).
+var Box ResampleFilter
+
+// Linear filter.
+var Linear ResampleFilter
+
+// Hermite cubic spline filter (BC-spline; B=0; C=0).
+var Hermite ResampleFilter
+
+// Mitchell-Netravali cubic filter (BC-spline; B=1/3; C=1/3).
+var MitchellNetravali ResampleFilter
+
+// Catmull-Rom - sharp cubic filter (BC-spline; B=0; C=0.5).
+var CatmullRom ResampleFilter
+
+// Cubic B-spline - smooth cubic filter (BC-spline; B=1; C=0).
+var BSpline ResampleFilter
+
+// Gaussian Blurring Filter.
+var Gaussian ResampleFilter
+
+// Bartlett-windowed sinc filter (3 lobes).
+var Bartlett ResampleFilter
+
+// Lanczos filter (3 lobes).
+var Lanczos ResampleFilter
+
+// Hann-windowed sinc filter (3 lobes).
+var Hann ResampleFilter
+
+// Hamming-windowed sinc filter (3 lobes).
+var Hamming ResampleFilter
+
+// Blackman-windowed sinc filter (3 lobes).
+var Blackman ResampleFilter
+
+// Welch-windowed sinc filter (parabolic window, 3 lobes).
+var Welch ResampleFilter
+
+// Cosine-windowed sinc filter (3 lobes).
+var Cosine ResampleFilter
+
+func bcspline(x, b, c float64) float64 {
+	x = math.Abs(x)
+	if x < 1.0 {
+		return ((12-9*b-6*c)*x*x*x + (-18+12*b+6*c)*x*x + (6 - 2*b)) / 6
+	}
+	if x < 2.0 {
+		return ((-b-6*c)*x*x*x + (6*b+30*c)*x*x + (-12*b-48*c)*x + (8*b + 24*c)) / 6
+	}
+	return 0
+}
+
+func sinc(x float64) float64 {
+	if x == 0 {
+		return 1
+	}
+	return math.Sin(math.Pi*x) / (math.Pi * x)
+}
+
+func init() {
+	NearestNeighbor = ResampleFilter{
+		Support: 0.0, // special case - not applying the filter
+	}
+
+	Box = ResampleFilter{
+		Support: 0.5,
+		Kernel: func(x float64) float64 {
+			x = math.Abs(x)
+			if x <= 0.5 {
+				return 1.0
+			}
+			return 0
+		},
+	}
+
+	Linear = ResampleFilter{
+		Support: 1.0,
+		Kernel: func(x float64) float64 {
+			x = math.Abs(x)
+			if x < 1.0 {
+				return 1.0 - x
+			}
+			return 0
+		},
+	}
+
+	Hermite = ResampleFilter{
+		Support: 1.0,
+		Kernel: func(x float64) float64 {
+			x = math.Abs(x)
+			if x < 1.0 {
+				return bcspline(x, 0.0, 0.0)
+			}
+			return 0
+		},
+	}
+
+	MitchellNetravali = ResampleFilter{
+		Support: 2.0,
+		Kernel: func(x float64) float64 {
+			x = math.Abs(x)
+			if x < 2.0 {
+				return bcspline(x, 1.0/3.0, 1.0/3.0)
+			}
+			return 0
+		},
+	}
+
+	CatmullRom = ResampleFilter{
+		Support: 2.0,
+		Kernel: func(x float64) float64 {
+			x = math.Abs(x)
+			if x < 2.0 {
+				return bcspline(x, 0.0, 0.5)
+			}
+			return 0
+		},
+	}
+
+	BSpline = ResampleFilter{
+		Support: 2.0,
+		Kernel: func(x float64) float64 {
+			x = math.Abs(x)
+			if x < 2.0 {
+				return bcspline(x, 1.0, 0.0)
+			}
+			return 0
+		},
+	}
+
+	Gaussian = ResampleFilter{
+		Support: 2.0,
+		Kernel: func(x float64) float64 {
+			x = math.Abs(x)
+			if x < 2.0 {
+				return math.Exp(-2 * x * x)
+			}
+			return 0
+		},
+	}
+
+	Bartlett = ResampleFilter{
+		Support: 3.0,
+		Kernel: func(x float64) float64 {
+			x = math.Abs(x)
+			if x < 3.0 {
+				return sinc(x) * (3.0 - x) / 3.0
+			}
+			return 0
+		},
+	}
+
+	Lanczos = ResampleFilter{
+		Support: 3.0,
+		Kernel: func(x float64) float64 {
+			x = math.Abs(x)
+			if x < 3.0 {
+				return sinc(x) * sinc(x/3.0)
+			}
+			return 0
+		},
+	}
+
+	Hann = ResampleFilter{
+		Support: 3.0,
+		Kernel: func(x float64) float64 {
+			x = math.Abs(x)
+			if x < 3.0 {
+				return sinc(x) * (0.5 + 0.5*math.Cos(math.Pi*x/3.0))
+			}
+			return 0
+		},
+	}
+
+	Hamming = ResampleFilter{
+		Support: 3.0,
+		Kernel: func(x float64) float64 {
+			x = math.Abs(x)
+			if x < 3.0 {
+				return sinc(x) * (0.54 + 0.46*math.Cos(math.Pi*x/3.0))
+			}
+			return 0
+		},
+	}
+
+	Blackman = ResampleFilter{
+		Support: 3.0,
+		Kernel: func(x float64) float64 {
+			x = math.Abs(x)
+			if x < 3.0 {
+				return sinc(x) * (0.42 - 0.5*math.Cos(math.Pi*x/3.0+math.Pi) + 0.08*math.Cos(2.0*math.Pi*x/3.0))
+			}
+			return 0
+		},
+	}
+
+	Welch = ResampleFilter{
+		Support: 3.0,
+		Kernel: func(x float64) float64 {
+			x = math.Abs(x)
+			if x < 3.0 {
+				return sinc(x) * (1.0 - (x * x / 9.0))
+			}
+			return 0
+		},
+	}
+
+	Cosine = ResampleFilter{
+		Support: 3.0,
+		Kernel: func(x float64) float64 {
+			x = math.Abs(x)
+			if x < 3.0 {
+				return sinc(x) * math.Cos((math.Pi/2.0)*(x/3.0))
+			}
+			return 0
+		},
+	}
+}

+ 0 - 46
sharpen.go

@@ -1,46 +0,0 @@
-package imaging
-
-import (
-	"image"
-)
-
-// Sharpen produces a sharpened version of the image.
-// Sigma parameter must be positive and indicates how much the image will be sharpened.
-//
-// Usage example:
-//
-//		dstImage := imaging.Sharpen(srcImage, 3.5)
-//
-func Sharpen(img image.Image, sigma float64) *image.NRGBA {
-	if sigma <= 0 {
-		// sigma parameter must be positive!
-		return Clone(img)
-	}
-
-	src := toNRGBA(img)
-	blurred := Blur(img, sigma)
-
-	width := src.Bounds().Max.X
-	height := src.Bounds().Max.Y
-	dst := image.NewNRGBA(image.Rect(0, 0, width, height))
-
-	parallel(height, func(partStart, partEnd int) {
-		for y := partStart; y < partEnd; y++ {
-			for x := 0; x < width; x++ {
-				i := y*src.Stride + x*4
-				for j := 0; j < 4; j++ {
-					k := i + j
-					val := int(src.Pix[k]) + (int(src.Pix[k]) - int(blurred.Pix[k]))
-					if val < 0 {
-						val = 0
-					} else if val > 255 {
-						val = 255
-					}
-					dst.Pix[k] = uint8(val)
-				}
-			}
-		}
-	})
-
-	return dst
-}

+ 139 - 0
tools.go

@@ -0,0 +1,139 @@
+package imaging
+
+import (
+	"image"
+	"math"
+)
+
+// Crop cuts out a rectangular region with the specified bounds
+// from the image and returns the cropped image.
+func Crop(img image.Image, rect image.Rectangle) *image.NRGBA {
+	src := toNRGBA(img)
+	srcRect := rect.Sub(img.Bounds().Min)
+	sub := src.SubImage(srcRect)
+	return Clone(sub) // New image Bounds().Min point will be (0, 0)
+}
+
+// CropCenter cuts out a rectangular region with the specified size
+// from the center of the image and returns the cropped image.
+func CropCenter(img image.Image, width, height int) *image.NRGBA {
+	cropW, cropH := width, height
+
+	srcBounds := img.Bounds()
+	srcW := srcBounds.Dx()
+	srcH := srcBounds.Dy()
+	srcMinX := srcBounds.Min.X
+	srcMinY := srcBounds.Min.Y
+
+	centerX := srcMinX + srcW/2
+	centerY := srcMinY + srcH/2
+
+	x0 := centerX - cropW/2
+	y0 := centerY - cropH/2
+	x1 := x0 + cropW
+	y1 := y0 + cropH
+
+	return Crop(img, image.Rect(x0, y0, x1, y1))
+}
+
+// Paste pastes the img image to the background image at the specified position and returns the combined image.
+func Paste(background, img image.Image, pos image.Point) *image.NRGBA {
+	src := toNRGBA(img)
+	dst := Clone(background)                    // cloned image bounds start at (0, 0)
+	startPt := pos.Sub(background.Bounds().Min) // so we should translate start point
+	endPt := startPt.Add(src.Bounds().Size())
+	pasteBounds := image.Rectangle{startPt, endPt}
+
+	if dst.Bounds().Overlaps(pasteBounds) {
+		intersectBounds := dst.Bounds().Intersect(pasteBounds)
+
+		rowSize := intersectBounds.Dx() * 4
+		numRows := intersectBounds.Dy()
+
+		srcStartX := intersectBounds.Min.X - pasteBounds.Min.X
+		srcStartY := intersectBounds.Min.Y - pasteBounds.Min.Y
+
+		i0 := dst.PixOffset(intersectBounds.Min.X, intersectBounds.Min.Y)
+		j0 := src.PixOffset(srcStartX, srcStartY)
+
+		di := dst.Stride
+		dj := src.Stride
+
+		for row := 0; row < numRows; row++ {
+			copy(dst.Pix[i0:i0+rowSize], src.Pix[j0:j0+rowSize])
+			i0 += di
+			j0 += dj
+		}
+	}
+
+	return dst
+}
+
+// PasteCenter pastes the img image to the center of the background image and returns the combined image.
+func PasteCenter(background, img image.Image) *image.NRGBA {
+	bgBounds := background.Bounds()
+	bgW := bgBounds.Dx()
+	bgH := bgBounds.Dy()
+	bgMinX := bgBounds.Min.X
+	bgMinY := bgBounds.Min.Y
+
+	centerX := bgMinX + bgW/2
+	centerY := bgMinY + bgH/2
+
+	x0 := centerX - img.Bounds().Dx()/2
+	y0 := centerY - img.Bounds().Dy()/2
+
+	return Paste(background, img, image.Pt(x0, y0))
+}
+
+// Overlay draws the img image over the background image at given position
+// and returns the combined image. Opacity parameter is the opacity of the img
+// image layer, used to compose the images, it must be from 0.0 to 1.0.
+//
+// Usage examples:
+//
+//		// draw the sprite over the background at position (50, 50)
+//		dstImage := imaging.Overlay(backgroundImage, spriteImage, image.Pt(50, 50), 1.0)
+//
+//		// blend two opaque images of the same size
+//		dstImage := imaging.Overlay(imageOne, imageTwo, image.Pt(0, 0), 0.5)
+//
+func Overlay(background, img image.Image, pos image.Point, opacity float64) *image.NRGBA {
+	opacity = math.Min(math.Max(opacity, 0.0), 1.0) // check: 0.0 <= opacity <= 1.0
+
+	src := toNRGBA(img)
+	dst := Clone(background)                    // cloned image bounds start at (0, 0)
+	startPt := pos.Sub(background.Bounds().Min) // so we should translate start point
+	endPt := startPt.Add(src.Bounds().Size())
+	pasteBounds := image.Rectangle{startPt, endPt}
+
+	if dst.Bounds().Overlaps(pasteBounds) {
+		intersectBounds := dst.Bounds().Intersect(pasteBounds)
+
+		for y := intersectBounds.Min.Y; y < intersectBounds.Max.Y; y++ {
+			for x := intersectBounds.Min.X; x < intersectBounds.Max.X; x++ {
+				i := y*dst.Stride + x*4
+
+				srcX := x - pasteBounds.Min.X
+				srcY := y - pasteBounds.Min.Y
+				j := srcY*src.Stride + srcX*4
+
+				a1 := float64(dst.Pix[i+3])
+				a2 := float64(src.Pix[j+3])
+
+				coef2 := opacity * a2 / 255.0
+				coef1 := (1 - coef2) * a1 / 255.0
+				coefSum := coef1 + coef2
+				coef1 /= coefSum
+				coef2 /= coefSum
+
+				dst.Pix[i+0] = uint8(float64(dst.Pix[i+0])*coef1 + float64(src.Pix[j+0])*coef2)
+				dst.Pix[i+1] = uint8(float64(dst.Pix[i+1])*coef1 + float64(src.Pix[j+1])*coef2)
+				dst.Pix[i+2] = uint8(float64(dst.Pix[i+2])*coef1 + float64(src.Pix[j+2])*coef2)
+				dst.Pix[i+3] = uint8(math.Min(a1+a2*opacity*(255.0-a1)/255.0, 255.0))
+			}
+		}
+	}
+
+	return dst
+}

+ 13 - 0
parallel.go → utils.go

@@ -1,6 +1,7 @@
 package imaging
 
 import (
+	"math"
 	"runtime"
 	"sync"
 	"sync/atomic"
@@ -52,3 +53,15 @@ func parallel(dataSize int, fn func(partStart, partEnd int)) {
 		wg.Wait()
 	}
 }
+
+func absint(i int) int {
+	if i < 0 {
+		return -i
+	}
+	return i
+}
+
+// clamp & round float64 to uint8 (0..255)
+func clamp(v float64) uint8 {
+	return uint8(math.Min(math.Max(v, 0.0), 255.0) + 0.5)
+}