From ee0a6d3efb7d7b05d6a5c506980e2957b69de87b Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Sun, 3 Jun 2018 14:33:40 -0600 Subject: [PATCH] feat: updated to disintegration/imaging 1.4.2 --- Gopkg.lock | 6 +- Gopkg.toml | 2 +- .../disintegration/imaging/.travis.yml | 18 +- .../github.com/disintegration/imaging/LICENSE | 2 +- .../disintegration/imaging/README.md | 206 ++++----- .../disintegration/imaging/adjust.go | 256 +++++----- .../disintegration/imaging/convolution.go | 146 ++++++ .../github.com/disintegration/imaging/doc.go | 7 + .../disintegration/imaging/effects.go | 192 ++++---- .../disintegration/imaging/helpers.go | 436 +++++++----------- .../disintegration/imaging/histogram.go | 48 +- .../disintegration/imaging/resize.go | 321 +++++++------ .../disintegration/imaging/scanner.go | 250 ++++++++++ .../disintegration/imaging/tools.go | 138 +++--- .../disintegration/imaging/transform.go | 356 ++++++++------ .../disintegration/imaging/utils.go | 110 ++--- 16 files changed, 1417 insertions(+), 1077 deletions(-) create mode 100644 vendor/github.com/disintegration/imaging/convolution.go create mode 100644 vendor/github.com/disintegration/imaging/doc.go create mode 100644 vendor/github.com/disintegration/imaging/scanner.go diff --git a/Gopkg.lock b/Gopkg.lock index 5c036bd..a9523c0 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -15,8 +15,8 @@ [[projects]] name = "github.com/disintegration/imaging" packages = ["."] - revision = "243d2d8673c1225a6afceeb9b3b4423d485dc8df" - version = "v1.0.0" + revision = "bbcee2f5c9d5e94ca42c8b50ec847fec64a6c134" + version = "v1.4.2" [[projects]] name = "github.com/golang/protobuf" @@ -226,6 +226,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "e0aa6d212222225180441c74d86bafb0dfd9f958d4ec3c6a4ee5fc716c8db172" + inputs-digest = "0711e13ad5ccfeef99e9a0558836476ff6d001fceb05fe69a570ccbc41840aa8" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index bf70cd0..ab1d578 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -27,7 +27,7 @@ [[constraint]] name = "github.com/disintegration/imaging" - version = "1.0.0" + version = "1.4.2" [[constraint]] name = "github.com/rs/cors" diff --git a/vendor/github.com/disintegration/imaging/.travis.yml b/vendor/github.com/disintegration/imaging/.travis.yml index 0e214c0..89370ed 100644 --- a/vendor/github.com/disintegration/imaging/.travis.yml +++ b/vendor/github.com/disintegration/imaging/.travis.yml @@ -1,19 +1,13 @@ language: go - -sudo: false - go: - - 1.2 - - 1.3 - - 1.4 - - 1.5 - - 1.6 - - 1.7 + - "1.7.x" + - "1.8.x" + - "1.9.x" + - "1.10.x" before_install: - - go get golang.org/x/tools/cmd/cover - go get github.com/mattn/goveralls script: - - go test -v -covermode=count -coverprofile=coverage.out - - $HOME/gopath/bin/goveralls -service=travis-ci -coverprofile=coverage.out + - go test -v -race -cover + - $GOPATH/bin/goveralls -service=travis-ci diff --git a/vendor/github.com/disintegration/imaging/LICENSE b/vendor/github.com/disintegration/imaging/LICENSE index 95ae410..c68f7ab 100644 --- a/vendor/github.com/disintegration/imaging/LICENSE +++ b/vendor/github.com/disintegration/imaging/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2012-2014 Grigory Dryapak +Copyright (c) 2012-2018 Grigory Dryapak Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/vendor/github.com/disintegration/imaging/README.md b/vendor/github.com/disintegration/imaging/README.md index 3dcea20..c7ee30f 100644 --- a/vendor/github.com/disintegration/imaging/README.md +++ b/vendor/github.com/disintegration/imaging/README.md @@ -2,19 +2,16 @@ [![GoDoc](https://godoc.org/github.com/disintegration/imaging?status.svg)](https://godoc.org/github.com/disintegration/imaging) [![Build Status](https://travis-ci.org/disintegration/imaging.svg?branch=master)](https://travis-ci.org/disintegration/imaging) -[![Coverage Status](https://coveralls.io/repos/github/disintegration/imaging/badge.svg?branch=master)](https://coveralls.io/github/disintegration/imaging?branch=master) +[![Coverage Status](https://coveralls.io/repos/github/disintegration/imaging/badge.svg?branch=master&service=github)](https://coveralls.io/github/disintegration/imaging?branch=master) +[![Go Report Card](https://goreportcard.com/badge/github.com/disintegration/imaging)](https://goreportcard.com/report/github.com/disintegration/imaging) -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. +Package imaging provides basic image processing functions (resize, rotate, crop, brightness/contrast adjustments, etc.). -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). +All the image processing functions provided by the package accept 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). ## Installation -Imaging requires Go version 1.2 or greater. - go get -u github.com/disintegration/imaging ## Documentation @@ -26,17 +23,18 @@ http://godoc.org/github.com/disintegration/imaging A few usage examples can be found below. See the documentation for the full list of supported functions. ### Image resizing + ```go -// resize srcImage to size = 128x128px using the Lanczos filter +// Resize srcImage to size = 128x128px using the Lanczos filter. dstImage128 := imaging.Resize(srcImage, 128, 128, imaging.Lanczos) -// resize srcImage to width = 800px preserving the aspect ratio +// Resize srcImage to width = 800px preserving the aspect ratio. dstImage800 := imaging.Resize(srcImage, 800, 0, imaging.Lanczos) -// scale down srcImage to fit the 800x600px bounding box +// Scale down srcImage to fit the 800x600px bounding box. dstImageFit := imaging.Fit(srcImage, 800, 600, imaging.Lanczos) -// resize and crop the srcImage to fill the 100x100px area +// Resize and crop the srcImage to fill the 100x100px area. dstImageFill := imaging.Fill(srcImage, 100, 100, imaging.Center, imaging.Lanczos) ``` @@ -45,154 +43,146 @@ Imaging supports image resizing using various resampling filters. The most notab - `Box` - Simple and fast averaging filter appropriate for downscaling. When upscaling it's similar to NearestNeighbor. - `Linear` - Bilinear filter, smooth and reasonably fast. - `MitchellNetravali` - А smooth bicubic filter. -- `CatmullRom` - A sharp bicubic filter. +- `CatmullRom` - A sharp bicubic filter. - `Gaussian` - Blurring filter that uses gaussian function, useful for noise removal. -- `Lanczos` - High-quality resampling filter for photographic images yielding sharp results, but it's slower than cubic filters. +- `Lanczos` - High-quality resampling filter for photographic images yielding sharp results, slower than cubic filters. The full list of supported filters: NearestNeighbor, Box, Linear, Hermite, MitchellNetravali, CatmullRom, BSpline, Gaussian, Lanczos, Hann, Hamming, Blackman, Bartlett, Welch, Cosine. Custom filters can be created using ResampleFilter struct. **Resampling filters comparison** -Original image. Will be resized from 512x512px to 128x128px. - -![srcImage](http://disintegration.github.io/imaging/in_lena_bw_512.png) - -Filter | Resize result ----|--- -`imaging.NearestNeighbor` | ![dstImage](http://disintegration.github.io/imaging/out_resize_down_nearest.png) -`imaging.Box` | ![dstImage](http://disintegration.github.io/imaging/out_resize_down_box.png) -`imaging.Linear` | ![dstImage](http://disintegration.github.io/imaging/out_resize_down_linear.png) -`imaging.MitchellNetravali` | ![dstImage](http://disintegration.github.io/imaging/out_resize_down_mitchell.png) -`imaging.CatmullRom` | ![dstImage](http://disintegration.github.io/imaging/out_resize_down_catrom.png) -`imaging.Gaussian` | ![dstImage](http://disintegration.github.io/imaging/out_resize_down_gaussian.png) -`imaging.Lanczos` | ![dstImage](http://disintegration.github.io/imaging/out_resize_down_lanczos.png) - -**Resize functions comparison** - Original image: -![srcImage](http://disintegration.github.io/imaging/in.jpg) - -Resize the image to width=100px and height=100px: - -```go -dstImage := imaging.Resize(srcImage, 100, 100, imaging.Lanczos) -``` -![dstImage](http://disintegration.github.io/imaging/out-comp-resize.jpg) - -Resize the image to width=100px preserving the aspect ratio: - -```go -dstImage := imaging.Resize(srcImage, 100, 0, imaging.Lanczos) -``` -![dstImage](http://disintegration.github.io/imaging/out-comp-fit.jpg) - -Resize the image to fit the 100x100px boundng box preserving the aspect ratio: +![srcImage](testdata/branches.png) -```go -dstImage := imaging.Fit(srcImage, 100, 100, imaging.Lanczos) -``` -![dstImage](http://disintegration.github.io/imaging/out-comp-fit.jpg) +The same image resized from 600x400px to 150x100px using different resampling filters. +From faster (lower quality) to slower (higher quality): -Resize and crop the image with a center anchor point to fill the 100x100px area: +Filter | Resize result +--------------------------|--------------------------------------------- +`imaging.NearestNeighbor` | ![dstImage](testdata/out_resize_nearest.png) +`imaging.Linear` | ![dstImage](testdata/out_resize_linear.png) +`imaging.CatmullRom` | ![dstImage](testdata/out_resize_catrom.png) +`imaging.Lanczos` | ![dstImage](testdata/out_resize_lanczos.png) -```go -dstImage := imaging.Fill(srcImage, 100, 100, imaging.Center, imaging.Lanczos) -``` -![dstImage](http://disintegration.github.io/imaging/out-comp-fill.jpg) ### Gaussian Blur + ```go dstImage := imaging.Blur(srcImage, 0.5) ``` Sigma parameter allows to control the strength of the blurring effect. -Original image | Sigma = 0.5 | Sigma = 1.5 ----|---|--- -![srcImage](http://disintegration.github.io/imaging/in_lena_bw_128.png) | ![dstImage](http://disintegration.github.io/imaging/out_blur_0.5.png) | ![dstImage](http://disintegration.github.io/imaging/out_blur_1.5.png) +Original image | Sigma = 0.5 | Sigma = 1.5 +-----------------------------------|----------------------------------------|--------------------------------------- +![srcImage](testdata/flowers_small.png) | ![dstImage](testdata/out_blur_0.5.png) | ![dstImage](testdata/out_blur_1.5.png) ### Sharpening + ```go dstImage := imaging.Sharpen(srcImage, 0.5) ``` -Uses gaussian function internally. Sigma parameter allows to control the strength of the sharpening effect. +`Sharpen` uses gaussian function internally. Sigma parameter allows to control the strength of the sharpening effect. -Original image | Sigma = 0.5 | Sigma = 1.5 ----|---|--- -![srcImage](http://disintegration.github.io/imaging/in_lena_bw_128.png) | ![dstImage](http://disintegration.github.io/imaging/out_sharpen_0.5.png) | ![dstImage](http://disintegration.github.io/imaging/out_sharpen_1.5.png) +Original image | Sigma = 0.5 | Sigma = 1.5 +-----------------------------------|-------------------------------------------|------------------------------------------ +![srcImage](testdata/flowers_small.png) | ![dstImage](testdata/out_sharpen_0.5.png) | ![dstImage](testdata/out_sharpen_1.5.png) ### Gamma correction + ```go dstImage := imaging.AdjustGamma(srcImage, 0.75) ``` -Original image | Gamma = 0.75 | Gamma = 1.25 ----|---|--- -![srcImage](http://disintegration.github.io/imaging/in_lena_bw_128.png) | ![dstImage](http://disintegration.github.io/imaging/out_gamma_0.75.png) | ![dstImage](http://disintegration.github.io/imaging/out_gamma_1.25.png) +Original image | Gamma = 0.75 | Gamma = 1.25 +-----------------------------------|------------------------------------------|----------------------------------------- +![srcImage](testdata/flowers_small.png) | ![dstImage](testdata/out_gamma_0.75.png) | ![dstImage](testdata/out_gamma_1.25.png) ### Contrast adjustment + ```go dstImage := imaging.AdjustContrast(srcImage, 20) ``` -Original image | Contrast = 20 | Contrast = -20 ----|---|--- -![srcImage](http://disintegration.github.io/imaging/in_lena_bw_128.png) | ![dstImage](http://disintegration.github.io/imaging/out_contrast_p20.png) | ![dstImage](http://disintegration.github.io/imaging/out_contrast_m20.png) +Original image | Contrast = 15 | Contrast = -15 +-----------------------------------|--------------------------------------------|------------------------------------------- +![srcImage](testdata/flowers_small.png) | ![dstImage](testdata/out_contrast_p15.png) | ![dstImage](testdata/out_contrast_m15.png) ### Brightness adjustment + ```go dstImage := imaging.AdjustBrightness(srcImage, 20) ``` -Original image | Brightness = 20 | Brightness = -20 ----|---|--- -![srcImage](http://disintegration.github.io/imaging/in_lena_bw_128.png) | ![dstImage](http://disintegration.github.io/imaging/out_brightness_p20.png) | ![dstImage](http://disintegration.github.io/imaging/out_brightness_m20.png) - +Original image | Brightness = 10 | Brightness = -10 +-----------------------------------|----------------------------------------------|--------------------------------------------- +![srcImage](testdata/flowers_small.png) | ![dstImage](testdata/out_brightness_p10.png) | ![dstImage](testdata/out_brightness_m10.png) -### Complete code example -Here is the code example that loads several images, makes thumbnails of them -and combines them together side-by-side. +## Example code ```go package main import ( - "image" - "image/color" - - "github.com/disintegration/imaging" + "image" + "image/color" + "log" + + "github.com/disintegration/imaging" ) func main() { - - // 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) - } + // Open a test image. + src, err := imaging.Open("testdata/flowers.png") + if err != nil { + log.Fatalf("failed to open image: %v", err) + } + + // Crop the original image to 300x300px size using the center anchor. + src = imaging.CropAnchor(src, 300, 300, imaging.Center) + + // Resize the cropped image to width = 200px preserving the aspect ratio. + src = imaging.Resize(src, 200, 0, imaging.Lanczos) + + // Create a blurred version of the image. + img1 := imaging.Blur(src, 5) + + // Create a grayscale version of the image with higher contrast and sharpness. + img2 := imaging.Grayscale(src) + img2 = imaging.AdjustContrast(img2, 20) + img2 = imaging.Sharpen(img2, 2) + + // Create an inverted version of the image. + img3 := imaging.Invert(src) + + // Create an embossed version of the image using a convolution filter. + img4 := imaging.Convolve3x3( + src, + [9]float64{ + -1, -1, 0, + -1, 1, 1, + 0, 1, 1, + }, + nil, + ) + + // Create a new image and paste the four produced images into it. + dst := imaging.New(400, 400, color.NRGBA{0, 0, 0, 0}) + dst = imaging.Paste(dst, img1, image.Pt(0, 0)) + dst = imaging.Paste(dst, img2, image.Pt(0, 200)) + dst = imaging.Paste(dst, img3, image.Pt(200, 0)) + dst = imaging.Paste(dst, img4, image.Pt(200, 200)) + + // Save the resulting image as JPEG. + err = imaging.Save(dst, "testdata/out_example.jpg") + if err != nil { + log.Fatalf("failed to save image: %v", err) + } } ``` + +Output: + +![dstImage](testdata/out_example.jpg) \ No newline at end of file diff --git a/vendor/github.com/disintegration/imaging/adjust.go b/vendor/github.com/disintegration/imaging/adjust.go index 9b1b83a..fb3a9ce 100644 --- a/vendor/github.com/disintegration/imaging/adjust.go +++ b/vendor/github.com/disintegration/imaging/adjust.go @@ -6,50 +6,95 @@ import ( "math" ) -// AdjustFunc applies the fn function to each pixel of the img image and returns the adjusted image. +// Grayscale produces a grayscale version of the image. +func Grayscale(img image.Image) *image.NRGBA { + src := newScanner(img) + dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h)) + 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++ { + r := dst.Pix[i+0] + g := dst.Pix[i+1] + b := dst.Pix[i+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 + i += 4 + } + } + }) + return dst +} + +// Invert produces an inverted (negated) version of the image. +func Invert(img image.Image) *image.NRGBA { + src := newScanner(img) + dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h)) + 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] = 255 - dst.Pix[i+0] + dst.Pix[i+1] = 255 - dst.Pix[i+1] + dst.Pix[i+2] = 255 - dst.Pix[i+2] + i += 4 + } + } + }) + return dst +} + +// AdjustContrast changes the contrast of the image using the percentage parameter and returns the adjusted image. +// The percentage must be in range (-100, 100). The percentage = 0 gives the original image. +// The percentage = -100 gives solid gray image. // -// Example: +// Examples: // -// 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} -// } -// ) +// dstImage = imaging.AdjustContrast(srcImage, -10) // decrease image contrast by 10% +// dstImage = imaging.AdjustContrast(srcImage, 20) // increase image contrast by 20% // -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 - 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 - j := y*dst.Stride + x*4 - - r := src.Pix[i+0] - g := src.Pix[i+1] - b := src.Pix[i+2] - a := src.Pix[i+3] - - c := fn(color.NRGBA{r, g, b, a}) +func AdjustContrast(img image.Image, percentage float64) *image.NRGBA { + percentage = math.Min(math.Max(percentage, -100.0), 100.0) + lut := make([]uint8, 256) - dst.Pix[j+0] = c.R - dst.Pix[j+1] = c.G - dst.Pix[j+2] = c.B - dst.Pix[j+3] = c.A - } + v := (100.0 + percentage) / 100.0 + for i := 0; i < 256; i++ { + if 0 <= v && v <= 1 { + lut[i] = clamp((0.5 + (float64(i)/255.0-0.5)*v) * 255.0) + } else if 1 < v && v < 2 { + lut[i] = clamp((0.5 + (float64(i)/255.0-0.5)*(1/(2.0-v))) * 255.0) + } else { + lut[i] = uint8(float64(i)/255.0+0.5) * 255 } - }) + } - return dst + return adjustLUT(img, lut) +} + +// AdjustBrightness changes the brightness of the image using the percentage parameter and returns the adjusted image. +// The percentage must be in range (-100, 100). The percentage = 0 gives the original image. +// The percentage = -100 gives solid black image. The percentage = 100 gives solid white image. +// +// Examples: +// +// dstImage = imaging.AdjustBrightness(srcImage, -15) // decrease image brightness by 15% +// dstImage = imaging.AdjustBrightness(srcImage, 10) // increase image brightness by 10% +// +func AdjustBrightness(img image.Image, percentage float64) *image.NRGBA { + percentage = math.Min(math.Max(percentage, -100.0), 100.0) + lut := make([]uint8, 256) + + shift := 255.0 * percentage / 100.0 + for i := 0; i < 256; i++ { + lut[i] = clamp(float64(i) + shift) + } + + return adjustLUT(img, lut) } // AdjustGamma performs a gamma correction on the image and returns the adjusted image. @@ -68,15 +113,7 @@ func AdjustGamma(img image.Image, gamma float64) *image.NRGBA { lut[i] = clamp(math.Pow(float64(i)/255.0, e) * 255.0) } - fn := func(c color.NRGBA) color.NRGBA { - return color.NRGBA{lut[c.R], lut[c.G], lut[c.B], c.A} - } - - return AdjustFunc(img, fn) -} - -func sigmoid(a, b, x float64) float64 { - return 1 / (1 + math.Exp(b*(a-x))) + return adjustLUT(img, lut) } // AdjustSigmoid changes the contrast of the image using a sigmoidal function and returns the adjusted image. @@ -118,83 +155,68 @@ func AdjustSigmoid(img image.Image, midpoint, factor float64) *image.NRGBA { } } - fn := func(c color.NRGBA) color.NRGBA { - return color.NRGBA{lut[c.R], lut[c.G], lut[c.B], c.A} - } - - return AdjustFunc(img, fn) + return adjustLUT(img, lut) } -// AdjustContrast changes the contrast of the image using the percentage parameter and returns the adjusted image. -// The percentage must be in range (-100, 100). The percentage = 0 gives the original image. -// The percentage = -100 gives solid grey image. -// -// Examples: -// -// dstImage = imaging.AdjustContrast(srcImage, -10) // decrease image contrast by 10% -// dstImage = imaging.AdjustContrast(srcImage, 20) // increase image contrast by 20% -// -func AdjustContrast(img image.Image, percentage float64) *image.NRGBA { - percentage = math.Min(math.Max(percentage, -100.0), 100.0) - lut := make([]uint8, 256) +func sigmoid(a, b, x float64) float64 { + return 1 / (1 + math.Exp(b*(a-x))) +} - v := (100.0 + percentage) / 100.0 - for i := 0; i < 256; i++ { - if 0 <= v && v <= 1 { - lut[i] = clamp((0.5 + (float64(i)/255.0-0.5)*v) * 255.0) - } else if 1 < v && v < 2 { - lut[i] = clamp((0.5 + (float64(i)/255.0-0.5)*(1/(2.0-v))) * 255.0) - } else { - lut[i] = uint8(float64(i)/255.0+0.5) * 255 +// adjustLUT applies the given lookup table to the colors of the image. +func adjustLUT(img image.Image, lut []uint8) *image.NRGBA { + src := newScanner(img) + dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h)) + 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]] + i += 4 + } } - } - - fn := func(c color.NRGBA) color.NRGBA { - return color.NRGBA{lut[c.R], lut[c.G], lut[c.B], c.A} - } - - return AdjustFunc(img, fn) + }) + return dst } -// AdjustBrightness changes the brightness of the image using the percentage parameter and returns the adjusted image. -// The percentage must be in range (-100, 100). The percentage = 0 gives the original image. -// The percentage = -100 gives solid black image. The percentage = 100 gives solid white image. +// AdjustFunc applies the fn function to each pixel of the img image and returns the adjusted image. // -// Examples: +// Example: // -// dstImage = imaging.AdjustBrightness(srcImage, -15) // decrease image brightness by 15% -// dstImage = imaging.AdjustBrightness(srcImage, 10) // increase image brightness by 10% +// 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 AdjustBrightness(img image.Image, percentage float64) *image.NRGBA { - percentage = math.Min(math.Max(percentage, -100.0), 100.0) - lut := make([]uint8, 256) - - shift := 255.0 * percentage / 100.0 - for i := 0; i < 256; i++ { - lut[i] = clamp(float64(i) + shift) - } - - fn := func(c color.NRGBA) color.NRGBA { - return color.NRGBA{lut[c.R], lut[c.G], lut[c.B], c.A} - } - - return AdjustFunc(img, fn) -} - -// Grayscale produces grayscale version of the image. -func Grayscale(img image.Image) *image.NRGBA { - fn := func(c color.NRGBA) color.NRGBA { - f := 0.299*float64(c.R) + 0.587*float64(c.G) + 0.114*float64(c.B) - y := uint8(f + 0.5) - return color.NRGBA{y, y, y, c.A} - } - return AdjustFunc(img, fn) -} - -// Invert produces inverted (negated) version of the image. -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 AdjustFunc(img, fn) +func AdjustFunc(img image.Image, fn func(c color.NRGBA) color.NRGBA) *image.NRGBA { + src := newScanner(img) + dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h)) + 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++ { + r := dst.Pix[i+0] + g := dst.Pix[i+1] + b := dst.Pix[i+2] + a := dst.Pix[i+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 + i += 4 + } + } + }) + return dst } diff --git a/vendor/github.com/disintegration/imaging/convolution.go b/vendor/github.com/disintegration/imaging/convolution.go new file mode 100644 index 0000000..9e6404d --- /dev/null +++ b/vendor/github.com/disintegration/imaging/convolution.go @@ -0,0 +1,146 @@ +package imaging + +import ( + "image" +) + +// ConvolveOptions are convolution parameters. +type ConvolveOptions struct { + // If Normalize is true the kernel is normalized before convolution. + Normalize bool + + // If Abs is true the absolute value of each color channel is taken after convolution. + Abs bool + + // Bias is added to each color channel value after convolution. + Bias int +} + +// Convolve3x3 convolves the image with the specified 3x3 convolution kernel. +// Default parameters are used if a nil *ConvolveOptions is passed. +func Convolve3x3(img image.Image, kernel [9]float64, options *ConvolveOptions) *image.NRGBA { + return convolve(img, kernel[:], options) +} + +// Convolve5x5 convolves the image with the specified 5x5 convolution kernel. +// Default parameters are used if a nil *ConvolveOptions is passed. +func Convolve5x5(img image.Image, kernel [25]float64, options *ConvolveOptions) *image.NRGBA { + return convolve(img, kernel[:], options) +} + +func convolve(img image.Image, kernel []float64, options *ConvolveOptions) *image.NRGBA { + src := toNRGBA(img) + w := src.Bounds().Max.X + h := src.Bounds().Max.Y + dst := image.NewNRGBA(image.Rect(0, 0, w, h)) + + if w < 1 || h < 1 { + return dst + } + + if options == nil { + options = &ConvolveOptions{} + } + + if options.Normalize { + normalizeKernel(kernel) + } + + type coef struct { + x, y int + k float64 + } + var coefs []coef + var m int + + switch len(kernel) { + case 9: + m = 1 + case 25: + m = 2 + } + + i := 0 + for y := -m; y <= m; y++ { + for x := -m; x <= m; x++ { + if kernel[i] != 0 { + coefs = append(coefs, coef{x: x, y: y, k: kernel[i]}) + } + i++ + } + } + + parallel(0, h, func(ys <-chan int) { + for y := range ys { + for x := 0; x < w; x++ { + var r, g, b float64 + for _, c := range coefs { + ix := x + c.x + if ix < 0 { + ix = 0 + } else if ix >= w { + ix = w - 1 + } + + iy := y + c.y + if iy < 0 { + iy = 0 + } else if iy >= h { + iy = h - 1 + } + + 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 + } + + if options.Abs { + if r < 0 { + r = -r + } + if g < 0 { + g = -g + } + if b < 0 { + b = -b + } + } + + if options.Bias != 0 { + r += float64(options.Bias) + g += float64(options.Bias) + b += float64(options.Bias) + } + + 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] + } + } + }) + + return dst +} + +func normalizeKernel(kernel []float64) { + var sum, sumpos float64 + for i := range kernel { + sum += kernel[i] + if kernel[i] > 0 { + sumpos += kernel[i] + } + } + if sum != 0 { + for i := range kernel { + kernel[i] /= sum + } + } else if sumpos != 0 { + for i := range kernel { + kernel[i] /= sumpos + } + } +} diff --git a/vendor/github.com/disintegration/imaging/doc.go b/vendor/github.com/disintegration/imaging/doc.go new file mode 100644 index 0000000..5d59b46 --- /dev/null +++ b/vendor/github.com/disintegration/imaging/doc.go @@ -0,0 +1,7 @@ +/* +Package imaging provides basic image processing functions (resize, rotate, crop, brightness/contrast adjustments, etc.). + +All the image processing functions provided by the package accept 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). +*/ +package imaging diff --git a/vendor/github.com/disintegration/imaging/effects.go b/vendor/github.com/disintegration/imaging/effects.go index 19d6e40..b16781f 100644 --- a/vendor/github.com/disintegration/imaging/effects.go +++ b/vendor/github.com/disintegration/imaging/effects.go @@ -14,15 +14,13 @@ func gaussianBlurKernel(x, sigma float64) float64 { // // Usage example: // -// dstImage := imaging.Blur(srcImage, 3.5) +// dstImage := imaging.Blur(srcImage, 3.5) // func Blur(img image.Image, sigma float64) *image.NRGBA { if sigma <= 0 { - // sigma parameter must be positive! return Clone(img) } - src := toNRGBA(img) radius := int(math.Ceil(sigma * 3.0)) kernel := make([]float64, radius+1) @@ -30,61 +28,50 @@ func Blur(img image.Image, sigma float64) *image.NRGBA { kernel[i] = gaussianBlurKernel(float64(i), sigma) } - var dst *image.NRGBA - dst = blurHorizontal(src, kernel) - dst = blurVertical(dst, kernel) - - return dst + return blurVertical(blurHorizontal(img, kernel), kernel) } -func blurHorizontal(src *image.NRGBA, kernel []float64) *image.NRGBA { +func blurHorizontal(img image.Image, kernel []float64) *image.NRGBA { + src := newScanner(img) + dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h)) radius := len(kernel) - 1 - width := src.Bounds().Max.X - height := src.Bounds().Max.Y - - dst := image.NewNRGBA(image.Rect(0, 0, width, height)) - - parallel(width, func(partStart, partEnd int) { - for x := partStart; x < partEnd; x++ { - start := x - radius - if start < 0 { - start = 0 - } - - end := x + radius - if end > width-1 { - end = width - 1 - } - weightSum := 0.0 - for ix := start; ix <= end; ix++ { - weightSum += kernel[absint(x-ix)] - } - - for y := 0; y < height; y++ { + parallel(0, src.h, func(ys <-chan int) { + scanLine := make([]uint8, src.w*4) + for y := range ys { + src.scan(0, y, src.w, y+1, scanLine) + for x := 0; x < src.w; x++ { + min := x - radius + if min < 0 { + min = 0 + } + max := x + radius + if max > src.w-1 { + max = src.w - 1 + } - r, g, b, a := 0.0, 0.0, 0.0, 0.0 - for ix := start; ix <= end; ix++ { + var r, g, b, a, wsum float64 + for ix := min; ix <= max; ix++ { + i := ix * 4 weight := kernel[absint(x-ix)] - i := y*src.Stride + ix*4 - wa := float64(src.Pix[i+3]) * weight - r += float64(src.Pix[i+0]) * wa - g += float64(src.Pix[i+1]) * wa - b += float64(src.Pix[i+2]) * wa + wsum += weight + wa := float64(scanLine[i+3]) * weight + r += float64(scanLine[i+0]) * wa + g += float64(scanLine[i+1]) * wa + b += float64(scanLine[i+2]) * wa a += wa } - - r = math.Min(math.Max(r/a, 0.0), 255.0) - g = math.Min(math.Max(g/a, 0.0), 255.0) - b = math.Min(math.Max(b/a, 0.0), 255.0) - a = math.Min(math.Max(a/weightSum, 0.0), 255.0) + if a != 0 { + r /= a + g /= a + b /= a + } j := y*dst.Stride + x*4 - dst.Pix[j+0] = uint8(r + 0.5) - dst.Pix[j+1] = uint8(g + 0.5) - dst.Pix[j+2] = uint8(b + 0.5) - dst.Pix[j+3] = uint8(a + 0.5) - + 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) } } }) @@ -92,54 +79,47 @@ func blurHorizontal(src *image.NRGBA, kernel []float64) *image.NRGBA { return dst } -func blurVertical(src *image.NRGBA, kernel []float64) *image.NRGBA { +func blurVertical(img image.Image, kernel []float64) *image.NRGBA { + src := newScanner(img) + dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h)) radius := len(kernel) - 1 - 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++ { - start := y - radius - if start < 0 { - start = 0 - } - end := y + radius - if end > height-1 { - end = height - 1 - } - - weightSum := 0.0 - for iy := start; iy <= end; iy++ { - weightSum += kernel[absint(y-iy)] - } - - for x := 0; x < width; x++ { + parallel(0, src.w, func(xs <-chan int) { + scanLine := make([]uint8, src.h*4) + for x := range xs { + src.scan(x, 0, x+1, src.h, scanLine) + for y := 0; y < src.h; y++ { + min := y - radius + if min < 0 { + min = 0 + } + max := y + radius + if max > src.h-1 { + max = src.h - 1 + } - r, g, b, a := 0.0, 0.0, 0.0, 0.0 - for iy := start; iy <= end; iy++ { + var r, g, b, a, wsum float64 + for iy := min; iy <= max; iy++ { + i := iy * 4 weight := kernel[absint(y-iy)] - i := iy*src.Stride + x*4 - wa := float64(src.Pix[i+3]) * weight - r += float64(src.Pix[i+0]) * wa - g += float64(src.Pix[i+1]) * wa - b += float64(src.Pix[i+2]) * wa + wsum += weight + wa := float64(scanLine[i+3]) * weight + r += float64(scanLine[i+0]) * wa + g += float64(scanLine[i+1]) * wa + b += float64(scanLine[i+2]) * wa a += wa } - - r = math.Min(math.Max(r/a, 0.0), 255.0) - g = math.Min(math.Max(g/a, 0.0), 255.0) - b = math.Min(math.Max(b/a, 0.0), 255.0) - a = math.Min(math.Max(a/weightSum, 0.0), 255.0) + if a != 0 { + r /= a + g /= a + b /= a + } j := y*dst.Stride + x*4 - dst.Pix[j+0] = uint8(r + 0.5) - dst.Pix[j+1] = uint8(g + 0.5) - dst.Pix[j+2] = uint8(b + 0.5) - dst.Pix[j+3] = uint8(a + 0.5) - + 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) } } }) @@ -152,35 +132,31 @@ func blurVertical(src *image.NRGBA, kernel []float64) *image.NRGBA { // // Usage example: // -// dstImage := imaging.Sharpen(srcImage, 3.5) +// 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) + src := newScanner(img) + dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h)) 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) + parallel(0, src.h, func(ys <-chan int) { + scanLine := make([]uint8, src.w*4) + for y := range ys { + src.scan(0, y, src.w, y+1, scanLine) + j := y * dst.Stride + for i := 0; i < src.w*4; i++ { + val := int(scanLine[i])<<1 - int(blurred.Pix[j]) + if val < 0 { + val = 0 + } else if val > 0xff { + val = 0xff } + dst.Pix[j] = uint8(val) + j++ } } }) diff --git a/vendor/github.com/disintegration/imaging/helpers.go b/vendor/github.com/disintegration/imaging/helpers.go index 79967ae..dcb4d7e 100644 --- a/vendor/github.com/disintegration/imaging/helpers.go +++ b/vendor/github.com/disintegration/imaging/helpers.go @@ -1,17 +1,11 @@ -/* -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). -*/ package imaging import ( + "bytes" "errors" "image" "image/color" + "image/draw" "image/gif" "image/jpeg" "image/png" @@ -24,8 +18,10 @@ import ( "golang.org/x/image/tiff" ) +// Format is an image file format. type Format int +// Image file formats. const ( JPEG Format = iota PNG @@ -51,32 +47,125 @@ func (f Format) String() string { } } +var formatFromExt = map[string]Format{ + ".jpg": JPEG, + ".jpeg": JPEG, + ".png": PNG, + ".tif": TIFF, + ".tiff": TIFF, + ".bmp": BMP, + ".gif": GIF, +} + +// FormatFromFilename parses image format from filename extension: +// "jpg" (or "jpeg"), "png", "gif", "tif" (or "tiff") and "bmp" are supported. +func FormatFromFilename(filename string) (Format, error) { + ext := strings.ToLower(filepath.Ext(filename)) + if f, ok := formatFromExt[ext]; ok { + return f, nil + } + return -1, ErrUnsupportedFormat +} + var ( + // ErrUnsupportedFormat means the given image format (or file extension) is unsupported. ErrUnsupportedFormat = errors.New("imaging: unsupported image format") ) +type fileSystem interface { + Create(string) (io.WriteCloser, error) + Open(string) (io.ReadCloser, error) +} + +type localFS struct{} + +func (localFS) Create(name string) (io.WriteCloser, error) { return os.Create(name) } +func (localFS) Open(name string) (io.ReadCloser, error) { return os.Open(name) } + +var fs fileSystem = localFS{} + // Decode reads an image from r. func Decode(r io.Reader) (image.Image, error) { img, _, err := image.Decode(r) - if err != nil { - return nil, err - } - return toNRGBA(img), nil + return img, err } // Open loads an image from file func Open(filename string) (image.Image, error) { - file, err := os.Open(filename) + file, err := fs.Open(filename) if err != nil { return nil, err } defer file.Close() - img, err := Decode(file) - return img, err + return Decode(file) +} + +type encodeConfig struct { + jpegQuality int + gifNumColors int + gifQuantizer draw.Quantizer + gifDrawer draw.Drawer + pngCompressionLevel png.CompressionLevel +} + +var defaultEncodeConfig = encodeConfig{ + jpegQuality: 95, + gifNumColors: 256, + gifQuantizer: nil, + gifDrawer: nil, + pngCompressionLevel: png.DefaultCompression, +} + +// EncodeOption sets an optional parameter for the Encode and Save functions. +type EncodeOption func(*encodeConfig) + +// JPEGQuality returns an EncodeOption that sets the output JPEG quality. +// Quality ranges from 1 to 100 inclusive, higher is better. Default is 95. +func JPEGQuality(quality int) EncodeOption { + return func(c *encodeConfig) { + c.jpegQuality = quality + } +} + +// GIFNumColors returns an EncodeOption that sets the maximum number of colors +// used in the GIF-encoded image. It ranges from 1 to 256. Default is 256. +func GIFNumColors(numColors int) EncodeOption { + return func(c *encodeConfig) { + c.gifNumColors = numColors + } +} + +// GIFQuantizer returns an EncodeOption that sets the quantizer that is used to produce +// a palette of the GIF-encoded image. +func GIFQuantizer(quantizer draw.Quantizer) EncodeOption { + return func(c *encodeConfig) { + c.gifQuantizer = quantizer + } +} + +// GIFDrawer returns an EncodeOption that sets the drawer that is used to convert +// the source image to the desired palette of the GIF-encoded image. +func GIFDrawer(drawer draw.Drawer) EncodeOption { + return func(c *encodeConfig) { + c.gifDrawer = drawer + } +} + +// PNGCompressionLevel returns an EncodeOption that sets the compression level +// of the PNG-encoded image. Default is png.DefaultCompression. +func PNGCompressionLevel(level png.CompressionLevel) EncodeOption { + return func(c *encodeConfig) { + c.pngCompressionLevel = level + } } // Encode writes the image img to w in the specified format (JPEG, PNG, GIF, TIFF or BMP). -func Encode(w io.Writer, img image.Image, format Format) error { +func Encode(w io.Writer, img image.Image, format Format, opts ...EncodeOption) error { + cfg := defaultEncodeConfig + for _, option := range opts { + option(&cfg) + } + var err error switch format { case JPEG: @@ -91,19 +180,28 @@ func Encode(w io.Writer, img image.Image, format Format) error { } } if rgba != nil { - err = jpeg.Encode(w, rgba, &jpeg.Options{Quality: 95}) + err = jpeg.Encode(w, rgba, &jpeg.Options{Quality: cfg.jpegQuality}) } else { - err = jpeg.Encode(w, img, &jpeg.Options{Quality: 95}) + err = jpeg.Encode(w, img, &jpeg.Options{Quality: cfg.jpegQuality}) } case PNG: - err = png.Encode(w, img) + enc := png.Encoder{CompressionLevel: cfg.pngCompressionLevel} + err = enc.Encode(w, img) + case GIF: - err = gif.Encode(w, img, &gif.Options{NumColors: 256}) + err = gif.Encode(w, img, &gif.Options{ + NumColors: cfg.gifNumColors, + Quantizer: cfg.gifQuantizer, + Drawer: cfg.gifDrawer, + }) + case TIFF: err = tiff.Encode(w, img, &tiff.Options{Compression: tiff.Deflate, Predictor: true}) + case BMP: err = bmp.Encode(w, img) + default: err = ErrUnsupportedFormat } @@ -112,30 +210,33 @@ func Encode(w io.Writer, img image.Image, format Format) error { // Save saves the image to file with the specified filename. // The format is determined from the filename extension: "jpg" (or "jpeg"), "png", "gif", "tif" (or "tiff") and "bmp" are supported. -func Save(img image.Image, filename string) (err error) { - formats := map[string]Format{ - ".jpg": JPEG, - ".jpeg": JPEG, - ".png": PNG, - ".tif": TIFF, - ".tiff": TIFF, - ".bmp": BMP, - ".gif": GIF, - } - - ext := strings.ToLower(filepath.Ext(filename)) - f, ok := formats[ext] - if !ok { - return ErrUnsupportedFormat +// +// Examples: +// +// // Save the image as PNG. +// err := imaging.Save(img, "out.png") +// +// // Save the image as JPEG with optional quality parameter set to 80. +// err := imaging.Save(img, "out.jpg", imaging.JPEGQuality(80)) +// +func Save(img image.Image, filename string, opts ...EncodeOption) (err error) { + f, err := FormatFromFilename(filename) + if err != nil { + return err } - - file, err := os.Create(filename) + file, err := fs.Create(filename) if err != nil { return err } - defer file.Close() - return Encode(file, img, f) + defer func() { + cerr := file.Close() + if err == nil { + err = cerr + } + }() + + return Encode(file, img, f, opts...) } // New creates a new image with the specified width and height, and fills it with the specified color. @@ -144,257 +245,28 @@ func New(width, height int, fillColor color.Color) *image.NRGBA { return &image.NRGBA{} } - dst := image.NewNRGBA(image.Rect(0, 0, width, height)) c := color.NRGBAModel.Convert(fillColor).(color.NRGBA) - - if c.R == 0 && c.G == 0 && c.B == 0 && c.A == 0 { - return dst + if (c == color.NRGBA{0, 0, 0, 0}) { + return image.NewNRGBA(image.Rect(0, 0, width, height)) } - cs := []uint8{c.R, c.G, c.B, c.A} - - // fill the first row - for x := 0; x < width; x++ { - copy(dst.Pix[x*4:(x+1)*4], cs) - } - // copy the first row to other rows - for y := 1; y < height; y++ { - copy(dst.Pix[y*dst.Stride:y*dst.Stride+width*4], dst.Pix[0:width*4]) + return &image.NRGBA{ + Pix: bytes.Repeat([]byte{c.R, c.G, c.B, c.A}, width*height), + Stride: 4 * width, + Rect: image.Rect(0, 0, width, height), } - - return dst } // Clone returns a copy of the given image. func Clone(img image.Image) *image.NRGBA { - srcBounds := img.Bounds() - srcMinX := srcBounds.Min.X - srcMinY := srcBounds.Min.Y - - dstBounds := srcBounds.Sub(srcBounds.Min) - dstW := dstBounds.Dx() - dstH := dstBounds.Dy() - dst := image.NewNRGBA(dstBounds) - - switch src := img.(type) { - - case *image.NRGBA: - rowSize := srcBounds.Dx() * 4 - parallel(dstH, func(partStart, partEnd int) { - for dstY := partStart; dstY < partEnd; dstY++ { - di := dst.PixOffset(0, dstY) - si := src.PixOffset(srcMinX, srcMinY+dstY) - copy(dst.Pix[di:di+rowSize], src.Pix[si:si+rowSize]) - } - }) - - case *image.NRGBA64: - parallel(dstH, func(partStart, partEnd int) { - for dstY := partStart; dstY < partEnd; dstY++ { - di := dst.PixOffset(0, dstY) - si := src.PixOffset(srcMinX, srcMinY+dstY) - for dstX := 0; dstX < dstW; dstX++ { - - dst.Pix[di+0] = src.Pix[si+0] - dst.Pix[di+1] = src.Pix[si+2] - dst.Pix[di+2] = src.Pix[si+4] - dst.Pix[di+3] = src.Pix[si+6] - - di += 4 - si += 8 - - } - } - }) - - case *image.RGBA: - parallel(dstH, func(partStart, partEnd int) { - for dstY := partStart; dstY < partEnd; dstY++ { - di := dst.PixOffset(0, dstY) - si := src.PixOffset(srcMinX, srcMinY+dstY) - for dstX := 0; dstX < dstW; dstX++ { - - a := src.Pix[si+3] - dst.Pix[di+3] = a - switch a { - case 0: - dst.Pix[di+0] = 0 - dst.Pix[di+1] = 0 - dst.Pix[di+2] = 0 - case 0xff: - dst.Pix[di+0] = src.Pix[si+0] - dst.Pix[di+1] = src.Pix[si+1] - dst.Pix[di+2] = src.Pix[si+2] - default: - var tmp uint16 - tmp = uint16(src.Pix[si+0]) * 0xff / uint16(a) - dst.Pix[di+0] = uint8(tmp) - tmp = uint16(src.Pix[si+1]) * 0xff / uint16(a) - dst.Pix[di+1] = uint8(tmp) - tmp = uint16(src.Pix[si+2]) * 0xff / uint16(a) - dst.Pix[di+2] = uint8(tmp) - } - - di += 4 - si += 4 - - } - } - }) - - case *image.RGBA64: - parallel(dstH, func(partStart, partEnd int) { - for dstY := partStart; dstY < partEnd; dstY++ { - di := dst.PixOffset(0, dstY) - si := src.PixOffset(srcMinX, srcMinY+dstY) - for dstX := 0; dstX < dstW; dstX++ { - - a := src.Pix[si+6] - dst.Pix[di+3] = a - switch a { - case 0: - dst.Pix[di+0] = 0 - dst.Pix[di+1] = 0 - dst.Pix[di+2] = 0 - case 0xff: - dst.Pix[di+0] = src.Pix[si+0] - dst.Pix[di+1] = src.Pix[si+2] - dst.Pix[di+2] = src.Pix[si+4] - default: - var tmp uint16 - tmp = uint16(src.Pix[si+0]) * 0xff / uint16(a) - dst.Pix[di+0] = uint8(tmp) - tmp = uint16(src.Pix[si+2]) * 0xff / uint16(a) - dst.Pix[di+1] = uint8(tmp) - tmp = uint16(src.Pix[si+4]) * 0xff / uint16(a) - dst.Pix[di+2] = uint8(tmp) - } - - di += 4 - si += 8 - - } - } - }) - - case *image.Gray: - parallel(dstH, func(partStart, partEnd int) { - for dstY := partStart; dstY < partEnd; dstY++ { - di := dst.PixOffset(0, dstY) - si := src.PixOffset(srcMinX, srcMinY+dstY) - for dstX := 0; dstX < dstW; dstX++ { - - c := src.Pix[si] - dst.Pix[di+0] = c - dst.Pix[di+1] = c - dst.Pix[di+2] = c - dst.Pix[di+3] = 0xff - - di += 4 - si += 1 - - } - } - }) - - case *image.Gray16: - parallel(dstH, func(partStart, partEnd int) { - for dstY := partStart; dstY < partEnd; dstY++ { - di := dst.PixOffset(0, dstY) - si := src.PixOffset(srcMinX, srcMinY+dstY) - for dstX := 0; dstX < dstW; dstX++ { - - c := src.Pix[si] - dst.Pix[di+0] = c - dst.Pix[di+1] = c - dst.Pix[di+2] = c - dst.Pix[di+3] = 0xff - - di += 4 - si += 2 - - } - } - }) - - case *image.YCbCr: - parallel(dstH, func(partStart, partEnd int) { - for dstY := partStart; dstY < partEnd; dstY++ { - di := dst.PixOffset(0, dstY) - for dstX := 0; dstX < dstW; dstX++ { - - srcX := srcMinX + dstX - srcY := srcMinY + dstY - siy := src.YOffset(srcX, srcY) - sic := src.COffset(srcX, srcY) - r, g, b := color.YCbCrToRGB(src.Y[siy], src.Cb[sic], src.Cr[sic]) - dst.Pix[di+0] = r - dst.Pix[di+1] = g - dst.Pix[di+2] = b - dst.Pix[di+3] = 0xff - - di += 4 - - } - } - }) - - case *image.Paletted: - plen := len(src.Palette) - pnew := make([]color.NRGBA, plen) - for i := 0; i < plen; i++ { - pnew[i] = color.NRGBAModel.Convert(src.Palette[i]).(color.NRGBA) + src := newScanner(img) + dst := image.NewNRGBA(image.Rect(0, 0, src.w, src.h)) + size := src.w * 4 + 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+size]) } - - parallel(dstH, func(partStart, partEnd int) { - for dstY := partStart; dstY < partEnd; dstY++ { - di := dst.PixOffset(0, dstY) - si := src.PixOffset(srcMinX, srcMinY+dstY) - for dstX := 0; dstX < dstW; dstX++ { - - c := pnew[src.Pix[si]] - dst.Pix[di+0] = c.R - dst.Pix[di+1] = c.G - dst.Pix[di+2] = c.B - dst.Pix[di+3] = c.A - - di += 4 - si += 1 - - } - } - }) - - default: - parallel(dstH, func(partStart, partEnd int) { - for dstY := partStart; dstY < partEnd; dstY++ { - di := dst.PixOffset(0, dstY) - for dstX := 0; dstX < dstW; dstX++ { - - c := color.NRGBAModel.Convert(img.At(srcMinX+dstX, srcMinY+dstY)).(color.NRGBA) - dst.Pix[di+0] = c.R - dst.Pix[di+1] = c.G - dst.Pix[di+2] = c.B - dst.Pix[di+3] = c.A - - di += 4 - - } - } - }) - - } - + }) 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() - if srcBounds.Min.X == 0 && srcBounds.Min.Y == 0 { - if src0, ok := img.(*image.NRGBA); ok { - return src0 - } - } - return Clone(img) -} diff --git a/vendor/github.com/disintegration/imaging/histogram.go b/vendor/github.com/disintegration/imaging/histogram.go index aef3338..5bcb001 100644 --- a/vendor/github.com/disintegration/imaging/histogram.go +++ b/vendor/github.com/disintegration/imaging/histogram.go @@ -2,6 +2,7 @@ package imaging import ( "image" + "sync" ) // Histogram returns a normalized histogram of an image. @@ -9,35 +10,42 @@ import ( // Resulting histogram is represented as an array of 256 floats, where // histogram[i] is a probability of a pixel being of a particular luminance i. func Histogram(img image.Image) [256]float64 { - src := toNRGBA(img) - width := src.Bounds().Max.X - height := src.Bounds().Max.Y - + var mu sync.Mutex var histogram [256]float64 var total float64 - if width == 0 || height == 0 { - return histogram + src := newScanner(img) + if src.w == 0 || src.h == 0 { + return histogram } - for y := 0; y < height; y++ { - for x := 0; x < width; x++ { - i := y*src.Stride + x*4 - - r := src.Pix[i+0] - g := src.Pix[i+1] - b := src.Pix[i+2] - - var y float32 = 0.299*float32(r) + 0.587*float32(g) + 0.114*float32(b) - - histogram[int(y+0.5)]++ - total++ + parallel(0, src.h, func(ys <-chan int) { + var tmpHistogram [256]float64 + var tmpTotal float64 + scanLine := make([]uint8, src.w*4) + for y := range ys { + 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] + y := 0.299*float32(r) + 0.587*float32(g) + 0.114*float32(b) + tmpHistogram[int(y+0.5)]++ + tmpTotal++ + i += 4 + } } - } + mu.Lock() + for i := 0; i < 256; i++ { + histogram[i] += tmpHistogram[i] + } + total += tmpTotal + mu.Unlock() + }) for i := 0; i < 256; i++ { histogram[i] = histogram[i] / total } - return histogram } diff --git a/vendor/github.com/disintegration/imaging/resize.go b/vendor/github.com/disintegration/imaging/resize.go index b21eed5..97f498a 100644 --- a/vendor/github.com/disintegration/imaging/resize.go +++ b/vendor/github.com/disintegration/imaging/resize.go @@ -5,17 +5,12 @@ import ( "math" ) -type iwpair struct { - i int - w int32 +type indexWeight struct { + index int + weight float64 } -type pweights struct { - iwpairs []iwpair - wsum int32 -} - -func precomputeWeights(dstSize, srcSize int, filter ResampleFilter) []pweights { +func precomputeWeights(dstSize, srcSize int, filter ResampleFilter) [][]indexWeight { du := float64(srcSize) / float64(dstSize) scale := du if scale < 1.0 { @@ -23,29 +18,37 @@ func precomputeWeights(dstSize, srcSize int, filter ResampleFilter) []pweights { } ru := math.Ceil(scale * filter.Support) - out := make([]pweights, dstSize) + out := make([][]indexWeight, dstSize) + tmp := make([]indexWeight, 0, dstSize*int(ru+2)*2) for v := 0; v < dstSize; v++ { fu := (float64(v)+0.5)*du - 0.5 - startu := int(math.Ceil(fu - ru)) - if startu < 0 { - startu = 0 + begin := int(math.Ceil(fu - ru)) + if begin < 0 { + begin = 0 } - endu := int(math.Floor(fu + ru)) - if endu > srcSize-1 { - endu = srcSize - 1 + end := int(math.Floor(fu + ru)) + if end > srcSize-1 { + end = srcSize - 1 } - wsum := int32(0) - for u := startu; u <= endu; u++ { - w := int32(0xff * filter.Kernel((float64(u)-fu)/scale)) + var sum float64 + for u := begin; u <= end; u++ { + w := filter.Kernel((float64(u) - fu) / scale) if w != 0 { - wsum += w - out[v].iwpairs = append(out[v].iwpairs, iwpair{u, w}) + sum += w + tmp = append(tmp, indexWeight{index: u, weight: w}) + } + } + if sum != 0 { + for i := range tmp { + tmp[i].weight /= sum } } - out[v].wsum = wsum + + out[v] = tmp + tmp = tmp[len(tmp):] } return out @@ -60,11 +63,10 @@ func precomputeWeights(dstSize, srcSize int, filter ResampleFilter) []pweights { // // Usage example: // -// dstImage := imaging.Resize(srcImage, 800, 600, imaging.Lanczos) +// dstImage := imaging.Resize(srcImage, 800, 600, imaging.Lanczos) // func Resize(img image.Image, width, height int, filter ResampleFilter) *image.NRGBA { dstW, dstH := width, height - if dstW < 0 || dstH < 0 { return &image.NRGBA{} } @@ -72,16 +74,13 @@ func Resize(img image.Image, width, height int, filter ResampleFilter) *image.NR return &image.NRGBA{} } - src := toNRGBA(img) - - srcW := src.Bounds().Max.X - srcH := src.Bounds().Max.Y - + srcW := img.Bounds().Dx() + srcH := img.Bounds().Dy() if srcW <= 0 || srcH <= 0 { return &image.NRGBA{} } - // if new width or height is 0 then preserve aspect ratio, minimum 1px + // If new width or height is 0 then preserve aspect ratio, minimum 1px. if dstW == 0 { tmpW := float64(dstH) * float64(srcW) / float64(srcH) dstW = int(math.Max(1.0, math.Floor(tmpW+0.5))) @@ -91,136 +90,123 @@ func Resize(img image.Image, width, height int, filter ResampleFilter) *image.NR dstH = int(math.Max(1.0, math.Floor(tmpH+0.5))) } - var dst *image.NRGBA - - if filter.Support <= 0.0 { - // nearest-neighbor special case - dst = resizeNearest(src, dstW, dstH) - - } else { - // two-pass resize - if srcW != dstW { - dst = resizeHorizontal(src, dstW, filter) - } else { - dst = src - } - - if srcH != dstH { - dst = resizeVertical(dst, dstH, filter) - } + if filter.Support <= 0 { + // Nearest-neighbor special case. + return resizeNearest(img, dstW, dstH) } - return dst + if srcW != dstW && srcH != dstH { + return resizeVertical(resizeHorizontal(img, dstW, filter), dstH, filter) + } + if srcW != dstW { + return resizeHorizontal(img, dstW, filter) + } + if srcH != dstH { + return resizeVertical(img, dstH, filter) + } + return Clone(img) } -func resizeHorizontal(src *image.NRGBA, width int, filter ResampleFilter) *image.NRGBA { - srcBounds := src.Bounds() - srcW := srcBounds.Max.X - srcH := srcBounds.Max.Y - - dstW := width - dstH := srcH - - dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH)) - - weights := precomputeWeights(dstW, srcW, filter) - - parallel(dstH, func(partStart, partEnd int) { - for dstY := partStart; dstY < partEnd; dstY++ { - for dstX := 0; dstX < dstW; dstX++ { - var c [4]int64 - for _, iw := range weights[dstX].iwpairs { - i := dstY*src.Stride + iw.i*4 - a := int64(src.Pix[i+3]) * int64(iw.w) - c[0] += int64(src.Pix[i+0]) * a - c[1] += int64(src.Pix[i+1]) * a - c[2] += int64(src.Pix[i+2]) * a - c[3] += a +func resizeHorizontal(img image.Image, width int, filter ResampleFilter) *image.NRGBA { + src := newScanner(img) + dst := image.NewNRGBA(image.Rect(0, 0, width, src.h)) + weights := precomputeWeights(width, src.w, filter) + parallel(0, src.h, func(ys <-chan int) { + scanLine := make([]uint8, src.w*4) + for y := range ys { + src.scan(0, y, src.w, y+1, scanLine) + j0 := y * dst.Stride + for x := 0; x < width; x++ { + var r, g, b, a float64 + for _, w := range weights[x] { + i := w.index * 4 + aw := float64(scanLine[i+3]) * w.weight + r += float64(scanLine[i+0]) * aw + g += float64(scanLine[i+1]) * aw + b += float64(scanLine[i+2]) * aw + a += aw + } + if a != 0 { + aInv := 1 / a + j := j0 + x*4 + dst.Pix[j+0] = clamp(r * aInv) + dst.Pix[j+1] = clamp(g * aInv) + dst.Pix[j+2] = clamp(b * aInv) + dst.Pix[j+3] = clamp(a) } - j := dstY*dst.Stride + dstX*4 - sum := weights[dstX].wsum - dst.Pix[j+0] = clampint32(int32(float64(c[0])/float64(c[3]) + 0.5)) - dst.Pix[j+1] = clampint32(int32(float64(c[1])/float64(c[3]) + 0.5)) - dst.Pix[j+2] = clampint32(int32(float64(c[2])/float64(c[3]) + 0.5)) - dst.Pix[j+3] = clampint32(int32(float64(c[3])/float64(sum) + 0.5)) } } }) - return dst } -func resizeVertical(src *image.NRGBA, height int, filter ResampleFilter) *image.NRGBA { - srcBounds := src.Bounds() - srcW := srcBounds.Max.X - srcH := srcBounds.Max.Y - - dstW := srcW - dstH := height - - dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH)) - - weights := precomputeWeights(dstH, srcH, filter) - - parallel(dstW, func(partStart, partEnd int) { - - for dstX := partStart; dstX < partEnd; dstX++ { - for dstY := 0; dstY < dstH; dstY++ { - var c [4]int64 - for _, iw := range weights[dstY].iwpairs { - i := iw.i*src.Stride + dstX*4 - a := int64(src.Pix[i+3]) * int64(iw.w) - c[0] += int64(src.Pix[i+0]) * a - c[1] += int64(src.Pix[i+1]) * a - c[2] += int64(src.Pix[i+2]) * a - c[3] += a +func resizeVertical(img image.Image, height int, filter ResampleFilter) *image.NRGBA { + src := newScanner(img) + dst := image.NewNRGBA(image.Rect(0, 0, src.w, height)) + weights := precomputeWeights(height, src.h, filter) + parallel(0, src.w, func(xs <-chan int) { + scanLine := make([]uint8, src.h*4) + for x := range xs { + src.scan(x, 0, x+1, src.h, scanLine) + for y := 0; y < height; y++ { + var r, g, b, a float64 + for _, w := range weights[y] { + i := w.index * 4 + aw := float64(scanLine[i+3]) * w.weight + r += float64(scanLine[i+0]) * aw + g += float64(scanLine[i+1]) * aw + b += float64(scanLine[i+2]) * aw + a += aw + } + if a != 0 { + aInv := 1 / a + j := y*dst.Stride + x*4 + dst.Pix[j+0] = clamp(r * aInv) + dst.Pix[j+1] = clamp(g * aInv) + dst.Pix[j+2] = clamp(b * aInv) + dst.Pix[j+3] = clamp(a) } - j := dstY*dst.Stride + dstX*4 - sum := weights[dstY].wsum - dst.Pix[j+0] = clampint32(int32(float64(c[0])/float64(c[3]) + 0.5)) - dst.Pix[j+1] = clampint32(int32(float64(c[1])/float64(c[3]) + 0.5)) - dst.Pix[j+2] = clampint32(int32(float64(c[2])/float64(c[3]) + 0.5)) - dst.Pix[j+3] = clampint32(int32(float64(c[3])/float64(sum) + 0.5)) } } - }) - return dst } -// fast nearest-neighbor resize, no filtering -func resizeNearest(src *image.NRGBA, width, height int) *image.NRGBA { - dstW, dstH := width, height - - srcBounds := src.Bounds() - srcW := srcBounds.Max.X - srcH := srcBounds.Max.Y - - dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH)) - - dx := float64(srcW) / float64(dstW) - dy := float64(srcH) / float64(dstH) - - parallel(dstH, func(partStart, partEnd int) { - - for dstY := partStart; dstY < partEnd; dstY++ { - fy := (float64(dstY)+0.5)*dy - 0.5 - - for dstX := 0; dstX < dstW; dstX++ { - fx := (float64(dstX)+0.5)*dx - 0.5 - - srcX := int(math.Min(math.Max(math.Floor(fx+0.5), 0.0), float64(srcW))) - srcY := int(math.Min(math.Max(math.Floor(fy+0.5), 0.0), float64(srcH))) - - srcOff := srcY*src.Stride + srcX*4 - dstOff := dstY*dst.Stride + dstX*4 - - copy(dst.Pix[dstOff:dstOff+4], src.Pix[srcOff:srcOff+4]) +// resizeNearest is a fast nearest-neighbor resize, no filtering. +func resizeNearest(img image.Image, width, height int) *image.NRGBA { + dst := image.NewNRGBA(image.Rect(0, 0, width, height)) + dx := float64(img.Bounds().Dx()) / float64(width) + dy := float64(img.Bounds().Dy()) / float64(height) + + if dx > 1 && dy > 1 { + src := newScanner(img) + parallel(0, height, func(ys <-chan int) { + for y := range ys { + srcY := int((float64(y) + 0.5) * dy) + dstOff := y * dst.Stride + for x := 0; x < width; x++ { + srcX := int((float64(x) + 0.5) * dx) + src.scan(srcX, srcY, srcX+1, srcY+1, dst.Pix[dstOff:dstOff+4]) + dstOff += 4 + } } - } - - }) + }) + } else { + src := toNRGBA(img) + parallel(0, height, func(ys <-chan int) { + for y := range ys { + srcY := int((float64(y) + 0.5) * dy) + srcOff0 := srcY * src.Stride + dstOff := y * dst.Stride + for x := 0; x < width; x++ { + srcX := int((float64(x) + 0.5) * dx) + srcOff := srcOff0 + srcX*4 + copy(dst.Pix[dstOff:dstOff+4], src.Pix[srcOff:srcOff+4]) + dstOff += 4 + } + } + }) + } return dst } @@ -233,7 +219,7 @@ func resizeNearest(src *image.NRGBA, width, height int) *image.NRGBA { // // Usage example: // -// dstImage := imaging.Fit(srcImage, 800, 600, imaging.Lanczos) +// dstImage := imaging.Fit(srcImage, 800, 600, imaging.Lanczos) // func Fit(img image.Image, width, height int, filter ResampleFilter) *image.NRGBA { maxW, maxH := width, height @@ -278,7 +264,7 @@ func Fit(img image.Image, width, height int, filter ResampleFilter) *image.NRGBA // // Usage example: // -// dstImage := imaging.Fill(srcImage, 800, 600, imaging.Center, imaging.Lanczos) +// dstImage := imaging.Fill(srcImage, 800, 600, imaging.Center, imaging.Lanczos) // func Fill(img image.Image, width, height int, anchor Anchor, filter ResampleFilter) *image.NRGBA { minW, minH := width, height @@ -320,13 +306,13 @@ func Fill(img image.Image, width, height int, anchor Anchor, filter ResampleFilt // // Usage example: // -// dstImage := imaging.Thumbnail(srcImage, 100, 100, imaging.Lanczos) +// dstImage := imaging.Thumbnail(srcImage, 100, 100, imaging.Lanczos) // func Thumbnail(img image.Image, width, height int, filter ResampleFilter) *image.NRGBA { return Fill(img, width, height, Center, filter) } -// Resample filter struct. It can be used to make custom filters. +// ResampleFilter is a resampling filter struct. It can be used to define custom filters. // // Supported resample filters: NearestNeighbor, Box, Linear, Hermite, MitchellNetravali, // CatmullRom, BSpline, Gaussian, Lanczos, Hann, Hamming, Blackman, Bartlett, Welch, Cosine. @@ -334,34 +320,35 @@ func Thumbnail(img image.Image, width, height int, filter ResampleFilter) *image // General filter recommendations: // // - Lanczos -// Probably the best resampling filter for photographic images yielding sharp results, -// but it's slower than cubic filters (see below). +// High-quality resampling filter for photographic images yielding sharp results. +// 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. +// A high quality cubic filter that produces smoother results with less ringing artifacts 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. +// Bilinear interpolation filter, produces reasonably good, smooth output. +// It's faster than cubic filters. // // - Box -// Simple and fast resampling filter appropriate for downscaling. +// Simple and fast averaging filter appropriate for downscaling. // When upscaling it's similar to NearestNeighbor. // // - NearestNeighbor -// Fastest resample filter, no antialiasing at all. Rarely used. +// Fastest resampling filter, no antialiasing. // type ResampleFilter struct { Support float64 Kernel func(float64) float64 } -// Nearest-neighbor filter, no anti-aliasing. +// NearestNeighbor is a nearest-neighbor filter (no anti-aliasing). var NearestNeighbor ResampleFilter // Box filter (averaging pixels). @@ -373,48 +360,48 @@ 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). +// MitchellNetravali is 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). +// CatmullRom is a 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). +// BSpline is a smooth cubic filter (BC-spline; B=1; C=0). var BSpline ResampleFilter -// Gaussian Blurring Filter. +// Gaussian is a Gaussian blurring Filter. var Gaussian ResampleFilter -// Bartlett-windowed sinc filter (3 lobes). +// Bartlett is a Bartlett-windowed sinc filter (3 lobes). var Bartlett ResampleFilter // Lanczos filter (3 lobes). var Lanczos ResampleFilter -// Hann-windowed sinc filter (3 lobes). +// Hann is a Hann-windowed sinc filter (3 lobes). var Hann ResampleFilter -// Hamming-windowed sinc filter (3 lobes). +// Hamming is a Hamming-windowed sinc filter (3 lobes). var Hamming ResampleFilter -// Blackman-windowed sinc filter (3 lobes). +// Blackman is a Blackman-windowed sinc filter (3 lobes). var Blackman ResampleFilter -// Welch-windowed sinc filter (parabolic window, 3 lobes). +// Welch is a Welch-windowed sinc filter (parabolic window, 3 lobes). var Welch ResampleFilter -// Cosine-windowed sinc filter (3 lobes). +// Cosine is a Cosine-windowed sinc filter (3 lobes). var Cosine ResampleFilter func bcspline(x, b, c float64) float64 { + var y 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 + y = ((12-9*b-6*c)*x*x*x + (-18+12*b+6*c)*x*x + (6 - 2*b)) / 6 + } else if x < 2.0 { + y = ((-b-6*c)*x*x*x + (6*b+30*c)*x*x + (-12*b-48*c)*x + (8*b + 24*c)) / 6 } - return 0 + return y } func sinc(x float64) float64 { diff --git a/vendor/github.com/disintegration/imaging/scanner.go b/vendor/github.com/disintegration/imaging/scanner.go new file mode 100644 index 0000000..c4dbfe1 --- /dev/null +++ b/vendor/github.com/disintegration/imaging/scanner.go @@ -0,0 +1,250 @@ +package imaging + +import ( + "image" + "image/color" +) + +type scanner struct { + image image.Image + w, h int + palette []color.NRGBA +} + +func newScanner(img image.Image) *scanner { + s := &scanner{ + image: img, + w: img.Bounds().Dx(), + h: img.Bounds().Dy(), + } + if img, ok := img.(*image.Paletted); ok { + s.palette = make([]color.NRGBA, len(img.Palette)) + for i := 0; i < len(img.Palette); i++ { + s.palette[i] = color.NRGBAModel.Convert(img.Palette[i]).(color.NRGBA) + } + } + return s +} + +// scan scans the given rectangular region of the image into dst. +func (s *scanner) scan(x1, y1, x2, y2 int, dst []uint8) { + switch img := s.image.(type) { + case *image.NRGBA: + size := (x2 - x1) * 4 + j := 0 + i := y1*img.Stride + x1*4 + for y := y1; y < y2; y++ { + copy(dst[j:j+size], img.Pix[i:i+size]) + j += size + i += img.Stride + } + + case *image.NRGBA64: + j := 0 + for y := y1; y < y2; y++ { + i := y*img.Stride + x1*8 + for x := x1; x < x2; x++ { + dst[j+0] = img.Pix[i+0] + dst[j+1] = img.Pix[i+2] + dst[j+2] = img.Pix[i+4] + dst[j+3] = img.Pix[i+6] + j += 4 + i += 8 + } + } + + case *image.RGBA: + j := 0 + for y := y1; y < y2; y++ { + i := y*img.Stride + x1*4 + for x := x1; x < x2; x++ { + a := img.Pix[i+3] + switch a { + case 0: + dst[j+0] = 0 + dst[j+1] = 0 + dst[j+2] = 0 + case 0xff: + dst[j+0] = img.Pix[i+0] + dst[j+1] = img.Pix[i+1] + dst[j+2] = img.Pix[i+2] + default: + r16 := uint16(img.Pix[i+0]) + g16 := uint16(img.Pix[i+1]) + b16 := uint16(img.Pix[i+2]) + a16 := uint16(a) + dst[j+0] = uint8(r16 * 0xff / a16) + dst[j+1] = uint8(g16 * 0xff / a16) + dst[j+2] = uint8(b16 * 0xff / a16) + } + dst[j+3] = a + j += 4 + i += 4 + } + } + + case *image.RGBA64: + j := 0 + for y := y1; y < y2; y++ { + i := y*img.Stride + x1*8 + for x := x1; x < x2; x++ { + a := img.Pix[i+6] + switch a { + case 0: + dst[j+0] = 0 + dst[j+1] = 0 + dst[j+2] = 0 + case 0xff: + dst[j+0] = img.Pix[i+0] + dst[j+1] = img.Pix[i+2] + dst[j+2] = img.Pix[i+4] + default: + r32 := uint32(img.Pix[i+0])<<8 | uint32(img.Pix[i+1]) + g32 := uint32(img.Pix[i+2])<<8 | uint32(img.Pix[i+3]) + b32 := uint32(img.Pix[i+4])<<8 | uint32(img.Pix[i+5]) + a32 := uint32(img.Pix[i+6])<<8 | uint32(img.Pix[i+7]) + dst[j+0] = uint8((r32 * 0xffff / a32) >> 8) + dst[j+1] = uint8((g32 * 0xffff / a32) >> 8) + dst[j+2] = uint8((b32 * 0xffff / a32) >> 8) + } + dst[j+3] = a + j += 4 + i += 8 + } + } + + case *image.Gray: + j := 0 + for y := y1; y < y2; y++ { + i := y*img.Stride + x1 + for x := x1; x < x2; x++ { + c := img.Pix[i] + dst[j+0] = c + dst[j+1] = c + dst[j+2] = c + dst[j+3] = 0xff + j += 4 + i++ + } + } + + case *image.Gray16: + j := 0 + for y := y1; y < y2; y++ { + i := y*img.Stride + x1*2 + for x := x1; x < x2; x++ { + c := img.Pix[i] + dst[j+0] = c + dst[j+1] = c + dst[j+2] = c + dst[j+3] = 0xff + j += 4 + i += 2 + } + } + + case *image.YCbCr: + j := 0 + x1 += img.Rect.Min.X + x2 += img.Rect.Min.X + y1 += img.Rect.Min.Y + y2 += img.Rect.Min.Y + for y := y1; y < y2; y++ { + iy := (y-img.Rect.Min.Y)*img.YStride + (x1 - img.Rect.Min.X) + for x := x1; x < x2; x++ { + var ic int + switch img.SubsampleRatio { + case image.YCbCrSubsampleRatio444: + ic = (y-img.Rect.Min.Y)*img.CStride + (x - img.Rect.Min.X) + case image.YCbCrSubsampleRatio422: + ic = (y-img.Rect.Min.Y)*img.CStride + (x/2 - img.Rect.Min.X/2) + case image.YCbCrSubsampleRatio420: + ic = (y/2-img.Rect.Min.Y/2)*img.CStride + (x/2 - img.Rect.Min.X/2) + case image.YCbCrSubsampleRatio440: + ic = (y/2-img.Rect.Min.Y/2)*img.CStride + (x - img.Rect.Min.X) + default: + ic = img.COffset(x, y) + } + + yy := int(img.Y[iy]) + cb := int(img.Cb[ic]) - 128 + cr := int(img.Cr[ic]) - 128 + + r := (yy<<16 + 91881*cr + 1<<15) >> 16 + if r > 0xff { + r = 0xff + } else if r < 0 { + r = 0 + } + + g := (yy<<16 - 22554*cb - 46802*cr + 1<<15) >> 16 + if g > 0xff { + g = 0xff + } else if g < 0 { + g = 0 + } + + b := (yy<<16 + 116130*cb + 1<<15) >> 16 + if b > 0xff { + b = 0xff + } else if b < 0 { + b = 0 + } + + dst[j+0] = uint8(r) + dst[j+1] = uint8(g) + dst[j+2] = uint8(b) + dst[j+3] = 0xff + + iy++ + j += 4 + } + } + + case *image.Paletted: + j := 0 + for y := y1; y < y2; y++ { + i := y*img.Stride + x1 + for x := x1; x < x2; x++ { + c := s.palette[img.Pix[i]] + dst[j+0] = c.R + dst[j+1] = c.G + dst[j+2] = c.B + dst[j+3] = c.A + j += 4 + i++ + } + } + + default: + j := 0 + b := s.image.Bounds() + x1 += b.Min.X + x2 += b.Min.X + y1 += b.Min.Y + y2 += b.Min.Y + for y := y1; y < y2; y++ { + for x := x1; x < x2; x++ { + r16, g16, b16, a16 := s.image.At(x, y).RGBA() + switch a16 { + case 0xffff: + dst[j+0] = uint8(r16 >> 8) + dst[j+1] = uint8(g16 >> 8) + dst[j+2] = uint8(b16 >> 8) + dst[j+3] = 0xff + case 0: + dst[j+0] = 0 + dst[j+1] = 0 + dst[j+2] = 0 + dst[j+3] = 0 + default: + dst[j+0] = uint8(((r16 * 0xffff) / a16) >> 8) + dst[j+1] = uint8(((g16 * 0xffff) / a16) >> 8) + dst[j+2] = uint8(((b16 * 0xffff) / a16) >> 8) + dst[j+3] = uint8(a16 >> 8) + } + j += 4 + } + } + } +} diff --git a/vendor/github.com/disintegration/imaging/tools.go b/vendor/github.com/disintegration/imaging/tools.go index 76f1444..fae1fa1 100644 --- a/vendor/github.com/disintegration/imaging/tools.go +++ b/vendor/github.com/disintegration/imaging/tools.go @@ -8,6 +8,7 @@ import ( // Anchor is the anchor point for image alignment. type Anchor int +// Anchor point positions. const ( Center Anchor = iota TopLeft @@ -57,10 +58,20 @@ func anchorPt(b image.Rectangle, w, h int, anchor Anchor) image.Point { // 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) + r := rect.Intersect(img.Bounds()).Sub(img.Bounds().Min) + if r.Empty() { + return &image.NRGBA{} + } + src := newScanner(img) + dst := image.NewNRGBA(image.Rect(0, 0, r.Dx(), r.Dy())) + rowSize := r.Dx() * 4 + parallel(r.Min.Y, r.Max.Y, func(ys <-chan int) { + for y := range ys { + i := (y - r.Min.Y) * dst.Stride + src.scan(r.Min.X, y, r.Max.X, y+1, dst.Pix[i:i+rowSize]) + } + }) + return dst } // CropAnchor cuts out a rectangular region with the specified size @@ -81,34 +92,25 @@ func CropCenter(img image.Image, width, height int) *image.NRGBA { // 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 - } + dst := Clone(background) + pos = pos.Sub(background.Bounds().Min) + pasteRect := image.Rectangle{Min: pos, Max: pos.Add(img.Bounds().Size())} + interRect := pasteRect.Intersect(dst.Bounds()) + if interRect.Empty() { + return dst } - + src := newScanner(img) + parallel(interRect.Min.Y, interRect.Max.Y, func(ys <-chan int) { + for y := range ys { + x1 := interRect.Min.X - pasteRect.Min.X + x2 := interRect.Max.X - pasteRect.Min.X + y1 := y - pasteRect.Min.Y + y2 := y1 + 1 + i1 := y*dst.Stride + interRect.Min.X*4 + i2 := i1 + interRect.Dx()*4 + src.scan(x1, y1, x2, y2, dst.Pix[i1:i2]) + } + }) return dst } @@ -135,49 +137,59 @@ func PasteCenter(background, img image.Image) *image.NRGBA { // // Usage examples: // -// // draw the sprite over the background at position (50, 50) -// dstImage := imaging.Overlay(backgroundImage, spriteImage, image.Pt(50, 50), 1.0) +// // Draw spriteImage over backgroundImage at the given position (x=50, y=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) +// // 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 - + opacity = math.Min(math.Max(opacity, 0.0), 1.0) // Ensure 0.0 <= opacity <= 1.0. + dst := Clone(background) + pos = pos.Sub(background.Bounds().Min) + pasteRect := image.Rectangle{Min: pos, Max: pos.Add(img.Bounds().Size())} + interRect := pasteRect.Intersect(dst.Bounds()) + if interRect.Empty() { + return dst + } + src := newScanner(img) + parallel(interRect.Min.Y, interRect.Max.Y, func(ys <-chan int) { + scanLine := make([]uint8, interRect.Dx()*4) + for y := range ys { + x1 := interRect.Min.X - pasteRect.Min.X + x2 := interRect.Max.X - pasteRect.Min.X + y1 := y - pasteRect.Min.Y + y2 := y1 + 1 + src.scan(x1, y1, x2, y2, scanLine) + 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]) - a2 := float64(src.Pix[j+3]) - coef2 := opacity * a2 / 255.0 - coef1 := (1 - coef2) * a1 / 255.0 + r2 := float64(scanLine[j+0]) + g2 := float64(scanLine[j+1]) + b2 := float64(scanLine[j+2]) + a2 := float64(scanLine[j+3]) + + coef2 := opacity * a2 / 255 + coef1 := (1 - coef2) * a1 / 255 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)) + 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)) + + i += 4 + j += 4 } } - } - + }) return dst } diff --git a/vendor/github.com/disintegration/imaging/transform.go b/vendor/github.com/disintegration/imaging/transform.go index a11601b..d788d0d 100644 --- a/vendor/github.com/disintegration/imaging/transform.go +++ b/vendor/github.com/disintegration/imaging/transform.go @@ -2,200 +2,270 @@ package imaging import ( "image" + "image/color" + "math" ) -// Rotate90 rotates the image 90 degrees counterclockwise and returns the transformed image. -func Rotate90(img image.Image) *image.NRGBA { - src := toNRGBA(img) - srcW := src.Bounds().Max.X - srcH := src.Bounds().Max.Y - dstW := srcH - dstH := srcW +// FlipH flips the image horizontally (from left to right) and returns the transformed image. +func FlipH(img image.Image) *image.NRGBA { + src := newScanner(img) + dstW := src.w + dstH := src.h + rowSize := dstW * 4 dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH)) - - parallel(dstH, func(partStart, partEnd int) { - - for dstY := partStart; dstY < partEnd; dstY++ { - for dstX := 0; dstX < dstW; dstX++ { - srcX := dstH - dstY - 1 - srcY := dstX - - srcOff := srcY*src.Stride + srcX*4 - dstOff := dstY*dst.Stride + dstX*4 - - copy(dst.Pix[dstOff:dstOff+4], src.Pix[srcOff:srcOff+4]) - } + parallel(0, dstH, func(ys <-chan int) { + for dstY := range ys { + i := dstY * dst.Stride + srcY := dstY + src.scan(0, srcY, src.w, srcY+1, dst.Pix[i:i+rowSize]) + reverse(dst.Pix[i : i+rowSize]) } - }) - return dst } -// Rotate180 rotates the image 180 degrees counterclockwise and returns the transformed image. -func Rotate180(img image.Image) *image.NRGBA { - src := toNRGBA(img) - srcW := src.Bounds().Max.X - srcH := src.Bounds().Max.Y - dstW := srcW - dstH := srcH +// FlipV flips the image vertically (from top to bottom) and returns the transformed image. +func FlipV(img image.Image) *image.NRGBA { + src := newScanner(img) + dstW := src.w + dstH := src.h + rowSize := dstW * 4 dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH)) - - parallel(dstH, func(partStart, partEnd int) { - - for dstY := partStart; dstY < partEnd; dstY++ { - for dstX := 0; dstX < dstW; dstX++ { - srcX := dstW - dstX - 1 - srcY := dstH - dstY - 1 - - srcOff := srcY*src.Stride + srcX*4 - dstOff := dstY*dst.Stride + dstX*4 - - copy(dst.Pix[dstOff:dstOff+4], src.Pix[srcOff:srcOff+4]) - } + parallel(0, dstH, func(ys <-chan int) { + for dstY := range ys { + i := dstY * dst.Stride + srcY := dstH - dstY - 1 + src.scan(0, srcY, src.w, srcY+1, dst.Pix[i:i+rowSize]) } - }) - return dst } -// Rotate270 rotates the image 270 degrees counterclockwise and returns the transformed image. -func Rotate270(img image.Image) *image.NRGBA { - src := toNRGBA(img) - srcW := src.Bounds().Max.X - srcH := src.Bounds().Max.Y - dstW := srcH - dstH := srcW +// Transpose flips the image horizontally and rotates 90 degrees counter-clockwise. +func Transpose(img image.Image) *image.NRGBA { + src := newScanner(img) + dstW := src.h + dstH := src.w + rowSize := dstW * 4 dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH)) - - parallel(dstH, func(partStart, partEnd int) { - - for dstY := partStart; dstY < partEnd; dstY++ { - for dstX := 0; dstX < dstW; dstX++ { - srcX := dstY - srcY := dstW - dstX - 1 - - srcOff := srcY*src.Stride + srcX*4 - dstOff := dstY*dst.Stride + dstX*4 - - copy(dst.Pix[dstOff:dstOff+4], src.Pix[srcOff:srcOff+4]) - } + parallel(0, dstH, func(ys <-chan int) { + for dstY := range ys { + i := dstY * dst.Stride + srcX := dstY + src.scan(srcX, 0, srcX+1, src.h, dst.Pix[i:i+rowSize]) } - }) - return dst } -// FlipH flips the image horizontally (from left to right) and returns the transformed image. -func FlipH(img image.Image) *image.NRGBA { - src := toNRGBA(img) - srcW := src.Bounds().Max.X - srcH := src.Bounds().Max.Y - dstW := srcW - dstH := srcH +// Transverse flips the image vertically and rotates 90 degrees counter-clockwise. +func Transverse(img image.Image) *image.NRGBA { + src := newScanner(img) + dstW := src.h + dstH := src.w + rowSize := dstW * 4 dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH)) - - parallel(dstH, func(partStart, partEnd int) { - - for dstY := partStart; dstY < partEnd; dstY++ { - for dstX := 0; dstX < dstW; dstX++ { - srcX := dstW - dstX - 1 - srcY := dstY - - srcOff := srcY*src.Stride + srcX*4 - dstOff := dstY*dst.Stride + dstX*4 - - copy(dst.Pix[dstOff:dstOff+4], src.Pix[srcOff:srcOff+4]) - } + parallel(0, dstH, func(ys <-chan int) { + for dstY := range ys { + i := dstY * dst.Stride + srcX := dstH - dstY - 1 + src.scan(srcX, 0, srcX+1, src.h, dst.Pix[i:i+rowSize]) + reverse(dst.Pix[i : i+rowSize]) } - }) - return dst } -// FlipV flips the image vertically (from top to bottom) and returns the transformed image. -func FlipV(img image.Image) *image.NRGBA { - src := toNRGBA(img) - srcW := src.Bounds().Max.X - srcH := src.Bounds().Max.Y - dstW := srcW - dstH := srcH +// Rotate90 rotates the image 90 degrees counter-clockwise and returns the transformed image. +func Rotate90(img image.Image) *image.NRGBA { + src := newScanner(img) + dstW := src.h + dstH := src.w + rowSize := dstW * 4 dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH)) - - parallel(dstH, func(partStart, partEnd int) { - - for dstY := partStart; dstY < partEnd; dstY++ { - for dstX := 0; dstX < dstW; dstX++ { - srcX := dstX - srcY := dstH - dstY - 1 - - srcOff := srcY*src.Stride + srcX*4 - dstOff := dstY*dst.Stride + dstX*4 - - copy(dst.Pix[dstOff:dstOff+4], src.Pix[srcOff:srcOff+4]) - } + parallel(0, dstH, func(ys <-chan int) { + for dstY := range ys { + i := dstY * dst.Stride + srcX := dstH - dstY - 1 + src.scan(srcX, 0, srcX+1, src.h, dst.Pix[i:i+rowSize]) } + }) + return dst +} +// Rotate180 rotates the image 180 degrees counter-clockwise and returns the transformed image. +func Rotate180(img image.Image) *image.NRGBA { + src := newScanner(img) + dstW := src.w + dstH := src.h + rowSize := dstW * 4 + dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH)) + parallel(0, dstH, func(ys <-chan int) { + for dstY := range ys { + i := dstY * dst.Stride + srcY := dstH - dstY - 1 + src.scan(0, srcY, src.w, srcY+1, dst.Pix[i:i+rowSize]) + reverse(dst.Pix[i : i+rowSize]) + } }) + return dst +} +// Rotate270 rotates the image 270 degrees counter-clockwise and returns the transformed image. +func Rotate270(img image.Image) *image.NRGBA { + src := newScanner(img) + dstW := src.h + dstH := src.w + rowSize := dstW * 4 + dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH)) + parallel(0, dstH, func(ys <-chan int) { + for dstY := range ys { + i := dstY * dst.Stride + srcX := dstY + src.scan(srcX, 0, srcX+1, src.h, dst.Pix[i:i+rowSize]) + reverse(dst.Pix[i : i+rowSize]) + } + }) return dst } -// Transpose flips the image horizontally and rotates 90 degrees counter-clockwise. -func Transpose(img image.Image) *image.NRGBA { +// Rotate rotates an image by the given angle counter-clockwise . +// The angle parameter is the rotation angle in degrees. +// The bgColor parameter specifies the color of the uncovered zone after the rotation. +func Rotate(img image.Image, angle float64, bgColor color.Color) *image.NRGBA { + angle = angle - math.Floor(angle/360)*360 + + switch angle { + case 0: + return Clone(img) + case 90: + return Rotate90(img) + case 180: + return Rotate180(img) + case 270: + return Rotate270(img) + } + src := toNRGBA(img) srcW := src.Bounds().Max.X srcH := src.Bounds().Max.Y - dstW := srcH - dstH := srcW + dstW, dstH := rotatedSize(srcW, srcH, angle) dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH)) - parallel(dstH, func(partStart, partEnd int) { + if dstW <= 0 || dstH <= 0 { + return dst + } - for dstY := partStart; dstY < partEnd; dstY++ { - for dstX := 0; dstX < dstW; dstX++ { - srcX := dstY - srcY := dstX + srcXOff := float64(srcW)/2 - 0.5 + srcYOff := float64(srcH)/2 - 0.5 + dstXOff := float64(dstW)/2 - 0.5 + dstYOff := float64(dstH)/2 - 0.5 - srcOff := srcY*src.Stride + srcX*4 - dstOff := dstY*dst.Stride + dstX*4 + bgColorNRGBA := color.NRGBAModel.Convert(bgColor).(color.NRGBA) + sin, cos := math.Sincos(math.Pi * angle / 180) - copy(dst.Pix[dstOff:dstOff+4], src.Pix[srcOff:srcOff+4]) + parallel(0, dstH, func(ys <-chan int) { + for dstY := range ys { + for dstX := 0; dstX < dstW; dstX++ { + xf, yf := rotatePoint(float64(dstX)-dstXOff, float64(dstY)-dstYOff, sin, cos) + xf, yf = xf+srcXOff, yf+srcYOff + interpolatePoint(dst, dstX, dstY, src, xf, yf, bgColorNRGBA) } } - }) return dst } -// Transverse flips the image vertically and rotates 90 degrees counter-clockwise. -func Transverse(img image.Image) *image.NRGBA { - src := toNRGBA(img) - srcW := src.Bounds().Max.X - srcH := src.Bounds().Max.Y - dstW := srcH - dstH := srcW - dst := image.NewNRGBA(image.Rect(0, 0, dstW, dstH)) - - parallel(dstH, func(partStart, partEnd int) { - - for dstY := partStart; dstY < partEnd; dstY++ { - for dstX := 0; dstX < dstW; dstX++ { - srcX := dstH - dstY - 1 - srcY := dstW - dstX - 1 +func rotatePoint(x, y, sin, cos float64) (float64, float64) { + return x*cos - y*sin, x*sin + y*cos +} - srcOff := srcY*src.Stride + srcX*4 - dstOff := dstY*dst.Stride + dstX*4 +func rotatedSize(w, h int, angle float64) (int, int) { + if w <= 0 || h <= 0 { + return 0, 0 + } + + sin, cos := math.Sincos(math.Pi * angle / 180) + x1, y1 := rotatePoint(float64(w-1), 0, sin, cos) + x2, y2 := rotatePoint(float64(w-1), float64(h-1), sin, cos) + x3, y3 := rotatePoint(0, float64(h-1), sin, cos) + + minx := math.Min(x1, math.Min(x2, math.Min(x3, 0))) + maxx := math.Max(x1, math.Max(x2, math.Max(x3, 0))) + miny := math.Min(y1, math.Min(y2, math.Min(y3, 0))) + maxy := math.Max(y1, math.Max(y2, math.Max(y3, 0))) + + neww := maxx - minx + 1 + if neww-math.Floor(neww) > 0.1 { + neww++ + } + newh := maxy - miny + 1 + if newh-math.Floor(newh) > 0.1 { + newh++ + } + + return int(neww), int(newh) +} - copy(dst.Pix[dstOff:dstOff+4], src.Pix[srcOff:srcOff+4]) +func interpolatePoint(dst *image.NRGBA, dstX, dstY int, src *image.NRGBA, xf, yf float64, bgColor color.NRGBA) { + dstIndex := dstY*dst.Stride + dstX*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 + 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 } } - - }) - - return dst + } + + 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 + } + + if a != 0 { + r /= a + g /= a + b /= 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/vendor/github.com/disintegration/imaging/utils.go b/vendor/github.com/disintegration/imaging/utils.go index 8b1ab8a..3b6ad2e 100644 --- a/vendor/github.com/disintegration/imaging/utils.go +++ b/vendor/github.com/disintegration/imaging/utils.go @@ -1,59 +1,41 @@ package imaging import ( - "math" + "image" "runtime" "sync" - "sync/atomic" ) -var parallelizationEnabled = true - -// if GOMAXPROCS = 1: no goroutines used -// if GOMAXPROCS > 1: spawn N=GOMAXPROCS workers in separate goroutines -func parallel(dataSize int, fn func(partStart, partEnd int)) { - numGoroutines := 1 - partSize := dataSize - - if parallelizationEnabled { - numProcs := runtime.GOMAXPROCS(0) - if numProcs > 1 { - numGoroutines = numProcs - partSize = dataSize / (numGoroutines * 10) - if partSize < 1 { - partSize = 1 - } - } +// parallel processes the data in separate goroutines. +func parallel(start, stop int, fn func(<-chan int)) { + count := stop - start + if count < 1 { + return } - if numGoroutines == 1 { - fn(0, dataSize) - } else { - var wg sync.WaitGroup - wg.Add(numGoroutines) - idx := uint64(0) + procs := runtime.GOMAXPROCS(0) + if procs > count { + procs = count + } - for p := 0; p < numGoroutines; p++ { - go func() { - defer wg.Done() - for { - partStart := int(atomic.AddUint64(&idx, uint64(partSize))) - partSize - if partStart >= dataSize { - break - } - partEnd := partStart + partSize - if partEnd > dataSize { - partEnd = dataSize - } - fn(partStart, partEnd) - } - }() - } + c := make(chan int, count) + for i := start; i < stop; i++ { + c <- i + } + close(c) - wg.Wait() + var wg sync.WaitGroup + for i := 0; i < procs; i++ { + wg.Add(1) + go func() { + defer wg.Done() + fn(c) + }() } + wg.Wait() } +// absint returns the absolute value of i. func absint(i int) int { if i < 0 { return -i @@ -61,17 +43,41 @@ func absint(i int) int { 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) +// clamp rounds and clamps float64 value to fit into uint8. +func clamp(x float64) uint8 { + v := int64(x + 0.5) + if v > 255 { + return 255 + } + if v > 0 { + return uint8(v) + } + return 0 } -// clamp int32 to uint8 (0..255) -func clampint32(v int32) uint8 { - if v < 0 { - return 0 - } else if v > 255 { - return 255 +func reverse(pix []uint8) { + if len(pix) <= 4 { + return + } + 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] + i += 4 + j -= 4 + } +} + +func toNRGBA(img image.Image) *image.NRGBA { + if img, ok := img.(*image.NRGBA); ok { + return &image.NRGBA{ + Pix: img.Pix, + Stride: img.Stride, + Rect: img.Rect.Sub(img.Rect.Min), + } } - return uint8(v) + return Clone(img) }