diff --git a/type.go b/type.go index dc92aca..260adbf 100644 --- a/type.go +++ b/type.go @@ -51,23 +51,28 @@ var ImageTypes = map[ImageType]string{ // for SupportedImageTypes map. var imageMutex = &sync.RWMutex{} +// SupportedImageType represents whether a type can be loaded and/or saved by +// the current libvips compilation. +type SupportedImageType struct { + Load bool + Save bool +} + // SupportedImageTypes stores the optional image type supported // by the current libvips compilation. // Note: lazy evaluation as demand is required due // to bootstrap runtime limitation with C/libvips world. -var SupportedImageTypes = map[ImageType]bool{} +var SupportedImageTypes = map[ImageType]SupportedImageType{} // discoverSupportedImageTypes is used to fill SupportedImageTypes map. func discoverSupportedImageTypes() { imageMutex.Lock() - SupportedImageTypes[JPEG] = VipsIsTypeSupported(JPEG) - SupportedImageTypes[PNG] = VipsIsTypeSupported(PNG) - SupportedImageTypes[GIF] = VipsIsTypeSupported(GIF) - SupportedImageTypes[WEBP] = VipsIsTypeSupported(WEBP) - SupportedImageTypes[SVG] = VipsIsTypeSupported(SVG) - SupportedImageTypes[TIFF] = VipsIsTypeSupported(TIFF) - SupportedImageTypes[PDF] = VipsIsTypeSupported(PDF) - SupportedImageTypes[MAGICK] = VipsIsTypeSupported(MAGICK) + for imageType := range ImageTypes { + SupportedImageTypes[imageType] = SupportedImageType{ + Load: VipsIsTypeSupported(imageType), + Save: VipsIsTypeSupportedSave(imageType), + } + } imageMutex.Unlock() } @@ -102,7 +107,7 @@ func DetermineImageTypeName(buf []byte) string { // IsImageTypeSupportedByVips returns true if the given image type // is supported by current libvips compilation. -func IsImageTypeSupportedByVips(t ImageType) bool { +func IsImageTypeSupportedByVips(t ImageType) SupportedImageType { imageMutex.RLock() // Discover supported image types and cache the result @@ -113,25 +118,45 @@ func IsImageTypeSupportedByVips(t ImageType) bool { } // Check if image type is actually supported - isSupported, ok := SupportedImageTypes[t] + supported, ok := SupportedImageTypes[t] if !itShouldDiscover { imageMutex.RUnlock() } - return ok && isSupported + if ok { + return supported + } + return SupportedImageType{Load: false, Save: false} } // IsTypeSupported checks if a given image type is supported func IsTypeSupported(t ImageType) bool { _, ok := ImageTypes[t] - return ok && IsImageTypeSupportedByVips(t) + return ok && IsImageTypeSupportedByVips(t).Load } // IsTypeNameSupported checks if a given image type name is supported func IsTypeNameSupported(t string) bool { for imageType, name := range ImageTypes { if name == t { - return IsImageTypeSupportedByVips(imageType) + return IsImageTypeSupportedByVips(imageType).Load + } + } + return false +} + +// IsTypeSupportedSave checks if a given image type is support for saving +func IsTypeSupportedSave(t ImageType) bool { + _, ok := ImageTypes[t] + return ok && IsImageTypeSupportedByVips(t).Save +} + +// IsTypeNameSupportedSave checks if a given image type name is supported for +// saving +func IsTypeNameSupportedSave(t string) bool { + for imageType, name := range ImageTypes { + if name == t { + return IsImageTypeSupportedByVips(imageType).Save } } return false diff --git a/type_test.go b/type_test.go index e769f53..9c83b0f 100644 --- a/type_test.go +++ b/type_test.go @@ -66,7 +66,7 @@ func TestIsTypeSupported(t *testing.T) { for _, n := range types { if IsTypeSupported(n.name) == false { - t.Fatal("Image type is not valid") + t.Fatalf("Image type %#v is not valid", ImageTypes[n.name]) } } } @@ -85,7 +85,44 @@ func TestIsTypeNameSupported(t *testing.T) { for _, n := range types { if IsTypeNameSupported(n.name) != n.expected { - t.Fatal("Image type is not valid") + t.Fatalf("Image type %#v is not valid", n.name) + } + } +} + +func TestIsTypeSupportedSave(t *testing.T) { + types := []struct { + name ImageType + }{ + {JPEG}, {PNG}, {WEBP}, + } + if VipsVersion >= "8.5.0" { + types = append(types, struct{ name ImageType }{TIFF}) + } + + for _, n := range types { + if IsTypeSupportedSave(n.name) == false { + t.Fatalf("Image type %#v is not valid", ImageTypes[n.name]) + } + } +} + +func TestIsTypeNameSupportedSave(t *testing.T) { + types := []struct { + name string + expected bool + }{ + {"jpeg", true}, + {"png", true}, + {"webp", true}, + {"gif", false}, + {"pdf", false}, + {"tiff", VipsVersion >= "8.5.0"}, + } + + for _, n := range types { + if IsTypeNameSupportedSave(n.name) != n.expected { + t.Fatalf("Image type %#v is not valid", n.name) } } } diff --git a/vips.go b/vips.go index 1bbd6ed..1544b4a 100644 --- a/vips.go +++ b/vips.go @@ -8,6 +8,7 @@ import "C" import ( "errors" + "fmt" "math" "os" "runtime" @@ -167,6 +168,25 @@ func VipsIsTypeSupported(t ImageType) bool { return false } +// VipsIsTypeSupportedSave returns true if the given image type +// is supported by the current libvips compilation for the +// save operation. +func VipsIsTypeSupportedSave(t ImageType) bool { + if t == JPEG { + return int(C.vips_type_find_save_bridge(C.JPEG)) != 0 + } + if t == WEBP { + return int(C.vips_type_find_save_bridge(C.WEBP)) != 0 + } + if t == PNG { + return int(C.vips_type_find_save_bridge(C.PNG)) != 0 + } + if t == TIFF { + return int(C.vips_type_find_save_bridge(C.TIFF)) != 0 + } + return false +} + func vipsExifOrientation(image *C.VipsImage) int { return int(C.vips_exif_orientation(image)) } @@ -366,25 +386,19 @@ func vipsSave(image *C.VipsImage, o vipsSaveOptions) ([]byte, error) { interlace := C.int(boolToInt(o.Interlace)) quality := C.int(o.Quality) + if o.Type != 0 && !IsTypeSupportedSave(o.Type) { + return nil, fmt.Errorf("VIPS cannot save to %#v", ImageTypes[o.Type]) + } var ptr unsafe.Pointer switch o.Type { case WEBP: saveErr = C.vips_webpsave_bridge(tmpImage, &ptr, &length, 1, quality) - break case PNG: saveErr = C.vips_pngsave_bridge(tmpImage, &ptr, &length, 1, C.int(o.Compression), quality, interlace) - break - case GIF: - return nil, errors.New("VIPS cannot save to GIF") - case PDF: - return nil, errors.New("VIPS cannot save to PDF") - case SVG: - return nil, errors.New("VIPS cannot save to SVG") - case MAGICK: - return nil, errors.New("VIPS cannot save to MAGICK") + case TIFF: + saveErr = C.vips_tiffsave_bridge(tmpImage, &ptr, &length) default: saveErr = C.vips_jpegsave_bridge(tmpImage, &ptr, &length, 1, quality, interlace) - break } if int(saveErr) != 0 { @@ -506,24 +520,24 @@ func vipsImageType(buf []byte) ImageType { if buf[0] == 0xFF && buf[1] == 0xD8 && buf[2] == 0xFF { return JPEG } - if IsImageTypeSupportedByVips(WEBP) && buf[8] == 0x57 && buf[9] == 0x45 && buf[10] == 0x42 && buf[11] == 0x50 { + if IsTypeSupported(WEBP) && buf[8] == 0x57 && buf[9] == 0x45 && buf[10] == 0x42 && buf[11] == 0x50 { return WEBP } - if IsImageTypeSupportedByVips(TIFF) && + if IsTypeSupported(TIFF) && ((buf[0] == 0x49 && buf[1] == 0x49 && buf[2] == 0x2A && buf[3] == 0x0) || (buf[0] == 0x4D && buf[1] == 0x4D && buf[2] == 0x0 && buf[3] == 0x2A)) { return TIFF } - if IsImageTypeSupportedByVips(GIF) && buf[0] == 0x47 && buf[1] == 0x49 && buf[2] == 0x46 { + if IsTypeSupported(GIF) && buf[0] == 0x47 && buf[1] == 0x49 && buf[2] == 0x46 { return GIF } - if IsImageTypeSupportedByVips(PDF) && buf[0] == 0x25 && buf[1] == 0x50 && buf[2] == 0x44 && buf[3] == 0x46 { + if IsTypeSupported(PDF) && buf[0] == 0x25 && buf[1] == 0x50 && buf[2] == 0x44 && buf[3] == 0x46 { return PDF } - if IsImageTypeSupportedByVips(SVG) && IsSVGImage(buf) { + if IsTypeSupported(SVG) && IsSVGImage(buf) { return SVG } - if IsImageTypeSupportedByVips(MAGICK) && strings.HasSuffix(readImageType(buf), "MagickBuffer") { + if IsTypeSupported(MAGICK) && strings.HasSuffix(readImageType(buf), "MagickBuffer") { return MAGICK } return UNKNOWN diff --git a/vips.h b/vips.h index c3f7d9e..e9129b5 100644 --- a/vips.h +++ b/vips.h @@ -144,6 +144,23 @@ vips_type_find_bridge(int t) { return 0; } +int +vips_type_find_save_bridge(int t) { + if (t == TIFF) { + return vips_type_find("VipsOperation", "tiffsave_buffer"); + } + if (t == WEBP) { + return vips_type_find("VipsOperation", "webpsave_buffer"); + } + if (t == PNG) { + return vips_type_find("VipsOperation", "pngsave_buffer"); + } + if (t == JPEG) { + return vips_type_find("VipsOperation", "jpegsave_buffer"); + } + return 0; +} + int vips_rotate(VipsImage *in, VipsImage **out, int angle) { int rotate = VIPS_ANGLE_D0; @@ -276,6 +293,17 @@ vips_webpsave_bridge(VipsImage *in, void **buf, size_t *len, int strip, int qual ); } +int +vips_tiffsave_bridge(VipsImage *in, void **buf, size_t *len) { +#if (VIPS_MAJOR_VERSION >= 8 && VIPS_MINOR_VERSION >= 5) + return vips_tiffsave_buffer(in, buf, len, + NULL + ); +#else + return 0; +#endif +} + int vips_is_16bit (VipsInterpretation interpretation) { return interpretation == VIPS_INTERPRETATION_RGB16 || interpretation == VIPS_INTERPRETATION_GREY16; diff --git a/vips_test.go b/vips_test.go index 95c6658..7001c32 100644 --- a/vips_test.go +++ b/vips_test.go @@ -29,7 +29,7 @@ func TestVipsRead(t *testing.T) { } func TestVipsSave(t *testing.T) { - types := [...]ImageType{JPEG, PNG, TIFF, WEBP} + types := [...]ImageType{JPEG, PNG, WEBP} for _, typ := range types { image, _, _ := vipsRead(readImage("test.jpg")) @@ -45,20 +45,16 @@ func TestVipsSave(t *testing.T) { } } -func TestVipsCannotSave(t *testing.T) { - types := [...]ImageType{GIF, PDF, SVG, MAGICK} - - for _, typ := range types { - image, _, _ := vipsRead(readImage("test.jpg")) - options := vipsSaveOptions{Quality: 95, Type: typ} +func TestVipsSaveTiff(t *testing.T) { + if !IsTypeSupportedSave(TIFF) { + t.Skipf("Format %#v is not supported", ImageTypes[TIFF]) + } + image, _, _ := vipsRead(readImage("test.jpg")) + options := vipsSaveOptions{Quality: 95, Type: TIFF} + buf, _ := vipsSave(image, options) - buf, err := vipsSave(image, options) - if err == nil { - t.Fatalf("Format '%v' shouldn't be supported", ImageTypes[typ]) - } - if len(buf) != 0 { - t.Fatalf("'%v' image is not empty", ImageTypes[typ]) - } + if len(buf) == 0 { + t.Fatalf("Empty saved '%v' image", ImageTypes[TIFF]) } } @@ -118,9 +114,9 @@ func TestVipsWatermark(t *testing.T) { t.Errorf("Cannot add watermark: %s", err) } - buf, _ := vipsSave(newImg, vipsSaveOptions{Quality: 95}) - if len(buf) == 0 { - t.Fatal("Empty image") + buf, err := vipsSave(newImg, vipsSaveOptions{Quality: 95}) + if len(buf) == 0 || err != nil { + t.Fatalf("Empty image. %#v", err) } }