mirror of
https://github.com/willnorris/imageproxy.git
synced 2025-04-15 03:03:10 -05:00
Merge 493fe180d3
into 572ad2db78
This commit is contained in:
commit
cd57595520
3 changed files with 239 additions and 1 deletions
18
data.go
18
data.go
|
@ -30,6 +30,7 @@ const (
|
|||
optCropWidth = "cw"
|
||||
optCropHeight = "ch"
|
||||
optSmartCrop = "sc"
|
||||
optTrim = "trim"
|
||||
)
|
||||
|
||||
// URLError reports a malformed URL error.
|
||||
|
@ -80,6 +81,9 @@ type Options struct {
|
|||
|
||||
// Automatically find good crop points based on image content.
|
||||
SmartCrop bool
|
||||
|
||||
// If true, automatically trim pixels of the same color around the edges
|
||||
Trim bool
|
||||
}
|
||||
|
||||
func (o Options) String() string {
|
||||
|
@ -123,6 +127,9 @@ func (o Options) String() string {
|
|||
if o.SmartCrop {
|
||||
opts = append(opts, optSmartCrop)
|
||||
}
|
||||
if o.Trim {
|
||||
opts = append(opts, optTrim)
|
||||
}
|
||||
sort.Strings(opts)
|
||||
return strings.Join(opts, ",")
|
||||
}
|
||||
|
@ -132,7 +139,7 @@ func (o Options) String() string {
|
|||
// the presence of other fields (like Fit). A non-empty Format value is
|
||||
// assumed to involve a transformation.
|
||||
func (o Options) transform() bool {
|
||||
return o.Width != 0 || o.Height != 0 || o.Rotate != 0 || o.FlipHorizontal || o.FlipVertical || o.Quality != 0 || o.Format != "" || o.CropX != 0 || o.CropY != 0 || o.CropWidth != 0 || o.CropHeight != 0
|
||||
return o.Width != 0 || o.Height != 0 || o.Rotate != 0 || o.FlipHorizontal || o.FlipVertical || o.Quality != 0 || o.Format != "" || o.CropX != 0 || o.CropY != 0 || o.CropWidth != 0 || o.CropHeight != 0 || o.Trim
|
||||
}
|
||||
|
||||
// ParseOptions parses str as a list of comma separated transformation options.
|
||||
|
@ -219,6 +226,13 @@ func (o Options) transform() bool {
|
|||
// See https://github.com/willnorris/imageproxy/blob/master/docs/url-signing.md
|
||||
// for examples of generating signatures.
|
||||
//
|
||||
// # Trim
|
||||
//
|
||||
// The "trim" option will automatically trim pixels of the same color around
|
||||
// the edges of the image. This is useful for removing borders from images
|
||||
// that have been resized or cropped. The trim option is applied after any
|
||||
// cropping or resizing has been performed.
|
||||
//
|
||||
// Examples
|
||||
//
|
||||
// 0x0 - no resizing
|
||||
|
@ -251,6 +265,8 @@ func ParseOptions(str string) Options {
|
|||
options.Format = opt
|
||||
case opt == optSmartCrop:
|
||||
options.SmartCrop = true
|
||||
case opt == optTrim:
|
||||
options.Trim = true
|
||||
case strings.HasPrefix(opt, optRotatePrefix):
|
||||
value := strings.TrimPrefix(opt, optRotatePrefix)
|
||||
options.Rotate, _ = strconv.Atoi(value)
|
||||
|
|
42
transform.go
42
transform.go
|
@ -309,5 +309,47 @@ func transformImage(m image.Image, opt Options) image.Image {
|
|||
m = imaging.FlipH(m)
|
||||
}
|
||||
|
||||
// trim
|
||||
if opt.Trim {
|
||||
m = trimEdges(m)
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func trimEdges(img image.Image) image.Image {
|
||||
bounds := img.Bounds()
|
||||
minX, minY, maxX, maxY := bounds.Max.X, bounds.Max.Y, bounds.Min.X, bounds.Min.Y
|
||||
|
||||
// Get the color of the first pixel (top-left corner)
|
||||
baseColor := img.At(bounds.Min.X, bounds.Min.Y)
|
||||
|
||||
// Check each pixel and find the bounding box of non-matching pixels
|
||||
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
|
||||
for x := bounds.Min.X; x < bounds.Max.X; x++ {
|
||||
if img.At(x, y) != baseColor { // Non-matching pixel
|
||||
if x < minX {
|
||||
minX = x
|
||||
}
|
||||
if y < minY {
|
||||
minY = y
|
||||
}
|
||||
if x > maxX {
|
||||
maxX = x
|
||||
}
|
||||
if y > maxY {
|
||||
maxY = y
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If no non-matching pixels are found, return the original image
|
||||
if minX > maxX || minY > maxY {
|
||||
return img
|
||||
}
|
||||
|
||||
// Crop the image to the bounding box of non-matching pixels
|
||||
croppedImg := imaging.Crop(img, image.Rect(minX, minY, maxX+1, maxY+1))
|
||||
return croppedImg
|
||||
}
|
||||
|
|
|
@ -375,3 +375,183 @@ func TestTransformImage(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrimBordersOfSameColor(t *testing.T) {
|
||||
w := color.NRGBA{255, 255, 255, 255}
|
||||
r := color.NRGBA{255, 0, 0, 255}
|
||||
src := newImage(4, 4,
|
||||
w, w, w, w,
|
||||
w, r, r, w,
|
||||
w, r, r, w,
|
||||
w, w, w, w,
|
||||
)
|
||||
|
||||
want := newImage(2, 2,
|
||||
r, r,
|
||||
r, r,
|
||||
)
|
||||
|
||||
got := trimEdges(src)
|
||||
// Compare pixel data
|
||||
if !compareImages(got, want) {
|
||||
t.Errorf("trimEdges() pixel data does not match expected result")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrimEdgesSingleColorImage(t *testing.T) {
|
||||
// Create an 8x8 image filled with a single color (white)
|
||||
src := newImage(8, 8, color.NRGBA{255, 255, 255, 255})
|
||||
|
||||
// The expected result should be the same as the source image
|
||||
want := src
|
||||
|
||||
// Apply the trimEdges function
|
||||
got := trimEdges(src)
|
||||
|
||||
// Compare pixel data
|
||||
if !compareImages(got, want) {
|
||||
t.Errorf("trimEdges() pixel data does not match expected result")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrimEdgesCircle(t *testing.T) {
|
||||
// Define colors for better readability
|
||||
w := color.NRGBA{255, 255, 255, 255}
|
||||
r := color.NRGBA{255, 0, 0, 255}
|
||||
|
||||
// Create a 9x9 image with a white background and a larger red circle in the center
|
||||
src := newImage(9, 9,
|
||||
w, w, w, w, w, w, w, w, w,
|
||||
w, w, w, r, r, r, w, w, w,
|
||||
w, w, r, r, r, r, r, w, w,
|
||||
w, r, r, r, r, r, r, r, w,
|
||||
w, r, r, r, r, r, r, r, w,
|
||||
w, r, r, r, r, r, r, r, w,
|
||||
w, w, r, r, r, r, r, w, w,
|
||||
w, w, w, r, r, r, w, w, w,
|
||||
w, w, w, w, w, w, w, w, w,
|
||||
)
|
||||
|
||||
// Expected result: a trimmed 7x7 image containing only the circle
|
||||
want := newImage(7, 7,
|
||||
w, w, r, r, r, w, w,
|
||||
w, r, r, r, r, r, w,
|
||||
r, r, r, r, r, r, r,
|
||||
r, r, r, r, r, r, r,
|
||||
r, r, r, r, r, r, r,
|
||||
w, r, r, r, r, r, w,
|
||||
w, w, r, r, r, w, w,
|
||||
)
|
||||
|
||||
// Apply the trimEdges function
|
||||
got := trimEdges(src)
|
||||
|
||||
// Compare pixel data
|
||||
if !compareImages(got, want) {
|
||||
t.Errorf("trimEdges() pixel data does not match expected result")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrimEdgesUnevenVerticalRectangle(t *testing.T) {
|
||||
// Define colors for better readability
|
||||
w := color.NRGBA{255, 255, 255, 255}
|
||||
r := color.NRGBA{255, 0, 0, 255}
|
||||
|
||||
// Create a 9x5 image with a white background and a red diagonal shape
|
||||
src := newImage(5, 9,
|
||||
w, w, w, w, w,
|
||||
w, w, w, r, w,
|
||||
w, w, r, w, w,
|
||||
w, r, w, w, w,
|
||||
w, r, w, w, w,
|
||||
w, r, w, w, w,
|
||||
w, w, r, w, w,
|
||||
w, w, w, r, w,
|
||||
w, w, w, w, w,
|
||||
)
|
||||
|
||||
// Expected result: a trimmed 5x5 image containing only the diagonal shape
|
||||
want := newImage(3, 7,
|
||||
w, w, r,
|
||||
w, r, w,
|
||||
r, w, w,
|
||||
r, w, w,
|
||||
r, w, w,
|
||||
w, r, w,
|
||||
w, w, r,
|
||||
)
|
||||
|
||||
// Apply the trimEdges function
|
||||
got := trimEdges(src)
|
||||
|
||||
// Compare pixel data
|
||||
if !compareImages(got, want) {
|
||||
t.Errorf("trimEdges() pixel data does not match expected result")
|
||||
}
|
||||
}
|
||||
|
||||
func compareImages(img1, img2 image.Image) bool {
|
||||
bounds1 := img1.Bounds()
|
||||
bounds2 := img2.Bounds()
|
||||
if !bounds1.Eq(bounds2) {
|
||||
return false
|
||||
}
|
||||
|
||||
for y := bounds1.Min.Y; y < bounds1.Max.Y; y++ {
|
||||
for x := bounds1.Min.X; x < bounds1.Max.X; x++ {
|
||||
r1, g1, b1, a1 := img1.At(x, y).RGBA()
|
||||
r2, g2, b2, a2 := img2.At(x, y).RGBA()
|
||||
if r1 != r2 || g1 != g2 || b1 != b2 || a1 != a2 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func TestTrimEdgesUneven(t *testing.T) {
|
||||
// Define colors for better readability
|
||||
w := color.NRGBA{255, 255, 255, 255} // white
|
||||
r := color.NRGBA{255, 0, 0, 255} // red
|
||||
|
||||
// Create a 6x4 image with a white background and a red inner rectangle
|
||||
src := newImage(4, 6,
|
||||
w, w, w, w,
|
||||
w, w, r, w,
|
||||
w, r, r, w,
|
||||
w, r, r, w,
|
||||
w, w, w, w,
|
||||
w, w, w, w,
|
||||
)
|
||||
|
||||
// Expected result: a trimmed 2x3 image containing only the red rectangle
|
||||
want := newImage(2, 3,
|
||||
w, r,
|
||||
r, r,
|
||||
r, r,
|
||||
)
|
||||
|
||||
// Apply the trimEdges function
|
||||
got := trimEdges(src)
|
||||
|
||||
// Compare pixel data
|
||||
if !compareImages(got, want) {
|
||||
t.Errorf("trimEdges() pixel data does not match expected result")
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrimEdgesEmptyImage(t *testing.T) {
|
||||
// Create an empty image (0x0 dimensions)
|
||||
src := newImage(0, 0)
|
||||
|
||||
// The expected result should also be an empty image
|
||||
want := src
|
||||
|
||||
// Apply the trimEdges function
|
||||
got := trimEdges(src)
|
||||
|
||||
// Compare pixel data
|
||||
if !compareImages(got, want) {
|
||||
t.Errorf("trimEdges() for empty image returned %v, want %v", got.Bounds(), want.Bounds())
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue