1
0

scale_on_load.go 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. package processing
  2. import (
  3. "log/slog"
  4. "math"
  5. "github.com/imgproxy/imgproxy/v3/imagedata"
  6. "github.com/imgproxy/imgproxy/v3/imagetype"
  7. "github.com/imgproxy/imgproxy/v3/imath"
  8. "github.com/imgproxy/imgproxy/v3/options"
  9. "github.com/imgproxy/imgproxy/v3/vips"
  10. )
  11. func canScaleOnLoad(c *Context, imgdata imagedata.ImageData, scale float64) bool {
  12. if imgdata == nil || scale == 1 {
  13. return false
  14. }
  15. if imgdata.Format().IsVector() {
  16. return true
  17. }
  18. if c.Config.DisableShrinkOnLoad || scale >= 1 {
  19. return false
  20. }
  21. return imgdata.Format() == imagetype.JPEG ||
  22. imgdata.Format() == imagetype.WEBP ||
  23. imgdata.Format() == imagetype.HEIC ||
  24. imgdata.Format() == imagetype.AVIF
  25. }
  26. func calcJpegShink(shrink float64) int {
  27. switch {
  28. case shrink >= 8:
  29. return 8
  30. case shrink >= 4:
  31. return 4
  32. case shrink >= 2:
  33. return 2
  34. }
  35. return 1
  36. }
  37. func scaleOnLoad(c *Context) error {
  38. wshrink := float64(c.SrcWidth) / float64(imath.Scale(c.SrcWidth, c.WScale))
  39. hshrink := float64(c.SrcHeight) / float64(imath.Scale(c.SrcHeight, c.HScale))
  40. preshrink := math.Min(wshrink, hshrink)
  41. prescale := 1.0 / preshrink
  42. if c.ImgData != nil && c.ImgData.Format().IsVector() {
  43. // For vector images, apply the vector base scale
  44. prescale *= c.VectorBaseScale
  45. }
  46. if !canScaleOnLoad(c, c.ImgData, prescale) {
  47. return nil
  48. }
  49. var newWidth, newHeight int
  50. if c.ImgData.Format().SupportsThumbnail() {
  51. thumbnail := new(vips.Image)
  52. defer thumbnail.Clear()
  53. if err := thumbnail.LoadThumbnail(c.ImgData); err != nil {
  54. slog.Debug("Can't load thumbnail: %s", "error", err)
  55. return nil
  56. }
  57. angle, flip := 0, false
  58. newWidth, newHeight, angle, flip = extractMeta(thumbnail, c.PO.Rotate, c.PO.AutoRotate)
  59. if newWidth >= c.SrcWidth || float64(newWidth)/float64(c.SrcWidth) < prescale {
  60. return nil
  61. }
  62. c.Img.Swap(thumbnail)
  63. c.Angle = angle
  64. c.Flip = flip
  65. } else {
  66. jpegShrink := calcJpegShink(preshrink)
  67. if c.ImgData.Format() == imagetype.JPEG && jpegShrink == 1 {
  68. return nil
  69. }
  70. if err := c.Img.Load(c.ImgData, jpegShrink, prescale, 1); err != nil {
  71. return err
  72. }
  73. newWidth, newHeight, _, _ = extractMeta(c.Img, c.PO.Rotate, c.PO.AutoRotate)
  74. }
  75. // Update scales after scale-on-load
  76. wpreshrink := float64(c.SrcWidth) / float64(newWidth)
  77. hpreshrink := float64(c.SrcHeight) / float64(newHeight)
  78. c.WScale = wpreshrink * c.WScale
  79. if newWidth == imath.Scale(newWidth, c.WScale) {
  80. c.WScale = 1.0
  81. }
  82. c.HScale = hpreshrink * c.HScale
  83. if newHeight == imath.Scale(newHeight, c.HScale) {
  84. c.HScale = 1.0
  85. }
  86. // We should crop before scaling, but we scaled the image on load,
  87. // so we need to adjust crop options
  88. if c.CropWidth > 0 {
  89. c.CropWidth = max(1, imath.Shrink(c.CropWidth, wpreshrink))
  90. }
  91. if c.CropHeight > 0 {
  92. c.CropHeight = max(1, imath.Shrink(c.CropHeight, hpreshrink))
  93. }
  94. if c.CropGravity.Type != options.GravityFocusPoint {
  95. // Adjust only when crop gravity offsets are absolute
  96. if math.Abs(c.CropGravity.X) >= 1.0 {
  97. // Round offsets to prevent turning absolute offsets to relative (ex: 1.0 => 0.5)
  98. c.CropGravity.X = math.RoundToEven(c.CropGravity.X / wpreshrink)
  99. }
  100. if math.Abs(c.CropGravity.Y) >= 1.0 {
  101. c.CropGravity.Y = math.RoundToEven(c.CropGravity.Y / hpreshrink)
  102. }
  103. }
  104. return nil
  105. }