github.com/kovansky/hugo@v0.92.3-0.20220224232819-63076e4ff19f/resources/images/smartcrop.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  	"image"
    18  	"math"
    19  
    20  	"github.com/disintegration/gift"
    21  
    22  	"github.com/muesli/smartcrop"
    23  )
    24  
    25  const (
    26  	// Do not change.
    27  	smartCropIdentifier = "smart"
    28  
    29  	// This is just a increment, starting on 1. If Smart Crop improves its cropping, we
    30  	// need a way to trigger a re-generation of the crops in the wild, so increment this.
    31  	smartCropVersionNumber = 1
    32  )
    33  
    34  func (p *ImageProcessor) newSmartCropAnalyzer(filter gift.Resampling) smartcrop.Analyzer {
    35  	return smartcrop.NewAnalyzer(imagingResizer{p: p, filter: filter})
    36  }
    37  
    38  // Needed by smartcrop
    39  type imagingResizer struct {
    40  	p      *ImageProcessor
    41  	filter gift.Resampling
    42  }
    43  
    44  func (r imagingResizer) Resize(img image.Image, width, height uint) image.Image {
    45  	// See https://github.com/gohugoio/hugo/issues/7955#issuecomment-861710681
    46  	scaleX, scaleY := calcFactorsNfnt(width, height, float64(img.Bounds().Dx()), float64(img.Bounds().Dy()))
    47  	if width == 0 {
    48  		width = uint(math.Ceil(float64(img.Bounds().Dx()) / scaleX))
    49  	}
    50  	if height == 0 {
    51  		height = uint(math.Ceil(float64(img.Bounds().Dy()) / scaleY))
    52  	}
    53  	result, _ := r.p.Filter(img, gift.Resize(int(width), int(height), r.filter))
    54  	return result
    55  }
    56  
    57  func (p *ImageProcessor) smartCrop(img image.Image, width, height int, filter gift.Resampling) (image.Rectangle, error) {
    58  	if width <= 0 || height <= 0 {
    59  		return image.Rectangle{}, nil
    60  	}
    61  
    62  	srcBounds := img.Bounds()
    63  	srcW := srcBounds.Dx()
    64  	srcH := srcBounds.Dy()
    65  
    66  	if srcW <= 0 || srcH <= 0 {
    67  		return image.Rectangle{}, nil
    68  	}
    69  
    70  	if srcW == width && srcH == height {
    71  		return srcBounds, nil
    72  	}
    73  
    74  	smart := p.newSmartCropAnalyzer(filter)
    75  
    76  	rect, err := smart.FindBestCrop(img, width, height)
    77  	if err != nil {
    78  		return image.Rectangle{}, err
    79  	}
    80  
    81  	return img.Bounds().Intersect(rect), nil
    82  }
    83  
    84  // Calculates scaling factors using old and new image dimensions.
    85  // Code borrowed from https://github.com/nfnt/resize/blob/83c6a9932646f83e3267f353373d47347b6036b2/resize.go#L593
    86  func calcFactorsNfnt(width, height uint, oldWidth, oldHeight float64) (scaleX, scaleY float64) {
    87  	if width == 0 {
    88  		if height == 0 {
    89  			scaleX = 1.0
    90  			scaleY = 1.0
    91  		} else {
    92  			scaleY = oldHeight / float64(height)
    93  			scaleX = scaleY
    94  		}
    95  	} else {
    96  		scaleX = oldWidth / float64(width)
    97  		if height == 0 {
    98  			scaleY = scaleX
    99  		} else {
   100  			scaleY = oldHeight / float64(height)
   101  		}
   102  	}
   103  	return
   104  }