github.com/anakojm/hugo-katex@v0.0.0-20231023141351-42d6f5de9c0b/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  	"errors"
    18  	"fmt"
    19  	"image"
    20  	"image/color"
    21  	"image/draw"
    22  	"image/gif"
    23  	"image/jpeg"
    24  	"image/png"
    25  	"io"
    26  	"sync"
    27  
    28  	"github.com/bep/gowebp/libwebp/webpoptions"
    29  	"github.com/gohugoio/hugo/config"
    30  	"github.com/gohugoio/hugo/resources/images/webp"
    31  
    32  	"github.com/gohugoio/hugo/media"
    33  	"github.com/gohugoio/hugo/resources/images/exif"
    34  
    35  	"github.com/disintegration/gift"
    36  	"golang.org/x/image/bmp"
    37  	"golang.org/x/image/tiff"
    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 *config.ConfigNamespace[ImagingConfig, ImagingConfigInternal]) (*ImageProcessor, error) {
   178  	e := cfg.Config.Imaging.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         *config.ConfigNamespace[ImagingConfig, ImagingConfigInternal]
   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) FiltersFromConfig(src image.Image, conf ImageConfig) ([]gift.Filter, 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  
   248  	}
   249  	return filters, nil
   250  }
   251  
   252  func (p *ImageProcessor) ApplyFiltersFromConfig(src image.Image, conf ImageConfig) (image.Image, error) {
   253  	filters, err := p.FiltersFromConfig(src, conf)
   254  	if err != nil {
   255  		return nil, err
   256  	}
   257  
   258  	if len(filters) == 0 {
   259  		return p.resolveSrc(src, conf.TargetFormat), nil
   260  	}
   261  
   262  	img, err := p.doFilter(src, conf.TargetFormat, filters...)
   263  	if err != nil {
   264  		return nil, err
   265  	}
   266  
   267  	return img, nil
   268  }
   269  
   270  func (p *ImageProcessor) Filter(src image.Image, filters ...gift.Filter) (image.Image, error) {
   271  	return p.doFilter(src, 0, filters...)
   272  }
   273  
   274  func (p *ImageProcessor) resolveSrc(src image.Image, targetFormat Format) image.Image {
   275  	if giph, ok := src.(Giphy); ok {
   276  		g := giph.GIF()
   277  		if len(g.Image) < 2 || (targetFormat == 0 || targetFormat != GIF) {
   278  			src = g.Image[0]
   279  		}
   280  	}
   281  	return src
   282  }
   283  
   284  func (p *ImageProcessor) doFilter(src image.Image, targetFormat Format, filters ...gift.Filter) (image.Image, error) {
   285  	filter := gift.New(filters...)
   286  
   287  	if giph, ok := src.(Giphy); ok {
   288  		g := giph.GIF()
   289  		if len(g.Image) < 2 || (targetFormat == 0 || targetFormat != GIF) {
   290  			src = g.Image[0]
   291  		} else {
   292  			var bounds image.Rectangle
   293  			firstFrame := g.Image[0]
   294  			tmp := image.NewNRGBA(firstFrame.Bounds())
   295  			for i := range g.Image {
   296  				gift.New().DrawAt(tmp, g.Image[i], g.Image[i].Bounds().Min, gift.OverOperator)
   297  				bounds = filter.Bounds(tmp.Bounds())
   298  				dst := image.NewPaletted(bounds, g.Image[i].Palette)
   299  				filter.Draw(dst, tmp)
   300  				g.Image[i] = dst
   301  			}
   302  			g.Config.Width = bounds.Dx()
   303  			g.Config.Height = bounds.Dy()
   304  
   305  			return giph, nil
   306  		}
   307  
   308  	}
   309  
   310  	bounds := filter.Bounds(src.Bounds())
   311  
   312  	var dst draw.Image
   313  	switch src.(type) {
   314  	case *image.RGBA:
   315  		dst = image.NewRGBA(bounds)
   316  	case *image.NRGBA:
   317  		dst = image.NewNRGBA(bounds)
   318  	case *image.Gray:
   319  		dst = image.NewGray(bounds)
   320  	default:
   321  		dst = image.NewNRGBA(bounds)
   322  	}
   323  	filter.Draw(dst, src)
   324  
   325  	return dst, nil
   326  }
   327  
   328  func GetDefaultImageConfig(action string, defaults *config.ConfigNamespace[ImagingConfig, ImagingConfigInternal]) ImageConfig {
   329  	if defaults == nil {
   330  		defaults = defaultImageConfig
   331  	}
   332  	return ImageConfig{
   333  		Action:  action,
   334  		Hint:    defaults.Config.Hint,
   335  		Quality: defaults.Config.Imaging.Quality,
   336  	}
   337  }
   338  
   339  type Spec interface {
   340  	// Loads the image source.
   341  	ReadSeekCloser() (hugio.ReadSeekCloser, error)
   342  }
   343  
   344  // Format is an image file format.
   345  type Format int
   346  
   347  const (
   348  	JPEG Format = iota + 1
   349  	PNG
   350  	GIF
   351  	TIFF
   352  	BMP
   353  	WEBP
   354  )
   355  
   356  // RequiresDefaultQuality returns if the default quality needs to be applied to
   357  // images of this format.
   358  func (f Format) RequiresDefaultQuality() bool {
   359  	return f == JPEG || f == WEBP
   360  }
   361  
   362  // SupportsTransparency reports whether it supports transparency in any form.
   363  func (f Format) SupportsTransparency() bool {
   364  	return f != JPEG
   365  }
   366  
   367  // DefaultExtension returns the default file extension of this format, starting with a dot.
   368  // For example: .jpg for JPEG
   369  func (f Format) DefaultExtension() string {
   370  	return f.MediaType().FirstSuffix.FullSuffix
   371  }
   372  
   373  // MediaType returns the media type of this image, e.g. image/jpeg for JPEG
   374  func (f Format) MediaType() media.Type {
   375  	switch f {
   376  	case JPEG:
   377  		return media.Builtin.JPEGType
   378  	case PNG:
   379  		return media.Builtin.PNGType
   380  	case GIF:
   381  		return media.Builtin.GIFType
   382  	case TIFF:
   383  		return media.Builtin.TIFFType
   384  	case BMP:
   385  		return media.Builtin.BMPType
   386  	case WEBP:
   387  		return media.Builtin.WEBPType
   388  	default:
   389  		panic(fmt.Sprintf("%d is not a valid image format", f))
   390  	}
   391  }
   392  
   393  type imageConfig struct {
   394  	config       image.Config
   395  	configInit   sync.Once
   396  	configLoaded bool
   397  }
   398  
   399  func imageConfigFromImage(img image.Image) image.Config {
   400  	if giphy, ok := img.(Giphy); ok {
   401  		return giphy.GIF().Config
   402  	}
   403  	b := img.Bounds()
   404  	return image.Config{Width: b.Max.X, Height: b.Max.Y}
   405  }
   406  
   407  // UnwrapFilter unwraps the given filter if it is a filter wrapper.
   408  func UnwrapFilter(in gift.Filter) gift.Filter {
   409  	if f, ok := in.(filter); ok {
   410  		return f.Filter
   411  	}
   412  	return in
   413  }
   414  
   415  // ToFilters converts the given input to a slice of gift.Filter.
   416  func ToFilters(in any) []gift.Filter {
   417  	switch v := in.(type) {
   418  	case []gift.Filter:
   419  		return v
   420  	case []filter:
   421  		vv := make([]gift.Filter, len(v))
   422  		for i, f := range v {
   423  			vv[i] = f
   424  		}
   425  		return vv
   426  	case gift.Filter:
   427  		return []gift.Filter{v}
   428  	default:
   429  		panic(fmt.Sprintf("%T is not an image filter", in))
   430  	}
   431  }
   432  
   433  // IsOpaque returns false if the image has alpha channel and there is at least 1
   434  // pixel that is not (fully) opaque.
   435  func IsOpaque(img image.Image) bool {
   436  	if oim, ok := img.(interface {
   437  		Opaque() bool
   438  	}); ok {
   439  		return oim.Opaque()
   440  	}
   441  
   442  	return false
   443  }
   444  
   445  // ImageSource identifies and decodes an image.
   446  type ImageSource interface {
   447  	DecodeImage() (image.Image, error)
   448  	Key() string
   449  }
   450  
   451  // Giphy represents a GIF Image that may be animated.
   452  type Giphy interface {
   453  	image.Image    // The first frame.
   454  	GIF() *gif.GIF // All frames.
   455  }