smartcrop.go (2938B)
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 }