Browse Source

Change image format to *image.NRGBA

disintegration 13 years ago
parent
commit
be5d32c03d
2 changed files with 274 additions and 67 deletions
  1. 2 3
      README.md
  2. 272 64
      imaging.go

+ 2 - 3
README.md

@@ -4,8 +4,7 @@ Package imaging provides basic image manipulation functions 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`.
 
 ###Installation
 
@@ -32,7 +31,7 @@ func main() {
     var dst draw.Image
     
     dst = imaging.New(800, 600, color.NRGBA(255, 0, 0, 255)) // create a new 800x600px image filled with red color
-    dst = imaging.Copy(src) // copy the image
+    dst = imaging.Clone(src) // make a copy of the image
     
     dst = imaging.Rotate90(src) // rotate 90 degrees clockwise 
     dst = imaging.Rotate180(src) // rotate 180 degrees clockwise

+ 272 - 64
imaging.go

@@ -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)