github.com/kovansky/hugo@v0.92.3-0.20220224232819-63076e4ff19f/resources/images/image.go (about)

     1  // Copyright 2019 The Hugo Authors. All rights reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package images
    15  
    16  import (
    17  	"fmt"
    18  	"image"
    19  	"image/color"
    20  	"image/draw"
    21  	"image/gif"
    22  	"image/jpeg"
    23  	"image/png"
    24  	"io"
    25  	"sync"
    26  
    27  	"github.com/bep/gowebp/libwebp/webpoptions"
    28  	"github.com/gohugoio/hugo/resources/images/webp"
    29  
    30  	"github.com/gohugoio/hugo/media"
    31  	"github.com/gohugoio/hugo/resources/images/exif"
    32  
    33  	"github.com/disintegration/gift"
    34  	"golang.org/x/image/bmp"
    35  	"golang.org/x/image/tiff"
    36  
    37  	"github.com/gohugoio/hugo/common/hugio"
    38  	"github.com/pkg/errors"
    39  )
    40  
    41  func NewImage(f Format, proc *ImageProcessor, img image.Image, s Spec) *Image {
    42  	if img != nil {
    43  		return &Image{
    44  			Format: f,
    45  			Proc:   proc,
    46  			Spec:   s,
    47  			imageConfig: &imageConfig{
    48  				config:       imageConfigFromImage(img),
    49  				configLoaded: true,
    50  			},
    51  		}
    52  	}
    53  	return &Image{Format: f, Proc: proc, Spec: s, imageConfig: &imageConfig{}}
    54  }
    55  
    56  type Image struct {
    57  	Format Format
    58  	Proc   *ImageProcessor
    59  	Spec   Spec
    60  	*imageConfig
    61  }
    62  
    63  func (i *Image) EncodeTo(conf ImageConfig, img image.Image, w io.Writer) error {
    64  	switch conf.TargetFormat {
    65  	case JPEG:
    66  
    67  		var rgba *image.RGBA
    68  		quality := conf.Quality
    69  
    70  		if nrgba, ok := img.(*image.NRGBA); ok {
    71  			if nrgba.Opaque() {
    72  				rgba = &image.RGBA{
    73  					Pix:    nrgba.Pix,
    74  					Stride: nrgba.Stride,
    75  					Rect:   nrgba.Rect,
    76  				}
    77  			}
    78  		}
    79  		if rgba != nil {
    80  			return jpeg.Encode(w, rgba, &jpeg.Options{Quality: quality})
    81  		}
    82  		return jpeg.Encode(w, img, &jpeg.Options{Quality: quality})
    83  	case PNG:
    84  		encoder := png.Encoder{CompressionLevel: png.DefaultCompression}
    85  		return encoder.Encode(w, img)
    86  
    87  	case GIF:
    88  		return gif.Encode(w, img, &gif.Options{
    89  			NumColors: 256,
    90  		})
    91  	case TIFF:
    92  		return tiff.Encode(w, img, &tiff.Options{Compression: tiff.Deflate, Predictor: true})
    93  
    94  	case BMP:
    95  		return bmp.Encode(w, img)
    96  	case WEBP:
    97  		return webp.Encode(
    98  			w,
    99  			img, webpoptions.EncodingOptions{
   100  				Quality:        conf.Quality,
   101  				EncodingPreset: webpoptions.EncodingPreset(conf.Hint),
   102  				UseSharpYuv:    true,
   103  			},
   104  		)
   105  	default:
   106  		return errors.New("format not supported")
   107  	}
   108  }
   109  
   110  // Height returns i's height.
   111  func (i *Image) Height() int {
   112  	i.initConfig()
   113  	return i.config.Height
   114  }
   115  
   116  // Width returns i's width.
   117  func (i *Image) Width() int {
   118  	i.initConfig()
   119  	return i.config.Width
   120  }
   121  
   122  func (i Image) WithImage(img image.Image) *Image {
   123  	i.Spec = nil
   124  	i.imageConfig = &imageConfig{
   125  		config:       imageConfigFromImage(img),
   126  		configLoaded: true,
   127  	}
   128  
   129  	return &i
   130  }
   131  
   132  func (i Image) WithSpec(s Spec) *Image {
   133  	i.Spec = s
   134  	i.imageConfig = &imageConfig{}
   135  	return &i
   136  }
   137  
   138  // InitConfig reads the image config from the given reader.
   139  func (i *Image) InitConfig(r io.Reader) error {
   140  	var err error
   141  	i.configInit.Do(func() {
   142  		i.config, _, err = image.DecodeConfig(r)
   143  	})
   144  	return err
   145  }
   146  
   147  func (i *Image) initConfig() error {
   148  	var err error
   149  	i.configInit.Do(func() {
   150  		if i.configLoaded {
   151  			return
   152  		}
   153  
   154  		var f hugio.ReadSeekCloser
   155  
   156  		f, err = i.Spec.ReadSeekCloser()
   157  		if err != nil {
   158  			return
   159  		}
   160  		defer f.Close()
   161  
   162  		i.config, _, err = image.DecodeConfig(f)
   163  	})
   164  
   165  	if err != nil {
   166  		return errors.Wrap(err, "failed to load image config")
   167  	}
   168  
   169  	return nil
   170  }
   171  
   172  func NewImageProcessor(cfg ImagingConfig) (*ImageProcessor, error) {
   173  	e := cfg.Cfg.Exif
   174  	exifDecoder, err := exif.NewDecoder(
   175  		exif.WithDateDisabled(e.DisableDate),
   176  		exif.WithLatLongDisabled(e.DisableLatLong),
   177  		exif.ExcludeFields(e.ExcludeFields),
   178  		exif.IncludeFields(e.IncludeFields),
   179  	)
   180  	if err != nil {
   181  		return nil, err
   182  	}
   183  
   184  	return &ImageProcessor{
   185  		Cfg:         cfg,
   186  		exifDecoder: exifDecoder,
   187  	}, nil
   188  }
   189  
   190  type ImageProcessor struct {
   191  	Cfg         ImagingConfig
   192  	exifDecoder *exif.Decoder
   193  }
   194  
   195  func (p *ImageProcessor) DecodeExif(r io.Reader) (*exif.Exif, error) {
   196  	return p.exifDecoder.Decode(r)
   197  }
   198  
   199  func (p *ImageProcessor) ApplyFiltersFromConfig(src image.Image, conf ImageConfig) (image.Image, error) {
   200  	var filters []gift.Filter
   201  
   202  	if conf.Rotate != 0 {
   203  		// Apply any rotation before any resize.
   204  		filters = append(filters, gift.Rotate(float32(conf.Rotate), color.Transparent, gift.NearestNeighborInterpolation))
   205  	}
   206  
   207  	switch conf.Action {
   208  	case "resize":
   209  		filters = append(filters, gift.Resize(conf.Width, conf.Height, conf.Filter))
   210  	case "crop":
   211  		if conf.AnchorStr == smartCropIdentifier {
   212  			bounds, err := p.smartCrop(src, conf.Width, conf.Height, conf.Filter)
   213  			if err != nil {
   214  				return nil, err
   215  			}
   216  
   217  			// First crop using the bounds returned by smartCrop.
   218  			filters = append(filters, gift.Crop(bounds))
   219  			// Then center crop the image to get an image the desired size without resizing.
   220  			filters = append(filters, gift.CropToSize(conf.Width, conf.Height, gift.CenterAnchor))
   221  
   222  		} else {
   223  			filters = append(filters, gift.CropToSize(conf.Width, conf.Height, conf.Anchor))
   224  		}
   225  	case "fill":
   226  		if conf.AnchorStr == smartCropIdentifier {
   227  			bounds, err := p.smartCrop(src, conf.Width, conf.Height, conf.Filter)
   228  			if err != nil {
   229  				return nil, err
   230  			}
   231  
   232  			// First crop it, then resize it.
   233  			filters = append(filters, gift.Crop(bounds))
   234  			filters = append(filters, gift.Resize(conf.Width, conf.Height, conf.Filter))
   235  
   236  		} else {
   237  			filters = append(filters, gift.ResizeToFill(conf.Width, conf.Height, conf.Filter, conf.Anchor))
   238  		}
   239  	case "fit":
   240  		filters = append(filters, gift.ResizeToFit(conf.Width, conf.Height, conf.Filter))
   241  	default:
   242  		return nil, errors.Errorf("unsupported action: %q", conf.Action)
   243  	}
   244  
   245  	img, err := p.Filter(src, filters...)
   246  	if err != nil {
   247  		return nil, err
   248  	}
   249  
   250  	return img, nil
   251  }
   252  
   253  func (p *ImageProcessor) Filter(src image.Image, filters ...gift.Filter) (image.Image, error) {
   254  	g := gift.New(filters...)
   255  	bounds := g.Bounds(src.Bounds())
   256  	var dst draw.Image
   257  	switch src.(type) {
   258  	case *image.RGBA:
   259  		dst = image.NewRGBA(bounds)
   260  	case *image.NRGBA:
   261  		dst = image.NewNRGBA(bounds)
   262  	case *image.Gray:
   263  		dst = image.NewGray(bounds)
   264  	default:
   265  		dst = image.NewNRGBA(bounds)
   266  	}
   267  	g.Draw(dst, src)
   268  	return dst, nil
   269  }
   270  
   271  func GetDefaultImageConfig(action string, defaults ImagingConfig) ImageConfig {
   272  	return ImageConfig{
   273  		Action:  action,
   274  		Hint:    defaults.Hint,
   275  		Quality: defaults.Cfg.Quality,
   276  	}
   277  }
   278  
   279  type Spec interface {
   280  	// Loads the image source.
   281  	ReadSeekCloser() (hugio.ReadSeekCloser, error)
   282  }
   283  
   284  // Format is an image file format.
   285  type Format int
   286  
   287  const (
   288  	JPEG Format = iota + 1
   289  	PNG
   290  	GIF
   291  	TIFF
   292  	BMP
   293  	WEBP
   294  )
   295  
   296  // RequiresDefaultQuality returns if the default quality needs to be applied to
   297  // images of this format.
   298  func (f Format) RequiresDefaultQuality() bool {
   299  	return f == JPEG || f == WEBP
   300  }
   301  
   302  // SupportsTransparency reports whether it supports transparency in any form.
   303  func (f Format) SupportsTransparency() bool {
   304  	return f != JPEG
   305  }
   306  
   307  // DefaultExtension returns the default file extension of this format, starting with a dot.
   308  // For example: .jpg for JPEG
   309  func (f Format) DefaultExtension() string {
   310  	return f.MediaType().FirstSuffix.FullSuffix
   311  }
   312  
   313  // MediaType returns the media type of this image, e.g. image/jpeg for JPEG
   314  func (f Format) MediaType() media.Type {
   315  	switch f {
   316  	case JPEG:
   317  		return media.JPEGType
   318  	case PNG:
   319  		return media.PNGType
   320  	case GIF:
   321  		return media.GIFType
   322  	case TIFF:
   323  		return media.TIFFType
   324  	case BMP:
   325  		return media.BMPType
   326  	case WEBP:
   327  		return media.WEBPType
   328  	default:
   329  		panic(fmt.Sprintf("%d is not a valid image format", f))
   330  	}
   331  }
   332  
   333  type imageConfig struct {
   334  	config       image.Config
   335  	configInit   sync.Once
   336  	configLoaded bool
   337  }
   338  
   339  func imageConfigFromImage(img image.Image) image.Config {
   340  	b := img.Bounds()
   341  	return image.Config{Width: b.Max.X, Height: b.Max.Y}
   342  }
   343  
   344  func ToFilters(in interface{}) []gift.Filter {
   345  	switch v := in.(type) {
   346  	case []gift.Filter:
   347  		return v
   348  	case []filter:
   349  		vv := make([]gift.Filter, len(v))
   350  		for i, f := range v {
   351  			vv[i] = f
   352  		}
   353  		return vv
   354  	case gift.Filter:
   355  		return []gift.Filter{v}
   356  	default:
   357  		panic(fmt.Sprintf("%T is not an image filter", in))
   358  	}
   359  }
   360  
   361  // IsOpaque returns false if the image has alpha channel and there is at least 1
   362  // pixel that is not (fully) opaque.
   363  func IsOpaque(img image.Image) bool {
   364  	if oim, ok := img.(interface {
   365  		Opaque() bool
   366  	}); ok {
   367  		return oim.Opaque()
   368  	}
   369  
   370  	return false
   371  }
   372  
   373  // ImageSource identifies and decodes an image.
   374  type ImageSource interface {
   375  	DecodeImage() (image.Image, error)
   376  	Key() string
   377  }