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 }