mirror of
https://github.com/talgo-cloud/bimg.git
synced 2026-03-14 18:05:55 -07:00
feat: support multiple outputs
This commit is contained in:
parent
d471c49348
commit
ef10d7d7ec
9 changed files with 220 additions and 58 deletions
28
image.go
28
image.go
|
|
@ -9,7 +9,7 @@ func (i *Image) Resize(width int, height int) ([]byte, error) {
|
||||||
Width: width,
|
Width: width,
|
||||||
Height: height,
|
Height: height,
|
||||||
}
|
}
|
||||||
return Resize(i.buffer, options)
|
return i.Process(options)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Image) Extract(top int, left int, width int, height int) ([]byte, error) {
|
func (i *Image) Extract(top int, left int, width int, height int) ([]byte, error) {
|
||||||
|
|
@ -19,22 +19,36 @@ func (i *Image) Extract(top int, left int, width int, height int) ([]byte, error
|
||||||
Top: top,
|
Top: top,
|
||||||
Left: left,
|
Left: left,
|
||||||
}
|
}
|
||||||
return Resize(i.buffer, options)
|
return i.Process(options)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Image) Rotate(degrees Angle) ([]byte, error) {
|
func (i *Image) Rotate(a Angle) ([]byte, error) {
|
||||||
options := Options{Rotate: degrees}
|
options := Options{Rotate: a}
|
||||||
return Resize(i.buffer, options)
|
return i.Process(options)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Image) Flip() ([]byte, error) {
|
func (i *Image) Flip() ([]byte, error) {
|
||||||
options := Options{Flip: VERTICAL}
|
options := Options{Flip: VERTICAL}
|
||||||
return Resize(i.buffer, options)
|
return i.Process(options)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Image) Flop() ([]byte, error) {
|
func (i *Image) Flop() ([]byte, error) {
|
||||||
options := Options{Flip: HORIZONTAL}
|
options := Options{Flip: HORIZONTAL}
|
||||||
return Resize(i.buffer, options)
|
return i.Process(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Image) Convert(t ImageType) ([]byte, error) {
|
||||||
|
options := Options{Type: t}
|
||||||
|
return i.Process(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Image) Process(o Options) ([]byte, error) {
|
||||||
|
image, err := Resize(i.buffer, o)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
i.buffer = image
|
||||||
|
return image, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Image) Type() string {
|
func (i *Image) Type() string {
|
||||||
|
|
|
||||||
20
image_test.go
Normal file
20
image_test.go
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
package bimg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestImageResize(t *testing.T) {
|
||||||
|
data, _ := os.Open(path.Join("fixtures/test.jpg"))
|
||||||
|
buf, err := ioutil.ReadAll(data)
|
||||||
|
|
||||||
|
image := NewImage(buf)
|
||||||
|
|
||||||
|
buf, err = image.Resize(300, 240)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Cannot process the image: %#v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -48,6 +48,8 @@ type Options struct {
|
||||||
Extend int
|
Extend int
|
||||||
Embed bool
|
Embed bool
|
||||||
Quality int
|
Quality int
|
||||||
|
Compression int
|
||||||
|
Type ImageType
|
||||||
Rotate Angle
|
Rotate Angle
|
||||||
Flip Direction
|
Flip Direction
|
||||||
Gravity Gravity
|
Gravity Gravity
|
||||||
|
|
|
||||||
21
resize.go
21
resize.go
|
|
@ -7,6 +7,7 @@ package bimg
|
||||||
import "C"
|
import "C"
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"math"
|
"math"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -27,7 +28,7 @@ const (
|
||||||
func Resize(buf []byte, o Options) ([]byte, error) {
|
func Resize(buf []byte, o Options) ([]byte, error) {
|
||||||
defer C.vips_thread_shutdown()
|
defer C.vips_thread_shutdown()
|
||||||
|
|
||||||
image, err := vipsRead(buf)
|
image, imageType, err := vipsRead(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -36,6 +37,12 @@ func Resize(buf []byte, o Options) ([]byte, error) {
|
||||||
if o.Quality == 0 {
|
if o.Quality == 0 {
|
||||||
o.Quality = QUALITY
|
o.Quality = QUALITY
|
||||||
}
|
}
|
||||||
|
if o.Compression == 0 {
|
||||||
|
o.Compression = 6
|
||||||
|
}
|
||||||
|
if o.Type == 0 {
|
||||||
|
o.Type = imageType
|
||||||
|
}
|
||||||
|
|
||||||
// get WxH
|
// get WxH
|
||||||
inWidth := int(image.Xsize)
|
inWidth := int(image.Xsize)
|
||||||
|
|
@ -74,7 +81,17 @@ func Resize(buf []byte, o Options) ([]byte, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buf, err = vipsSave(image, vipsSaveOptions{Quality: o.Quality})
|
if IsTypeSupported(o.Type) == false {
|
||||||
|
return nil, errors.New("Unsupported image output type")
|
||||||
|
}
|
||||||
|
|
||||||
|
saveOptions := vipsSaveOptions{
|
||||||
|
Quality: o.Quality,
|
||||||
|
Type: o.Type,
|
||||||
|
Compression: o.Compression,
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err = vipsSave(image, saveOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,3 +33,31 @@ func TestResize(t *testing.T) {
|
||||||
t.Fatal("Cannot save the image")
|
t.Fatal("Cannot save the image")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestConvert(t *testing.T) {
|
||||||
|
options := Options{Width: 640, Height: 480, Crop: false, Type: PNG}
|
||||||
|
img, err := os.Open("fixtures/test.jpg")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer img.Close()
|
||||||
|
|
||||||
|
buf, err := ioutil.ReadAll(img)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
newImg, err := Resize(buf, options)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Resize(imgData, %#v) error: %#v", options, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if DetermineImageType(newImg) != PNG {
|
||||||
|
t.Fatal("Image is not png")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ioutil.WriteFile("result.png", newImg, 0644)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Cannot save the image")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
17
type.go
17
type.go
|
|
@ -1,7 +1,9 @@
|
||||||
package bimg
|
package bimg
|
||||||
|
|
||||||
|
type ImageType int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
UNKNOWN = iota
|
UNKNOWN ImageType = iota
|
||||||
JPEG
|
JPEG
|
||||||
WEBP
|
WEBP
|
||||||
PNG
|
PNG
|
||||||
|
|
@ -9,7 +11,7 @@ const (
|
||||||
MAGICK
|
MAGICK
|
||||||
)
|
)
|
||||||
|
|
||||||
func DetermineImageType(buf []byte) int {
|
func DetermineImageType(buf []byte) ImageType {
|
||||||
return vipsImageType(buf)
|
return vipsImageType(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -28,7 +30,7 @@ func DetermineImageTypeName(buf []byte) string {
|
||||||
imageType = "png"
|
imageType = "png"
|
||||||
break
|
break
|
||||||
case imageCode == TIFF:
|
case imageCode == TIFF:
|
||||||
imageType = "png"
|
imageType = "tiff"
|
||||||
break
|
break
|
||||||
case imageCode == MAGICK:
|
case imageCode == MAGICK:
|
||||||
imageType = "magick"
|
imageType = "magick"
|
||||||
|
|
@ -37,3 +39,12 @@ func DetermineImageTypeName(buf []byte) string {
|
||||||
|
|
||||||
return imageType
|
return imageType
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsTypeSupported(t ImageType) bool {
|
||||||
|
return t == JPEG || t == PNG || t == WEBP
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsTypeNameSupported(t string) bool {
|
||||||
|
return t == "jpeg" || t == "jpg" ||
|
||||||
|
t == "png" || t == "webp"
|
||||||
|
}
|
||||||
|
|
|
||||||
50
type_test.go
Normal file
50
type_test.go
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
package bimg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDeterminateImageType(t *testing.T) {
|
||||||
|
files := []struct {
|
||||||
|
name string
|
||||||
|
expected ImageType
|
||||||
|
}{
|
||||||
|
{"test.jpg", JPEG},
|
||||||
|
{"test.png", PNG},
|
||||||
|
{"test.webp", WEBP},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file := range files {
|
||||||
|
img, _ := os.Open(path.Join("fixtures", file.name))
|
||||||
|
buf, _ := ioutil.ReadAll(img)
|
||||||
|
defer img.Close()
|
||||||
|
|
||||||
|
if DetermineImageType(buf) != file.expected {
|
||||||
|
t.Fatal("Image type is not valid")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeterminateImageTypeName(t *testing.T) {
|
||||||
|
files := []struct {
|
||||||
|
name string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{"test.jpg", "jpeg"},
|
||||||
|
{"test.png", "png"},
|
||||||
|
{"test.webp", "webp"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file := range files {
|
||||||
|
img, _ := os.Open(path.Join("fixtures", file.name))
|
||||||
|
buf, _ := ioutil.ReadAll(img)
|
||||||
|
defer img.Close()
|
||||||
|
|
||||||
|
if DetermineImageTypeName(buf) != file.expected {
|
||||||
|
t.Fatal("Image type is not valid")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
30
vips.go
30
vips.go
|
|
@ -60,12 +60,12 @@ func vipsFlip(image *C.struct__VipsImage, direction Direction) (*C.struct__VipsI
|
||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func vipsRead(buf []byte) (*C.struct__VipsImage, error) {
|
func vipsRead(buf []byte) (*C.struct__VipsImage, ImageType, error) {
|
||||||
var image *C.struct__VipsImage
|
var image *C.struct__VipsImage
|
||||||
imageType := vipsImageType(buf)
|
imageType := vipsImageType(buf)
|
||||||
|
|
||||||
if imageType == UNKNOWN {
|
if imageType == UNKNOWN {
|
||||||
return nil, errors.New("Input buffer contains unsupported image format")
|
return nil, UNKNOWN, errors.New("Input buffer contains unsupported image format")
|
||||||
}
|
}
|
||||||
|
|
||||||
// feed it
|
// feed it
|
||||||
|
|
@ -75,10 +75,10 @@ func vipsRead(buf []byte) (*C.struct__VipsImage, error) {
|
||||||
|
|
||||||
err := C.vips_init_image(imageBuf, length, imageTypeC, &image)
|
err := C.vips_init_image(imageBuf, length, imageTypeC, &image)
|
||||||
if err != 0 {
|
if err != 0 {
|
||||||
return nil, catchVipsError()
|
return nil, UNKNOWN, catchVipsError()
|
||||||
}
|
}
|
||||||
|
|
||||||
return image, nil
|
return image, imageType, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func vipsExtract(image *C.struct__VipsImage, left int, top int, width int, height int) (*C.struct__VipsImage, error) {
|
func vipsExtract(image *C.struct__VipsImage, left int, top int, width int, height int) (*C.struct__VipsImage, error) {
|
||||||
|
|
@ -93,7 +93,7 @@ func vipsExtract(image *C.struct__VipsImage, left int, top int, width int, heigh
|
||||||
return buf, nil
|
return buf, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func vipsImageType(buf []byte) int {
|
func vipsImageType(buf []byte) ImageType {
|
||||||
imageType := UNKNOWN
|
imageType := UNKNOWN
|
||||||
|
|
||||||
length := C.size_t(len(buf))
|
length := C.size_t(len(buf))
|
||||||
|
|
@ -126,18 +126,32 @@ func vipsExifOrientation(image *C.struct__VipsImage) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
type vipsSaveOptions struct {
|
type vipsSaveOptions struct {
|
||||||
Quality int
|
Quality int
|
||||||
|
Compression int
|
||||||
|
Type ImageType
|
||||||
}
|
}
|
||||||
|
|
||||||
func vipsSave(image *C.struct__VipsImage, o vipsSaveOptions) ([]byte, error) {
|
func vipsSave(image *C.struct__VipsImage, o vipsSaveOptions) ([]byte, error) {
|
||||||
var ptr unsafe.Pointer
|
var ptr unsafe.Pointer
|
||||||
length := C.size_t(0)
|
length := C.size_t(0)
|
||||||
|
err := C.int(0)
|
||||||
|
|
||||||
err := C.vips_jpegsave_custom(image, &ptr, &length, 1, C.int(o.Quality), 0)
|
switch {
|
||||||
|
case o.Type == PNG:
|
||||||
|
err = C.vips_pngsave_custom(image, &ptr, &length, 1, C.int(o.Compression), C.int(o.Quality), 0)
|
||||||
|
break
|
||||||
|
case o.Type == WEBP:
|
||||||
|
err = C.vips_webpsave_custom(image, &ptr, &length, 1, C.int(o.Quality), 0)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
err = C.vips_jpegsave_custom(image, &ptr, &length, 1, C.int(o.Quality), 0)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
C.g_object_unref(C.gpointer(image))
|
||||||
if err != 0 {
|
if err != 0 {
|
||||||
return nil, catchVipsError()
|
return nil, catchVipsError()
|
||||||
}
|
}
|
||||||
C.g_object_unref(C.gpointer(image))
|
|
||||||
|
|
||||||
buf := C.GoBytes(ptr, C.int(length))
|
buf := C.GoBytes(ptr, C.int(length))
|
||||||
// cleanup
|
// cleanup
|
||||||
|
|
|
||||||
82
vips.h
82
vips.h
|
|
@ -36,24 +36,12 @@ vips_jpegload_buffer_shrink(void *buf, size_t len, VipsImage **out, int shrink)
|
||||||
return vips_jpegload_buffer(buf, len, out, "shrink", shrink, NULL);
|
return vips_jpegload_buffer(buf, len, out, "shrink", shrink, NULL);
|
||||||
};
|
};
|
||||||
|
|
||||||
int
|
|
||||||
vips_webpload_buffer_seq(void *buf, size_t len, VipsImage **out)
|
|
||||||
{
|
|
||||||
return vips_webpload_buffer(buf, len, out, "access", VIPS_ACCESS_SEQUENTIAL, NULL);
|
|
||||||
};
|
|
||||||
|
|
||||||
int
|
int
|
||||||
vips_flip_seq(VipsImage *in, VipsImage **out)
|
vips_flip_seq(VipsImage *in, VipsImage **out)
|
||||||
{
|
{
|
||||||
return vips_flip(in, out, VIPS_DIRECTION_HORIZONTAL, NULL);
|
return vips_flip(in, out, VIPS_DIRECTION_HORIZONTAL, NULL);
|
||||||
};
|
};
|
||||||
|
|
||||||
int
|
|
||||||
vips_pngload_buffer_seq(void *buf, size_t len, VipsImage **out)
|
|
||||||
{
|
|
||||||
return vips_pngload_buffer(buf, len, out, "access", VIPS_ACCESS_SEQUENTIAL, NULL);
|
|
||||||
};
|
|
||||||
|
|
||||||
int
|
int
|
||||||
vips_shrink_0(VipsImage *in, VipsImage **out, double xshrink, double yshrink)
|
vips_shrink_0(VipsImage *in, VipsImage **out, double xshrink, double yshrink)
|
||||||
{
|
{
|
||||||
|
|
@ -82,32 +70,6 @@ vips_rotate(VipsImage *in, VipsImage **buf, int angle)
|
||||||
return vips_rot(in, buf, rotate, NULL);
|
return vips_rot(in, buf, rotate, NULL);
|
||||||
};
|
};
|
||||||
|
|
||||||
int
|
|
||||||
vips_init_image(void *buf, size_t len, int imageType, VipsImage **out) {
|
|
||||||
int code = 1;
|
|
||||||
|
|
||||||
if (imageType == JPEG) {
|
|
||||||
code = vips_jpegload_buffer(buf, len, out, "access", VIPS_ACCESS_SEQUENTIAL, NULL);
|
|
||||||
} else if (imageType == PNG) {
|
|
||||||
code = vips_pngload_buffer(buf, len, out, "access", VIPS_ACCESS_SEQUENTIAL, NULL);
|
|
||||||
} else if (imageType == WEBP) {
|
|
||||||
code = vips_webpload_buffer(buf, len, out, "access", VIPS_ACCESS_SEQUENTIAL, NULL);
|
|
||||||
} else if (imageType == TIFF) {
|
|
||||||
code = vips_tiffload_buffer(buf, len, out, "access", VIPS_ACCESS_SEQUENTIAL, NULL);
|
|
||||||
#if (VIPS_MAJOR_VERSION >= 8)
|
|
||||||
} else if (imageType == MAGICK) {
|
|
||||||
code = vips_magickload_buffer(buf, len, out, "access", VIPS_ACCESS_SEQUENTIAL, NULL);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
if (out != NULL) {
|
|
||||||
// Listen for "postclose" signal to delete input buffer
|
|
||||||
//g_signal_connect(out, "postclose", G_CALLBACK(vips_malloc_cb), buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
return code;
|
|
||||||
};
|
|
||||||
|
|
||||||
int
|
int
|
||||||
vips_exif_orientation(VipsImage *image) {
|
vips_exif_orientation(VipsImage *image) {
|
||||||
int orientation = 0;
|
int orientation = 0;
|
||||||
|
|
@ -144,3 +106,47 @@ vips_jpegsave_custom(VipsImage *in, void **buf, size_t *len, int strip, int qual
|
||||||
{
|
{
|
||||||
return vips_jpegsave_buffer(in, buf, len, "strip", strip, "Q", quality, "optimize_coding", TRUE, "interlace", interlace, NULL);
|
return vips_jpegsave_buffer(in, buf, len, "strip", strip, "Q", quality, "optimize_coding", TRUE, "interlace", interlace, NULL);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
int
|
||||||
|
vips_pngsave_custom(VipsImage *in, void **buf, size_t *len, int strip, int compression, int quality, int interlace)
|
||||||
|
{
|
||||||
|
#if (VIPS_MAJOR_VERSION >= 8 || (VIPS_MAJOR_VERSION >= 7 && VIPS_MINOR_VERSION >= 42))
|
||||||
|
return vips_pngsave_buffer(in, buf, len, "strip", FALSE, "compression", compression,
|
||||||
|
"interlace", interlace, "filter", VIPS_FOREIGN_PNG_FILTER_NONE, NULL);
|
||||||
|
#else
|
||||||
|
return vips_pngsave_buffer(image, buf, len, "strip", FALSE, "compression", compression,
|
||||||
|
"interlace", interlace, NULL);
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
int
|
||||||
|
vips_webpsave_custom(VipsImage *in, void **buf, size_t *len, int strip, int quality, int interlace)
|
||||||
|
{
|
||||||
|
return vips_webpsave_buffer(in, buf, len, "strip", strip, "Q", quality, "optimize_coding", TRUE, "interlace", interlace, NULL);
|
||||||
|
};
|
||||||
|
|
||||||
|
int
|
||||||
|
vips_init_image(void *buf, size_t len, int imageType, VipsImage **out) {
|
||||||
|
int code = 1;
|
||||||
|
|
||||||
|
if (imageType == JPEG) {
|
||||||
|
code = vips_jpegload_buffer(buf, len, out, "access", VIPS_ACCESS_SEQUENTIAL, NULL);
|
||||||
|
} else if (imageType == PNG) {
|
||||||
|
code = vips_pngload_buffer(buf, len, out, "access", VIPS_ACCESS_SEQUENTIAL, NULL);
|
||||||
|
} else if (imageType == WEBP) {
|
||||||
|
code = vips_webpload_buffer(buf, len, out, "access", VIPS_ACCESS_SEQUENTIAL, NULL);
|
||||||
|
} else if (imageType == TIFF) {
|
||||||
|
code = vips_tiffload_buffer(buf, len, out, "access", VIPS_ACCESS_SEQUENTIAL, NULL);
|
||||||
|
#if (VIPS_MAJOR_VERSION >= 8)
|
||||||
|
} else if (imageType == MAGICK) {
|
||||||
|
code = vips_magickload_buffer(buf, len, out, "access", VIPS_ACCESS_SEQUENTIAL, NULL);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
if (out != NULL) {
|
||||||
|
// Listen for "postclose" signal to delete input buffer
|
||||||
|
//g_signal_connect(out, "postclose", G_CALLBACK(vips_malloc_cb), buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
return code;
|
||||||
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue