diff --git a/image.go b/image.go index 3d13444..a3fd629 100644 --- a/image.go +++ b/image.go @@ -154,6 +154,11 @@ func (i *Image) Rotate(a Angle) ([]byte, error) { 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. func (i *Image) Flip() ([]byte, error) { options := Options{Flip: true} diff --git a/image_test.go b/image_test.go index 5af0431..4915d64 100644 --- a/image_test.go +++ b/image_test.go @@ -345,6 +345,38 @@ func TestImageRotate(t *testing.T) { 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) { buf, err := initImage("test.jpg").Convert(PNG) if err != nil { diff --git a/metadata.go b/metadata.go index 8e5e9ae..cca8d67 100644 --- a/metadata.go +++ b/metadata.go @@ -25,6 +25,7 @@ type ImageMetadata struct { EXIF EXIF } +// EXIF image metadata type EXIF struct { Make string Model string diff --git a/options.go b/options.go index 5212ef9..b893e97 100644 --- a/options.go +++ b/options.go @@ -8,7 +8,7 @@ import "C" const ( // Quality defines the default JPEG quality to be used. - Quality = 80 + Quality = 75 // MaxSize defines the maximum pixels width or height supported. MaxSize = 16383 ) @@ -226,4 +226,7 @@ type Options struct { OutputICC string InputICC string Palette bool + + // private fields + autoRotateOnly bool } diff --git a/resizer.go b/resizer.go index be9748b..427cada 100644 --- a/resizer.go +++ b/resizer.go @@ -30,10 +30,20 @@ func resizer(buf []byte, o Options) ([]byte, error) { // Clone and define default options o = applyDefaults(o, imageType) + // Ensure supported type if !IsTypeSupported(o.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 image, rotated, err := rotateAndFlipImage(image, o) 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) { - if len(w.Buf) == 0 { return image, nil } diff --git a/testdata/exif/Landscape_1_out.jpg b/testdata/exif/Landscape_1_out.jpg index 0f7ca1f..6a97a05 100644 Binary files a/testdata/exif/Landscape_1_out.jpg and b/testdata/exif/Landscape_1_out.jpg differ diff --git a/testdata/exif/Landscape_2_out.jpg b/testdata/exif/Landscape_2_out.jpg index 7f71884..26f26aa 100644 Binary files a/testdata/exif/Landscape_2_out.jpg and b/testdata/exif/Landscape_2_out.jpg differ diff --git a/testdata/exif/Landscape_3_out.jpg b/testdata/exif/Landscape_3_out.jpg index c487686..9399669 100644 Binary files a/testdata/exif/Landscape_3_out.jpg and b/testdata/exif/Landscape_3_out.jpg differ diff --git a/testdata/exif/Landscape_4_out.jpg b/testdata/exif/Landscape_4_out.jpg index 39533f6..1f7ef6d 100644 Binary files a/testdata/exif/Landscape_4_out.jpg and b/testdata/exif/Landscape_4_out.jpg differ diff --git a/testdata/exif/Landscape_5_out.jpg b/testdata/exif/Landscape_5_out.jpg index 2edad25..280656d 100644 Binary files a/testdata/exif/Landscape_5_out.jpg and b/testdata/exif/Landscape_5_out.jpg differ diff --git a/testdata/exif/Landscape_6_out.jpg b/testdata/exif/Landscape_6_out.jpg index 7e8044f..9f9913e 100644 Binary files a/testdata/exif/Landscape_6_out.jpg and b/testdata/exif/Landscape_6_out.jpg differ diff --git a/testdata/exif/Landscape_7_out.jpg b/testdata/exif/Landscape_7_out.jpg index 0bb5462..632da8c 100644 Binary files a/testdata/exif/Landscape_7_out.jpg and b/testdata/exif/Landscape_7_out.jpg differ diff --git a/testdata/exif/Landscape_8_out.jpg b/testdata/exif/Landscape_8_out.jpg index 995f310..03f5d89 100644 Binary files a/testdata/exif/Landscape_8_out.jpg and b/testdata/exif/Landscape_8_out.jpg differ diff --git a/testdata/exif/Portrait_1_out.jpg b/testdata/exif/Portrait_1_out.jpg index 4c46526..1e5e4f2 100644 Binary files a/testdata/exif/Portrait_1_out.jpg and b/testdata/exif/Portrait_1_out.jpg differ diff --git a/testdata/exif/Portrait_2_out.jpg b/testdata/exif/Portrait_2_out.jpg index 0c68143..45cf4a5 100644 Binary files a/testdata/exif/Portrait_2_out.jpg and b/testdata/exif/Portrait_2_out.jpg differ diff --git a/testdata/exif/Portrait_3_out.jpg b/testdata/exif/Portrait_3_out.jpg index 55e44ca..8625fd3 100644 Binary files a/testdata/exif/Portrait_3_out.jpg and b/testdata/exif/Portrait_3_out.jpg differ diff --git a/testdata/exif/Portrait_4_out.jpg b/testdata/exif/Portrait_4_out.jpg index 23eae85..05ce827 100644 Binary files a/testdata/exif/Portrait_4_out.jpg and b/testdata/exif/Portrait_4_out.jpg differ diff --git a/testdata/exif/Portrait_5_out.jpg b/testdata/exif/Portrait_5_out.jpg index 51ca714..675a67a 100644 Binary files a/testdata/exif/Portrait_5_out.jpg and b/testdata/exif/Portrait_5_out.jpg differ diff --git a/testdata/exif/Portrait_6_out.jpg b/testdata/exif/Portrait_6_out.jpg index 867eda8..d982b7a 100644 Binary files a/testdata/exif/Portrait_6_out.jpg and b/testdata/exif/Portrait_6_out.jpg differ diff --git a/testdata/exif/Portrait_7_out.jpg b/testdata/exif/Portrait_7_out.jpg index e9d4989..acde0cc 100644 Binary files a/testdata/exif/Portrait_7_out.jpg and b/testdata/exif/Portrait_7_out.jpg differ diff --git a/testdata/exif/Portrait_8_out.jpg b/testdata/exif/Portrait_8_out.jpg index 71c42ab..2c0f901 100644 Binary files a/testdata/exif/Portrait_8_out.jpg and b/testdata/exif/Portrait_8_out.jpg differ diff --git a/testdata/test_gif.jpg b/testdata/test_gif.jpg index 1e8c2aa..a3d61aa 100644 Binary files a/testdata/test_gif.jpg and b/testdata/test_gif.jpg differ diff --git a/testdata/test_pdf.jpg b/testdata/test_pdf.jpg index 7a1ba3c..6b28307 100644 Binary files a/testdata/test_pdf.jpg and b/testdata/test_pdf.jpg differ diff --git a/testdata/test_smart_crop.jpg b/testdata/test_smart_crop.jpg index f5d928b..1dd3ec1 100644 Binary files a/testdata/test_smart_crop.jpg and b/testdata/test_smart_crop.jpg differ diff --git a/testdata/test_svg.jpg b/testdata/test_svg.jpg index a8a1b40..adb6d27 100644 Binary files a/testdata/test_svg.jpg and b/testdata/test_svg.jpg differ diff --git a/vips.go b/vips.go index 45d0886..8795686 100644 --- a/vips.go +++ b/vips.go @@ -272,6 +272,18 @@ func vipsRotate(image *C.VipsImage, angle Angle) (*C.VipsImage, error) { 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) { var out *C.VipsImage defer C.g_object_unref(C.gpointer(image)) diff --git a/vips.h b/vips.h index 894adf1..196c335 100644 --- a/vips.h +++ b/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 * vips_exif_tag(VipsImage *image, const char *tag) { const char *exif; diff --git a/vips_test.go b/vips_test.go index d8bd2b2..5a696f2 100644 --- a/vips_test.go +++ b/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) { image, _, _ := vipsRead(readImage("test.jpg"))