add trim option to image processing and implement trimEdges function

This commit is contained in:
Vetle Leinonen-Roeim 2025-03-28 10:11:43 +01:00
parent 572ad2db78
commit 3ff7fa8b64
3 changed files with 71 additions and 1 deletions

11
data.go
View file

@ -30,6 +30,7 @@ const (
optCropWidth = "cw" optCropWidth = "cw"
optCropHeight = "ch" optCropHeight = "ch"
optSmartCrop = "sc" optSmartCrop = "sc"
optTrim = "trim"
) )
// URLError reports a malformed URL error. // URLError reports a malformed URL error.
@ -80,6 +81,9 @@ type Options struct {
// Automatically find good crop points based on image content. // Automatically find good crop points based on image content.
SmartCrop bool SmartCrop bool
// If true, automatically trim pixels of the same color around the edges
Trim bool
} }
func (o Options) String() string { func (o Options) String() string {
@ -123,6 +127,9 @@ func (o Options) String() string {
if o.SmartCrop { if o.SmartCrop {
opts = append(opts, optSmartCrop) opts = append(opts, optSmartCrop)
} }
if o.Trim {
opts = append(opts, optTrim)
}
sort.Strings(opts) sort.Strings(opts)
return strings.Join(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 // the presence of other fields (like Fit). A non-empty Format value is
// assumed to involve a transformation. // assumed to involve a transformation.
func (o Options) transform() bool { 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. // ParseOptions parses str as a list of comma separated transformation options.
@ -251,6 +258,8 @@ func ParseOptions(str string) Options {
options.Format = opt options.Format = opt
case opt == optSmartCrop: case opt == optSmartCrop:
options.SmartCrop = true options.SmartCrop = true
case opt == optTrim:
options.Trim = true
case strings.HasPrefix(opt, optRotatePrefix): case strings.HasPrefix(opt, optRotatePrefix):
value := strings.TrimPrefix(opt, optRotatePrefix) value := strings.TrimPrefix(opt, optRotatePrefix)
options.Rotate, _ = strconv.Atoi(value) options.Rotate, _ = strconv.Atoi(value)

View file

@ -309,5 +309,47 @@ func transformImage(m image.Image, opt Options) image.Image {
m = imaging.FlipH(m) m = imaging.FlipH(m)
} }
// trim
if opt.Trim {
m = trimEdges(m)
}
return 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
}

View file

@ -375,3 +375,22 @@ func TestTransformImage(t *testing.T) {
} }
} }
} }
func TestTrimBordersOfSameColor(t *testing.T) {
src := newImage(4, 4,
color.NRGBA{255, 255, 255, 255}, color.NRGBA{255, 255, 255, 255}, color.NRGBA{255, 255, 255, 255}, color.NRGBA{255, 255, 255, 255},
color.NRGBA{255, 255, 255, 255}, color.NRGBA{255, 0, 0, 255}, color.NRGBA{255, 0, 0, 255}, color.NRGBA{255, 255, 255, 255},
color.NRGBA{255, 255, 255, 255}, color.NRGBA{255, 0, 0, 255}, color.NRGBA{255, 0, 0, 255}, color.NRGBA{255, 255, 255, 255},
color.NRGBA{255, 255, 255, 255}, color.NRGBA{255, 255, 255, 255}, color.NRGBA{255, 255, 255, 255}, color.NRGBA{255, 255, 255, 255},
)
want := newImage(2, 2,
color.NRGBA{255, 0, 0, 255}, color.NRGBA{255, 0, 0, 255},
color.NRGBA{255, 0, 0, 255}, color.NRGBA{255, 0, 0, 255},
)
got := trimEdges(src)
if !reflect.DeepEqual(got, want) {
t.Errorf("trimEdges() = %v, want %v", got, want)
}
}