1
0

save_fit_bytes.go 2.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
  1. package processing
  2. import (
  3. "context"
  4. "github.com/imgproxy/imgproxy/v3/imagedata"
  5. "github.com/imgproxy/imgproxy/v3/imath"
  6. "github.com/imgproxy/imgproxy/v3/options"
  7. "github.com/imgproxy/imgproxy/v3/server"
  8. "github.com/imgproxy/imgproxy/v3/vips"
  9. )
  10. // saveImageToFitBytes tries to save the image to fit into the specified max bytes
  11. // by lowering the quality. It returns the image data that fits the requirement
  12. // or the best effort data if it was not possible to fit into the limit.
  13. func saveImageToFitBytes(
  14. ctx context.Context,
  15. po *options.ProcessingOptions,
  16. img *vips.Image,
  17. ) (imagedata.ImageData, error) {
  18. var newQuality int
  19. // Start with the quality specified in the options.
  20. quality := po.GetQuality()
  21. // We will probably save the image multiple times, so we need to process its pixels
  22. // to ensure that it is in random access mode.
  23. if err := img.CopyMemory(); err != nil {
  24. return nil, err
  25. }
  26. for {
  27. // Check for timeout or cancellation before each attempt as we might spend too much
  28. // time processing the image or making previous attempts.
  29. if err := server.CheckTimeout(ctx); err != nil {
  30. return nil, err
  31. }
  32. imgdata, err := img.Save(po.Format, quality)
  33. if err != nil {
  34. return nil, err
  35. }
  36. size, err := imgdata.Size()
  37. if err != nil {
  38. imgdata.Close()
  39. return nil, err
  40. }
  41. // If we fit the limit or quality is too low, return the result.
  42. if size <= po.MaxBytes || quality <= 10 {
  43. return imgdata, err
  44. }
  45. // We don't need the image data anymore, close it to free resources.
  46. imgdata.Close()
  47. // Tune quality for the next attempt based on how much we exceed the limit.
  48. delta := float64(size) / float64(po.MaxBytes)
  49. switch {
  50. case delta > 3:
  51. newQuality = imath.Scale(quality, 0.25)
  52. case delta > 1.5:
  53. newQuality = imath.Scale(quality, 0.5)
  54. default:
  55. newQuality = imath.Scale(quality, 0.75)
  56. }
  57. // Ensure that quality is always lowered, even if the scaling
  58. // doesn't change it due to rounding.
  59. // Also, ensure that quality doesn't go below the minimum.
  60. quality = max(1, min(quality-1, newQuality))
  61. }
  62. }