diff --git a/History.md b/History.md index 62a07fd..11d7ba8 100644 --- a/History.md +++ b/History.md @@ -1,7 +1,15 @@ +## 1.0.3 / 28-09-2016 + +- fix(#95): better image type inference and support check. +- fix(background): pass proper background RGB color for PNG image conversion. +- feat(types): validate supported image types by current `libvips` compilation. +- feat(types): consistent SVG image checking. +- feat(api): add public functions `VipsIsTypeSupported()`, `IsImageTypeSupportedByVips()` and `IsSVGImage()`. + ## 1.0.2 / 27-09-2016 -- feat(#95): support GIF, SVG and PDF formats -- fix(#108): auto-width and height calculations now round instead of floor +- feat(#95): support GIF, SVG and PDF formats. +- fix(#108): auto-width and height calculations now round instead of floor. ## 1.0.1 / 22-06-2016 diff --git a/resize.go b/resize.go index 8d732fb..4ccc92b 100644 --- a/resize.go +++ b/resize.go @@ -28,7 +28,7 @@ func Resize(buf []byte, o Options) ([]byte, error) { // Clone and define default options o = applyDefaults(o, imageType) - if IsTypeSupported(o.Type) == false { + if !IsTypeSupported(o.Type) { return nil, errors.New("Unsupported image output type") } @@ -330,7 +330,6 @@ func imageFlatten(image *C.VipsImage, imageType ImageType, o Options) (*C.VipsIm if imageType != PNG || o.Background == ColorBlack { return image, nil } - return vipsFlattenBackground(image, o.Background) } diff --git a/type.go b/type.go index 60722eb..beb5b3d 100644 --- a/type.go +++ b/type.go @@ -1,7 +1,10 @@ package bimg -// ImageType represents an image type value. -type ImageType int +import ( + "regexp" + "sync" + "unicode/utf8" +) const ( // UNKNOWN represents an unknow image type value. @@ -24,6 +27,14 @@ const ( MAGICK ) +// ImageType represents an image type value. +type ImageType int + +var ( + htmlCommentRegex = regexp.MustCompile("(?i)") + svgRegex = regexp.MustCompile(`(?i)^\s*(?:<\?xml[^>]*>\s*)?(?:]*>\s*)?]*>[^*]*<\/svg>\s*$`) +) + // ImageTypes stores as pairs of image types supported and its alias names. var ImageTypes = map[ImageType]string{ JPEG: "jpeg", @@ -36,6 +47,49 @@ var ImageTypes = map[ImageType]string{ MAGICK: "magick", } +// imageMutex is used to provide thread-safe synchronization +// for SupportedImageTypes map. +var imageMutex = &sync.RWMutex{} + +// 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{} + +// 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) + imageMutex.Unlock() +} + +// isBinary checks if the given buffer is a binary file. +func isBinary(buf []byte) bool { + if len(buf) < 24 { + return false + } + for i := 0; i < 24; i++ { + charCode, _ := utf8.DecodeRuneInString(string(buf[i])) + if charCode == 65533 || charCode <= 8 { + return true + } + } + return false +} + +// IsSVGImage returns true if the given buffer is a valid SVG image. +func IsSVGImage(buf []byte) bool { + return !isBinary(buf) && svgRegex.Match(htmlCommentRegex.ReplaceAll(buf, []byte{})) +} + // DetermineImageType determines the image type format (jpeg, png, webp or tiff) func DetermineImageType(buf []byte) ImageType { return vipsImageType(buf) @@ -46,16 +100,38 @@ func DetermineImageTypeName(buf []byte) string { return ImageTypeName(vipsImageType(buf)) } +// IsImageTypeSupportedByVips returns true if the given image type +// is supported by current libvips compilation. +func IsImageTypeSupportedByVips(t ImageType) bool { + imageMutex.RLock() + + // Discover supported image types and cache the result + itShouldDiscovery := len(SupportedImageTypes) == 0 + if itShouldDiscovery { + imageMutex.RUnlock() + discoverSupportedImageTypes() + } + + // Check if image type is actually supported + isSupported, ok := SupportedImageTypes[t] + if !itShouldDiscovery { + imageMutex.RUnlock() + } + + return ok && isSupported +} + // IsTypeSupported checks if a given image type is supported func IsTypeSupported(t ImageType) bool { - return ImageTypes[t] != "" + _, ok := ImageTypes[t] + return ok && IsImageTypeSupportedByVips(t) } // IsTypeNameSupported checks if a given image type name is supported func IsTypeNameSupported(t string) bool { - for _, name := range ImageTypes { + for imageType, name := range ImageTypes { if name == t { - return true + return IsImageTypeSupportedByVips(imageType) } } return false diff --git a/version.go b/version.go index b10c1e9..d03c288 100644 --- a/version.go +++ b/version.go @@ -1,4 +1,4 @@ package bimg // Version represents the current package semantic version. -const Version = "1.0.2" +const Version = "1.0.3" diff --git a/vips.go b/vips.go index 1fec575..33ab442 100644 --- a/vips.go +++ b/vips.go @@ -30,10 +30,6 @@ const VipsMajorVersion = int(C.VIPS_MAJOR_VERSION) // VipsMinorVersion exposes the current libvips minor version number const VipsMinorVersion = int(C.VIPS_MINOR_VERSION) -// HasMagickSupport exposes if the current libvips compilation -// supports libmagick bindings. -const HasMagickSupport = int(C.VIPS_MAGICK_SUPPORT) == 1 - const ( maxCacheMem = 100 * 1024 * 1024 maxCacheSize = 500 @@ -141,6 +137,33 @@ func VipsMemory() VipsMemoryInfo { } } +// VipsIsTypeSupported returns true if the given image type +// is supported by the current libvips compilation. +func VipsIsTypeSupported(t ImageType) bool { + if t == JPEG { + return int(C.vips_type_find_bridge(C.JPEG)) != 0 + } + if t == WEBP { + return int(C.vips_type_find_bridge(C.WEBP)) != 0 + } + if t == PNG { + return int(C.vips_type_find_bridge(C.PNG)) != 0 + } + if t == GIF { + return int(C.vips_type_find_bridge(C.GIF)) != 0 + } + if t == PDF { + return int(C.vips_type_find_bridge(C.PDF)) != 0 + } + if t == SVG { + return int(C.vips_type_find_bridge(C.SVG)) != 0 + } + if t == TIFF { + return int(C.vips_type_find_bridge(C.TIFF)) != 0 + } + return false +} + func vipsExifOrientation(image *C.VipsImage) int { return int(C.vips_exif_orientation(image)) } @@ -281,7 +304,8 @@ func vipsFlattenBackground(image *C.VipsImage, background Color) (*C.VipsImage, } if vipsHasAlpha(image) { - err := C.vips_flatten_background_brigde(image, &outImage, (*C.double)(&backgroundC[0])) + err := C.vips_flatten_background_brigde(image, &outImage, + backgroundC[0], backgroundC[1], backgroundC[2]) if int(err) != 0 { return nil, catchVipsError() } @@ -461,39 +485,36 @@ func vipsAffine(input *C.VipsImage, residualx, residualy float64, i Interpolator return image, nil } -func vipsImageType(bytes []byte) ImageType { - if len(bytes) == 0 { +func vipsImageType(buf []byte) ImageType { + if len(buf) == 0 { return UNKNOWN } - - if bytes[0] == 0x89 && bytes[1] == 0x50 && bytes[2] == 0x4E && bytes[3] == 0x47 { + if buf[0] == 0x89 && buf[1] == 0x50 && buf[2] == 0x4E && buf[3] == 0x47 { return PNG } - if bytes[0] == 0xFF && bytes[1] == 0xD8 && bytes[2] == 0xFF { + if buf[0] == 0xFF && buf[1] == 0xD8 && buf[2] == 0xFF { return JPEG } - if bytes[8] == 0x57 && bytes[9] == 0x45 && bytes[10] == 0x42 && bytes[11] == 0x50 { + if IsImageTypeSupportedByVips(WEBP) && buf[8] == 0x57 && buf[9] == 0x45 && buf[10] == 0x42 && buf[11] == 0x50 { return WEBP } - if (bytes[0] == 0x49 && bytes[1] == 0x49 && bytes[2] == 0x2A && bytes[3] == 0x0) || - (bytes[0] == 0x4D && bytes[1] == 0x4D && bytes[2] == 0x0 && bytes[3] == 0x2A) { + if IsImageTypeSupportedByVips(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 bytes[0] == 'G' && bytes[1] == 'I' && bytes[2] == 'F' && bytes[3] == '8' && - bytes[4] == '9' && bytes[5] == 'a' { + if IsImageTypeSupportedByVips(GIF) && buf[0] == 0x47 && buf[1] == 0x49 && buf[2] == 0x46 { return GIF } - if bytes[0] == '%' && bytes[1] == 'P' && bytes[2] == 'D' && bytes[3] == 'F' { + if IsImageTypeSupportedByVips(PDF) && buf[0] == 0x25 && buf[1] == 0x50 && buf[2] == 0x44 && buf[3] == 0x46 { return PDF } - if bytes[0] == '<' && bytes[1] == '?' && bytes[2] == 'x' && bytes[3] == 'm' && - bytes[4] == 'l' && bytes[5] == ' ' { + if IsImageTypeSupportedByVips(SVG) && IsSVGImage(buf) { return SVG } - if HasMagickSupport && strings.HasSuffix(readImageType(bytes), "MagickBuffer") { + if strings.HasSuffix(readImageType(buf), "MagickBuffer") { return MAGICK } - return UNKNOWN } diff --git a/vips.h b/vips.h index b5b3bb2..3cbe6cf 100644 --- a/vips.h +++ b/vips.h @@ -1,13 +1,8 @@ #include #include +#include #include -#ifdef VIPS_MAGICK_H -#define VIPS_MAGICK_SUPPORT 1 -#else -#define VIPS_MAGICK_SUPPORT 0 -#endif - /** * Starting libvips 7.41, VIPS_ANGLE_x has been renamed to VIPS_ANGLE_Dx * "to help python". So we provide the macro to correctly build for versions @@ -120,6 +115,35 @@ vips_shrink_bridge(VipsImage *in, VipsImage **out, double xshrink, double yshrin return vips_shrink(in, out, xshrink, yshrink, NULL); } +int +vips_type_find_bridge(int t) { + if (t == GIF) { + return vips_type_find("VipsOperation", "gifload"); + } + if (t == PDF) { + return vips_type_find("VipsOperation", "pdfload"); + } + if (t == TIFF) { + return vips_type_find("VipsOperation", "tiffload"); + } + if (t == SVG) { + return vips_type_find("VipsOperation", "svgload"); + } + if (t == WEBP) { + return vips_type_find("VipsOperation", "webpload"); + } + if (t == PNG) { + return vips_type_find("VipsOperation", "pngload"); + } + if (t == JPEG) { + return vips_type_find("VipsOperation", "jpegload"); + } + if (t == MAGICK) { + return vips_type_find("VipsOperation", "magickload"); + } + return 0; +} + int vips_rotate(VipsImage *in, VipsImage **out, int angle) { int rotate = VIPS_ANGLE_D0; @@ -253,12 +277,10 @@ vips_is_16bit (VipsInterpretation interpretation) { } int -vips_flatten_background_brigde(VipsImage *in, VipsImage **out, double background[3]) { - background[0] *= 256; - background[1] *= 256; - background[2] *= 256; - +vips_flatten_background_brigde(VipsImage *in, VipsImage **out, double r, double g, double b) { + double background[3] = {r, g, b}; VipsArrayDouble *vipsBackground = vips_array_double_new(background, 3); + return vips_flatten(in, out, "background", vipsBackground, "max_alpha", vips_is_16bit(in->Type) ? 65535.0 : 255.0,