1
0

prepare.go 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. package processing
  2. import (
  3. "math"
  4. "github.com/imgproxy/imgproxy/v3/imath"
  5. "github.com/imgproxy/imgproxy/v3/vips"
  6. )
  7. // ExtractGeometry extracts image width, height, orientation angle and flip flag from the image metadata.
  8. func ExtractGeometry(img *vips.Image, baseAngle int, autoRotate bool) (int, int, int, bool) {
  9. width := img.Width()
  10. height := img.Height()
  11. angle, flip := angleFlip(img, autoRotate)
  12. if (angle+baseAngle)%180 != 0 {
  13. width, height = height, width
  14. }
  15. return width, height, angle, flip
  16. }
  17. // angleFlip returns the orientation angle and flip flag based on the image metadata
  18. // and po.AutoRotate flag.
  19. func angleFlip(img *vips.Image, autoRotate bool) (int, bool) {
  20. if !autoRotate {
  21. return 0, false
  22. }
  23. angle := 0
  24. flip := false
  25. orientation := img.Orientation()
  26. if orientation == 3 || orientation == 4 {
  27. angle = 180
  28. }
  29. if orientation == 5 || orientation == 6 {
  30. angle = 90
  31. }
  32. if orientation == 7 || orientation == 8 {
  33. angle = 270
  34. }
  35. if orientation == 2 || orientation == 4 || orientation == 5 || orientation == 7 {
  36. flip = true
  37. }
  38. return angle, flip
  39. }
  40. // CalcCropSize calculates the crop size based on the original size and crop scale.
  41. func CalcCropSize(orig int, crop float64) int {
  42. switch {
  43. case crop == 0.0:
  44. return 0
  45. case crop >= 1.0:
  46. return int(crop)
  47. default:
  48. return max(1, imath.Scale(orig, crop))
  49. }
  50. }
  51. func (c *Context) calcScale(width, height int, po ProcessingOptions) {
  52. wshrink, hshrink := 1.0, 1.0
  53. srcW, srcH := float64(width), float64(height)
  54. poWidth := po.Width()
  55. poHeight := po.Height()
  56. dstW := imath.NonZero(float64(poWidth), srcW)
  57. dstH := imath.NonZero(float64(poHeight), srcH)
  58. if dstW != srcW {
  59. wshrink = srcW / dstW
  60. }
  61. if dstH != srcH {
  62. hshrink = srcH / dstH
  63. }
  64. if wshrink != 1 || hshrink != 1 {
  65. rt := po.ResizingType()
  66. if rt == ResizeAuto {
  67. srcD := srcW - srcH
  68. dstD := dstW - dstH
  69. if (srcD >= 0 && dstD >= 0) || (srcD < 0 && dstD < 0) {
  70. rt = ResizeFill
  71. } else {
  72. rt = ResizeFit
  73. }
  74. }
  75. switch {
  76. case poWidth == 0 && rt != ResizeForce:
  77. wshrink = hshrink
  78. case poHeight == 0 && rt != ResizeForce:
  79. hshrink = wshrink
  80. case rt == ResizeFit:
  81. wshrink = math.Max(wshrink, hshrink)
  82. hshrink = wshrink
  83. case rt == ResizeFill || rt == ResizeFillDown:
  84. wshrink = math.Min(wshrink, hshrink)
  85. hshrink = wshrink
  86. }
  87. }
  88. wshrink /= po.ZoomWidth()
  89. hshrink /= po.ZoomHeight()
  90. c.DprScale = po.DPR()
  91. isVector := c.ImgData != nil && c.ImgData.Format().IsVector()
  92. if !po.Enlarge() && !isVector {
  93. minShrink := math.Min(wshrink, hshrink)
  94. if minShrink < 1 {
  95. wshrink /= minShrink
  96. hshrink /= minShrink
  97. // If we reached this point, this means that we can't reach the target size
  98. // because the image is smaller than it, and the enlargement is disabled.
  99. // If the DprScale is less than 1, the image will be downscaled, moving
  100. // even further from the target size, so we need to compensate it.
  101. // The compensation may increase the DprScale too much, but this is okay,
  102. // because we'll handle this further in the code.
  103. //
  104. // If the Extend option is enabled, we want to keep the resulting image
  105. // composition the same regardless of the DPR, so we don't apply this compensation
  106. // in this case.
  107. if !po.ExtendEnabled() {
  108. c.DprScale /= minShrink
  109. }
  110. }
  111. // The minimum of wshrink and hshrink is the maximum dprScale value
  112. // that can be used without enlarging the image.
  113. c.DprScale = math.Min(c.DprScale, math.Min(wshrink, hshrink))
  114. }
  115. if minWidth := po.MinWidth(); minWidth > 0 {
  116. if minShrink := srcW / float64(minWidth); minShrink < wshrink {
  117. hshrink /= wshrink / minShrink
  118. wshrink = minShrink
  119. }
  120. }
  121. if minHeight := po.MinHeight(); minHeight > 0 {
  122. if minShrink := srcH / float64(minHeight); minShrink < hshrink {
  123. wshrink /= hshrink / minShrink
  124. hshrink = minShrink
  125. }
  126. }
  127. wshrink /= c.DprScale
  128. hshrink /= c.DprScale
  129. if wshrink > srcW {
  130. wshrink = srcW
  131. }
  132. if hshrink > srcH {
  133. hshrink = srcH
  134. }
  135. c.WScale = 1.0 / wshrink
  136. c.HScale = 1.0 / hshrink
  137. }
  138. func (c *Context) calcSizes(widthToScale, heightToScale int, po ProcessingOptions) {
  139. c.TargetWidth = imath.Scale(po.Width(), c.DprScale*po.ZoomWidth())
  140. c.TargetHeight = imath.Scale(po.Height(), c.DprScale*po.ZoomHeight())
  141. c.ScaledWidth = imath.Scale(widthToScale, c.WScale)
  142. c.ScaledHeight = imath.Scale(heightToScale, c.HScale)
  143. if po.ResizingType() == ResizeFillDown && !po.Enlarge() {
  144. diffW := float64(c.TargetWidth) / float64(c.ScaledWidth)
  145. diffH := float64(c.TargetHeight) / float64(c.ScaledHeight)
  146. switch {
  147. case diffW > diffH && diffW > 1.0:
  148. c.ResultCropHeight = imath.Scale(c.ScaledWidth, float64(c.TargetHeight)/float64(c.TargetWidth))
  149. c.ResultCropWidth = c.ScaledWidth
  150. case diffH > diffW && diffH > 1.0:
  151. c.ResultCropWidth = imath.Scale(c.ScaledHeight, float64(c.TargetWidth)/float64(c.TargetHeight))
  152. c.ResultCropHeight = c.ScaledHeight
  153. default:
  154. c.ResultCropWidth = c.TargetWidth
  155. c.ResultCropHeight = c.TargetHeight
  156. }
  157. } else {
  158. c.ResultCropWidth = c.TargetWidth
  159. c.ResultCropHeight = c.TargetHeight
  160. }
  161. if po.ExtendAspectRatioEnabled() && c.TargetWidth > 0 && c.TargetHeight > 0 {
  162. outWidth := imath.MinNonZero(c.ScaledWidth, c.ResultCropWidth)
  163. outHeight := imath.MinNonZero(c.ScaledHeight, c.ResultCropHeight)
  164. diffW := float64(c.TargetWidth) / float64(outWidth)
  165. diffH := float64(c.TargetHeight) / float64(outHeight)
  166. switch {
  167. case diffH > diffW:
  168. c.ExtendAspectRatioHeight = imath.Scale(outWidth, float64(c.TargetHeight)/float64(c.TargetWidth))
  169. c.ExtendAspectRatioWidth = outWidth
  170. case diffW > diffH:
  171. c.ExtendAspectRatioWidth = imath.Scale(outHeight, float64(c.TargetWidth)/float64(c.TargetHeight))
  172. c.ExtendAspectRatioHeight = outHeight
  173. }
  174. }
  175. }
  176. func (c *Context) limitScale(widthToScale, heightToScale int, po ProcessingOptions) {
  177. maxresultDim := c.SecOps.MaxResultDimension
  178. if maxresultDim <= 0 {
  179. return
  180. }
  181. outWidth := imath.MinNonZero(c.ScaledWidth, c.ResultCropWidth)
  182. outHeight := imath.MinNonZero(c.ScaledHeight, c.ResultCropHeight)
  183. if po.ExtendEnabled() {
  184. outWidth = max(outWidth, c.TargetWidth)
  185. outHeight = max(outHeight, c.TargetHeight)
  186. } else if po.ExtendAspectRatioEnabled() {
  187. outWidth = max(outWidth, c.ExtendAspectRatioWidth)
  188. outHeight = max(outHeight, c.ExtendAspectRatioHeight)
  189. }
  190. outWidth += imath.ScaleToEven(po.PaddingLeft(), c.DprScale)
  191. outWidth += imath.ScaleToEven(po.PaddingRight(), c.DprScale)
  192. outHeight += imath.ScaleToEven(po.PaddingTop(), c.DprScale)
  193. outHeight += imath.ScaleToEven(po.PaddingBottom(), c.DprScale)
  194. if maxresultDim > 0 && (outWidth > maxresultDim || outHeight > maxresultDim) {
  195. downScale := float64(maxresultDim) / float64(max(outWidth, outHeight))
  196. c.WScale *= downScale
  197. c.HScale *= downScale
  198. // Prevent scaling below 1px
  199. if minWScale := 1.0 / float64(widthToScale); c.WScale < minWScale {
  200. c.WScale = minWScale
  201. }
  202. if minHScale := 1.0 / float64(heightToScale); c.HScale < minHScale {
  203. c.HScale = minHScale
  204. }
  205. c.DprScale *= downScale
  206. // Recalculate the sizes after changing the scales
  207. c.calcSizes(widthToScale, heightToScale, po)
  208. }
  209. }
  210. // Prepare calculates context image parameters based on the current image size.
  211. // Some steps (like trim) must call this function when finished.
  212. func (c *Context) CalcParams() {
  213. c.SrcWidth, c.SrcHeight, c.Angle, c.Flip = ExtractGeometry(c.Img, c.PO.Rotate(), c.PO.AutoRotate())
  214. c.CropWidth = CalcCropSize(c.SrcWidth, c.PO.CropWidth())
  215. c.CropHeight = CalcCropSize(c.SrcHeight, c.PO.CropHeight())
  216. widthToScale := imath.MinNonZero(c.CropWidth, c.SrcWidth)
  217. heightToScale := imath.MinNonZero(c.CropHeight, c.SrcHeight)
  218. c.calcScale(widthToScale, heightToScale, c.PO)
  219. c.calcSizes(widthToScale, heightToScale, c.PO)
  220. c.limitScale(widthToScale, heightToScale, c.PO)
  221. }