github.com/graemephi/kahugo@v0.62.3-0.20211121071557-d78c0423784d/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 "fill":
   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 it, then resize it.
   218  			filters = append(filters, gift.Crop(bounds))
   219  			filters = append(filters, gift.Resize(conf.Width, conf.Height, conf.Filter))
   220  
   221  		} else {
   222  			filters = append(filters, gift.ResizeToFill(conf.Width, conf.Height, conf.Filter, conf.Anchor))
   223  		}
   224  	case "fit":
   225  		filters = append(filters, gift.ResizeToFit(conf.Width, conf.Height, conf.Filter))
   226  	default:
   227  		return nil, errors.Errorf("unsupported action: %q", conf.Action)
   228  	}
   229  
   230  	img, err := p.Filter(src, filters...)
   231  	if err != nil {
   232  		return nil, err
   233  	}
   234  
   235  	return img, nil
   236  }
   237  
   238  func (p *ImageProcessor) Filter(src image.Image, filters ...gift.Filter) (image.Image, error) {
   239  	g := gift.New(filters...)
   240  	bounds := g.Bounds(src.Bounds())
   241  	var dst draw.Image
   242  	switch src.(type) {
   243  	case *image.RGBA:
   244  		dst = image.NewRGBA(bounds)
   245  	case *image.NRGBA:
   246  		dst = image.NewNRGBA(bounds)
   247  	case *image.Gray:
   248  		dst = image.NewGray(bounds)
   249  	default:
   250  		dst = image.NewNRGBA(bounds)
   251  	}
   252  	g.Draw(dst, src)
   253  	return dst, nil
   254  }
   255  
   256  func GetDefaultImageConfig(action string, defaults ImagingConfig) ImageConfig {
   257  	return ImageConfig{
   258  		Action:  action,
   259  		Hint:    defaults.Hint,
   260  		Quality: defaults.Cfg.Quality,
   261  	}
   262  }
   263  
   264  type Spec interface {
   265  	// Loads the image source.
   266  	ReadSeekCloser() (hugio.ReadSeekCloser, error)
   267  }
   268  
   269  // Format is an image file format.
   270  type Format int
   271  
   272  const (
   273  	JPEG Format = iota + 1
   274  	PNG
   275  	GIF
   276  	TIFF
   277  	BMP
   278  	WEBP
   279  )
   280  
   281  // RequiresDefaultQuality returns if the default quality needs to be applied to
   282  // images of this format.
   283  func (f Format) RequiresDefaultQuality() bool {
   284  	return f == JPEG || f == WEBP
   285  }
   286  
   287  // SupportsTransparency reports whether it supports transparency in any form.
   288  func (f Format) SupportsTransparency() bool {
   289  	return f != JPEG
   290  }
   291  
   292  // DefaultExtension returns the default file extension of this format, starting with a dot.
   293  // For example: .jpg for JPEG
   294  func (f Format) DefaultExtension() string {
   295  	return f.MediaType().FirstSuffix.FullSuffix
   296  }
   297  
   298  // MediaType returns the media type of this image, e.g. image/jpeg for JPEG
   299  func (f Format) MediaType() media.Type {
   300  	switch f {
   301  	case JPEG:
   302  		return media.JPEGType
   303  	case PNG:
   304  		return media.PNGType
   305  	case GIF:
   306  		return media.GIFType
   307  	case TIFF:
   308  		return media.TIFFType
   309  	case BMP:
   310  		return media.BMPType
   311  	case WEBP:
   312  		return media.WEBPType
   313  	default:
   314  		panic(fmt.Sprintf("%d is not a valid image format", f))
   315  	}
   316  }
   317  
   318  type imageConfig struct {
   319  	config       image.Config
   320  	configInit   sync.Once
   321  	configLoaded bool
   322  }
   323  
   324  func imageConfigFromImage(img image.Image) image.Config {
   325  	b := img.Bounds()
   326  	return image.Config{Width: b.Max.X, Height: b.Max.Y}
   327  }
   328  
   329  func ToFilters(in interface{}) []gift.Filter {
   330  	switch v := in.(type) {
   331  	case []gift.Filter:
   332  		return v
   333  	case []filter:
   334  		vv := make([]gift.Filter, len(v))
   335  		for i, f := range v {
   336  			vv[i] = f
   337  		}
   338  		return vv
   339  	case gift.Filter:
   340  		return []gift.Filter{v}
   341  	default:
   342  		panic(fmt.Sprintf("%T is not an image filter", in))
   343  	}
   344  }
   345  
   346  // IsOpaque returns false if the image has alpha channel and there is at least 1
   347  // pixel that is not (fully) opaque.
   348  func IsOpaque(img image.Image) bool {
   349  	if oim, ok := img.(interface {
   350  		Opaque() bool
   351  	}); ok {
   352  		return oim.Opaque()
   353  	}
   354  
   355  	return false
   356  }
   357  
   358  // ImageSource identifies and decodes an image.
   359  type ImageSource interface {
   360  	DecodeImage() (image.Image, error)
   361  	Key() string
   362  }