feat: autorotate
5
image.go
|
|
@ -154,6 +154,11 @@ func (i *Image) Rotate(a Angle) ([]byte, error) {
|
||||||
return i.Process(options)
|
return i.Process(options)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AutoRotate automatically rotates the image with no additional transformation based on the EXIF oritentation metadata, if available.
|
||||||
|
func (i *Image) AutoRotate() ([]byte, error) {
|
||||||
|
return i.Process(Options{autoRotateOnly: true})
|
||||||
|
}
|
||||||
|
|
||||||
// Flip flips the image about the vertical Y axis.
|
// Flip flips the image about the vertical Y axis.
|
||||||
func (i *Image) Flip() ([]byte, error) {
|
func (i *Image) Flip() ([]byte, error) {
|
||||||
options := Options{Flip: true}
|
options := Options{Flip: true}
|
||||||
|
|
|
||||||
|
|
@ -345,6 +345,38 @@ func TestImageRotate(t *testing.T) {
|
||||||
Write("testdata/test_image_rotate_out.jpg", buf)
|
Write("testdata/test_image_rotate_out.jpg", buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestImageAutoRotate(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
file string
|
||||||
|
orientation int
|
||||||
|
}{
|
||||||
|
{"exif/Landscape_1.jpg", 1},
|
||||||
|
{"exif/Landscape_2.jpg", 2},
|
||||||
|
{"exif/Landscape_3.jpg", 1},
|
||||||
|
{"exif/Landscape_4.jpg", 4},
|
||||||
|
{"exif/Landscape_5.jpg", 5},
|
||||||
|
{"exif/Landscape_6.jpg", 1},
|
||||||
|
{"exif/Landscape_7.jpg", 7},
|
||||||
|
}
|
||||||
|
|
||||||
|
for index, test := range tests {
|
||||||
|
img := initImage(test.file)
|
||||||
|
buf, err := img.AutoRotate()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Cannot process the image: %#v", err)
|
||||||
|
}
|
||||||
|
Write(fmt.Sprintf("testdata/test_autorotate_%d_out.jpg", index), buf)
|
||||||
|
|
||||||
|
meta, err := img.Metadata()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Cannot read image metadata: %#v", err)
|
||||||
|
}
|
||||||
|
if meta.Orientation != test.orientation {
|
||||||
|
t.Errorf("Invalid image orientation for %s: %d != %d", test.file, meta.Orientation, test.orientation)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestImageConvert(t *testing.T) {
|
func TestImageConvert(t *testing.T) {
|
||||||
buf, err := initImage("test.jpg").Convert(PNG)
|
buf, err := initImage("test.jpg").Convert(PNG)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ type ImageMetadata struct {
|
||||||
EXIF EXIF
|
EXIF EXIF
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EXIF image metadata
|
||||||
type EXIF struct {
|
type EXIF struct {
|
||||||
Make string
|
Make string
|
||||||
Model string
|
Model string
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import "C"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Quality defines the default JPEG quality to be used.
|
// Quality defines the default JPEG quality to be used.
|
||||||
Quality = 80
|
Quality = 75
|
||||||
// MaxSize defines the maximum pixels width or height supported.
|
// MaxSize defines the maximum pixels width or height supported.
|
||||||
MaxSize = 16383
|
MaxSize = 16383
|
||||||
)
|
)
|
||||||
|
|
@ -226,4 +226,7 @@ type Options struct {
|
||||||
OutputICC string
|
OutputICC string
|
||||||
InputICC string
|
InputICC string
|
||||||
Palette bool
|
Palette bool
|
||||||
|
|
||||||
|
// private fields
|
||||||
|
autoRotateOnly bool
|
||||||
}
|
}
|
||||||
|
|
|
||||||
11
resizer.go
|
|
@ -30,10 +30,20 @@ func resizer(buf []byte, o Options) ([]byte, error) {
|
||||||
// Clone and define default options
|
// Clone and define default options
|
||||||
o = applyDefaults(o, imageType)
|
o = applyDefaults(o, imageType)
|
||||||
|
|
||||||
|
// Ensure supported type
|
||||||
if !IsTypeSupported(o.Type) {
|
if !IsTypeSupported(o.Type) {
|
||||||
return nil, errors.New("Unsupported image output type")
|
return nil, errors.New("Unsupported image output type")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Autorate only
|
||||||
|
if o.autoRotateOnly {
|
||||||
|
image, err = vipsAutoRotate(image)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return saveImage(image, o)
|
||||||
|
}
|
||||||
|
|
||||||
// Auto rotate image based on EXIF orientation header
|
// Auto rotate image based on EXIF orientation header
|
||||||
image, rotated, err := rotateAndFlipImage(image, o)
|
image, rotated, err := rotateAndFlipImage(image, o)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -375,7 +385,6 @@ func watermarkImageWithText(image *C.VipsImage, w Watermark) (*C.VipsImage, erro
|
||||||
}
|
}
|
||||||
|
|
||||||
func watermarkImageWithAnotherImage(image *C.VipsImage, w WatermarkImage) (*C.VipsImage, error) {
|
func watermarkImageWithAnotherImage(image *C.VipsImage, w WatermarkImage) (*C.VipsImage, error) {
|
||||||
|
|
||||||
if len(w.Buf) == 0 {
|
if len(w.Buf) == 0 {
|
||||||
return image, nil
|
return image, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
BIN
testdata/exif/Landscape_1_out.jpg
vendored
|
Before Width: | Height: | Size: 198 KiB After Width: | Height: | Size: 187 KiB |
BIN
testdata/exif/Landscape_2_out.jpg
vendored
|
Before Width: | Height: | Size: 199 KiB After Width: | Height: | Size: 188 KiB |
BIN
testdata/exif/Landscape_3_out.jpg
vendored
|
Before Width: | Height: | Size: 200 KiB After Width: | Height: | Size: 188 KiB |
BIN
testdata/exif/Landscape_4_out.jpg
vendored
|
Before Width: | Height: | Size: 199 KiB After Width: | Height: | Size: 187 KiB |
BIN
testdata/exif/Landscape_5_out.jpg
vendored
|
Before Width: | Height: | Size: 205 KiB After Width: | Height: | Size: 189 KiB |
BIN
testdata/exif/Landscape_6_out.jpg
vendored
|
Before Width: | Height: | Size: 205 KiB After Width: | Height: | Size: 189 KiB |
BIN
testdata/exif/Landscape_7_out.jpg
vendored
|
Before Width: | Height: | Size: 204 KiB After Width: | Height: | Size: 188 KiB |
BIN
testdata/exif/Landscape_8_out.jpg
vendored
|
Before Width: | Height: | Size: 205 KiB After Width: | Height: | Size: 189 KiB |
BIN
testdata/exif/Portrait_1_out.jpg
vendored
|
Before Width: | Height: | Size: 165 KiB After Width: | Height: | Size: 153 KiB |
BIN
testdata/exif/Portrait_2_out.jpg
vendored
|
Before Width: | Height: | Size: 166 KiB After Width: | Height: | Size: 154 KiB |
BIN
testdata/exif/Portrait_3_out.jpg
vendored
|
Before Width: | Height: | Size: 166 KiB After Width: | Height: | Size: 155 KiB |
BIN
testdata/exif/Portrait_4_out.jpg
vendored
|
Before Width: | Height: | Size: 166 KiB After Width: | Height: | Size: 154 KiB |
BIN
testdata/exif/Portrait_5_out.jpg
vendored
|
Before Width: | Height: | Size: 174 KiB After Width: | Height: | Size: 156 KiB |
BIN
testdata/exif/Portrait_6_out.jpg
vendored
|
Before Width: | Height: | Size: 175 KiB After Width: | Height: | Size: 156 KiB |
BIN
testdata/exif/Portrait_7_out.jpg
vendored
|
Before Width: | Height: | Size: 173 KiB After Width: | Height: | Size: 155 KiB |
BIN
testdata/exif/Portrait_8_out.jpg
vendored
|
Before Width: | Height: | Size: 175 KiB After Width: | Height: | Size: 157 KiB |
BIN
testdata/test_gif.jpg
vendored
|
Before Width: | Height: | Size: 110 KiB After Width: | Height: | Size: 99 KiB |
BIN
testdata/test_pdf.jpg
vendored
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 62 KiB |
BIN
testdata/test_smart_crop.jpg
vendored
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 22 KiB |
BIN
testdata/test_svg.jpg
vendored
|
Before Width: | Height: | Size: 116 KiB After Width: | Height: | Size: 104 KiB |
12
vips.go
|
|
@ -272,6 +272,18 @@ func vipsRotate(image *C.VipsImage, angle Angle) (*C.VipsImage, error) {
|
||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func vipsAutoRotate(image *C.VipsImage) (*C.VipsImage, error) {
|
||||||
|
var out *C.VipsImage
|
||||||
|
defer C.g_object_unref(C.gpointer(image))
|
||||||
|
|
||||||
|
err := C.vips_autorot_bridge(image, &out)
|
||||||
|
if err != 0 {
|
||||||
|
return nil, catchVipsError()
|
||||||
|
}
|
||||||
|
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
func vipsTransformICC(image *C.VipsImage, inputICC string, outputICC string) (*C.VipsImage, error) {
|
func vipsTransformICC(image *C.VipsImage, inputICC string, outputICC string) (*C.VipsImage, error) {
|
||||||
var out *C.VipsImage
|
var out *C.VipsImage
|
||||||
defer C.g_object_unref(C.gpointer(image))
|
defer C.g_object_unref(C.gpointer(image))
|
||||||
|
|
|
||||||
5
vips.h
|
|
@ -222,6 +222,11 @@ vips_rotate_bridge(VipsImage *in, VipsImage **out, int angle) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
vips_autorot_bridge(VipsImage *in, VipsImage **out) {
|
||||||
|
return vips_autorot(in, out, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
const char *
|
const char *
|
||||||
vips_exif_tag(VipsImage *image, const char *tag) {
|
vips_exif_tag(VipsImage *image, const char *tag) {
|
||||||
const char *exif;
|
const char *exif;
|
||||||
|
|
|
||||||
36
vips_test.go
|
|
@ -82,6 +82,42 @@ func TestVipsRotate(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestVipsAutoRotate(t *testing.T) {
|
||||||
|
files := []struct {
|
||||||
|
name string
|
||||||
|
orientation int
|
||||||
|
}{
|
||||||
|
{"test.jpg", 0},
|
||||||
|
{"test_exif.jpg", 0},
|
||||||
|
{"exif/Landscape_1.jpg", 0},
|
||||||
|
{"exif/Landscape_2.jpg", 2},
|
||||||
|
{"exif/Landscape_3.jpg", 0},
|
||||||
|
{"exif/Landscape_4.jpg", 4},
|
||||||
|
{"exif/Landscape_5.jpg", 5},
|
||||||
|
{"exif/Landscape_6.jpg", 0},
|
||||||
|
{"exif/Landscape_7.jpg", 7},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file := range files {
|
||||||
|
image, _, _ := vipsRead(readImage(file.name))
|
||||||
|
|
||||||
|
newImg, err := vipsAutoRotate(image)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Cannot auto rotate the image")
|
||||||
|
}
|
||||||
|
|
||||||
|
orientation := vipsExifOrientation(newImg)
|
||||||
|
if orientation != file.orientation {
|
||||||
|
t.Fatalf("Invalid image orientation: %d != %d", orientation, file.orientation)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, _ := vipsSave(newImg, vipsSaveOptions{Quality: 95})
|
||||||
|
if len(buf) == 0 {
|
||||||
|
t.Fatal("Empty image")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestVipsZoom(t *testing.T) {
|
func TestVipsZoom(t *testing.T) {
|
||||||
image, _, _ := vipsRead(readImage("test.jpg"))
|
image, _, _ := vipsRead(readImage("test.jpg"))
|
||||||
|
|
||||||
|
|
|
||||||