|
@@ -1,16 +1,14 @@
|
|
|
-// Package imaging provides basic image manipulation functions as well as
|
|
|
-// simplified image loading and saving.
|
|
|
+// Package imaging provides basic image manipulation functions
|
|
|
+// (resize, rotate, crop, etc.) as well as simplified image loading and saving.
|
|
|
//
|
|
|
// This package is based on the standard Go image package. All the image
|
|
|
-// manipulation functions provided by the package return a new draw.Image.
|
|
|
-// Currently, *image.RGBA type is used to build new images.
|
|
|
+// manipulation functions provided by the package return a new *image.NRGBA.
|
|
|
package imaging
|
|
|
|
|
|
import (
|
|
|
"fmt"
|
|
|
"image"
|
|
|
"image/color"
|
|
|
- "image/draw"
|
|
|
_ "image/gif"
|
|
|
"image/jpeg"
|
|
|
"image/png"
|
|
@@ -55,50 +53,231 @@ func Save(img image.Image, filename string, format string) (err error) {
|
|
|
}
|
|
|
|
|
|
// New creates a new image with the specified width and height, and fills it with the specified color.
|
|
|
-func New(width, height int, fillColor color.Color) draw.Image {
|
|
|
- dst := image.NewRGBA(image.Rect(0, 0, width, height))
|
|
|
- unf := image.NewUniform(fillColor)
|
|
|
- draw.Draw(dst, dst.Bounds(), unf, image.ZP, draw.Src)
|
|
|
- return dst
|
|
|
-}
|
|
|
+func New(width, height int, fillColor color.Color) *image.NRGBA {
|
|
|
+ dst := image.NewNRGBA(image.Rect(0, 0, width, height))
|
|
|
+ c := color.NRGBAModel.Convert(fillColor).(color.NRGBA)
|
|
|
+
|
|
|
+ i0 := dst.PixOffset(0, 0)
|
|
|
+ for y := 0; y < height; y, i0 = y+1, i0+dst.Stride {
|
|
|
+ for x, i := 0, i0; x < width; x, i = x+1, i+4 {
|
|
|
+ dst.Pix[i+0] = c.R
|
|
|
+ dst.Pix[i+1] = c.G
|
|
|
+ dst.Pix[i+2] = c.B
|
|
|
+ dst.Pix[i+3] = c.A
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
-// Copy returns a copy of the img. New image bounds will be (0, 0)-(width, height).
|
|
|
-func Copy(img image.Image) draw.Image {
|
|
|
- imgBounds := img.Bounds()
|
|
|
- newBounds := imgBounds.Sub(imgBounds.Min) // new image bounds start at (0, 0)
|
|
|
- dst := image.NewRGBA(newBounds)
|
|
|
- draw.Draw(dst, newBounds, img, imgBounds.Min, draw.Src)
|
|
|
return dst
|
|
|
}
|
|
|
|
|
|
-// This function is used internally to check if the image type is image.RGBA
|
|
|
-// If not - converts any image type to image.RGBA for faster pixel access
|
|
|
-func convertToRGBA(src image.Image) *image.RGBA {
|
|
|
- var dst *image.RGBA
|
|
|
+// This function converts any image type to *image.NRGBA for faster pixel access
|
|
|
+// Optimized for most standard image types: NRGBA64, RGBA, RGBA64, YCbCr, Gray, Gray16
|
|
|
+// If clone is true, the new image bounds will start at (0,0), also, a new copy
|
|
|
+// will be created even if the source image's type is already NRGBA
|
|
|
+func toNRGBA(src image.Image, clone bool) *image.NRGBA {
|
|
|
+ if !clone {
|
|
|
+ if src0, ok := src.(*image.NRGBA); ok {
|
|
|
+ return src0
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ srcBounds := src.Bounds()
|
|
|
+ dstBounds := srcBounds
|
|
|
+
|
|
|
+ // if we need a copy - translate Min point to (0, 0)
|
|
|
+ if clone {
|
|
|
+ dstBounds = dstBounds.Sub(dstBounds.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 := src.(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]
|
|
|
+
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- switch src.(type) {
|
|
|
case *image.RGBA:
|
|
|
- dst = src.(*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
|
|
|
+
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
default:
|
|
|
- b := src.Bounds()
|
|
|
- dst = image.NewRGBA(b) // converted image have the same bounds as a source
|
|
|
- draw.Draw(dst, dst.Bounds(), src, b.Min, draw.Src)
|
|
|
+ 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(src.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 is used internally to check if the image type is *image.NRGBA
|
|
|
+// If not - converts any image type to *image.NRGBA for faster pixel access
|
|
|
+func convertToNRGBA(img image.Image) *image.NRGBA {
|
|
|
+ // 'false' indicates that we don't need a new copy of img if it is already NRGBA
|
|
|
+ // and that the new image's bounds will be equal the bounds of the source image
|
|
|
+ return toNRGBA(img, false)
|
|
|
+}
|
|
|
+
|
|
|
+// Clone returns a copy of the img. New image bounds will be (0, 0)-(width, height).
|
|
|
+func Clone(img image.Image) *image.NRGBA {
|
|
|
+ // 'true' indicates that we need a new copy of img even if it is already NRGBA
|
|
|
+ // and that the new image's bounds will start at point (0, 0)
|
|
|
+ return toNRGBA(img, true)
|
|
|
+}
|
|
|
+
|
|
|
// 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) draw.Image {
|
|
|
- src := convertToRGBA(img)
|
|
|
+func Crop(img image.Image, rect image.Rectangle) *image.NRGBA {
|
|
|
+ src := convertToNRGBA(img)
|
|
|
sub := src.SubImage(rect)
|
|
|
- return Copy(sub) // New image Bounds().Min point will be (0, 0)
|
|
|
+ return Clone(sub) // New image Bounds().Min point will be (0, 0)
|
|
|
}
|
|
|
|
|
|
// Crop 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) draw.Image {
|
|
|
+func CropCenter(img image.Image, width, height int) *image.NRGBA {
|
|
|
cropW, cropH := width, height
|
|
|
|
|
|
srcBounds := img.Bounds()
|
|
@@ -119,16 +298,43 @@ func CropCenter(img image.Image, width, height int) draw.Image {
|
|
|
}
|
|
|
|
|
|
// Paste pastes the src image to the img image at the specified position and returns the combined image.
|
|
|
-func Paste(img, src image.Image, pos image.Point) draw.Image {
|
|
|
- dst := Copy(img) // copied image bounds start at (0, 0)
|
|
|
+func Paste(img, src image.Image, pos image.Point) *image.NRGBA {
|
|
|
+ srcBounds := src.Bounds()
|
|
|
+ src0 := convertToNRGBA(src)
|
|
|
+
|
|
|
+ dst := Clone(img) // cloned image bounds start at (0, 0)
|
|
|
startPt := pos.Sub(img.Bounds().Min) // so we should translate start point
|
|
|
- endPt := startPt.Add(src.Bounds().Size())
|
|
|
- draw.Draw(dst, image.Rectangle{startPt, endPt}, src, src.Bounds().Min, draw.Src)
|
|
|
+ endPt := startPt.Add(srcBounds.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 + srcBounds.Min.X
|
|
|
+ srcStartY := intersectBounds.Min.Y - pasteBounds.Min.Y + srcBounds.Min.Y
|
|
|
+
|
|
|
+ i0 := dst.PixOffset(intersectBounds.Min.X, intersectBounds.Min.Y)
|
|
|
+ j0 := src0.PixOffset(srcStartX, srcStartY)
|
|
|
+
|
|
|
+ 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
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
return dst
|
|
|
}
|
|
|
|
|
|
// Paste pastes the src image to the center of the img image and returns the combined image.
|
|
|
-func PasteCenter(img, src image.Image) draw.Image {
|
|
|
+func PasteCenter(img, src image.Image) *image.NRGBA {
|
|
|
imgBounds := img.Bounds()
|
|
|
imgW := imgBounds.Dx()
|
|
|
imgH := imgBounds.Dy()
|
|
@@ -145,15 +351,15 @@ func PasteCenter(img, src image.Image) draw.Image {
|
|
|
}
|
|
|
|
|
|
// Rotate90 rotates the image 90 degrees clockwise and returns the transformed image.
|
|
|
-func Rotate90(img image.Image) draw.Image {
|
|
|
- src := convertToRGBA(img)
|
|
|
+func Rotate90(img image.Image) *image.NRGBA {
|
|
|
+ src := convertToNRGBA(img)
|
|
|
srcBounds := src.Bounds()
|
|
|
srcMaxX := srcBounds.Max.X
|
|
|
srcMinY := srcBounds.Min.Y
|
|
|
|
|
|
dstW := srcBounds.Dy()
|
|
|
dstH := srcBounds.Dx()
|
|
|
- dst := image.NewRGBA(image.Rect(0, 0, dstW, dstH))
|
|
|
+ dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
|
|
|
|
|
|
for dstY := 0; dstY < dstH; dstY++ {
|
|
|
for dstX := 0; dstX < dstW; dstX++ {
|
|
@@ -175,15 +381,15 @@ func Rotate90(img image.Image) draw.Image {
|
|
|
}
|
|
|
|
|
|
// Rotate180 rotates the image 180 degrees clockwise and returns the transformed image.
|
|
|
-func Rotate180(img image.Image) draw.Image {
|
|
|
- src := convertToRGBA(img)
|
|
|
+func Rotate180(img image.Image) *image.NRGBA {
|
|
|
+ src := convertToNRGBA(img)
|
|
|
srcBounds := src.Bounds()
|
|
|
srcMaxX := srcBounds.Max.X
|
|
|
srcMaxY := srcBounds.Max.Y
|
|
|
|
|
|
dstW := srcBounds.Dx()
|
|
|
dstH := srcBounds.Dy()
|
|
|
- dst := image.NewRGBA(image.Rect(0, 0, dstW, dstH))
|
|
|
+ dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
|
|
|
|
|
|
for dstY := 0; dstY < dstH; dstY++ {
|
|
|
for dstX := 0; dstX < dstW; dstX++ {
|
|
@@ -205,15 +411,15 @@ func Rotate180(img image.Image) draw.Image {
|
|
|
}
|
|
|
|
|
|
// Rotate270 rotates the image 270 degrees clockwise and returns the transformed image.
|
|
|
-func Rotate270(img image.Image) draw.Image {
|
|
|
- src := convertToRGBA(img)
|
|
|
+func Rotate270(img image.Image) *image.NRGBA {
|
|
|
+ src := convertToNRGBA(img)
|
|
|
srcBounds := src.Bounds()
|
|
|
srcMaxY := srcBounds.Max.Y
|
|
|
srcMinX := srcBounds.Min.X
|
|
|
|
|
|
dstW := srcBounds.Dy()
|
|
|
dstH := srcBounds.Dx()
|
|
|
- dst := image.NewRGBA(image.Rect(0, 0, dstW, dstH))
|
|
|
+ dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
|
|
|
|
|
|
for dstY := 0; dstY < dstH; dstY++ {
|
|
|
for dstX := 0; dstX < dstW; dstX++ {
|
|
@@ -235,15 +441,15 @@ func Rotate270(img image.Image) draw.Image {
|
|
|
}
|
|
|
|
|
|
// FlipH flips the image horizontally (from left to right) and returns the transformed image.
|
|
|
-func FlipH(img image.Image) draw.Image {
|
|
|
- src := convertToRGBA(img)
|
|
|
+func FlipH(img image.Image) *image.NRGBA {
|
|
|
+ src := convertToNRGBA(img)
|
|
|
srcBounds := src.Bounds()
|
|
|
srcMaxX := srcBounds.Max.X
|
|
|
srcMinY := srcBounds.Min.Y
|
|
|
|
|
|
dstW := srcBounds.Dx()
|
|
|
dstH := srcBounds.Dy()
|
|
|
- dst := image.NewRGBA(image.Rect(0, 0, dstW, dstH))
|
|
|
+ dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
|
|
|
|
|
|
for dstY := 0; dstY < dstH; dstY++ {
|
|
|
for dstX := 0; dstX < dstW; dstX++ {
|
|
@@ -265,15 +471,15 @@ func FlipH(img image.Image) draw.Image {
|
|
|
}
|
|
|
|
|
|
// FlipV flips the image vertically (from top to bottom) and returns the transformed image.
|
|
|
-func FlipV(img image.Image) draw.Image {
|
|
|
- src := convertToRGBA(img)
|
|
|
+func FlipV(img image.Image) *image.NRGBA {
|
|
|
+ src := convertToNRGBA(img)
|
|
|
srcBounds := src.Bounds()
|
|
|
srcMaxY := srcBounds.Max.Y
|
|
|
srcMinX := srcBounds.Min.X
|
|
|
|
|
|
dstW := srcBounds.Dx()
|
|
|
dstH := srcBounds.Dy()
|
|
|
- dst := image.NewRGBA(image.Rect(0, 0, dstW, dstH))
|
|
|
+ dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
|
|
|
|
|
|
for dstY := 0; dstY < dstH; dstY++ {
|
|
|
for dstX := 0; dstX < dstW; dstX++ {
|
|
@@ -305,17 +511,17 @@ func antialiasFilter(x float64) float64 {
|
|
|
|
|
|
// Resize resizes the image to the specified width and height and returns the transformed image.
|
|
|
// If one of width or height is 0, the image aspect ratio is preserved.
|
|
|
-func Resize(img image.Image, width, height int) draw.Image {
|
|
|
+func Resize(img image.Image, width, height int) *image.NRGBA {
|
|
|
// Antialiased resize algorithm. The quality is good, especially at downsizing,
|
|
|
// but the speed is not too good, some optimisations are needed.
|
|
|
|
|
|
dstW, dstH := width, height
|
|
|
|
|
|
if dstW < 0 || dstH < 0 {
|
|
|
- return &image.RGBA{}
|
|
|
+ return &image.NRGBA{}
|
|
|
}
|
|
|
if dstW == 0 && dstH == 0 {
|
|
|
- return &image.RGBA{}
|
|
|
+ return &image.NRGBA{}
|
|
|
}
|
|
|
|
|
|
srcBounds := img.Bounds()
|
|
@@ -327,7 +533,7 @@ func Resize(img image.Image, width, height int) draw.Image {
|
|
|
srcMaxY := srcBounds.Max.Y
|
|
|
|
|
|
if srcW <= 0 || srcH <= 0 {
|
|
|
- return &image.RGBA{}
|
|
|
+ return &image.NRGBA{}
|
|
|
}
|
|
|
|
|
|
// if new width or height is 0 then preserve aspect ratio, minimum 1px
|
|
@@ -340,8 +546,8 @@ func Resize(img image.Image, width, height int) draw.Image {
|
|
|
dstH = int(math.Max(1.0, math.Floor(tmpH+0.5)))
|
|
|
}
|
|
|
|
|
|
- src := convertToRGBA(img)
|
|
|
- dst := image.NewRGBA(image.Rect(0, 0, dstW, dstH))
|
|
|
+ src := convertToNRGBA(img)
|
|
|
+ dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH))
|
|
|
|
|
|
dy := float64(srcH) / float64(dstH)
|
|
|
dx := float64(srcW) / float64(dstW)
|
|
@@ -388,7 +594,7 @@ func Resize(img image.Image, width, height int) draw.Image {
|
|
|
var k, sumk, r, g, b, a float64
|
|
|
var i int
|
|
|
|
|
|
- // calculate combined rgba values for each column according to weights
|
|
|
+ // Calculate combined RGBA values for each column according to weights:
|
|
|
for x := startX; x <= endX; x++ {
|
|
|
|
|
|
r, g, b, a, sumk = 0.0, 0.0, 0.0, 0.0, 0.0
|
|
@@ -407,6 +613,7 @@ func Resize(img image.Image, width, height int) draw.Image {
|
|
|
xvals[i+1] = g / sumk
|
|
|
xvals[i+2] = b / sumk
|
|
|
xvals[i+3] = a / sumk
|
|
|
+
|
|
|
}
|
|
|
|
|
|
// calculate final rgba values
|
|
@@ -415,6 +622,7 @@ func Resize(img image.Image, width, height int) draw.Image {
|
|
|
k = antialiasFilter((fx - float64(x)) / radiusX)
|
|
|
sumk += k
|
|
|
i = (x - startX) * 4
|
|
|
+
|
|
|
r += xvals[i+0] * k
|
|
|
g += xvals[i+1] * k
|
|
|
b += xvals[i+2] * k
|
|
@@ -437,11 +645,11 @@ func Resize(img image.Image, width, height int) draw.Image {
|
|
|
}
|
|
|
|
|
|
// Fit scales down the image to fit the specified maximum width and height and returns the transformed image.
|
|
|
-func Fit(img image.Image, width, height int) draw.Image {
|
|
|
+func Fit(img image.Image, width, height int) *image.NRGBA {
|
|
|
maxW, maxH := width, height
|
|
|
|
|
|
if maxW <= 0 || maxH <= 0 {
|
|
|
- return &image.RGBA{}
|
|
|
+ return &image.NRGBA{}
|
|
|
}
|
|
|
|
|
|
srcBounds := img.Bounds()
|
|
@@ -449,11 +657,11 @@ func Fit(img image.Image, width, height int) draw.Image {
|
|
|
srcH := srcBounds.Dy()
|
|
|
|
|
|
if srcW <= 0 || srcH <= 0 {
|
|
|
- return &image.RGBA{}
|
|
|
+ return &image.NRGBA{}
|
|
|
}
|
|
|
|
|
|
if srcW <= maxW && srcH <= maxH {
|
|
|
- return Copy(img)
|
|
|
+ return Clone(img)
|
|
|
}
|
|
|
|
|
|
srcAspectRatio := float64(srcW) / float64(srcH)
|
|
@@ -472,11 +680,11 @@ func Fit(img image.Image, width, height int) draw.Image {
|
|
|
}
|
|
|
|
|
|
// Thumbnail scales the image up or down, crops it to the specified size and returns the transformed image.
|
|
|
-func Thumbnail(img image.Image, width, height int) draw.Image {
|
|
|
+func Thumbnail(img image.Image, width, height int) *image.NRGBA {
|
|
|
thumbW, thumbH := width, height
|
|
|
|
|
|
if thumbW <= 0 || thumbH <= 0 {
|
|
|
- return &image.RGBA{}
|
|
|
+ return &image.NRGBA{}
|
|
|
}
|
|
|
|
|
|
srcBounds := img.Bounds()
|
|
@@ -484,7 +692,7 @@ func Thumbnail(img image.Image, width, height int) draw.Image {
|
|
|
srcH := srcBounds.Dy()
|
|
|
|
|
|
if srcW <= 0 || srcH <= 0 {
|
|
|
- return &image.RGBA{}
|
|
|
+ return &image.NRGBA{}
|
|
|
}
|
|
|
|
|
|
srcAspectRatio := float64(srcW) / float64(srcH)
|