Added SaveProfileImage endpoint
Also added EncodeToBestSize func
This commit is contained in:
parent
9bc240fc71
commit
3b7fbf94d3
|
@ -1,19 +1,57 @@
|
||||||
package images
|
package images
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"image/jpeg"
|
"image/jpeg"
|
||||||
"io"
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Encode(w io.Writer, img image.Image, imgDetail *Details) error {
|
type EncodeConfig struct {
|
||||||
// Currently a wrapper for renderJpeg, but this function is useful if multiple render formats are needed
|
Quality int
|
||||||
return renderJpeg(w, img, imgDetail)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderJpeg(w io.Writer, m image.Image, imgDetail *Details) error {
|
func Encode(w io.Writer, img image.Image, config EncodeConfig) error {
|
||||||
|
// Currently a wrapper for renderJpeg, but this function is useful if multiple render formats are needed
|
||||||
|
return renderJpeg(w, img, config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderJpeg(w io.Writer, m image.Image, config EncodeConfig) error {
|
||||||
o := new(jpeg.Options)
|
o := new(jpeg.Options)
|
||||||
o.Quality = imgDetail.Quality
|
o.Quality = config.Quality
|
||||||
|
|
||||||
return jpeg.Encode(w, m, o)
|
return jpeg.Encode(w, m, o)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func EncodeToBestSize(bb *bytes.Buffer, img image.Image, size uint) error {
|
||||||
|
// TODO test
|
||||||
|
q := MaxJpegQuality
|
||||||
|
for q > MinJpegQuality-1 {
|
||||||
|
|
||||||
|
err := Encode(bb, img, EncodeConfig{Quality: q})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if DimensionSizeLimit[size].Ideal > bb.Len() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if q == MinJpegQuality {
|
||||||
|
if DimensionSizeLimit[size].Max > bb.Len(){
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf(
|
||||||
|
"image size after processing exceeds max, expect < '%d', received < '%d'",
|
||||||
|
DimensionSizeLimit[size].Max,
|
||||||
|
bb.Len(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
q -= 2
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ func TestEncode(t *testing.T) {
|
||||||
5834,
|
5834,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
options := Details{
|
options := EncodeConfig{
|
||||||
Quality: 70,
|
Quality: 70,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ func TestEncode(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
bb := bytes.NewBuffer([]byte{})
|
bb := bytes.NewBuffer([]byte{})
|
||||||
err = Encode(bb, img, &options)
|
err = Encode(bb, img, options)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
require.Exactly(t, c.RenderSize, bb.Len())
|
require.Exactly(t, c.RenderSize, bb.Len())
|
||||||
|
|
|
@ -1 +1,36 @@
|
||||||
package images
|
package images
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"image"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GenerateProfileImages(filepath string, aX, aY, bX, bY int) ([][]byte, error) {
|
||||||
|
img, err := Decode(filepath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cropRect := image.Rectangle{
|
||||||
|
Min: image.Point{X: aX, Y: aY},
|
||||||
|
Max: image.Point{X: bX, Y: bY},
|
||||||
|
}
|
||||||
|
cImg, err := Crop(img, cropRect)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
imgs := make([][]byte, len(ResizeDimensions))
|
||||||
|
for _, s := range ResizeDimensions {
|
||||||
|
rImg := Resize(s, cImg)
|
||||||
|
|
||||||
|
bb := bytes.NewBuffer([]byte{})
|
||||||
|
err = EncodeToBestSize(bb, rImg, s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
imgs = append(imgs, bb.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
return imgs, nil
|
||||||
|
}
|
|
@ -118,7 +118,7 @@ func TestCrop(t *testing.T) {
|
||||||
Max: image.Point{X: 1000000, Y: 1000000},
|
Max: image.Point{X: 1000000, Y: 1000000},
|
||||||
}
|
}
|
||||||
rect := image.Rectangle{}
|
rect := image.Rectangle{}
|
||||||
options := &Details{
|
options := EncodeConfig{
|
||||||
Quality: 70,
|
Quality: 70,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,13 +10,31 @@ const (
|
||||||
WEBP
|
WEBP
|
||||||
)
|
)
|
||||||
|
|
||||||
type Details struct {
|
const (
|
||||||
SizePixel uint
|
MaxJpegQuality = 80
|
||||||
SizeFile int64
|
MinJpegQuality = 50
|
||||||
Quality int
|
)
|
||||||
FileName string
|
|
||||||
Properties string
|
var (
|
||||||
|
ResizeDimensions = []uint{80, 240}
|
||||||
|
|
||||||
|
// DimensionSizeLimit the size limits imposed on each resize dimension
|
||||||
|
// Figures are based on the following sample data https://github.com/status-im/status-react/issues/11047#issuecomment-694970473
|
||||||
|
DimensionSizeLimit = map[uint]DimensionSize{
|
||||||
|
80: {
|
||||||
|
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)
|
||||||
|
},
|
||||||
|
240: {
|
||||||
|
Ideal: 16384, // Base on the largest sample image at quality 60% (16,143 bytes ∴ 1024 * 16)
|
||||||
|
Max: 38400, // Base on the largest sample image at quality 80% + 50% margin (24,290 bytes * 1.5 ≈ 37500 ∴ 1024 * 37.5)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type DimensionSize struct {
|
||||||
|
Ideal int
|
||||||
|
Max int
|
||||||
}
|
}
|
||||||
|
|
||||||
type FileType uint
|
type FileType uint
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
package statusgo
|
package statusgo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/status-im/status-go/images"
|
||||||
|
"image"
|
||||||
"os"
|
"os"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
|
@ -628,3 +631,16 @@ func MultiformatDeserializePublicKey(key, outBase string) string {
|
||||||
|
|
||||||
return pk
|
return pk
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SaveProfileImage takes the filepath of an image, crops it as per the rect coords and finally resizes the image.
|
||||||
|
// The resulting image(s) will be stored in the DB along with other user account information.
|
||||||
|
// aX and aY represent the pixel coordinates of the upper left corner of the image's cropping area
|
||||||
|
// bX and bY represent the pixel coordinates of the lower right corner of the image's cropping area
|
||||||
|
func SaveProfileImage(filepath string, aX, aY, bX, bY int) string {
|
||||||
|
imgs, err := images.GenerateProfileImages(filepath, aX, aY, bX, bY)
|
||||||
|
if err != nil {
|
||||||
|
return makeJSONResponse(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue