Merge pull request #124 from greut/tiffsave

feat: TIFF save
master
Tomás Aparicio 9 years ago committed by GitHub
commit 931a857edf

@ -51,23 +51,28 @@ var ImageTypes = map[ImageType]string{
// for SupportedImageTypes map. // for SupportedImageTypes map.
var imageMutex = &sync.RWMutex{} 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 // SupportedImageTypes stores the optional image type supported
// by the current libvips compilation. // by the current libvips compilation.
// Note: lazy evaluation as demand is required due // Note: lazy evaluation as demand is required due
// to bootstrap runtime limitation with C/libvips world. // 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. // discoverSupportedImageTypes is used to fill SupportedImageTypes map.
func discoverSupportedImageTypes() { func discoverSupportedImageTypes() {
imageMutex.Lock() imageMutex.Lock()
SupportedImageTypes[JPEG] = VipsIsTypeSupported(JPEG) for imageType := range ImageTypes {
SupportedImageTypes[PNG] = VipsIsTypeSupported(PNG) SupportedImageTypes[imageType] = SupportedImageType{
SupportedImageTypes[GIF] = VipsIsTypeSupported(GIF) Load: VipsIsTypeSupported(imageType),
SupportedImageTypes[WEBP] = VipsIsTypeSupported(WEBP) Save: VipsIsTypeSupportedSave(imageType),
SupportedImageTypes[SVG] = VipsIsTypeSupported(SVG) }
SupportedImageTypes[TIFF] = VipsIsTypeSupported(TIFF) }
SupportedImageTypes[PDF] = VipsIsTypeSupported(PDF)
SupportedImageTypes[MAGICK] = VipsIsTypeSupported(MAGICK)
imageMutex.Unlock() imageMutex.Unlock()
} }
@ -102,7 +107,7 @@ func DetermineImageTypeName(buf []byte) string {
// IsImageTypeSupportedByVips returns true if the given image type // IsImageTypeSupportedByVips returns true if the given image type
// is supported by current libvips compilation. // is supported by current libvips compilation.
func IsImageTypeSupportedByVips(t ImageType) bool { func IsImageTypeSupportedByVips(t ImageType) SupportedImageType {
imageMutex.RLock() imageMutex.RLock()
// Discover supported image types and cache the result // Discover supported image types and cache the result
@ -113,25 +118,45 @@ func IsImageTypeSupportedByVips(t ImageType) bool {
} }
// Check if image type is actually supported // Check if image type is actually supported
isSupported, ok := SupportedImageTypes[t] supported, ok := SupportedImageTypes[t]
if !itShouldDiscover { if !itShouldDiscover {
imageMutex.RUnlock() imageMutex.RUnlock()
} }
return ok && isSupported if ok {
return supported
}
return SupportedImageType{Load: false, Save: false}
} }
// IsTypeSupported checks if a given image type is supported // IsTypeSupported checks if a given image type is supported
func IsTypeSupported(t ImageType) bool { func IsTypeSupported(t ImageType) bool {
_, ok := ImageTypes[t] _, ok := ImageTypes[t]
return ok && IsImageTypeSupportedByVips(t) return ok && IsImageTypeSupportedByVips(t).Load
} }
// IsTypeNameSupported checks if a given image type name is supported // IsTypeNameSupported checks if a given image type name is supported
func IsTypeNameSupported(t string) bool { func IsTypeNameSupported(t string) bool {
for imageType, name := range ImageTypes { for imageType, name := range ImageTypes {
if name == t { 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 return false

@ -66,7 +66,7 @@ func TestIsTypeSupported(t *testing.T) {
for _, n := range types { for _, n := range types {
if IsTypeSupported(n.name) == false { 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 { for _, n := range types {
if IsTypeNameSupported(n.name) != n.expected { 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)
} }
} }
} }

@ -8,6 +8,7 @@ import "C"
import ( import (
"errors" "errors"
"fmt"
"math" "math"
"os" "os"
"runtime" "runtime"
@ -167,6 +168,25 @@ func VipsIsTypeSupported(t ImageType) bool {
return false 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 { func vipsExifOrientation(image *C.VipsImage) int {
return int(C.vips_exif_orientation(image)) 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)) interlace := C.int(boolToInt(o.Interlace))
quality := C.int(o.Quality) 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 var ptr unsafe.Pointer
switch o.Type { switch o.Type {
case WEBP: case WEBP:
saveErr = C.vips_webpsave_bridge(tmpImage, &ptr, &length, 1, quality) saveErr = C.vips_webpsave_bridge(tmpImage, &ptr, &length, 1, quality)
break
case PNG: case PNG:
saveErr = C.vips_pngsave_bridge(tmpImage, &ptr, &length, 1, C.int(o.Compression), quality, interlace) saveErr = C.vips_pngsave_bridge(tmpImage, &ptr, &length, 1, C.int(o.Compression), quality, interlace)
break case TIFF:
case GIF: saveErr = C.vips_tiffsave_bridge(tmpImage, &ptr, &length)
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")
default: default:
saveErr = C.vips_jpegsave_bridge(tmpImage, &ptr, &length, 1, quality, interlace) saveErr = C.vips_jpegsave_bridge(tmpImage, &ptr, &length, 1, quality, interlace)
break
} }
if int(saveErr) != 0 { if int(saveErr) != 0 {
@ -506,24 +520,24 @@ func vipsImageType(buf []byte) ImageType {
if buf[0] == 0xFF && buf[1] == 0xD8 && buf[2] == 0xFF { if buf[0] == 0xFF && buf[1] == 0xD8 && buf[2] == 0xFF {
return JPEG 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 return WEBP
} }
if IsImageTypeSupportedByVips(TIFF) && if IsTypeSupported(TIFF) &&
((buf[0] == 0x49 && buf[1] == 0x49 && buf[2] == 0x2A && buf[3] == 0x0) || ((buf[0] == 0x49 && buf[1] == 0x49 && buf[2] == 0x2A && buf[3] == 0x0) ||
(buf[0] == 0x4D && buf[1] == 0x4D && buf[2] == 0x0 && buf[3] == 0x2A)) { (buf[0] == 0x4D && buf[1] == 0x4D && buf[2] == 0x0 && buf[3] == 0x2A)) {
return TIFF 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 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 return PDF
} }
if IsImageTypeSupportedByVips(SVG) && IsSVGImage(buf) { if IsTypeSupported(SVG) && IsSVGImage(buf) {
return SVG return SVG
} }
if IsImageTypeSupportedByVips(MAGICK) && strings.HasSuffix(readImageType(buf), "MagickBuffer") { if IsTypeSupported(MAGICK) && strings.HasSuffix(readImageType(buf), "MagickBuffer") {
return MAGICK return MAGICK
} }
return UNKNOWN return UNKNOWN

@ -144,6 +144,23 @@ vips_type_find_bridge(int t) {
return 0; 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 int
vips_rotate(VipsImage *in, VipsImage **out, int angle) { vips_rotate(VipsImage *in, VipsImage **out, int angle) {
int rotate = VIPS_ANGLE_D0; 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 int
vips_is_16bit (VipsInterpretation interpretation) { vips_is_16bit (VipsInterpretation interpretation) {
return interpretation == VIPS_INTERPRETATION_RGB16 || interpretation == VIPS_INTERPRETATION_GREY16; return interpretation == VIPS_INTERPRETATION_RGB16 || interpretation == VIPS_INTERPRETATION_GREY16;

@ -29,7 +29,7 @@ func TestVipsRead(t *testing.T) {
} }
func TestVipsSave(t *testing.T) { func TestVipsSave(t *testing.T) {
types := [...]ImageType{JPEG, PNG, TIFF, WEBP} types := [...]ImageType{JPEG, PNG, WEBP}
for _, typ := range types { for _, typ := range types {
image, _, _ := vipsRead(readImage("test.jpg")) image, _, _ := vipsRead(readImage("test.jpg"))
@ -45,20 +45,16 @@ func TestVipsSave(t *testing.T) {
} }
} }
func TestVipsCannotSave(t *testing.T) { func TestVipsSaveTiff(t *testing.T) {
types := [...]ImageType{GIF, PDF, SVG, MAGICK} if !IsTypeSupportedSave(TIFF) {
t.Skipf("Format %#v is not supported", ImageTypes[TIFF])
for _, typ := range types { }
image, _, _ := vipsRead(readImage("test.jpg")) image, _, _ := vipsRead(readImage("test.jpg"))
options := vipsSaveOptions{Quality: 95, Type: typ} options := vipsSaveOptions{Quality: 95, Type: TIFF}
buf, _ := vipsSave(image, options)
buf, err := vipsSave(image, options) if len(buf) == 0 {
if err == nil { t.Fatalf("Empty saved '%v' image", ImageTypes[TIFF])
t.Fatalf("Format '%v' shouldn't be supported", ImageTypes[typ])
}
if len(buf) != 0 {
t.Fatalf("'%v' image is not empty", ImageTypes[typ])
}
} }
} }
@ -118,9 +114,9 @@ func TestVipsWatermark(t *testing.T) {
t.Errorf("Cannot add watermark: %s", err) t.Errorf("Cannot add watermark: %s", err)
} }
buf, _ := vipsSave(newImg, vipsSaveOptions{Quality: 95}) buf, err := vipsSave(newImg, vipsSaveOptions{Quality: 95})
if len(buf) == 0 { if len(buf) == 0 || err != nil {
t.Fatal("Empty image") t.Fatalf("Empty image. %#v", err)
} }
} }

Loading…
Cancel
Save