diff --git a/CHANGELOG.md b/CHANGELOG.md index 09bab2fc..49090332 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,12 @@ # Changelog ## [Unreleased] -## Add +### Add - Add support of RLE-encoded BMP. +### Change +- Use thumbnail embedded to HEIC/AVIF if its size is larger than or equal to the requested. + ## [3.4.0] - 2022-04-07 ### Add - Add `IMGPROXY_FALLBACK_IMAGE_TTL` config. diff --git a/imagetype/imagetype.go b/imagetype/imagetype.go index 56f1168d..16498568 100644 --- a/imagetype/imagetype.go +++ b/imagetype/imagetype.go @@ -130,3 +130,7 @@ func (it Type) SupportsColourProfile() bool { it == WEBP || it == AVIF } + +func (it Type) SupportsThumbnail() bool { + return it == HEIC || it == AVIF +} diff --git a/processing/scale_on_load.go b/processing/scale_on_load.go index 9b50e410..81203a47 100644 --- a/processing/scale_on_load.go +++ b/processing/scale_on_load.go @@ -11,8 +11,12 @@ import ( "github.com/imgproxy/imgproxy/v3/vips" ) -func canScaleOnLoad(imgtype imagetype.Type, scale float64) bool { - if imgtype == imagetype.SVG { +func canScaleOnLoad(pctx *pipelineContext, imgdata *imagedata.ImageData, scale float64) bool { + if imgdata == nil || pctx.trimmed || scale == 1 { + return false + } + + if imgdata.Type == imagetype.SVG { return true } @@ -20,7 +24,10 @@ func canScaleOnLoad(imgtype imagetype.Type, scale float64) bool { return false } - return imgtype == imagetype.JPEG || imgtype == imagetype.WEBP + return imgdata.Type == imagetype.JPEG || + imgdata.Type == imagetype.WEBP || + imgdata.Type == imagetype.HEIC || + imgdata.Type == imagetype.AVIF } func calcJpegShink(scale float64, imgtype imagetype.Type) int { @@ -41,23 +48,45 @@ func calcJpegShink(scale float64, imgtype imagetype.Type) int { func scaleOnLoad(pctx *pipelineContext, img *vips.Image, po *options.ProcessingOptions, imgdata *imagedata.ImageData) error { prescale := math.Max(pctx.wscale, pctx.hscale) - if pctx.trimmed || prescale == 1 || imgdata == nil || !canScaleOnLoad(pctx.imgtype, prescale) { + if !canScaleOnLoad(pctx, imgdata, prescale) { return nil } - jpegShrink := calcJpegShink(prescale, pctx.imgtype) + var newWidth, newHeight int - if pctx.imgtype == imagetype.JPEG && jpegShrink == 1 { - return nil - } + if imgdata.Type.SupportsThumbnail() { + thumbnail := new(vips.Image) + defer thumbnail.Clear() - if err := img.Load(imgdata, jpegShrink, prescale, 1); err != nil { - return err + if err := thumbnail.LoadThumbnail(imgdata); err != nil { + return err + } + + angle, flip := 0, false + newWidth, newHeight, angle, flip = extractMeta(thumbnail, po.Rotate, po.AutoRotate) + + if newWidth >= pctx.srcWidth || float64(newWidth)/float64(pctx.srcWidth) < prescale { + return nil + } + + img.Swap(thumbnail) + pctx.angle = angle + pctx.flip = flip + } else { + jpegShrink := calcJpegShink(prescale, pctx.imgtype) + + if pctx.imgtype == imagetype.JPEG && jpegShrink == 1 { + return nil + } + + if err := img.Load(imgdata, jpegShrink, prescale, 1); err != nil { + return err + } + + newWidth, newHeight, _, _ = extractMeta(img, po.Rotate, po.AutoRotate) } // Update scales after scale-on-load - newWidth, newHeight, _, _ := extractMeta(img, po.Rotate, po.AutoRotate) - wpreshrink := float64(pctx.srcWidth) / float64(newWidth) hpreshrink := float64(pctx.srcHeight) / float64(newHeight) diff --git a/vips/vips.c b/vips/vips.c index d6639923..d100c6fa 100644 --- a/vips/vips.c +++ b/vips/vips.c @@ -82,8 +82,13 @@ vips_svgload_go(void *buf, size_t len, double scale, VipsImage **out) { } int -vips_heifload_go(void *buf, size_t len, VipsImage **out) { - return vips_heifload_buffer(buf, len, out, "access", VIPS_ACCESS_SEQUENTIAL, NULL); +vips_heifload_go(void *buf, size_t len, VipsImage **out, int thumbnail) { + return vips_heifload_buffer( + buf, len, out, + "access", VIPS_ACCESS_SEQUENTIAL, + "thumbnail", thumbnail, + NULL + ); } int diff --git a/vips/vips.go b/vips/vips.go index 2b24197f..b80c45b7 100644 --- a/vips/vips.go +++ b/vips/vips.go @@ -229,7 +229,7 @@ func (img *Image) Load(imgdata *imagedata.ImageData, shrink int, scale float64, case imagetype.SVG: err = C.vips_svgload_go(data, dataSize, C.double(scale), &tmp) case imagetype.HEIC, imagetype.AVIF: - err = C.vips_heifload_go(data, dataSize, &tmp) + err = C.vips_heifload_go(data, dataSize, &tmp, C.int(0)) case imagetype.TIFF: err = C.vips_tiffload_go(data, dataSize, &tmp) default: @@ -244,6 +244,25 @@ func (img *Image) Load(imgdata *imagedata.ImageData, shrink int, scale float64, return nil } +func (img *Image) LoadThumbnail(imgdata *imagedata.ImageData) error { + if imgdata.Type != imagetype.HEIC && imgdata.Type != imagetype.AVIF { + return errors.New("Usupported image type to load thumbnail") + } + + var tmp *C.VipsImage + + data := unsafe.Pointer(&imgdata.Data[0]) + dataSize := C.size_t(len(imgdata.Data)) + + if err := C.vips_heifload_go(data, dataSize, &tmp, C.int(1)); err != 0 { + return Error() + } + + C.swap_and_clear(&img.VipsImage, tmp) + + return nil +} + func (img *Image) Save(imgtype imagetype.Type, quality int) (*imagedata.ImageData, error) { if imgtype == imagetype.ICO { return img.saveAsIco() @@ -314,6 +333,10 @@ func (img *Image) Arrayjoin(in []*Image) error { return nil } +func (img *Image) Swap(in *Image) { + img.VipsImage, in.VipsImage = in.VipsImage, img.VipsImage +} + func (img *Image) IsAnimated() bool { return C.vips_is_animated(img.VipsImage) > 0 } diff --git a/vips/vips.h b/vips/vips.h index 3f3666e8..fea2f713 100644 --- a/vips/vips.h +++ b/vips/vips.h @@ -19,7 +19,7 @@ int vips_pngload_go(void *buf, size_t len, VipsImage **out); int vips_webpload_go(void *buf, size_t len, double scale, int pages, VipsImage **out); int vips_gifload_go(void *buf, size_t len, int pages, VipsImage **out); int vips_svgload_go(void *buf, size_t len, double scale, VipsImage **out); -int vips_heifload_go(void *buf, size_t len, VipsImage **out); +int vips_heifload_go(void *buf, size_t len, VipsImage **out, int thumbnail); int vips_tiffload_go(void *buf, size_t len, VipsImage **out); int vips_black_go(VipsImage **out, int width, int height, int bands);