github.com/linchen2chris/hugo@v0.0.0-20230307053224-cec209389705/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  	"errors"
    38  
    39  	"github.com/gohugoio/hugo/common/hugio"
    40  )
    41  
    42  func NewImage(f Format, proc *ImageProcessor, img image.Image, s Spec) *Image {
    43  	if img != nil {
    44  		return &Image{
    45  			Format: f,
    46  			Proc:   proc,
    47  			Spec:   s,
    48  			imageConfig: &imageConfig{
    49  				config:       imageConfigFromImage(img),
    50  				configLoaded: true,
    51  			},
    52  		}
    53  	}
    54  	return &Image{Format: f, Proc: proc, Spec: s, imageConfig: &imageConfig{}}
    55  }
    56  
    57  type Image struct {
    58  	Format Format
    59  	Proc   *ImageProcessor
    60  	Spec   Spec
    61  	*imageConfig
    62  }
    63  
    64  func (i *Image) EncodeTo(conf ImageConfig, img image.Image, w io.Writer) error {
    65  	switch conf.TargetFormat {
    66  	case JPEG:
    67  
    68  		var rgba *image.RGBA
    69  		quality := conf.Quality
    70  
    71  		if nrgba, ok := img.(*image.NRGBA); ok {
    72  			if nrgba.Opaque() {
    73  				rgba = &image.RGBA{
    74  					Pix:    nrgba.Pix,
    75  					Stride: nrgba.Stride,
    76  					Rect:   nrgba.Rect,
    77  				}
    78  			}
    79  		}
    80  		if rgba != nil {
    81  			return jpeg.Encode(w, rgba, &jpeg.Options{Quality: quality})
    82  		}
    83  		return jpeg.Encode(w, img, &jpeg.Options{Quality: quality})
    84  	case PNG:
    85  		encoder := png.Encoder{CompressionLevel: png.DefaultCompression}
    86  		return encoder.Encode(w, img)
    87  
    88  	case GIF:
    89  		if giphy, ok := img.(Giphy); ok {
    90  			g := giphy.GIF()
    91  			return gif.EncodeAll(w, g)
    92  		}
    93  		return gif.Encode(w, img, &gif.Options{
    94  			NumColors: 256,
    95  		})
    96  	case TIFF:
    97  		return tiff.Encode(w, img, &tiff.Options{Compression: tiff.Deflate, Predictor: true})
    98  
    99  	case BMP:
   100  		return bmp.Encode(w, img)
   101  	case WEBP:
   102  		return webp.Encode(
   103  			w,
   104  			img, webpoptions.EncodingOptions{
   105  				Quality:        conf.Quality,
   106  				EncodingPreset: webpoptions.EncodingPreset(conf.Hint),
   107  				UseSharpYuv:    true,
   108  			},
   109  		)
   110  	default:
   111  		return errors.New("format not supported")
   112  	}
   113  }
   114  
   115  // Height returns i's height.
   116  func (i *Image) Height() int {
   117  	i.initConfig()
   118  	return i.config.Height
   119  }
   120  
   121  // Width returns i's width.
   122  func (i *Image) Width() int {
   123  	i.initConfig()
   124  	return i.config.Width
   125  }
   126  
   127  func (i Image) WithImage(img image.Image) *Image {
   128  	i.Spec = nil
   129  	i.imageConfig = &imageConfig{
   130  		config:       imageConfigFromImage(img),
   131  		configLoaded: true,
   132  	}
   133  
   134  	return &i
   135  }
   136  
   137  func (i Image) WithSpec(s Spec) *Image {
   138  	i.Spec = s
   139  	i.imageConfig = &imageConfig{}
   140  	return &i
   141  }
   142  
   143  // InitConfig reads the image config from the given reader.
   144  func (i *Image) InitConfig(r io.Reader) error {
   145  	var err error
   146  	i.configInit.Do(func() {
   147  		i.config, _, err = image.DecodeConfig(r)
   148  	})
   149  	return err
   150  }
   151  
   152  func (i *Image) initConfig() error {
   153  	var err error
   154  	i.configInit.Do(func() {
   155  		if i.configLoaded {
   156  			return
   157  		}
   158  
   159  		var f hugio.ReadSeekCloser
   160  
   161  		f, err = i.Spec.ReadSeekCloser()
   162  		if err != nil {
   163  			return
   164  		}
   165  		defer f.Close()
   166  
   167  		i.config, _, err = image.DecodeConfig(f)
   168  	})
   169  
   170  	if err != nil {
   171  		return fmt.Errorf("failed to load image config: %w", err)
   172  	}
   173  
   174  	return nil
   175  }
   176  
   177  func NewImageProcessor(cfg ImagingConfig) (*ImageProcessor, error) {
   178  	e := cfg.Cfg.Exif
   179  	exifDecoder, err := exif.NewDecoder(
   180  		exif.WithDateDisabled(e.DisableDate),
   181  		exif.WithLatLongDisabled(e.DisableLatLong),
   182  		exif.ExcludeFields(e.ExcludeFields),
   183  		exif.IncludeFields(e.IncludeFields),
   184  	)
   185  	if err != nil {
   186  		return nil, err
   187  	}
   188  
   189  	return &ImageProcessor{
   190  		Cfg:         cfg,
   191  		exifDecoder: exifDecoder,
   192  	}, nil
   193  }
   194  
   195  type ImageProcessor struct {
   196  	Cfg         ImagingConfig
   197  	exifDecoder *exif.Decoder
   198  }
   199  
   200  func (p *ImageProcessor) DecodeExif(r io.Reader) (*exif.ExifInfo, error) {
   201  	return p.exifDecoder.Decode(r)
   202  }
   203  
   204  func (p *ImageProcessor) ApplyFiltersFromConfig(src image.Image, conf ImageConfig) (image.Image, error) {
   205  	var filters []gift.Filter
   206  
   207  	if conf.Rotate != 0 {
   208  		// Apply any rotation before any resize.
   209  		filters = append(filters, gift.Rotate(float32(conf.Rotate), color.Transparent, gift.NearestNeighborInterpolation))
   210  	}
   211  
   212  	switch conf.Action {
   213  	case "resize":
   214  		filters = append(filters, gift.Resize(conf.Width, conf.Height, conf.Filter))
   215  	case "crop":
   216  		if conf.AnchorStr == smartCropIdentifier {
   217  			bounds, err := p.smartCrop(src, conf.Width, conf.Height, conf.Filter)
   218  			if err != nil {
   219  				return nil, err
   220  			}
   221  
   222  			// First crop using the bounds returned by smartCrop.
   223  			filters = append(filters, gift.Crop(bounds))
   224  			// Then center crop the image to get an image the desired size without resizing.
   225  			filters = append(filters, gift.CropToSize(conf.Width, conf.Height, gift.CenterAnchor))
   226  
   227  		} else {
   228  			filters = append(filters, gift.CropToSize(conf.Width, conf.Height, conf.Anchor))
   229  		}
   230  	case "fill":
   231  		if conf.AnchorStr == smartCropIdentifier {
   232  			bounds, err := p.smartCrop(src, conf.Width, conf.Height, conf.Filter)
   233  			if err != nil {
   234  				return nil, err
   235  			}
   236  
   237  			// First crop it, then resize it.
   238  			filters = append(filters, gift.Crop(bounds))
   239  			filters = append(filters, gift.Resize(conf.Width, conf.Height, conf.Filter))
   240  
   241  		} else {
   242  			filters = append(filters, gift.ResizeToFill(conf.Width, conf.Height, conf.Filter, conf.Anchor))
   243  		}
   244  	case "fit":
   245  		filters = append(filters, gift.ResizeToFit(conf.Width, conf.Height, conf.Filter))
   246  	default:
   247  		return nil, fmt.Errorf("unsupported action: %q", conf.Action)
   248  	}
   249  
   250  	img, err := p.doFilter(src, conf.TargetFormat, filters...)
   251  	if err != nil {
   252  		return nil, err
   253  	}
   254  
   255  	return img, nil
   256  }
   257  
   258  func (p *ImageProcessor) Filter(src image.Image, filters ...gift.Filter) (image.Image, error) {
   259  	return p.doFilter(src, 0, filters...)
   260  }
   261  
   262  func (p *ImageProcessor) doFilter(src image.Image, targetFormat Format, filters ...gift.Filter) (image.Image, error) {
   263  
   264  	filter := gift.New(filters...)
   265  
   266  	if giph, ok := src.(Giphy); ok {
   267  		g := giph.GIF()
   268  		if len(g.Image) < 2 || (targetFormat == 0 || targetFormat != GIF) {
   269  			src = g.Image[0]
   270  		} else {
   271  			var bounds image.Rectangle
   272  			firstFrame := g.Image[0]
   273  			tmp := image.NewNRGBA(firstFrame.Bounds())
   274  			for i := range g.Image {
   275  				gift.New().DrawAt(tmp, g.Image[i], g.Image[i].Bounds().Min, gift.OverOperator)
   276  				bounds = filter.Bounds(tmp.Bounds())
   277  				dst := image.NewPaletted(bounds, g.Image[i].Palette)
   278  				filter.Draw(dst, tmp)
   279  				g.Image[i] = dst
   280  			}
   281  			g.Config.Width = bounds.Dx()
   282  			g.Config.Height = bounds.Dy()
   283  
   284  			return giph, nil
   285  		}
   286  
   287  	}
   288  
   289  	bounds := filter.Bounds(src.Bounds())
   290  
   291  	var dst draw.Image
   292  	switch src.(type) {
   293  	case *image.RGBA:
   294  		dst = image.NewRGBA(bounds)
   295  	case *image.NRGBA:
   296  		dst = image.NewNRGBA(bounds)
   297  	case *image.Gray:
   298  		dst = image.NewGray(bounds)
   299  	default:
   300  		dst = image.NewNRGBA(bounds)
   301  	}
   302  	filter.Draw(dst, src)
   303  
   304  	return dst, nil
   305  }
   306  
   307  func GetDefaultImageConfig(action string, defaults ImagingConfig) ImageConfig {
   308  	return ImageConfig{
   309  		Action:  action,
   310  		Hint:    defaults.Hint,
   311  		Quality: defaults.Cfg.Quality,
   312  	}
   313  }
   314  
   315  type Spec interface {
   316  	// Loads the image source.
   317  	ReadSeekCloser() (hugio.ReadSeekCloser, error)
   318  }
   319  
   320  // Format is an image file format.
   321  type Format int
   322  
   323  const (
   324  	JPEG Format = iota + 1
   325  	PNG
   326  	GIF
   327  	TIFF
   328  	BMP
   329  	WEBP
   330  )
   331  
   332  // RequiresDefaultQuality returns if the default quality needs to be applied to
   333  // images of this format.
   334  func (f Format) RequiresDefaultQuality() bool {
   335  	return f == JPEG || f == WEBP
   336  }
   337  
   338  // SupportsTransparency reports whether it supports transparency in any form.
   339  func (f Format) SupportsTransparency() bool {
   340  	return f != JPEG
   341  }
   342  
   343  // DefaultExtension returns the default file extension of this format, starting with a dot.
   344  // For example: .jpg for JPEG
   345  func (f Format) DefaultExtension() string {
   346  	return f.MediaType().FirstSuffix.FullSuffix
   347  }
   348  
   349  // MediaType returns the media type of this image, e.g. image/jpeg for JPEG
   350  func (f Format) MediaType() media.Type {
   351  	switch f {
   352  	case JPEG:
   353  		return media.JPEGType
   354  	case PNG:
   355  		return media.PNGType
   356  	case GIF:
   357  		return media.GIFType
   358  	case TIFF:
   359  		return media.TIFFType
   360  	case BMP:
   361  		return media.BMPType
   362  	case WEBP:
   363  		return media.WEBPType
   364  	default:
   365  		panic(fmt.Sprintf("%d is not a valid image format", f))
   366  	}
   367  }
   368  
   369  type imageConfig struct {
   370  	config       image.Config
   371  	configInit   sync.Once
   372  	configLoaded bool
   373  }
   374  
   375  func imageConfigFromImage(img image.Image) image.Config {
   376  	b := img.Bounds()
   377  	return image.Config{Width: b.Max.X, Height: b.Max.Y}
   378  }
   379  
   380  func ToFilters(in any) []gift.Filter {
   381  	switch v := in.(type) {
   382  	case []gift.Filter:
   383  		return v
   384  	case []filter:
   385  		vv := make([]gift.Filter, len(v))
   386  		for i, f := range v {
   387  			vv[i] = f
   388  		}
   389  		return vv
   390  	case gift.Filter:
   391  		return []gift.Filter{v}
   392  	default:
   393  		panic(fmt.Sprintf("%T is not an image filter", in))
   394  	}
   395  }
   396  
   397  // IsOpaque returns false if the image has alpha channel and there is at least 1
   398  // pixel that is not (fully) opaque.
   399  func IsOpaque(img image.Image) bool {
   400  	if oim, ok := img.(interface {
   401  		Opaque() bool
   402  	}); ok {
   403  		return oim.Opaque()
   404  	}
   405  
   406  	return false
   407  }
   408  
   409  // ImageSource identifies and decodes an image.
   410  type ImageSource interface {
   411  	DecodeImage() (image.Image, error)
   412  	Key() string
   413  }
   414  
   415  // Giphy represents a GIF Image that may be animated.
   416  type Giphy interface {
   417  	image.Image    // The first frame.
   418  	GIF() *gif.GIF // All frames.
   419  }