Extract functionality to scale images into separate function

pull/5184/head
Florian Dewald 2 weeks ago
parent 455665a16b
commit 8f410d7105

@ -5,6 +5,9 @@ import (
"context"
"encoding/binary"
"fmt"
"image"
_ "image/jpeg"
_ "image/png"
"io"
"log/slog"
"os"
@ -526,8 +529,74 @@ func (s *APIV1Service) GetAttachmentBlob(attachment *store.Attachment) ([]byte,
const (
// thumbnailMaxSize is the maximum size in pixels for the largest dimension of the thumbnail image.
thumbnailMaxSize = 600
// defaultJPEGQuality is the default JPEG quality for downscaling images.
defaultJPEGQuality = 85
)
// downscaleImage takes an image blob and returns a downscaled version as a blob.
// The maxDimension parameter specifies the maximum size in pixels for the largest dimension.
// The quality parameter specifies the JPEG encoding quality (1-100, where 100 is best quality).
// PNG images are preserved as PNG, other formats are encoded as JPEG.
// Images smaller than maxDimension are not enlarged.
func downscaleImage(imageBlob []byte, maxDimension int, quality int) ([]byte, error) {
// Detect the image format before decoding
reader := bytes.NewReader(imageBlob)
_, formatName, err := image.DecodeConfig(reader)
if err != nil {
return nil, errors.Wrap(err, "failed to detect image format")
}
// Reset reader position for actual decoding
reader.Seek(0, 0)
// Decode the image with auto-orientation support
img, err := imaging.Decode(reader, imaging.AutoOrientation(true))
if err != nil {
return nil, errors.Wrap(err, "failed to decode image")
}
// Get original dimensions
width := img.Bounds().Dx()
height := img.Bounds().Dy()
var targetWidth, targetHeight int
// Only resize if the image is larger than maxDimension
if max(width, height) > maxDimension {
if width >= height {
// Landscape or square - constrain width, maintain aspect ratio for height
targetWidth = maxDimension
targetHeight = 0
} else {
// Portrait - constrain height, maintain aspect ratio for width
targetWidth = 0
targetHeight = maxDimension
}
} else {
// Keep original dimensions for small images
targetWidth = width
targetHeight = height
}
// Resize the image to the calculated dimensions
resizedImage := imaging.Resize(img, targetWidth, targetHeight, imaging.Lanczos)
// Encode the image based on the original format
var buf bytes.Buffer
if formatName == "png" {
// Preserve PNG format for PNG images
if err := imaging.Encode(&buf, resizedImage, imaging.PNG); err != nil {
return nil, errors.Wrap(err, "failed to encode PNG image")
}
} else {
// Encode as JPEG for all other formats
if err := imaging.Encode(&buf, resizedImage, imaging.JPEG, imaging.JPEGQuality(quality)); err != nil {
return nil, errors.Wrap(err, "failed to encode JPEG image")
}
}
return buf.Bytes(), nil
}
// getOrGenerateThumbnail returns the thumbnail image of the attachment.
func (s *APIV1Service) getOrGenerateThumbnail(attachment *store.Attachment) ([]byte, error) {
thumbnailCacheFolder := filepath.Join(s.Profile.Data, ThumbnailCacheFolder)
@ -545,39 +614,19 @@ func (s *APIV1Service) getOrGenerateThumbnail(attachment *store.Attachment) ([]b
if err != nil {
return nil, errors.Wrap(err, "failed to get attachment blob")
}
img, err := imaging.Decode(bytes.NewReader(blob), imaging.AutoOrientation(true))
// Downscale the image
thumbnailBlob, err := downscaleImage(blob, thumbnailMaxSize, defaultJPEGQuality)
if err != nil {
return nil, errors.Wrap(err, "failed to decode thumbnail image")
}
// The largest dimension is set to thumbnailMaxSize and the smaller dimension is scaled proportionally.
// Small images are not enlarged.
width := img.Bounds().Dx()
height := img.Bounds().Dy()
var thumbnailWidth, thumbnailHeight int
// Only resize if the image is larger than thumbnailMaxSize
if max(width, height) > thumbnailMaxSize {
if width >= height {
// Landscape or square - constrain width, maintain aspect ratio for height
thumbnailWidth = thumbnailMaxSize
thumbnailHeight = 0
} else {
// Portrait - constrain height, maintain aspect ratio for width
thumbnailWidth = 0
thumbnailHeight = thumbnailMaxSize
}
} else {
// Keep original dimensions for small images
thumbnailWidth = width
thumbnailHeight = height
return nil, errors.Wrap(err, "failed to downscale image")
}
// Resize the image to the calculated dimensions.
thumbnailImage := imaging.Resize(img, thumbnailWidth, thumbnailHeight, imaging.Lanczos)
if err := imaging.Save(thumbnailImage, filePath); err != nil {
// Save the thumbnail to disk
if err := os.WriteFile(filePath, thumbnailBlob, 0644); err != nil {
return nil, errors.Wrap(err, "failed to save thumbnail file")
}
return thumbnailBlob, nil
}
thumbnailFile, err := os.Open(filePath)

Loading…
Cancel
Save