diff --git a/metadata.go b/metadata.go index 9cb5143..8e5e9ae 100644 --- a/metadata.go +++ b/metadata.go @@ -22,6 +22,15 @@ type ImageMetadata struct { Space string Colourspace string Size ImageSize + EXIF EXIF +} + +type EXIF struct { + Make string + Model string + Orientation int + Software string + Datetime string } // Size returns the image size by width and height pixels. @@ -63,14 +72,23 @@ func Metadata(buf []byte) (ImageMetadata, error) { Height: int(image.Ysize), } + orientation := vipsExifOrientation(image) + metadata := ImageMetadata{ Size: size, Channels: int(image.Bands), - Orientation: vipsExifOrientation(image), + Orientation: orientation, Alpha: vipsHasAlpha(image), Profile: vipsHasProfile(image), Space: vipsSpace(image), Type: ImageTypeName(imageType), + EXIF: EXIF{ + Make: vipsExifMake(image), + Model: vipsExifModel(image), + Orientation: orientation, + Software: vipsExifSoftware(image), + Datetime: vipsExifDatetime(image), + }, } return metadata, nil diff --git a/metadata_test.go b/metadata_test.go index 83be45f..00ca244 100644 --- a/metadata_test.go +++ b/metadata_test.go @@ -89,6 +89,44 @@ func TestImageInterpretation(t *testing.T) { } } +func TestEXIF(t *testing.T) { + files := []struct { + name string + make string + model string + orientation int + software string + datetime string + }{ + {"test.jpg", "", "", 0, "", ""}, + {"exif/Landscape_1.jpg", "", "", 1, "", ""}, + {"test_exif.jpg", "Jolla", "Jolla", 1, "", "2014:09:21 16:00:56"}, + {"test_exif_canon.jpg", "Canon", "Canon EOS 40D", 1, "GIMP 2.4.5", "2008:07:31 10:38:11"}, + } + + for _, file := range files { + metadata, err := Metadata(readFile(file.name)) + if err != nil { + t.Fatalf("Cannot read the image: %s -> %s", file.name, err) + } + if metadata.EXIF.Make != file.make { + t.Fatalf("Unexpected image exif make: %s != %s", metadata.EXIF.Make, file.make) + } + if metadata.EXIF.Model != file.model { + t.Fatalf("Unexpected image exif model: %s != %s", metadata.EXIF.Model, file.model) + } + if metadata.EXIF.Orientation != file.orientation { + t.Fatalf("Unexpected image exif orientation: %d != %d", metadata.EXIF.Orientation, file.orientation) + } + if metadata.EXIF.Software != file.software { + t.Fatalf("Unexpected image exif software: %s != %s", metadata.EXIF.Software, file.software) + } + if metadata.EXIF.Datetime != file.datetime { + t.Fatalf("Unexpected image exif datetime: %s != %s", metadata.EXIF.Datetime, file.datetime) + } + } +} + func TestColourspaceIsSupported(t *testing.T) { files := []struct { name string diff --git a/testdata/test_exif.jpg b/testdata/test_exif.jpg new file mode 100644 index 0000000..a0d98b0 Binary files /dev/null and b/testdata/test_exif.jpg differ diff --git a/testdata/test_exif_canon.jpg b/testdata/test_exif_canon.jpg new file mode 100644 index 0000000..6eb33f1 Binary files /dev/null and b/testdata/test_exif_canon.jpg differ diff --git a/vips.go b/vips.go index 2e233eb..23e8676 100644 --- a/vips.go +++ b/vips.go @@ -215,10 +215,33 @@ func VipsIsTypeSupportedSave(t ImageType) bool { return false } +func vipsExifMake(image *C.VipsImage) string { + return vipsExifShort(C.GoString(C.vips_exif_make(image))) +} + +func vipsExifModel(image *C.VipsImage) string { + return vipsExifShort(C.GoString(C.vips_exif_model(image))) +} + func vipsExifOrientation(image *C.VipsImage) int { return int(C.vips_exif_orientation(image)) } +func vipsExifSoftware(image *C.VipsImage) string { + return vipsExifShort(C.GoString(C.vips_exif_software(image))) +} + +func vipsExifDatetime(image *C.VipsImage) string { + return vipsExifShort(C.GoString(C.vips_exif_datetime(image))) +} + +func vipsExifShort(s string) string { + if strings.Contains(s, " (") { + return s[:strings.Index(s, "(")-1] + } + return s +} + func vipsHasAlpha(image *C.VipsImage) bool { return int(C.has_alpha_channel(image)) > 0 } diff --git a/vips.h b/vips.h index 36a59d0..894adf1 100644 --- a/vips.h +++ b/vips.h @@ -18,7 +18,11 @@ #define VIPS_ANGLE_D270 VIPS_ANGLE_270 #endif +#define EXIF_IFD0_MAKE "exif-ifd0-Make" +#define EXIF_IFD0_MODEL "exif-ifd0-Model" #define EXIF_IFD0_ORIENTATION "exif-ifd0-Orientation" +#define EXIF_IFD0_SOFTWARE "exif-ifd0-Software" +#define EXIF_IFD0_DATETIME "exif-ifd0-DateTime" #define INT_TO_GBOOLEAN(bool) (bool > 0 ? TRUE : FALSE) @@ -218,19 +222,48 @@ vips_rotate_bridge(VipsImage *in, VipsImage **out, int angle) { } } -int -vips_exif_orientation(VipsImage *image) { - int orientation = 0; +const char * +vips_exif_tag(VipsImage *image, const char *tag) { const char *exif; if ( - vips_image_get_typeof(image, EXIF_IFD0_ORIENTATION) != 0 && - !vips_image_get_string(image, EXIF_IFD0_ORIENTATION, &exif) + vips_image_get_typeof(image, tag) != 0 && + !vips_image_get_string(image, tag, &exif) ) { + return &exif[0]; + } + return ""; +} + +const char * +vips_exif_make(VipsImage *image) { + return vips_exif_tag(image, EXIF_IFD0_MAKE); +} + +const char * +vips_exif_model(VipsImage *image) { + return vips_exif_tag(image, EXIF_IFD0_MODEL); +} + +int +vips_exif_orientation(VipsImage *image) { + int orientation = 0; + const char *exif = vips_exif_tag(image, EXIF_IFD0_ORIENTATION); + if (strcmp(exif, "")) { orientation = atoi(&exif[0]); } return orientation; } +const char * +vips_exif_software(VipsImage *image) { + return vips_exif_tag(image, EXIF_IFD0_SOFTWARE); +} + +const char * +vips_exif_datetime(VipsImage *image) { + return vips_exif_tag(image, EXIF_IFD0_DATETIME); +} + int interpolator_window_size(char const *name) { VipsInterpolate *interpolator = vips_interpolate_new(name);