mirror of
https://github.com/gohugoio/hugo.git
synced 2024-11-21 20:46:30 -05:00
parent
509d39fa6d
commit
33d5f80592
15 changed files with 344 additions and 117 deletions
|
@ -120,7 +120,11 @@ func GetDependencyList() []string {
|
||||||
}
|
}
|
||||||
|
|
||||||
if IsExtended {
|
if IsExtended {
|
||||||
deps = append(deps, formatDep("github.com/sass/libsass", "3.6.4"))
|
deps = append(
|
||||||
|
deps,
|
||||||
|
formatDep("github.com/sass/libsass", "3.6.4"),
|
||||||
|
formatDep("github.com/webmproject/libwebp", "v1.2.0"),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
bi, ok := debug.ReadBuildInfo()
|
bi, ok := debug.ReadBuildInfo()
|
||||||
|
|
|
@ -167,14 +167,28 @@ For color codes, see https://www.google.com/search?q=color+picker
|
||||||
|
|
||||||
**Note** that you also set a default background color to use, see [Image Processing Config](#image-processing-config).
|
**Note** that you also set a default background color to use, see [Image Processing Config](#image-processing-config).
|
||||||
|
|
||||||
### JPEG Quality
|
### JPEG and Webp Quality
|
||||||
|
|
||||||
Only relevant for JPEG images, values 1 to 100 inclusive, higher is better. Default is 75.
|
Only relevant for JPEG and Webp images, values 1 to 100 inclusive, higher is better. Default is 75.
|
||||||
|
|
||||||
```go
|
```go
|
||||||
{{ $image.Resize "600x q50" }}
|
{{ $image.Resize "600x q50" }}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
{{< new-in "0.83.0" >}} Webp support was added in Hugo 0.83.0.
|
||||||
|
|
||||||
|
### Hint {{< new-in "0.83.0" >}}
|
||||||
|
|
||||||
|
Hint about what type of image this is. Currently only used when encoding to Webp.
|
||||||
|
|
||||||
|
Default value is `photo`.
|
||||||
|
|
||||||
|
Valid values are `picture`, `photo`, `drawing`, `icon`, or `text`.
|
||||||
|
|
||||||
|
```go
|
||||||
|
{{ $image.Resize "600x webp drawing" }}
|
||||||
|
```
|
||||||
|
|
||||||
### Rotate
|
### 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.
|
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.
|
||||||
|
@ -258,9 +272,14 @@ You can configure an `imaging` section in `config.toml` with default image proce
|
||||||
# See https://github.com/disintegration/imaging
|
# See https://github.com/disintegration/imaging
|
||||||
resampleFilter = "box"
|
resampleFilter = "box"
|
||||||
|
|
||||||
# Default JPEG quality setting. Default is 75.
|
# Default JPEG or WEBP quality setting. Default is 75.
|
||||||
quality = 75
|
quality = 75
|
||||||
|
|
||||||
|
# Default hint about what type of image. Currently only used for Webp encoding.
|
||||||
|
# Default is "photo".
|
||||||
|
# Valid values are "picture", "photo", "drawing", "icon", or "text".
|
||||||
|
hint = "photo"
|
||||||
|
|
||||||
# Anchor used when cropping pictures.
|
# Anchor used when cropping pictures.
|
||||||
# Default is "smart" which does Smart Cropping, using https://github.com/muesli/smartcrop
|
# Default is "smart" which does Smart Cropping, using https://github.com/muesli/smartcrop
|
||||||
# Smart Cropping is content aware and tries to find the best crop for each image.
|
# Smart Cropping is content aware and tries to find the best crop for each image.
|
||||||
|
|
3
go.mod
3
go.mod
|
@ -13,6 +13,7 @@ require (
|
||||||
github.com/bep/gitmap v1.1.2
|
github.com/bep/gitmap v1.1.2
|
||||||
github.com/bep/godartsass v0.12.0
|
github.com/bep/godartsass v0.12.0
|
||||||
github.com/bep/golibsass v0.7.0
|
github.com/bep/golibsass v0.7.0
|
||||||
|
github.com/bep/gowebp v0.1.0 // indirect
|
||||||
github.com/bep/tmc v0.5.1
|
github.com/bep/tmc v0.5.1
|
||||||
github.com/cli/safeexec v1.0.0
|
github.com/cli/safeexec v1.0.0
|
||||||
github.com/disintegration/gift v1.2.1
|
github.com/disintegration/gift v1.2.1
|
||||||
|
@ -59,7 +60,7 @@ require (
|
||||||
github.com/yuin/goldmark v1.3.2
|
github.com/yuin/goldmark v1.3.2
|
||||||
github.com/yuin/goldmark-highlighting v0.0.0-20200307114337-60d527fdb691
|
github.com/yuin/goldmark-highlighting v0.0.0-20200307114337-60d527fdb691
|
||||||
gocloud.dev v0.20.0
|
gocloud.dev v0.20.0
|
||||||
golang.org/x/image v0.0.0-20191214001246-9130b4cfad52
|
golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb
|
||||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4
|
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||||
golang.org/x/text v0.3.5
|
golang.org/x/text v0.3.5
|
||||||
|
|
16
go.sum
16
go.sum
|
@ -134,6 +134,20 @@ github.com/bep/godartsass v0.12.0 h1:VvGLA4XpXUjKvp53SI05YFLhRFJ78G+Ybnlaz6Oul7E
|
||||||
github.com/bep/godartsass v0.12.0/go.mod h1:nXQlHHk4H1ghUk6n/JkYKG5RD43yJfcfp5aHRqT/pc4=
|
github.com/bep/godartsass v0.12.0/go.mod h1:nXQlHHk4H1ghUk6n/JkYKG5RD43yJfcfp5aHRqT/pc4=
|
||||||
github.com/bep/golibsass v0.7.0 h1:/ocxgtPZ5rgp7FA+mktzyent+fAg82tJq4iMsTMBAtA=
|
github.com/bep/golibsass v0.7.0 h1:/ocxgtPZ5rgp7FA+mktzyent+fAg82tJq4iMsTMBAtA=
|
||||||
github.com/bep/golibsass v0.7.0/go.mod h1:DL87K8Un/+pWUS75ggYv41bliGiolxzDKWJAq3eJ1MA=
|
github.com/bep/golibsass v0.7.0/go.mod h1:DL87K8Un/+pWUS75ggYv41bliGiolxzDKWJAq3eJ1MA=
|
||||||
|
github.com/bep/gowebp v0.0.0-20210408171434-03ecbe0b5d53 h1:bTIhFx2ZEAZD74LwuVdrdZ4070bE9UE5oR5NTBYLtVs=
|
||||||
|
github.com/bep/gowebp v0.0.0-20210408171434-03ecbe0b5d53/go.mod h1:ZhFodwdiFp8ehGJpF4LdPl6unxZm9lLFjxD3z2h2AgI=
|
||||||
|
github.com/bep/gowebp v0.0.0-20210409123354-5e38121e4f6b h1:LLrQFlG0VSxmyz3izTUQnPOGf7Mjiy7wlEu2sDLA+qg=
|
||||||
|
github.com/bep/gowebp v0.0.0-20210409123354-5e38121e4f6b/go.mod h1:ZhFodwdiFp8ehGJpF4LdPl6unxZm9lLFjxD3z2h2AgI=
|
||||||
|
github.com/bep/gowebp v0.0.0-20210410152255-50a32861b5a2 h1:uEpPD0fLZs5IjgF/96LqWHUNY9Pr/0KqLWIQ4gJnYhY=
|
||||||
|
github.com/bep/gowebp v0.0.0-20210410152255-50a32861b5a2/go.mod h1:ZhFodwdiFp8ehGJpF4LdPl6unxZm9lLFjxD3z2h2AgI=
|
||||||
|
github.com/bep/gowebp v0.0.0-20210410161412-b86a3337b39f h1:hvhG2nwoIvHhFnL8GnYtOquHE6dG+mHwthugLqf4spY=
|
||||||
|
github.com/bep/gowebp v0.0.0-20210410161412-b86a3337b39f/go.mod h1:ZhFodwdiFp8ehGJpF4LdPl6unxZm9lLFjxD3z2h2AgI=
|
||||||
|
github.com/bep/gowebp v0.0.0-20210411110227-3a211f6b6461 h1:5HLIo8LF4iKFdxPBDo9CO8oTac18mAx7FJsQG6MNbCU=
|
||||||
|
github.com/bep/gowebp v0.0.0-20210411110227-3a211f6b6461/go.mod h1:ZhFodwdiFp8ehGJpF4LdPl6unxZm9lLFjxD3z2h2AgI=
|
||||||
|
github.com/bep/gowebp v0.0.0-20210411155607-38d8f20d562b h1:VIW6UmIG4ogbswbDFBjVm6/7j9I5i0GouDJ2USn/NUI=
|
||||||
|
github.com/bep/gowebp v0.0.0-20210411155607-38d8f20d562b/go.mod h1:ZhFodwdiFp8ehGJpF4LdPl6unxZm9lLFjxD3z2h2AgI=
|
||||||
|
github.com/bep/gowebp v0.1.0 h1:4/iQpfnxHyXs3x/aTxMMdOpLEQQhFmF6G7EieWPTQyo=
|
||||||
|
github.com/bep/gowebp v0.1.0/go.mod h1:ZhFodwdiFp8ehGJpF4LdPl6unxZm9lLFjxD3z2h2AgI=
|
||||||
github.com/bep/tmc v0.5.1 h1:CsQnSC6MsomH64gw0cT5f+EwQDcvZz4AazKunFwTpuI=
|
github.com/bep/tmc v0.5.1 h1:CsQnSC6MsomH64gw0cT5f+EwQDcvZz4AazKunFwTpuI=
|
||||||
github.com/bep/tmc v0.5.1/go.mod h1:tGYHN8fS85aJPhDLgXETVKp+PR382OvFi2+q2GkGsq0=
|
github.com/bep/tmc v0.5.1/go.mod h1:tGYHN8fS85aJPhDLgXETVKp+PR382OvFi2+q2GkGsq0=
|
||||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||||
|
@ -566,6 +580,8 @@ golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMx
|
||||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/image v0.0.0-20191214001246-9130b4cfad52 h1:2fktqPPvDiVEEVT/vSTeoUPXfmRxRaGy6GU8jypvEn0=
|
golang.org/x/image v0.0.0-20191214001246-9130b4cfad52 h1:2fktqPPvDiVEEVT/vSTeoUPXfmRxRaGy6GU8jypvEn0=
|
||||||
golang.org/x/image v0.0.0-20191214001246-9130b4cfad52/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
golang.org/x/image v0.0.0-20191214001246-9130b4cfad52/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
|
golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb h1:fqpd0EBDzlHRCjiphRR5Zo/RSWWQlWv34418dnEixWk=
|
||||||
|
golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
|
|
@ -236,10 +236,10 @@ SUNSET2: {{ $resized2.RelPermalink }}/{{ $resized2.Width }}/Lat: {{ $resized2.Ex
|
||||||
// Check the file cache
|
// Check the file cache
|
||||||
b.AssertImage(200, 200, "resources/_gen/images/bundle/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_200x200_resize_q75_box.jpg")
|
b.AssertImage(200, 200, "resources/_gen/images/bundle/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_200x200_resize_q75_box.jpg")
|
||||||
|
|
||||||
b.AssertFileContent("resources/_gen/images/bundle/sunset_7645215769587362592.json",
|
b.AssertFileContent("resources/_gen/images/bundle/sunset_3166614710256882113.json",
|
||||||
"DateTimeDigitized|time.Time", "PENTAX")
|
"DateTimeDigitized|time.Time", "PENTAX")
|
||||||
b.AssertImage(123, 234, "resources/_gen/images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_123x234_resize_q75_box.jpg")
|
b.AssertImage(123, 234, "resources/_gen/images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_123x234_resize_q75_box.jpg")
|
||||||
b.AssertFileContent("resources/_gen/images/sunset_7645215769587362592.json",
|
b.AssertFileContent("resources/_gen/images/sunset_3166614710256882113.json",
|
||||||
"DateTimeDigitized|time.Time", "PENTAX")
|
"DateTimeDigitized|time.Time", "PENTAX")
|
||||||
|
|
||||||
// TODO(bep) add this as a default assertion after Build()?
|
// TODO(bep) add this as a default assertion after Build()?
|
||||||
|
|
|
@ -180,6 +180,7 @@ var (
|
||||||
GIFType = newMediaType("image", "gif", []string{"gif"})
|
GIFType = newMediaType("image", "gif", []string{"gif"})
|
||||||
TIFFType = newMediaType("image", "tiff", []string{"tif", "tiff"})
|
TIFFType = newMediaType("image", "tiff", []string{"tif", "tiff"})
|
||||||
BMPType = newMediaType("image", "bmp", []string{"bmp"})
|
BMPType = newMediaType("image", "bmp", []string{"bmp"})
|
||||||
|
WEBPType = newMediaType("image", "webp", []string{"webp"})
|
||||||
|
|
||||||
// Common video types
|
// Common video types
|
||||||
AVIType = newMediaType("video", "x-msvideo", []string{"avi"})
|
AVIType = newMediaType("video", "x-msvideo", []string{"avi"})
|
||||||
|
@ -214,6 +215,7 @@ var DefaultTypes = Types{
|
||||||
TOMLType,
|
TOMLType,
|
||||||
PNGType,
|
PNGType,
|
||||||
JPEGType,
|
JPEGType,
|
||||||
|
WEBPType,
|
||||||
AVIType,
|
AVIType,
|
||||||
MPEGType,
|
MPEGType,
|
||||||
MP4Type,
|
MP4Type,
|
||||||
|
|
|
@ -55,7 +55,7 @@ func TestDefaultTypes(t *testing.T) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Assert(len(DefaultTypes), qt.Equals, 26)
|
c.Assert(len(DefaultTypes), qt.Equals, 27)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetByType(t *testing.T) {
|
func TestGetByType(t *testing.T) {
|
||||||
|
|
|
@ -207,7 +207,7 @@ func (i *imageResource) Fill(spec string) (resource.Image, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *imageResource) Filter(filters ...interface{}) (resource.Image, error) {
|
func (i *imageResource) Filter(filters ...interface{}) (resource.Image, error) {
|
||||||
conf := i.Proc.GetDefaultImageConfig("filter")
|
conf := images.GetDefaultImageConfig("filter", i.Proc.Cfg)
|
||||||
|
|
||||||
var gfilters []gift.Filter
|
var gfilters []gift.Filter
|
||||||
|
|
||||||
|
@ -299,28 +299,11 @@ func (i *imageResource) doWithImageConfig(conf images.ImageConfig, f func(src im
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *imageResource) decodeImageConfig(action, spec string) (images.ImageConfig, error) {
|
func (i *imageResource) decodeImageConfig(action, spec string) (images.ImageConfig, error) {
|
||||||
conf, err := images.DecodeImageConfig(action, spec, i.Proc.Cfg.Cfg)
|
conf, err := images.DecodeImageConfig(action, spec, i.Proc.Cfg, i.Format)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return conf, err
|
return conf, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// default to the source format
|
|
||||||
if conf.TargetFormat == 0 {
|
|
||||||
conf.TargetFormat = i.Format
|
|
||||||
}
|
|
||||||
|
|
||||||
if conf.Quality <= 0 && conf.TargetFormat.RequiresDefaultQuality() {
|
|
||||||
// We need a quality setting for all JPEGs
|
|
||||||
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
|
return conf, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -360,15 +343,16 @@ func (i *imageResource) setBasePath(conf images.ImageConfig) {
|
||||||
func (i *imageResource) getImageMetaCacheTargetPath() string {
|
func (i *imageResource) getImageMetaCacheTargetPath() string {
|
||||||
const imageMetaVersionNumber = 1 // Increment to invalidate the meta cache
|
const imageMetaVersionNumber = 1 // Increment to invalidate the meta cache
|
||||||
|
|
||||||
cfg := i.getSpec().imaging.Cfg.Cfg
|
cfgHash := i.getSpec().imaging.Cfg.CfgHash
|
||||||
df := i.getResourcePaths().relTargetDirFile
|
df := i.getResourcePaths().relTargetDirFile
|
||||||
if fi := i.getFileInfo(); fi != nil {
|
if fi := i.getFileInfo(); fi != nil {
|
||||||
df.dir = filepath.Dir(fi.Meta().Path())
|
df.dir = filepath.Dir(fi.Meta().Path())
|
||||||
}
|
}
|
||||||
p1, _ := helpers.FileAndExt(df.file)
|
p1, _ := helpers.FileAndExt(df.file)
|
||||||
h, _ := i.hash()
|
h, _ := i.hash()
|
||||||
idStr := helpers.HashString(h, i.size(), imageMetaVersionNumber, cfg)
|
idStr := helpers.HashString(h, i.size(), imageMetaVersionNumber, cfgHash)
|
||||||
return path.Join(df.dir, fmt.Sprintf("%s_%s.json", p1, idStr))
|
p := path.Join(df.dir, fmt.Sprintf("%s_%s.json", p1, idStr))
|
||||||
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *imageResource) relTargetPathFromConfig(conf images.ImageConfig) dirFile {
|
func (i *imageResource) relTargetPathFromConfig(conf images.ImageConfig) dirFile {
|
||||||
|
|
41
resources/image_extended_test.go
Normal file
41
resources/image_extended_test.go
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
// +build extended
|
||||||
|
|
||||||
|
package resources
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gohugoio/hugo/media"
|
||||||
|
|
||||||
|
qt "github.com/frankban/quicktest"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestImageResizeWebP(t *testing.T) {
|
||||||
|
c := qt.New(t)
|
||||||
|
|
||||||
|
image := fetchImage(c, "sunset.webp")
|
||||||
|
|
||||||
|
c.Assert(image.MediaType(), qt.Equals, media.WEBPType)
|
||||||
|
c.Assert(image.RelPermalink(), qt.Equals, "/a/sunset.webp")
|
||||||
|
c.Assert(image.ResourceType(), qt.Equals, "image")
|
||||||
|
c.Assert(image.Exif(), qt.IsNil)
|
||||||
|
|
||||||
|
resized, err := image.Resize("123x")
|
||||||
|
c.Assert(err, qt.IsNil)
|
||||||
|
c.Assert(image.MediaType(), qt.Equals, media.WEBPType)
|
||||||
|
c.Assert(resized.RelPermalink(), qt.Equals, "/a/sunset_hu36ee0b61ba924719ad36da960c273f96_59826_123x0_resize_q68_h2_linear.webp")
|
||||||
|
c.Assert(resized.Width(), qt.Equals, 123)
|
||||||
|
}
|
|
@ -14,23 +14,22 @@
|
||||||
package images
|
package images
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"image/color"
|
"image/color"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gohugoio/hugo/helpers"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/bep/gowebp/libwebp/webpoptions"
|
||||||
|
|
||||||
"github.com/disintegration/gift"
|
"github.com/disintegration/gift"
|
||||||
|
|
||||||
"github.com/mitchellh/mapstructure"
|
"github.com/mitchellh/mapstructure"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
defaultJPEGQuality = 75
|
|
||||||
defaultResampleFilter = "box"
|
|
||||||
defaultBgColor = "ffffff"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
imageFormats = map[string]Format{
|
imageFormats = map[string]Format{
|
||||||
".jpg": JPEG,
|
".jpg": JPEG,
|
||||||
|
@ -40,6 +39,7 @@ var (
|
||||||
".tiff": TIFF,
|
".tiff": TIFF,
|
||||||
".bmp": BMP,
|
".bmp": BMP,
|
||||||
".gif": GIF,
|
".gif": GIF,
|
||||||
|
".webp": WEBP,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add or increment if changes to an image format's processing requires
|
// Add or increment if changes to an image format's processing requires
|
||||||
|
@ -65,6 +65,15 @@ var anchorPositions = map[string]gift.Anchor{
|
||||||
strings.ToLower("BottomRight"): gift.BottomRightAnchor,
|
strings.ToLower("BottomRight"): gift.BottomRightAnchor,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// These encoding hints are currently only relevant for Webp.
|
||||||
|
var hints = map[string]webpoptions.EncodingPreset{
|
||||||
|
"picture": webpoptions.EncodingPresetPicture,
|
||||||
|
"photo": webpoptions.EncodingPresetPhoto,
|
||||||
|
"drawing": webpoptions.EncodingPresetDrawing,
|
||||||
|
"icon": webpoptions.EncodingPresetIcon,
|
||||||
|
"text": webpoptions.EncodingPresetText,
|
||||||
|
}
|
||||||
|
|
||||||
var imageFilters = map[string]gift.Resampling{
|
var imageFilters = map[string]gift.Resampling{
|
||||||
|
|
||||||
strings.ToLower("NearestNeighbor"): gift.NearestNeighborResampling,
|
strings.ToLower("NearestNeighbor"): gift.NearestNeighborResampling,
|
||||||
|
@ -89,63 +98,71 @@ func ImageFormatFromExt(ext string) (Format, bool) {
|
||||||
return f, found
|
return f, found
|
||||||
}
|
}
|
||||||
|
|
||||||
func DecodeConfig(m map[string]interface{}) (ImagingConfig, error) {
|
const (
|
||||||
var i Imaging
|
defaultJPEGQuality = 75
|
||||||
var ic ImagingConfig
|
defaultResampleFilter = "box"
|
||||||
if err := mapstructure.WeakDecode(m, &i); err != nil {
|
defaultBgColor = "ffffff"
|
||||||
return ic, err
|
defaultHint = "photo"
|
||||||
}
|
)
|
||||||
|
|
||||||
if i.Quality == 0 {
|
var defaultImaging = Imaging{
|
||||||
i.Quality = defaultJPEGQuality
|
ResampleFilter: defaultResampleFilter,
|
||||||
} else if i.Quality < 0 || i.Quality > 100 {
|
BgColor: defaultBgColor,
|
||||||
return ic, errors.New("JPEG quality must be a number between 1 and 100")
|
Hint: defaultHint,
|
||||||
}
|
Quality: defaultJPEGQuality,
|
||||||
|
|
||||||
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) {
|
|
||||||
i.Anchor = smartCropIdentifier
|
|
||||||
} else {
|
|
||||||
i.Anchor = strings.ToLower(i.Anchor)
|
|
||||||
if _, found := anchorPositions[i.Anchor]; !found {
|
|
||||||
return ic, errors.New("invalid anchor value in imaging config")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if i.ResampleFilter == "" {
|
|
||||||
i.ResampleFilter = defaultResampleFilter
|
|
||||||
} else {
|
|
||||||
filter := strings.ToLower(i.ResampleFilter)
|
|
||||||
_, found := imageFilters[filter]
|
|
||||||
if !found {
|
|
||||||
return ic, fmt.Errorf("%q is not a valid resample filter", filter)
|
|
||||||
}
|
|
||||||
i.ResampleFilter = filter
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.TrimSpace(i.Exif.IncludeFields) == "" && strings.TrimSpace(i.Exif.ExcludeFields) == "" {
|
|
||||||
// Don't change this for no good reason. Please don't.
|
|
||||||
i.Exif.ExcludeFields = "GPS|Exif|Exposure[M|P|B]|Contrast|Resolution|Sharp|JPEG|Metering|Sensing|Saturation|ColorSpace|Flash|WhiteBalance"
|
|
||||||
}
|
|
||||||
|
|
||||||
ic.Cfg = i
|
|
||||||
|
|
||||||
return ic, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func DecodeImageConfig(action, config string, defaults Imaging) (ImageConfig, error) {
|
func DecodeConfig(m map[string]interface{}) (ImagingConfig, error) {
|
||||||
|
if m == nil {
|
||||||
|
m = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
i := ImagingConfig{
|
||||||
|
Cfg: defaultImaging,
|
||||||
|
CfgHash: helpers.HashString(m),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := mapstructure.WeakDecode(m, &i.Cfg); err != nil {
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := i.Cfg.init(); err != nil {
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
i.BgColor, err = hexStringToColor(i.Cfg.BgColor)
|
||||||
|
if err != nil {
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if i.Cfg.Anchor != "" && i.Cfg.Anchor != smartCropIdentifier {
|
||||||
|
anchor, found := anchorPositions[i.Cfg.Anchor]
|
||||||
|
if !found {
|
||||||
|
return i, errors.Errorf("invalid anchor value %q in imaging config", i.Anchor)
|
||||||
|
}
|
||||||
|
i.Anchor = anchor
|
||||||
|
} else {
|
||||||
|
i.Cfg.Anchor = smartCropIdentifier
|
||||||
|
}
|
||||||
|
|
||||||
|
filter, found := imageFilters[i.Cfg.ResampleFilter]
|
||||||
|
if !found {
|
||||||
|
return i, fmt.Errorf("%q is not a valid resample filter", filter)
|
||||||
|
}
|
||||||
|
i.ResampleFilter = filter
|
||||||
|
|
||||||
|
if strings.TrimSpace(i.Cfg.Exif.IncludeFields) == "" && strings.TrimSpace(i.Cfg.Exif.ExcludeFields) == "" {
|
||||||
|
// Don't change this for no good reason. Please don't.
|
||||||
|
i.Cfg.Exif.ExcludeFields = "GPS|Exif|Exposure[M|P|B]|Contrast|Resolution|Sharp|JPEG|Metering|Sensing|Saturation|ColorSpace|Flash|WhiteBalance"
|
||||||
|
}
|
||||||
|
|
||||||
|
return i, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func DecodeImageConfig(action, config string, defaults ImagingConfig, sourceFormat Format) (ImageConfig, error) {
|
||||||
var (
|
var (
|
||||||
c ImageConfig
|
c ImageConfig = GetDefaultImageConfig(action, defaults)
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -167,6 +184,8 @@ func DecodeImageConfig(action, config string, defaults Imaging) (ImageConfig, er
|
||||||
} else if filter, ok := imageFilters[part]; ok {
|
} else if filter, ok := imageFilters[part]; ok {
|
||||||
c.Filter = filter
|
c.Filter = filter
|
||||||
c.FilterStr = part
|
c.FilterStr = part
|
||||||
|
} else if hint, ok := hints[part]; ok {
|
||||||
|
c.Hint = hint
|
||||||
} else if part[0] == '#' {
|
} else if part[0] == '#' {
|
||||||
c.BgColorStr = part[1:]
|
c.BgColorStr = part[1:]
|
||||||
c.BgColor, err = hexStringToColor(c.BgColorStr)
|
c.BgColor, err = hexStringToColor(c.BgColorStr)
|
||||||
|
@ -181,6 +200,7 @@ func DecodeImageConfig(action, config string, defaults Imaging) (ImageConfig, er
|
||||||
if c.Quality < 1 || c.Quality > 100 {
|
if c.Quality < 1 || c.Quality > 100 {
|
||||||
return c, errors.New("quality ranges from 1 to 100 inclusive")
|
return c, errors.New("quality ranges from 1 to 100 inclusive")
|
||||||
}
|
}
|
||||||
|
c.qualitySetForImage = true
|
||||||
} else if part[0] == 'r' {
|
} else if part[0] == 'r' {
|
||||||
c.Rotate, err = strconv.Atoi(part[1:])
|
c.Rotate, err = strconv.Atoi(part[1:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -219,14 +239,33 @@ func DecodeImageConfig(action, config string, defaults Imaging) (ImageConfig, er
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.FilterStr == "" {
|
if c.FilterStr == "" {
|
||||||
c.FilterStr = defaults.ResampleFilter
|
c.FilterStr = defaults.Cfg.ResampleFilter
|
||||||
c.Filter = imageFilters[c.FilterStr]
|
c.Filter = defaults.ResampleFilter
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Hint == 0 {
|
||||||
|
c.Hint = webpoptions.EncodingPresetPhoto
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.AnchorStr == "" {
|
if c.AnchorStr == "" {
|
||||||
c.AnchorStr = defaults.Anchor
|
c.AnchorStr = defaults.Cfg.Anchor
|
||||||
if !strings.EqualFold(c.AnchorStr, smartCropIdentifier) {
|
c.Anchor = defaults.Anchor
|
||||||
c.Anchor = anchorPositions[c.AnchorStr]
|
}
|
||||||
|
|
||||||
|
// default to the source format
|
||||||
|
if c.TargetFormat == 0 {
|
||||||
|
c.TargetFormat = sourceFormat
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.Quality <= 0 && c.TargetFormat.RequiresDefaultQuality() {
|
||||||
|
// We need a quality setting for all JPEGs and WEBPs.
|
||||||
|
c.Quality = defaults.Cfg.Quality
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.BgColor == nil && c.TargetFormat != sourceFormat {
|
||||||
|
if sourceFormat.SupportsTransparency() && !c.TargetFormat.SupportsTransparency() {
|
||||||
|
c.BgColor = defaults.BgColor
|
||||||
|
c.BgColorStr = defaults.Cfg.BgColor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,7 +274,7 @@ func DecodeImageConfig(action, config string, defaults Imaging) (ImageConfig, er
|
||||||
|
|
||||||
// ImageConfig holds configuration to create a new image from an existing one, resize etc.
|
// ImageConfig holds configuration to create a new image from an existing one, resize etc.
|
||||||
type ImageConfig struct {
|
type ImageConfig struct {
|
||||||
// This defines the output format of the output image. It defaults to the source format
|
// This defines the output format of the output image. It defaults to the source format.
|
||||||
TargetFormat Format
|
TargetFormat Format
|
||||||
|
|
||||||
Action string
|
Action string
|
||||||
|
@ -244,9 +283,10 @@ type ImageConfig struct {
|
||||||
Key string
|
Key string
|
||||||
|
|
||||||
// Quality ranges from 1 to 100 inclusive, higher is better.
|
// Quality ranges from 1 to 100 inclusive, higher is better.
|
||||||
// This is only relevant for JPEG images.
|
// This is only relevant for JPEG and WEBP images.
|
||||||
// Default is 75.
|
// Default is 75.
|
||||||
Quality int
|
Quality int
|
||||||
|
qualitySetForImage bool // Whether the above is set for this image.
|
||||||
|
|
||||||
// Rotate rotates an image by the given angle counter-clockwise.
|
// Rotate rotates an image by the given angle counter-clockwise.
|
||||||
// The rotation will be performed first.
|
// The rotation will be performed first.
|
||||||
|
@ -260,6 +300,10 @@ type ImageConfig struct {
|
||||||
BgColor color.Color
|
BgColor color.Color
|
||||||
BgColorStr string
|
BgColorStr string
|
||||||
|
|
||||||
|
// Hint about what type of picture this is. Used to optimize encoding
|
||||||
|
// when target is set to webp.
|
||||||
|
Hint webpoptions.EncodingPreset
|
||||||
|
|
||||||
Width int
|
Width int
|
||||||
Height int
|
Height int
|
||||||
|
|
||||||
|
@ -279,7 +323,8 @@ func (i ImageConfig) GetKey(format Format) string {
|
||||||
if i.Action != "" {
|
if i.Action != "" {
|
||||||
k += "_" + i.Action
|
k += "_" + i.Action
|
||||||
}
|
}
|
||||||
if i.Quality > 0 {
|
// This slightly odd construct is here to preserve the old image keys.
|
||||||
|
if i.qualitySetForImage || i.TargetFormat.RequiresDefaultQuality() {
|
||||||
k += "_q" + strconv.Itoa(i.Quality)
|
k += "_q" + strconv.Itoa(i.Quality)
|
||||||
}
|
}
|
||||||
if i.Rotate != 0 {
|
if i.Rotate != 0 {
|
||||||
|
@ -289,6 +334,10 @@ func (i ImageConfig) GetKey(format Format) string {
|
||||||
k += "_bg" + i.BgColorStr
|
k += "_bg" + i.BgColorStr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if i.TargetFormat == WEBP {
|
||||||
|
k += "_h" + strconv.Itoa(int(i.Hint))
|
||||||
|
}
|
||||||
|
|
||||||
anchor := i.AnchorStr
|
anchor := i.AnchorStr
|
||||||
if anchor == smartCropIdentifier {
|
if anchor == smartCropIdentifier {
|
||||||
anchor = anchor + strconv.Itoa(smartCropVersionNumber)
|
anchor = anchor + strconv.Itoa(smartCropVersionNumber)
|
||||||
|
@ -312,10 +361,16 @@ func (i ImageConfig) GetKey(format Format) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
type ImagingConfig struct {
|
type ImagingConfig struct {
|
||||||
BgColor color.Color
|
BgColor color.Color
|
||||||
|
Hint webpoptions.EncodingPreset
|
||||||
|
ResampleFilter gift.Resampling
|
||||||
|
Anchor gift.Anchor
|
||||||
|
|
||||||
// Config as provided by the user.
|
// Config as provided by the user.
|
||||||
Cfg Imaging
|
Cfg Imaging
|
||||||
|
|
||||||
|
// Hash of the config map provided by the user.
|
||||||
|
CfgHash string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Imaging contains default image processing configuration. This will be fetched
|
// Imaging contains default image processing configuration. This will be fetched
|
||||||
|
@ -324,9 +379,15 @@ type Imaging struct {
|
||||||
// Default image quality setting (1-100). Only used for JPEG images.
|
// Default image quality setting (1-100). Only used for JPEG images.
|
||||||
Quality int
|
Quality int
|
||||||
|
|
||||||
// Resample filter to use in resize operations..
|
// Resample filter to use in resize operations.
|
||||||
ResampleFilter string
|
ResampleFilter string
|
||||||
|
|
||||||
|
// Hint about what type of image this is.
|
||||||
|
// Currently only used when encoding to Webp.
|
||||||
|
// Default is "photo".
|
||||||
|
// Valid values are "picture", "photo", "drawing", "icon", or "text".
|
||||||
|
Hint string
|
||||||
|
|
||||||
// The anchor to use in Fill. Default is "smart", i.e. Smart Crop.
|
// The anchor to use in Fill. Default is "smart", i.e. Smart Crop.
|
||||||
Anchor string
|
Anchor string
|
||||||
|
|
||||||
|
@ -336,6 +397,19 @@ type Imaging struct {
|
||||||
Exif ExifConfig
|
Exif ExifConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cfg *Imaging) init() error {
|
||||||
|
if cfg.Quality < 0 || cfg.Quality > 100 {
|
||||||
|
return errors.New("image quality must be a number between 1 and 100")
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.BgColor = strings.ToLower(strings.TrimPrefix(cfg.BgColor, "#"))
|
||||||
|
cfg.Anchor = strings.ToLower(cfg.Anchor)
|
||||||
|
cfg.ResampleFilter = strings.ToLower(cfg.ResampleFilter)
|
||||||
|
cfg.Hint = strings.ToLower(cfg.Hint)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type ExifConfig struct {
|
type ExifConfig struct {
|
||||||
|
|
||||||
// Regexp matching the Exif fields you want from the (massive) set of Exif info
|
// Regexp matching the Exif fields you want from the (massive) set of Exif info
|
||||||
|
|
|
@ -42,7 +42,6 @@ func TestDecodeConfig(t *testing.T) {
|
||||||
imagingConfig, err = DecodeConfig(m)
|
imagingConfig, err = DecodeConfig(m)
|
||||||
c.Assert(err, qt.IsNil)
|
c.Assert(err, qt.IsNil)
|
||||||
imaging = imagingConfig.Cfg
|
imaging = imagingConfig.Cfg
|
||||||
c.Assert(imaging.Quality, qt.Equals, defaultJPEGQuality)
|
|
||||||
c.Assert(imaging.ResampleFilter, qt.Equals, "box")
|
c.Assert(imaging.ResampleFilter, qt.Equals, "box")
|
||||||
c.Assert(imaging.Anchor, qt.Equals, "smart")
|
c.Assert(imaging.Anchor, qt.Equals, "smart")
|
||||||
|
|
||||||
|
@ -84,18 +83,22 @@ func TestDecodeImageConfig(t *testing.T) {
|
||||||
in string
|
in string
|
||||||
expect interface{}
|
expect interface{}
|
||||||
}{
|
}{
|
||||||
{"300x400", newImageConfig(300, 400, 0, 0, "", "", "")},
|
{"300x400", newImageConfig(300, 400, 75, 0, "box", "smart", "")},
|
||||||
{"300x400 #fff", newImageConfig(300, 400, 0, 0, "", "", "fff")},
|
{"300x400 #fff", newImageConfig(300, 400, 75, 0, "box", "smart", "fff")},
|
||||||
{"100x200 bottomRight", newImageConfig(100, 200, 0, 0, "", "BottomRight", "")},
|
{"100x200 bottomRight", newImageConfig(100, 200, 75, 0, "box", "BottomRight", "")},
|
||||||
{"10x20 topleft Lanczos", newImageConfig(10, 20, 0, 0, "Lanczos", "topleft", "")},
|
{"10x20 topleft Lanczos", newImageConfig(10, 20, 75, 0, "Lanczos", "topleft", "")},
|
||||||
{"linear left 10x r180", newImageConfig(10, 0, 0, 180, "linear", "left", "")},
|
{"linear left 10x r180", newImageConfig(10, 0, 75, 180, "linear", "left", "")},
|
||||||
{"x20 riGht Cosine q95", newImageConfig(0, 20, 95, 0, "cosine", "right", "")},
|
{"x20 riGht Cosine q95", newImageConfig(0, 20, 95, 0, "cosine", "right", "")},
|
||||||
|
|
||||||
{"", false},
|
{"", false},
|
||||||
{"foo", false},
|
{"foo", false},
|
||||||
} {
|
} {
|
||||||
|
|
||||||
result, err := DecodeImageConfig("resize", this.in, Imaging{})
|
cfg, err := DecodeConfig(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
result, err := DecodeImageConfig("resize", this.in, cfg, PNG)
|
||||||
if b, ok := this.expect.(bool); ok && !b {
|
if b, ok := this.expect.(bool); ok && !b {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("[%d] parseImageConfig didn't return an expected error", i)
|
t.Errorf("[%d] parseImageConfig didn't return an expected error", i)
|
||||||
|
@ -112,11 +115,13 @@ func TestDecodeImageConfig(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newImageConfig(width, height, quality, rotate int, filter, anchor, bgColor string) ImageConfig {
|
func newImageConfig(width, height, quality, rotate int, filter, anchor, bgColor string) ImageConfig {
|
||||||
var c ImageConfig
|
var c ImageConfig = GetDefaultImageConfig("resize", ImagingConfig{})
|
||||||
c.Action = "resize"
|
c.TargetFormat = PNG
|
||||||
|
c.Hint = 2
|
||||||
c.Width = width
|
c.Width = width
|
||||||
c.Height = height
|
c.Height = height
|
||||||
c.Quality = quality
|
c.Quality = quality
|
||||||
|
c.qualitySetForImage = quality != 75
|
||||||
c.Rotate = rotate
|
c.Rotate = rotate
|
||||||
c.BgColorStr = bgColor
|
c.BgColorStr = bgColor
|
||||||
c.BgColor, _ = hexStringToColor(bgColor)
|
c.BgColor, _ = hexStringToColor(bgColor)
|
||||||
|
@ -130,10 +135,14 @@ func newImageConfig(width, height, quality, rotate int, filter, anchor, bgColor
|
||||||
}
|
}
|
||||||
|
|
||||||
if anchor != "" {
|
if anchor != "" {
|
||||||
anchor = strings.ToLower(anchor)
|
if anchor == smartCropIdentifier {
|
||||||
if v, ok := anchorPositions[anchor]; ok {
|
|
||||||
c.Anchor = v
|
|
||||||
c.AnchorStr = anchor
|
c.AnchorStr = anchor
|
||||||
|
} else {
|
||||||
|
anchor = strings.ToLower(anchor)
|
||||||
|
if v, ok := anchorPositions[anchor]; ok {
|
||||||
|
c.Anchor = v
|
||||||
|
c.AnchorStr = anchor
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,9 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/bep/gowebp/libwebp/webpoptions"
|
||||||
|
"github.com/gohugoio/hugo/resources/images/webp"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/media"
|
"github.com/gohugoio/hugo/media"
|
||||||
"github.com/gohugoio/hugo/resources/images/exif"
|
"github.com/gohugoio/hugo/resources/images/exif"
|
||||||
|
|
||||||
|
@ -89,6 +92,15 @@ func (i *Image) EncodeTo(conf ImageConfig, img image.Image, w io.Writer) error {
|
||||||
|
|
||||||
case BMP:
|
case BMP:
|
||||||
return bmp.Encode(w, img)
|
return bmp.Encode(w, img)
|
||||||
|
case WEBP:
|
||||||
|
return webp.Encode(
|
||||||
|
w,
|
||||||
|
img, webpoptions.EncodingOptions{
|
||||||
|
Quality: conf.Quality,
|
||||||
|
EncodingPreset: webpoptions.EncodingPreset(conf.Hint),
|
||||||
|
UseSharpYuv: true,
|
||||||
|
},
|
||||||
|
)
|
||||||
default:
|
default:
|
||||||
return errors.New("format not supported")
|
return errors.New("format not supported")
|
||||||
}
|
}
|
||||||
|
@ -229,10 +241,11 @@ func (p *ImageProcessor) Filter(src image.Image, filters ...gift.Filter) (image.
|
||||||
return dst, nil
|
return dst, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ImageProcessor) GetDefaultImageConfig(action string) ImageConfig {
|
func GetDefaultImageConfig(action string, defaults ImagingConfig) ImageConfig {
|
||||||
return ImageConfig{
|
return ImageConfig{
|
||||||
Action: action,
|
Action: action,
|
||||||
Quality: p.Cfg.Cfg.Quality,
|
Hint: defaults.Hint,
|
||||||
|
Quality: defaults.Cfg.Quality,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -250,11 +263,13 @@ const (
|
||||||
GIF
|
GIF
|
||||||
TIFF
|
TIFF
|
||||||
BMP
|
BMP
|
||||||
|
WEBP
|
||||||
)
|
)
|
||||||
|
|
||||||
// RequiresDefaultQuality returns if the default quality needs to be applied to images of this format
|
// RequiresDefaultQuality returns if the default quality needs to be applied to
|
||||||
|
// images of this format.
|
||||||
func (f Format) RequiresDefaultQuality() bool {
|
func (f Format) RequiresDefaultQuality() bool {
|
||||||
return f == JPEG
|
return f == JPEG || f == WEBP
|
||||||
}
|
}
|
||||||
|
|
||||||
// SupportsTransparency reports whether it supports transparency in any form.
|
// SupportsTransparency reports whether it supports transparency in any form.
|
||||||
|
@ -281,6 +296,8 @@ func (f Format) MediaType() media.Type {
|
||||||
return media.TIFFType
|
return media.TIFFType
|
||||||
case BMP:
|
case BMP:
|
||||||
return media.BMPType
|
return media.BMPType
|
||||||
|
case WEBP:
|
||||||
|
return media.WEBPType
|
||||||
default:
|
default:
|
||||||
panic(fmt.Sprintf("%d is not a valid image format", f))
|
panic(fmt.Sprintf("%d is not a valid image format", f))
|
||||||
}
|
}
|
||||||
|
|
30
resources/images/webp/webp.go
Normal file
30
resources/images/webp/webp.go
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
// Copyright 2021 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.
|
||||||
|
|
||||||
|
// +build extended
|
||||||
|
|
||||||
|
package webp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/bep/gowebp/libwebp"
|
||||||
|
"github.com/bep/gowebp/libwebp/webpoptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Encode writes the Image m to w in Webp format with the given
|
||||||
|
// options.
|
||||||
|
func Encode(w io.Writer, m image.Image, o webpoptions.EncodingOptions) error {
|
||||||
|
return libwebp.Encode(w, m, o)
|
||||||
|
}
|
30
resources/images/webp/webp_notavailable.go
Normal file
30
resources/images/webp/webp_notavailable.go
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
// Copyright 2021 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.
|
||||||
|
|
||||||
|
// +build !extended
|
||||||
|
|
||||||
|
package webp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/gohugoio/hugo/common/herrors"
|
||||||
|
|
||||||
|
"github.com/bep/gowebp/libwebp/webpoptions"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Encode is only available in the extended version.
|
||||||
|
func Encode(w io.Writer, m image.Image, o webpoptions.EncodingOptions) error {
|
||||||
|
return herrors.ErrFeatureNotAvailable
|
||||||
|
}
|
BIN
resources/testdata/sunset.webp
vendored
Normal file
BIN
resources/testdata/sunset.webp
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 58 KiB |
Loading…
Reference in a new issue