Implemented more comprehensive file compression to handle large files
This commit is contained in:
parent
2f15730003
commit
45b287370a
Binary file not shown.
After Width: | Height: | Size: 4.6 MiB |
|
@ -9,6 +9,8 @@ import (
|
||||||
"image/jpeg"
|
"image/jpeg"
|
||||||
"io"
|
"io"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
|
"github.com/nfnt/resize"
|
||||||
)
|
)
|
||||||
|
|
||||||
type EncodeConfig struct {
|
type EncodeConfig struct {
|
||||||
|
@ -27,7 +29,7 @@ func renderJpeg(w io.Writer, m image.Image, config EncodeConfig) error {
|
||||||
return jpeg.Encode(w, m, o)
|
return jpeg.Encode(w, m, o)
|
||||||
}
|
}
|
||||||
|
|
||||||
func EncodeToLimits(bb *bytes.Buffer, img image.Image, bounds DimensionLimits) error {
|
func EncodeToLimits(bb *bytes.Buffer, img image.Image, bounds FileSizeLimits) error {
|
||||||
q := MaxJpegQuality
|
q := MaxJpegQuality
|
||||||
for q > MinJpegQuality-1 {
|
for q > MinJpegQuality-1 {
|
||||||
|
|
||||||
|
@ -58,6 +60,40 @@ func EncodeToLimits(bb *bytes.Buffer, img image.Image, bounds DimensionLimits) e
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CompressToFileLimits takes an image.Image and analyses the pixel dimensions, if the longest side is greater
|
||||||
|
// than the `longSideMax` image.Image will be resized, before compression begins.
|
||||||
|
// Next the image.Image is repeatedly encoded and resized until the data fits within
|
||||||
|
// the given FileSizeLimits. There is no limit on the number of times the cycle is performed, the image.Image
|
||||||
|
// is reduced to 95% of its size at the end of every round the file size exceeds the given limits.
|
||||||
|
func CompressToFileLimits(bb *bytes.Buffer, img image.Image, bounds FileSizeLimits) error {
|
||||||
|
longSideMax := 2000
|
||||||
|
|
||||||
|
// Do we need to do a pre-compression resize?
|
||||||
|
if img.Bounds().Max.X > img.Bounds().Max.Y {
|
||||||
|
// X is longer
|
||||||
|
if img.Bounds().Max.X > longSideMax {
|
||||||
|
img = resize.Resize(uint(longSideMax), 0, img, resize.Bilinear)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Y is longer or equal
|
||||||
|
if img.Bounds().Max.Y > longSideMax {
|
||||||
|
img = resize.Resize(0, uint(longSideMax), img, resize.Bilinear)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
err := EncodeToLimits(bb, img, bounds)
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err.Error()[:50] != "image size after processing exceeds max, expect < " {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
img = ResizeTo(95, img)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func EncodeToBestSize(bb *bytes.Buffer, img image.Image, size ResizeDimension) error {
|
func EncodeToBestSize(bb *bytes.Buffer, img image.Image, size ResizeDimension) error {
|
||||||
return EncodeToLimits(bb, img, DimensionSizeLimit[size])
|
return EncodeToLimits(bb, img, DimensionSizeLimit[size])
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,3 +90,13 @@ func TestEncodeToBestSize(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCompressToFileLimits(t *testing.T) {
|
||||||
|
img, err := Decode(path + "IMG_1205.HEIC.jpg")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
bb := bytes.NewBuffer([]byte{})
|
||||||
|
err = CompressToFileLimits(bb, img, FileSizeLimits{50000, 350000})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 291645, bb.Len())
|
||||||
|
}
|
||||||
|
|
|
@ -80,8 +80,7 @@ func GenerateBannerImage(filepath string, aX, aY, bX, bY int) (*IdentityImage, e
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
dimension := BannerDim
|
resizedImg := ShrinkOnly(BannerDim, croppedImg)
|
||||||
resizedImg := ShrinkOnly(dimension, croppedImg)
|
|
||||||
|
|
||||||
sizeLimits := GetBannerDimensionLimits()
|
sizeLimits := GetBannerDimensionLimits()
|
||||||
|
|
||||||
|
@ -97,7 +96,7 @@ func GenerateBannerImage(filepath string, aX, aY, bX, bY int) (*IdentityImage, e
|
||||||
Width: resizedImg.Bounds().Dx(),
|
Width: resizedImg.Bounds().Dx(),
|
||||||
Height: resizedImg.Bounds().Dy(),
|
Height: resizedImg.Bounds().Dy(),
|
||||||
FileSize: bb.Len(),
|
FileSize: bb.Len(),
|
||||||
ResizeTarget: int(dimension),
|
ResizeTarget: int(BannerDim),
|
||||||
}
|
}
|
||||||
|
|
||||||
return ii, nil
|
return ii, nil
|
||||||
|
|
|
@ -28,6 +28,13 @@ func Resize(size ResizeDimension, img image.Image) image.Image {
|
||||||
return resize.Resize(width, height, img, resize.Bilinear)
|
return resize.Resize(width, height, img, resize.Bilinear)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ResizeTo(percent int, img image.Image) image.Image {
|
||||||
|
width := uint(img.Bounds().Max.X * percent / 100)
|
||||||
|
height := uint(img.Bounds().Max.Y * percent / 100)
|
||||||
|
|
||||||
|
return resize.Resize(width, height, img, resize.Bilinear)
|
||||||
|
}
|
||||||
|
|
||||||
func ShrinkOnly(size ResizeDimension, img image.Image) image.Image {
|
func ShrinkOnly(size ResizeDimension, img image.Image) image.Image {
|
||||||
finalSize := int(math.Min(float64(size), math.Min(float64(img.Bounds().Dx()), float64(img.Bounds().Dy()))))
|
finalSize := int(math.Min(float64(size), math.Min(float64(img.Bounds().Dx()), float64(img.Bounds().Dy()))))
|
||||||
return Resize(ResizeDimension(finalSize), img)
|
return Resize(ResizeDimension(finalSize), img)
|
||||||
|
@ -50,9 +57,9 @@ func Crop(img image.Image, rect image.Rectangle) (image.Image, error) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// CropImage takes an image, usually downloaded from a URL
|
// CropCenter takes an image, usually downloaded from a URL
|
||||||
// If the image is square, the full image is returned
|
// If the image is square, the full image is returned
|
||||||
// It the image is rectangular, the largest central square is returned
|
// If the image is rectangular, the largest central square is returned
|
||||||
// calculations at _docs/image-center-crop-calculations.png
|
// calculations at _docs/image-center-crop-calculations.png
|
||||||
func CropCenter(img image.Image) (image.Image, error) {
|
func CropCenter(img image.Image) (image.Image, error) {
|
||||||
var cropRect image.Rectangle
|
var cropRect image.Rectangle
|
||||||
|
|
|
@ -31,7 +31,7 @@ var (
|
||||||
|
|
||||||
// DimensionSizeLimit the size limits imposed on each resize dimension
|
// DimensionSizeLimit the size limits imposed on each resize dimension
|
||||||
// Figures are based on the following sample data https://github.com/status-im/status-mobile/issues/11047#issuecomment-694970473
|
// Figures are based on the following sample data https://github.com/status-im/status-mobile/issues/11047#issuecomment-694970473
|
||||||
DimensionSizeLimit = map[ResizeDimension]DimensionLimits{
|
DimensionSizeLimit = map[ResizeDimension]FileSizeLimits{
|
||||||
SmallDim: {
|
SmallDim: {
|
||||||
Ideal: 2560, // Base on the largest sample image at quality 60% (2,554 bytes ∴ 1024 * 2.5)
|
Ideal: 2560, // Base on the largest sample image at quality 60% (2,554 bytes ∴ 1024 * 2.5)
|
||||||
Max: 5632, // Base on the largest sample image at quality 80% + 50% margin (3,683 bytes * 1.5 ≈ 5500 ∴ 1024 * 5.5)
|
Max: 5632, // Base on the largest sample image at quality 80% + 50% margin (3,683 bytes * 1.5 ≈ 5500 ∴ 1024 * 5.5)
|
||||||
|
@ -55,7 +55,7 @@ var (
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
type DimensionLimits struct {
|
type FileSizeLimits struct {
|
||||||
Ideal int
|
Ideal int
|
||||||
Max int
|
Max int
|
||||||
}
|
}
|
||||||
|
@ -63,8 +63,8 @@ type DimensionLimits struct {
|
||||||
type ImageType uint
|
type ImageType uint
|
||||||
type ResizeDimension uint
|
type ResizeDimension uint
|
||||||
|
|
||||||
func GetBannerDimensionLimits() DimensionLimits {
|
func GetBannerDimensionLimits() FileSizeLimits {
|
||||||
return DimensionLimits{
|
return FileSizeLimits{
|
||||||
Ideal: 307200, // We want to save space and traffic but keep to maximum compression
|
Ideal: 307200, // We want to save space and traffic but keep to maximum compression
|
||||||
Max: 460800, // Can't go bigger than 450 KB
|
Max: 460800, // Can't go bigger than 450 KB
|
||||||
}
|
}
|
||||||
|
|
|
@ -2631,7 +2631,7 @@ func (m *Messenger) OpenAndAdjustImage(inputImage userimage.CroppedImage, crop b
|
||||||
}
|
}
|
||||||
|
|
||||||
bb := bytes.NewBuffer([]byte{})
|
bb := bytes.NewBuffer([]byte{})
|
||||||
err = userimage.EncodeToLimits(bb, img, userimage.DimensionLimits{Ideal: idealTargetImageSize, Max: resizeTargetImageSize})
|
err = userimage.CompressToFileLimits(bb, img, userimage.FileSizeLimits{Ideal: idealTargetImageSize, Max: resizeTargetImageSize})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
Loading…
Reference in New Issue