From 4b286b9d2722909d0682e50eeecdfe16c1f47fd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Sun, 20 Oct 2019 10:39:00 +0200 Subject: [PATCH] resources/images: Allow to set background fill colour Closes #6298 --- .../image-processing/index.md | 45 +++++++-- hugolib/image_test.go | 5 +- resources/image.go | 38 +++++++- resources/image_test.go | 16 +++- resources/images/color.go | 85 +++++++++++++++++ resources/images/color_test.go | 90 ++++++++++++++++++ resources/images/config.go | 56 +++++++++-- resources/images/config_test.go | 27 ++++-- resources/images/image.go | 39 ++++++-- ...2285_13327_200x0_resize_bge3e615_box_2.png | Bin 0 -> 5622 bytes ..._13327_200x0_resize_q75_bge3e615_box_2.jpg | Bin 0 -> 7626 bytes ...ecfd_20069_200x0_resize_bge3e615_box_2.png | Bin 0 -> 4273 bytes ..._20069_200x0_resize_q75_bge3e615_box_2.jpg | Bin 0 -> 2918 bytes resources/testdata/gopher-hero8.png | Bin 0 -> 13327 bytes resources/testdata/gradient-circle.png | Bin 0 -> 20069 bytes 15 files changed, 356 insertions(+), 45 deletions(-) create mode 100644 resources/images/color.go create mode 100644 resources/images/color_test.go create mode 100644 resources/testdata/golden/gopher-hero8_huaa0cd7d2cfc14ff32a57f171896f2285_13327_200x0_resize_bge3e615_box_2.png create mode 100644 resources/testdata/golden/gopher-hero8_huaa0cd7d2cfc14ff32a57f171896f2285_13327_200x0_resize_q75_bge3e615_box_2.jpg create mode 100644 resources/testdata/golden/gradient-circle_huf3d35257a40a8d6f525263a856c5ecfd_20069_200x0_resize_bge3e615_box_2.png create mode 100644 resources/testdata/golden/gradient-circle_huf3d35257a40a8d6f525263a856c5ecfd_20069_200x0_resize_q75_bge3e615_box_2.jpg create mode 100644 resources/testdata/gopher-hero8.png create mode 100644 resources/testdata/gradient-circle.png diff --git a/docs/content/en/content-management/image-processing/index.md b/docs/content/en/content-management/image-processing/index.md index b83a6c103..f03c5bee6 100644 --- a/docs/content/en/content-management/image-processing/index.md +++ b/docs/content/en/content-management/image-processing/index.md @@ -2,7 +2,6 @@ title: "Image Processing" description: "Image Page resources can be resized and cropped." date: 2018-01-24T13:10:00-05:00 -lastmod: 2018-01-26T15:59:07-05:00 linktitle: "Image Processing" categories: ["content management"] keywords: [bundle,content,resources,images] @@ -72,31 +71,42 @@ Image operations in Hugo currently **do not preserve EXIF data** as this is not In addition to the dimensions (e.g. `600x400`), Hugo supports a set of additional image options. +### Background Color -JPEG Quality -: Only relevant for JPEG images, values 1 to 100 inclusive, higher is better. Default is 75. +The background color to fill into the transparency layer. This is mostly useful when converting to a format that does not support transparency, e.g. `JPEG`. + +You can set the background color to use with a 3 or 6 digit hex code starting with `#`. + +```go +{{ $image.Resize "600x jpg #b31280" }} +``` + +For color codes, see https://www.google.com/search?q=color+picker + +### JPEG Quality +Only relevant for JPEG images, values 1 to 100 inclusive, higher is better. Default is 75. ```go {{ $image.Resize "600x q50" }} ``` -Rotate -: Rotates an image by the given angle counter-clockwise. The rotation will be performed first to get the dimensions correct. The main use of this is to be able to manually correct for [EXIF orientation](https://github.com/golang/go/issues/4341) of JPEG images. +### Rotate +Rotates an image by the given angle counter-clockwise. The rotation will be performed first to get the dimensions correct. The main use of this is to be able to manually correct for [EXIF orientation](https://github.com/golang/go/issues/4341) of JPEG images. ```go {{ $image.Resize "600x r90" }} ``` -Anchor -: Only relevant for the `Fill` method. This is useful for thumbnail generation where the main motive is located in, say, the left corner. +### Anchor +Only relevant for the `Fill` method. This is useful for thumbnail generation where the main motive is located in, say, the left corner. Valid are `Center`, `TopLeft`, `Top`, `TopRight`, `Left`, `Right`, `BottomLeft`, `Bottom`, `BottomRight`. ```go {{ $image.Fill "300x200 BottomLeft" }} ``` -Resample Filter -: Filter used in resizing. Default is `Box`, a simple and fast resampling filter appropriate for downscaling. +### Resample Filter +Filter used in resizing. Default is `Box`, a simple and fast resampling filter appropriate for downscaling. Examples are: `Box`, `NearestNeighbor`, `Linear`, `Gaussian`. @@ -106,6 +116,16 @@ See https://github.com/disintegration/imaging for more. If you want to trade qua {{ $image.Resize "600x400 Gaussian" }} ``` +### Target Format + +By default the images is encoded in the source format, but you can set the target format as an option. + +Valid values are `jpg`, `png`, `tif`, `bmp`, and `gif`. + +```go +{{ $image.Resize "600x jpg" }} +``` + ## Image Processing Examples _The photo of the sunset used in the examples below is Copyright [Bjørn Erik Pedersen](https://commons.wikimedia.org/wiki/User:Bep) (Creative Commons Attribution-Share Alike 4.0 International license)_ @@ -160,6 +180,13 @@ quality = 75 # Valid values are Smart, Center, TopLeft, Top, TopRight, Left, Right, BottomLeft, Bottom, BottomRight anchor = "smart" +# Default background color. +# Hugo will preserve transparency for target formats that supports it, +# but will fall back to this color for JPEG. +# Expects a standard HEX color string with 3 or 6 digits. +# See https://www.google.com/search?q=color+picker +bgColor = "#ffffff" + ``` All of the above settings can also be set per image procecssing. diff --git a/hugolib/image_test.go b/hugolib/image_test.go index a13338afc..d0bff75a2 100644 --- a/hugolib/image_test.go +++ b/hugolib/image_test.go @@ -205,10 +205,11 @@ SUNSET2: {{ $resized2.RelPermalink }}/{{ $resized2.Width }}/Lat: {{ $resized2.Ex // Check the file cache b.AssertImage(200, 200, "resources/_gen/images/bundle/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_200x200_resize_q75_box.jpg") - b.AssertFileContent("resources/_gen/images/bundle/sunset_17701188623491591036.json", + + b.AssertFileContent("resources/_gen/images/bundle/sunset_7645215769587362592.json", "DateTimeDigitized|time.Time", "PENTAX") b.AssertImage(123, 234, "resources/_gen/images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_123x234_resize_q75_box.jpg") - b.AssertFileContent("resources/_gen/images/sunset_17701188623491591036.json", + b.AssertFileContent("resources/_gen/images/sunset_7645215769587362592.json", "DateTimeDigitized|time.Time", "PENTAX") // TODO(bep) add this as a default assertion after Build()? diff --git a/resources/image.go b/resources/image.go index bb9c987a5..1991e65f5 100644 --- a/resources/image.go +++ b/resources/image.go @@ -17,6 +17,7 @@ import ( "encoding/json" "fmt" "image" + "image/color" "image/draw" _ "image/gif" _ "image/png" @@ -254,10 +255,32 @@ func (i *imageResource) doWithImageConfig(conf images.ImageConfig, f func(src im return nil, nil, &os.PathError{Op: errOp, Path: errPath, Err: err} } + hasAlpha := !images.IsOpaque(converted) + shouldFill := conf.BgColor != nil && hasAlpha + shouldFill = shouldFill || (!conf.TargetFormat.SupportsTransparency() && hasAlpha) + var bgColor color.Color + + if shouldFill { + bgColor = conf.BgColor + if bgColor == nil { + bgColor = i.Proc.Cfg.BgColor + } + tmp := image.NewRGBA(converted.Bounds()) + draw.Draw(tmp, tmp.Bounds(), image.NewUniform(bgColor), image.Point{}, draw.Src) + draw.Draw(tmp, tmp.Bounds(), converted, converted.Bounds().Min, draw.Over) + converted = tmp + } + if conf.TargetFormat == images.PNG { // Apply the colour palette from the source if paletted, ok := src.(*image.Paletted); ok { - tmp := image.NewPaletted(converted.Bounds(), paletted.Palette) + palette := paletted.Palette + if bgColor != nil && len(palette) < 256 { + palette = images.AddColorToPalette(bgColor, palette) + } else if bgColor != nil { + images.ReplaceColorInPalette(bgColor, palette) + } + tmp := image.NewPaletted(converted.Bounds(), palette) draw.FloydSteinberg.Draw(tmp, tmp.Bounds(), converted, converted.Bounds().Min) converted = tmp } @@ -273,7 +296,7 @@ func (i *imageResource) doWithImageConfig(conf images.ImageConfig, f func(src im } func (i *imageResource) decodeImageConfig(action, spec string) (images.ImageConfig, error) { - conf, err := images.DecodeImageConfig(action, spec, i.Proc.Cfg) + conf, err := images.DecodeImageConfig(action, spec, i.Proc.Cfg.Cfg) if err != nil { return conf, err } @@ -285,7 +308,14 @@ func (i *imageResource) decodeImageConfig(action, spec string) (images.ImageConf if conf.Quality <= 0 && conf.TargetFormat.RequiresDefaultQuality() { // We need a quality setting for all JPEGs - conf.Quality = i.Proc.Cfg.Quality + conf.Quality = i.Proc.Cfg.Cfg.Quality + } + + if conf.BgColor == nil && conf.TargetFormat != i.Format { + if i.Format.SupportsTransparency() && !conf.TargetFormat.SupportsTransparency() { + conf.BgColor = i.Proc.Cfg.BgColor + conf.BgColorStr = i.Proc.Cfg.Cfg.BgColor + } } return conf, nil @@ -325,7 +355,7 @@ func (i *imageResource) setBasePath(conf images.ImageConfig) { func (i *imageResource) getImageMetaCacheTargetPath() string { const imageMetaVersionNumber = 1 // Increment to invalidate the meta cache - cfg := i.getSpec().imaging.Cfg + cfg := i.getSpec().imaging.Cfg.Cfg df := i.getResourcePaths().relTargetDirFile if fi := i.getFileInfo(); fi != nil { df.dir = filepath.Dir(fi.Meta().Path()) diff --git a/resources/image_test.go b/resources/image_test.go index 4b88b7aa1..89e686ed1 100644 --- a/resources/image_test.go +++ b/resources/image_test.go @@ -22,7 +22,6 @@ import ( "os" "path" "path/filepath" - "regexp" "runtime" "strconv" "sync" @@ -540,6 +539,18 @@ func TestImageOperationsGolden(t *testing.T) { fmt.Println(workDir) } + // Test PNGs with alpha channel. + for _, img := range []string{"gopher-hero8.png", "gradient-circle.png"} { + orig := fetchImageForSpec(spec, c, img) + for _, resizeSpec := range []string{"200x #e3e615", "200x jpg #e3e615"} { + resized, err := orig.Resize(resizeSpec) + c.Assert(err, qt.IsNil) + rel := resized.RelPermalink() + c.Log("resize", rel) + c.Assert(rel, qt.Not(qt.Equals), "") + } + } + for _, img := range testImages { orig := fetchImageForSpec(spec, c, img) @@ -618,9 +629,6 @@ func TestImageOperationsGolden(t *testing.T) { c.Assert(len(dirinfos1), qt.Equals, len(dirinfos2)) for i, fi1 := range dirinfos1 { - if regexp.MustCompile("gauss").MatchString(fi1.Name()) { - continue - } fi2 := dirinfos2[i] c.Assert(fi1.Name(), qt.Equals, fi2.Name()) diff --git a/resources/images/color.go b/resources/images/color.go new file mode 100644 index 000000000..b17173e26 --- /dev/null +++ b/resources/images/color.go @@ -0,0 +1,85 @@ +// Copyright 2019 The Hugo Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package images + +import ( + "encoding/hex" + "image/color" + "strings" + + "github.com/pkg/errors" +) + +// AddColorToPalette adds c as the first color in p if not already there. +// Note that it does no additional checks, so callers must make sure +// that the palette is valid for the relevant format. +func AddColorToPalette(c color.Color, p color.Palette) color.Palette { + var found bool + for _, cc := range p { + if c == cc { + found = true + break + } + } + + if !found { + p = append(color.Palette{c}, p...) + } + + return p +} + +// ReplaceColorInPalette will replace the color in palette p closest to c in Euclidean +// R,G,B,A space with c. +func ReplaceColorInPalette(c color.Color, p color.Palette) { + p[p.Index(c)] = c +} + +func hexStringToColor(s string) (color.Color, error) { + s = strings.TrimPrefix(s, "#") + + if len(s) != 3 && len(s) != 6 { + return nil, errors.Errorf("invalid color code: %q", s) + } + + s = strings.ToLower(s) + + if len(s) == 3 { + var v string + for _, r := range s { + v += string(r) + string(r) + } + s = v + } + + // Standard colors. + if s == "ffffff" { + return color.White, nil + } + + if s == "000000" { + return color.Black, nil + } + + // Set Alfa to white. + s += "ff" + + b, err := hex.DecodeString(s) + if err != nil { + return nil, err + } + + return color.RGBA{b[0], b[1], b[2], b[3]}, nil + +} diff --git a/resources/images/color_test.go b/resources/images/color_test.go new file mode 100644 index 000000000..3ef9f76cc --- /dev/null +++ b/resources/images/color_test.go @@ -0,0 +1,90 @@ +// Copyright 2019 The Hugo Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package images + +import ( + "image/color" + "testing" + + qt "github.com/frankban/quicktest" +) + +func TestHexStringToColor(t *testing.T) { + c := qt.New(t) + + for _, test := range []struct { + arg string + expect interface{} + }{ + {"f", false}, + {"#f", false}, + {"#fffffff", false}, + {"fffffff", false}, + {"#fff", color.White}, + {"fff", color.White}, + {"FFF", color.White}, + {"FfF", color.White}, + {"#ffffff", color.White}, + {"ffffff", color.White}, + {"#000", color.Black}, + {"#4287f5", color.RGBA{R: 0x42, G: 0x87, B: 0xf5, A: 0xff}}, + {"777", color.RGBA{R: 0x77, G: 0x77, B: 0x77, A: 0xff}}, + } { + + test := test + c.Run(test.arg, func(c *qt.C) { + c.Parallel() + + result, err := hexStringToColor(test.arg) + + if b, ok := test.expect.(bool); ok && !b { + c.Assert(err, qt.Not(qt.IsNil)) + return + } + + c.Assert(err, qt.IsNil) + c.Assert(result, qt.DeepEquals, test.expect) + }) + + } +} + +func TestAddColorToPalette(t *testing.T) { + c := qt.New(t) + + palette := color.Palette{color.White, color.Black} + + c.Assert(AddColorToPalette(color.White, palette), qt.HasLen, 2) + + blue1, _ := hexStringToColor("34c3eb") + blue2, _ := hexStringToColor("34c3eb") + white, _ := hexStringToColor("fff") + + c.Assert(AddColorToPalette(white, palette), qt.HasLen, 2) + c.Assert(AddColorToPalette(blue1, palette), qt.HasLen, 3) + c.Assert(AddColorToPalette(blue2, palette), qt.HasLen, 3) + +} + +func TestReplaceColorInPalette(t *testing.T) { + c := qt.New(t) + + palette := color.Palette{color.White, color.Black} + offWhite, _ := hexStringToColor("fcfcfc") + + ReplaceColorInPalette(offWhite, palette) + + c.Assert(palette, qt.HasLen, 2) + c.Assert(palette[0], qt.Equals, offWhite) +} diff --git a/resources/images/config.go b/resources/images/config.go index 6bc701bfe..7b2ade29f 100644 --- a/resources/images/config.go +++ b/resources/images/config.go @@ -16,6 +16,7 @@ package images import ( "errors" "fmt" + "image/color" "strconv" "strings" @@ -27,6 +28,7 @@ import ( const ( defaultJPEGQuality = 75 defaultResampleFilter = "box" + defaultBgColor = "ffffff" ) var ( @@ -87,16 +89,28 @@ func ImageFormatFromExt(ext string) (Format, bool) { return f, found } -func DecodeConfig(m map[string]interface{}) (Imaging, error) { +func DecodeConfig(m map[string]interface{}) (ImagingConfig, error) { var i Imaging + var ic ImagingConfig if err := mapstructure.WeakDecode(m, &i); err != nil { - return i, err + return ic, err } if i.Quality == 0 { i.Quality = defaultJPEGQuality } else if i.Quality < 0 || i.Quality > 100 { - return i, errors.New("JPEG quality must be a number between 1 and 100") + return ic, errors.New("JPEG quality must be a number between 1 and 100") + } + + if i.BgColor != "" { + i.BgColor = strings.TrimPrefix(i.BgColor, "#") + } else { + i.BgColor = defaultBgColor + } + var err error + ic.BgColor, err = hexStringToColor(i.BgColor) + if err != nil { + return ic, err } if i.Anchor == "" || strings.EqualFold(i.Anchor, smartCropIdentifier) { @@ -104,7 +118,7 @@ func DecodeConfig(m map[string]interface{}) (Imaging, error) { } else { i.Anchor = strings.ToLower(i.Anchor) if _, found := anchorPositions[i.Anchor]; !found { - return i, errors.New("invalid anchor value in imaging config") + return ic, errors.New("invalid anchor value in imaging config") } } @@ -114,7 +128,7 @@ func DecodeConfig(m map[string]interface{}) (Imaging, error) { filter := strings.ToLower(i.ResampleFilter) _, found := imageFilters[filter] if !found { - return i, fmt.Errorf("%q is not a valid resample filter", filter) + return ic, fmt.Errorf("%q is not a valid resample filter", filter) } i.ResampleFilter = filter } @@ -124,7 +138,9 @@ func DecodeConfig(m map[string]interface{}) (Imaging, error) { i.Exif.ExcludeFields = "GPS|Exif|Exposure[M|P|B]|Contrast|Resolution|Sharp|JPEG|Metering|Sensing|Saturation|ColorSpace|Flash|WhiteBalance" } - return i, nil + ic.Cfg = i + + return ic, nil } func DecodeImageConfig(action, config string, defaults Imaging) (ImageConfig, error) { @@ -151,6 +167,12 @@ func DecodeImageConfig(action, config string, defaults Imaging) (ImageConfig, er } else if filter, ok := imageFilters[part]; ok { c.Filter = filter c.FilterStr = part + } else if part[0] == '#' { + c.BgColorStr = part[1:] + c.BgColor, err = hexStringToColor(c.BgColorStr) + if err != nil { + return c, err + } } else if part[0] == 'q' { c.Quality, err = strconv.Atoi(part[1:]) if err != nil { @@ -230,6 +252,14 @@ type ImageConfig struct { // The rotation will be performed first. Rotate int + // Used to fill any transparency. + // When set in site config, it's used when converting to a format that does + // not support transparency. + // When set per image operation, it's used even for formats that does support + // transparency. + BgColor color.Color + BgColorStr string + Width int Height int @@ -255,6 +285,10 @@ func (i ImageConfig) GetKey(format Format) string { if i.Rotate != 0 { k += "_r" + strconv.Itoa(i.Rotate) } + if i.BgColorStr != "" { + k += "_bg" + i.BgColorStr + } + anchor := i.AnchorStr if anchor == smartCropIdentifier { anchor = anchor + strconv.Itoa(smartCropVersionNumber) @@ -277,6 +311,13 @@ func (i ImageConfig) GetKey(format Format) string { return k } +type ImagingConfig struct { + BgColor color.Color + + // Config as provided by the user. + Cfg Imaging +} + // Imaging contains default image processing configuration. This will be fetched // from site (or language) config. type Imaging struct { @@ -289,6 +330,9 @@ type Imaging struct { // The anchor to use in Fill. Default is "smart", i.e. Smart Crop. Anchor string + // Default color used in fill operations (e.g. "fff" for white). + BgColor string + Exif ExifConfig } diff --git a/resources/images/config_test.go b/resources/images/config_test.go index 46b0c9858..f60cce9ef 100644 --- a/resources/images/config_test.go +++ b/resources/images/config_test.go @@ -29,17 +29,19 @@ func TestDecodeConfig(t *testing.T) { "anchor": "topLeft", } - imaging, err := DecodeConfig(m) + imagingConfig, err := DecodeConfig(m) c.Assert(err, qt.IsNil) + imaging := imagingConfig.Cfg c.Assert(imaging.Quality, qt.Equals, 42) c.Assert(imaging.ResampleFilter, qt.Equals, "nearestneighbor") c.Assert(imaging.Anchor, qt.Equals, "topleft") m = map[string]interface{}{} - imaging, err = DecodeConfig(m) + imagingConfig, err = DecodeConfig(m) c.Assert(err, qt.IsNil) + imaging = imagingConfig.Cfg c.Assert(imaging.Quality, qt.Equals, defaultJPEGQuality) c.Assert(imaging.ResampleFilter, qt.Equals, "box") c.Assert(imaging.Anchor, qt.Equals, "smart") @@ -59,18 +61,20 @@ func TestDecodeConfig(t *testing.T) { }) c.Assert(err, qt.Not(qt.IsNil)) - imaging, err = DecodeConfig(map[string]interface{}{ + imagingConfig, err = DecodeConfig(map[string]interface{}{ "anchor": "Smart", }) + imaging = imagingConfig.Cfg c.Assert(err, qt.IsNil) c.Assert(imaging.Anchor, qt.Equals, "smart") - imaging, err = DecodeConfig(map[string]interface{}{ + imagingConfig, err = DecodeConfig(map[string]interface{}{ "exif": map[string]interface{}{ "disableLatLong": true, }, }) c.Assert(err, qt.IsNil) + imaging = imagingConfig.Cfg c.Assert(imaging.Exif.DisableLatLong, qt.Equals, true) c.Assert(imaging.Exif.ExcludeFields, qt.Equals, "GPS|Exif|Exposure[M|P|B]|Contrast|Resolution|Sharp|JPEG|Metering|Sensing|Saturation|ColorSpace|Flash|WhiteBalance") @@ -81,11 +85,12 @@ func TestDecodeImageConfig(t *testing.T) { in string expect interface{} }{ - {"300x400", newImageConfig(300, 400, 0, 0, "", "")}, - {"100x200 bottomRight", newImageConfig(100, 200, 0, 0, "", "BottomRight")}, - {"10x20 topleft Lanczos", newImageConfig(10, 20, 0, 0, "Lanczos", "topleft")}, - {"linear left 10x r180", newImageConfig(10, 0, 0, 180, "linear", "left")}, - {"x20 riGht Cosine q95", newImageConfig(0, 20, 95, 0, "cosine", "right")}, + {"300x400", newImageConfig(300, 400, 0, 0, "", "", "")}, + {"300x400 #fff", newImageConfig(300, 400, 0, 0, "", "", "fff")}, + {"100x200 bottomRight", newImageConfig(100, 200, 0, 0, "", "BottomRight", "")}, + {"10x20 topleft Lanczos", newImageConfig(10, 20, 0, 0, "Lanczos", "topleft", "")}, + {"linear left 10x r180", newImageConfig(10, 0, 0, 180, "linear", "left", "")}, + {"x20 riGht Cosine q95", newImageConfig(0, 20, 95, 0, "cosine", "right", "")}, {"", false}, {"foo", false}, @@ -107,13 +112,15 @@ func TestDecodeImageConfig(t *testing.T) { } } -func newImageConfig(width, height, quality, rotate int, filter, anchor string) ImageConfig { +func newImageConfig(width, height, quality, rotate int, filter, anchor, bgColor string) ImageConfig { var c ImageConfig c.Action = "resize" c.Width = width c.Height = height c.Quality = quality c.Rotate = rotate + c.BgColorStr = bgColor + c.BgColor, _ = hexStringToColor(bgColor) if filter != "" { filter = strings.ToLower(filter) diff --git a/resources/images/image.go b/resources/images/image.go index bd7500c28..bac05ab70 100644 --- a/resources/images/image.go +++ b/resources/images/image.go @@ -51,11 +51,8 @@ func NewImage(f Format, proc *ImageProcessor, img image.Image, s Spec) *Image { type Image struct { Format Format - - Proc *ImageProcessor - - Spec Spec - + Proc *ImageProcessor + Spec Spec *imageConfig } @@ -158,8 +155,8 @@ func (i *Image) initConfig() error { return nil } -func NewImageProcessor(cfg Imaging) (*ImageProcessor, error) { - e := cfg.Exif +func NewImageProcessor(cfg ImagingConfig) (*ImageProcessor, error) { + e := cfg.Cfg.Exif exifDecoder, err := exif.NewDecoder( exif.WithDateDisabled(e.DisableDate), exif.WithLatLongDisabled(e.DisableLatLong), @@ -179,7 +176,7 @@ func NewImageProcessor(cfg Imaging) (*ImageProcessor, error) { } type ImageProcessor struct { - Cfg Imaging + Cfg ImagingConfig exifDecoder *exif.Decoder } @@ -218,7 +215,12 @@ func (p *ImageProcessor) ApplyFiltersFromConfig(src image.Image, conf ImageConfi return nil, errors.Errorf("unsupported action: %q", conf.Action) } - return p.Filter(src, filters...) + img, err := p.Filter(src, filters...) + if err != nil { + return nil, err + } + + return img, nil } func (p *ImageProcessor) Filter(src image.Image, filters ...gift.Filter) (image.Image, error) { @@ -231,7 +233,7 @@ func (p *ImageProcessor) Filter(src image.Image, filters ...gift.Filter) (image. func (p *ImageProcessor) GetDefaultImageConfig(action string) ImageConfig { return ImageConfig{ Action: action, - Quality: p.Cfg.Quality, + Quality: p.Cfg.Cfg.Quality, } } @@ -256,6 +258,11 @@ func (f Format) RequiresDefaultQuality() bool { return f == JPEG } +// SupportsTransparency reports whether it supports transparency in any form. +func (f Format) SupportsTransparency() bool { + return f != JPEG +} + // DefaultExtension returns the default file extension of this format, starting with a dot. // For example: .jpg for JPEG func (f Format) DefaultExtension() string { @@ -307,3 +314,15 @@ func ToFilters(in interface{}) []gift.Filter { panic(fmt.Sprintf("%T is not an image filter", in)) } } + +// IsOpaque returns false if the image has alpha channel and there is at least 1 +// pixel that is not (fully) opaque. +func IsOpaque(img image.Image) bool { + if oim, ok := img.(interface { + Opaque() bool + }); ok { + return oim.Opaque() + } + + return false +} diff --git a/resources/testdata/golden/gopher-hero8_huaa0cd7d2cfc14ff32a57f171896f2285_13327_200x0_resize_bge3e615_box_2.png b/resources/testdata/golden/gopher-hero8_huaa0cd7d2cfc14ff32a57f171896f2285_13327_200x0_resize_bge3e615_box_2.png new file mode 100644 index 0000000000000000000000000000000000000000..830ee906b60e38ede61c23c71f1c5bb94cc3bc3b GIT binary patch literal 5622 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 Q%m4rY07*qoM6N<$f_4HK3jhEB literal 0 HcmV?d00001 diff --git a/resources/testdata/golden/gopher-hero8_huaa0cd7d2cfc14ff32a57f171896f2285_13327_200x0_resize_q75_bge3e615_box_2.jpg b/resources/testdata/golden/gopher-hero8_huaa0cd7d2cfc14ff32a57f171896f2285_13327_200x0_resize_q75_bge3e615_box_2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..4ae6f51737afcdff6a49cda9997f5ab31a1559e9 GIT binary patch literal 7626 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 SBBN)27l1@q88yH$#Qy;_?Y-*& literal 0 HcmV?d00001 diff --git a/resources/testdata/golden/gradient-circle_huf3d35257a40a8d6f525263a856c5ecfd_20069_200x0_resize_q75_bge3e615_box_2.jpg b/resources/testdata/golden/gradient-circle_huf3d35257a40a8d6f525263a856c5ecfd_20069_200x0_resize_q75_bge3e615_box_2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..beb80bb123dc0764a403e4a0e5d36fef804e69cf GIT binary patch literal 2918 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 literal 0 HcmV?d00001 diff --git a/resources/testdata/gopher-hero8.png b/resources/testdata/gopher-hero8.png new file mode 100644 index 0000000000000000000000000000000000000000..08ae570d220e4ac23316d9392e7703117cb4f412 GIT binary patch literal 13327 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 literal 0 HcmV?d00001