123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156 |
- package processing
- import (
- "log/slog"
- "math"
- "github.com/imgproxy/imgproxy/v3/imagetype"
- "github.com/imgproxy/imgproxy/v3/imath"
- "github.com/imgproxy/imgproxy/v3/vips"
- )
- func (p *Processor) canScaleOnLoad(c *Context, shrink float64) bool {
- if c.ImgData == nil || shrink == 1 {
- return false
- }
- if c.ImgData.Format().IsVector() {
- return true
- }
- if p.config.DisableShrinkOnLoad || shrink <= 1 {
- return false
- }
- return c.ImgData.Format() == imagetype.JPEG ||
- c.ImgData.Format() == imagetype.WEBP ||
- c.ImgData.Format().SupportsThumbnail()
- }
- func calcJpegShink(shrink float64) float64 {
- switch {
- case shrink >= 8:
- return 8
- case shrink >= 4:
- return 4
- case shrink >= 2:
- return 2
- }
- return 1
- }
- func (p *Processor) scaleOnLoad(c *Context) error {
- // Get the preshrink value based on the requested scales.
- // We calculate it based on the image dimentions that we would get
- // with the current scales.
- // We can't just use c.WScale and c.HScale since this may lead to
- // overshrinking when only one target dimension is set.
- wshrink := float64(c.SrcWidth) / float64(imath.Scale(c.SrcWidth, c.WScale))
- hshrink := float64(c.SrcHeight) / float64(imath.Scale(c.SrcHeight, c.HScale))
- preshrink := min(wshrink, hshrink)
- // For vector images, apply the vector base shrink.
- // We might set it in the [Processor.vectorGuardScale] step in case the image
- // is too large.
- if c.ImgData != nil && c.ImgData.Format().IsVector() {
- preshrink *= c.VectorBaseShrink
- }
- // Check if we can and should scale the image on load
- if !p.canScaleOnLoad(c, preshrink) {
- return nil
- }
- // We will load the prescaled image into this new image.
- // On success, we will swap it with the original image in the context,
- // so we can safely clear it on function exit.
- newImg := new(vips.Image)
- defer newImg.Clear()
- loadThumbnail := c.ImgData.Format().SupportsThumbnail()
- if loadThumbnail {
- // If the image supports embedded thumbnails, try to load it
- if err := newImg.LoadThumbnail(c.ImgData); err != nil {
- slog.Debug("Can't load thumbnail: %s", "error", err)
- return nil
- }
- } else {
- // JPEG shrink-on-load must be 1, 2, 4 or 8.
- // We need to normalize it before passing to libvips.
- // For other formats, we can pass any float value.
- if c.ImgData.Format() == imagetype.JPEG {
- preshrink = calcJpegShink(preshrink)
- }
- // if preshrink is 1, we can skip reloading the image
- if preshrink == 1 {
- return nil
- }
- // Reload the image with preshrink
- if err := newImg.Load(c.ImgData, preshrink, 0, 1); err != nil {
- return err
- }
- }
- // Get the geometry of the preshrunk image
- newWidth, newHeight, newAngle, newFlip := ExtractGeometry(
- newImg, c.PO.Rotate(), c.PO.AutoRotate(),
- )
- // Calculate the actual preshrink values
- wpreshrink := float64(c.SrcWidth) / float64(newWidth)
- hpreshrink := float64(c.SrcHeight) / float64(newHeight)
- // If we loaded a thumbnail, check if it's worth using it
- if loadThumbnail {
- // If the thumbnail is not smaller than the original image or
- // if it is shrunk too much, we better keep the original image
- if min(wpreshrink, hpreshrink) <= 1.0 || max(wpreshrink, hpreshrink) > preshrink {
- return nil
- }
- }
- // Swap the image with the preshrunk one and update its orientation in the context
- c.Img.Swap(newImg)
- c.Angle = newAngle
- c.Flip = newFlip
- // Update scales after scale-on-load
- c.WScale *= wpreshrink
- c.HScale *= hpreshrink
- // If preshrink is exact, it's better to set scale to 1.0
- // to prevent additional scaling passes
- if newWidth == imath.Scale(newWidth, c.WScale) {
- c.WScale = 1.0
- }
- if newHeight == imath.Scale(newHeight, c.HScale) {
- c.HScale = 1.0
- }
- // We should crop before scaling, but we scaled the image on load,
- // so we need to adjust crop options
- if c.CropWidth > 0 {
- c.CropWidth = max(1, imath.Shrink(c.CropWidth, wpreshrink))
- }
- if c.CropHeight > 0 {
- c.CropHeight = max(1, imath.Shrink(c.CropHeight, hpreshrink))
- }
- // Adjust crop gravity offsets.
- // We don't need to adjust focus point offsets since they are always relative.
- // For other gravity types, we need to adjust only absolute offsets (>= 1.0 or <= -1.0).
- // We round absolute offsets to prevent turning them to relative (ex: 1.0 => 0.5).
- if c.CropGravity.Type != GravityFocusPoint {
- if math.Abs(c.CropGravity.X) >= 1.0 {
- c.CropGravity.X = math.RoundToEven(c.CropGravity.X / wpreshrink)
- }
- if math.Abs(c.CropGravity.Y) >= 1.0 {
- c.CropGravity.Y = math.RoundToEven(c.CropGravity.Y / hpreshrink)
- }
- }
- return nil
- }
|