diff --git a/README.md b/README.md index 19e5a4e..f302ebd 100644 --- a/README.md +++ b/README.md @@ -238,7 +238,7 @@ bimg.Write("new.jpg", newImage) Run the process passing the `DEBUG` environment variable ``` -DEBUG=* ./app +DEBUG=bimg ./app ``` Enable libvips traces (note that a lot of data will be written in stdout): @@ -248,6 +248,12 @@ VIPS_TRACE=1 ./app ### Programmatic API +#### func ColourspaceIsSupported + +```go +func ColourspaceIsSupported(buf []byte) (bool, error) +``` +Check in the image colourspace is supported by libvips #### func DetermineImageTypeName @@ -295,7 +301,7 @@ func Resize(buf []byte, o Options) ([]byte, error) ```go func Shutdown() ``` -Thread-safe function to shutdown libvips. You could call this to drop caches as +Thread-safe function to shutdown libvips. You can call this to drop caches as well. If libvips was already initialized, the function is no-op #### func VipsDebugInfo @@ -383,6 +389,20 @@ func NewImage(buf []byte) *Image ``` Creates a new image +#### func (*Image) Colourspace + +```go +func (i *Image) Colourspace(c Interpretation) ([]byte, error) +``` +Colour space conversion + +#### func (*Image) ColourspaceIsSupported + +```go +func (i *Image) ColourspaceIsSupported() (bool, error) +``` +Check if the current image has a valid colourspace + #### func (*Image) Convert ```go @@ -453,6 +473,14 @@ func (i *Image) Image() []byte ``` Get image buffer +#### func (*Image) Interpretation + +```go +func (i *Image) Interpretation() (Interpretation, error) +``` +Get the image interpretation type See: +http://www.vips.ecs.soton.ac.uk/supported/current/doc/html/libvips/VipsImage.html#VipsInterpretation + #### func (*Image) Metadata ```go @@ -533,6 +561,7 @@ type ImageMetadata struct { Profile bool Type string Space string + Colourspace string Size ImageSize } ``` @@ -609,33 +638,65 @@ const ( func (i Interpolator) String() string ``` +#### type Interpretation + +```go +type Interpretation int +``` + +Image interpretation type See: +http://www.vips.ecs.soton.ac.uk/supported/current/doc/html/libvips/VipsImage.html#VipsInterpretation + +```go +const ( + INTERPRETATION_ERROR Interpretation = C.VIPS_INTERPRETATION_ERROR + INTERPRETATION_MULTIBAND Interpretation = C.VIPS_INTERPRETATION_MULTIBAND + INTERPRETATION_B_W Interpretation = C.VIPS_INTERPRETATION_B_W + INTERPRETATION_CMYK Interpretation = C.VIPS_INTERPRETATION_CMYK + INTERPRETATION_RGB Interpretation = C.VIPS_INTERPRETATION_RGB + INTERPRETATION_sRGB Interpretation = C.VIPS_INTERPRETATION_sRGB + INTERPRETATION_RGB16 Interpretation = C.VIPS_INTERPRETATION_RGB16 + INTERPRETATION_GREY16 Interpretation = C.VIPS_INTERPRETATION_GREY16 + INTERPRETATION_scRGB Interpretation = C.VIPS_INTERPRETATION_scRGB +) +``` + +#### func ImageInterpretation + +```go +func ImageInterpretation(buf []byte) (Interpretation, error) +``` +Get the image interpretation type See: +http://www.vips.ecs.soton.ac.uk/supported/current/doc/html/libvips/VipsImage.html#VipsInterpretation + #### type Options ```go type Options struct { - Height int - Width int - AreaHeight int - AreaWidth int - Top int - Left int - Extend int - Quality int - Compression int - Zoom int - Crop bool - Enlarge bool - Embed bool - Flip bool - Flop bool - NoAutoRotate bool - NoProfile bool - Interlace bool - Rotate Angle - Gravity Gravity - Watermark Watermark - Type ImageType - Interpolator Interpolator + Height int + Width int + AreaHeight int + AreaWidth int + Top int + Left int + Extend int + Quality int + Compression int + Zoom int + Crop bool + Enlarge bool + Embed bool + Flip bool + Flop bool + NoAutoRotate bool + NoProfile bool + Interlace bool + Rotate Angle + Gravity Gravity + Watermark Watermark + Type ImageType + Interpolator Interpolator + Interpretation Interpretation } ``` diff --git a/fixtures/test_image_colourspace_b_w.jpg b/fixtures/test_image_colourspace_b_w.jpg index 8afd5bc..9618548 100644 Binary files a/fixtures/test_image_colourspace_b_w.jpg and b/fixtures/test_image_colourspace_b_w.jpg differ diff --git a/image.go b/image.go index df41161..c0ae94b 100644 --- a/image.go +++ b/image.go @@ -155,6 +155,17 @@ func (i *Image) Metadata() (ImageMetadata, error) { return Metadata(i.buffer) } +// Get the image interpretation type +// See: http://www.vips.ecs.soton.ac.uk/supported/current/doc/html/libvips/VipsImage.html#VipsInterpretation +func (i *Image) Interpretation() (Interpretation, error) { + return ImageInterpretation(i.buffer) +} + +// Check if the current image has a valid colourspace +func (i *Image) ColourspaceIsSupported() (bool, error) { + return ColourspaceIsSupported(i.buffer) +} + // Get image type format (jpeg, png, webp, tiff) func (i *Image) Type() string { return DetermineImageTypeName(i.buffer) diff --git a/image_test.go b/image_test.go index dc56768..b837dc7 100644 --- a/image_test.go +++ b/image_test.go @@ -262,12 +262,36 @@ func TestImageMetadata(t *testing.T) { } } +func TestInterpretation(t *testing.T) { + interpretation, err := initImage("test.jpg").Interpretation() + if err != nil { + t.Errorf("Cannot process the image: %#v", err) + } + if interpretation != INTERPRETATION_sRGB { + t.Errorf("Invalid interpretation: %d", interpretation) + } +} + func TestImageColourspaceBW(t *testing.T) { - buf, err := initImage("test.jpg").Colourspace(B_W) + buf, err := initImage("test.jpg").Colourspace(INTERPRETATION_B_W) + if err != nil { + t.Errorf("Cannot process the image: %#v", err) + } + + interpretation, err := ImageInterpretation(buf) + if interpretation != INTERPRETATION_B_W { + t.Errorf("Invalid colourspace") + } +} + +func TestImageColourspaceIsSupported(t *testing.T) { + supported, err := initImage("test.jpg").ColourspaceIsSupported() if err != nil { t.Errorf("Cannot process the image: %#v", err) } - Write("fixtures/test_image_colourspace_b_w.jpg", buf) + if supported != true { + t.Errorf("Non-supported colourspace") + } } func TestFluentInterface(t *testing.T) { diff --git a/metadata.go b/metadata.go index a7c6d5a..1ec26de 100644 --- a/metadata.go +++ b/metadata.go @@ -18,6 +18,7 @@ type ImageMetadata struct { Profile bool Type string Space string + Colourspace string Size ImageSize } @@ -34,6 +35,17 @@ func Size(buf []byte) (ImageSize, error) { }, nil } +// Check in the image colourspace is supported by libvips +func ColourspaceIsSupported(buf []byte) (bool, error) { + return vipsColourspaceIsSupportedBuffer(buf) +} + +// Get the image interpretation type +// See: http://www.vips.ecs.soton.ac.uk/supported/current/doc/html/libvips/VipsImage.html#VipsInterpretation +func ImageInterpretation(buf []byte) (Interpretation, error) { + return vipsInterpretationBuffer(buf) +} + // Extract the image metadata (size, type, alpha channel, profile, EXIF orientation...) func Metadata(buf []byte) (ImageMetadata, error) { defer C.vips_thread_shutdown() diff --git a/metadata_test.go b/metadata_test.go index c21bf46..90ab09a 100644 --- a/metadata_test.go +++ b/metadata_test.go @@ -38,9 +38,9 @@ func TestMetadata(t *testing.T) { profile bool space string }{ - {"test.jpg", "jpeg", 0, false, false, "bicubic"}, - {"test.png", "png", 0, true, false, "bicubic"}, - {"test.webp", "webp", 0, false, false, "bicubic"}, + {"test.jpg", "jpeg", 0, false, false, "srgb"}, + {"test.png", "png", 0, true, false, "srgb"}, + {"test.webp", "webp", 0, false, false, "srgb"}, } for _, file := range files { @@ -61,6 +61,58 @@ func TestMetadata(t *testing.T) { if metadata.Profile != file.profile { t.Fatalf("Unexpected image profile: %s != %s", metadata.Profile, file.profile) } + if metadata.Space != file.space { + t.Fatalf("Unexpected image profile: %s != %s", metadata.Profile, file.profile) + } + } +} + +func TestImageInterpretation(t *testing.T) { + files := []struct { + name string + interpretation Interpretation + }{ + {"test.jpg", INTERPRETATION_sRGB}, + {"test.png", INTERPRETATION_sRGB}, + {"test.webp", INTERPRETATION_sRGB}, + } + + for _, file := range files { + interpretation, err := ImageInterpretation(readFile(file.name)) + if err != nil { + t.Fatalf("Cannot read the image: %s -> %s", file.name, err) + } + if interpretation != file.interpretation { + t.Fatalf("Unexpected image interpretation") + } + } +} + +func TestColourspaceIsSupported(t *testing.T) { + files := []struct { + name string + }{ + {"test.jpg"}, + {"test.png"}, + {"test.webp"}, + } + + for _, file := range files { + supported, err := ColourspaceIsSupported(readFile(file.name)) + if err != nil { + t.Fatalf("Cannot read the image: %s -> %s", file.name, err) + } + if supported != true { + t.Fatalf("Unsupported image colourspace") + } + } + + supported, err := initImage("test.jpg").ColourspaceIsSupported() + if err != nil { + t.Errorf("Cannot process the image: %#v", err) + } + if supported != true { + t.Errorf("Non-supported colourspace") } } diff --git a/options.go b/options.go index da7911e..1162335 100644 --- a/options.go +++ b/options.go @@ -55,18 +55,20 @@ const ( VERTICAL Direction = C.VIPS_DIRECTION_VERTICAL ) +// Image interpretation type +// See: http://www.vips.ecs.soton.ac.uk/supported/current/doc/html/libvips/VipsImage.html#VipsInterpretation type Interpretation int const ( - ERROR Interpretation = C.VIPS_INTERPRETATION_ERROR - MULTIBAND Interpretation = C.VIPS_INTERPRETATION_MULTIBAND - B_W Interpretation = C.VIPS_INTERPRETATION_B_W - CMYK Interpretation = C.VIPS_INTERPRETATION_CMYK - RGB Interpretation = C.VIPS_INTERPRETATION_RGB - sRGB Interpretation = C.VIPS_INTERPRETATION_sRGB - RGB16 Interpretation = C.VIPS_INTERPRETATION_RGB16 - GREY16 Interpretation = C.VIPS_INTERPRETATION_GREY16 - scRGB Interpretation = C.VIPS_INTERPRETATION_scRGB + INTERPRETATION_ERROR Interpretation = C.VIPS_INTERPRETATION_ERROR + INTERPRETATION_MULTIBAND Interpretation = C.VIPS_INTERPRETATION_MULTIBAND + INTERPRETATION_B_W Interpretation = C.VIPS_INTERPRETATION_B_W + INTERPRETATION_CMYK Interpretation = C.VIPS_INTERPRETATION_CMYK + INTERPRETATION_RGB Interpretation = C.VIPS_INTERPRETATION_RGB + INTERPRETATION_sRGB Interpretation = C.VIPS_INTERPRETATION_sRGB + INTERPRETATION_RGB16 Interpretation = C.VIPS_INTERPRETATION_RGB16 + INTERPRETATION_GREY16 Interpretation = C.VIPS_INTERPRETATION_GREY16 + INTERPRETATION_scRGB Interpretation = C.VIPS_INTERPRETATION_scRGB ) const WATERMARK_FONT = "sans 10" @@ -88,28 +90,28 @@ type Watermark struct { } type Options struct { - Height int - Width int - AreaHeight int - AreaWidth int - Top int - Left int - Extend int - Quality int - Compression int - Zoom int - Crop bool - Enlarge bool - Embed bool - Flip bool - Flop bool - NoAutoRotate bool - NoProfile bool - Interlace bool - Rotate Angle - Gravity Gravity - Watermark Watermark - Type ImageType - Interpolator Interpolator + Height int + Width int + AreaHeight int + AreaWidth int + Top int + Left int + Extend int + Quality int + Compression int + Zoom int + Crop bool + Enlarge bool + Embed bool + Flip bool + Flop bool + NoAutoRotate bool + NoProfile bool + Interlace bool + Rotate Angle + Gravity Gravity + Watermark Watermark + Type ImageType + Interpolator Interpolator Interpretation Interpretation } diff --git a/resize.go b/resize.go index c71b312..e77a2c1 100644 --- a/resize.go +++ b/resize.go @@ -117,15 +117,15 @@ func Resize(buf []byte, o Options) ([]byte, error) { } saveOptions := vipsSaveOptions{ - Quality: o.Quality, - Type: o.Type, - Compression: o.Compression, - Interlace: o.Interlace, - NoProfile: o.NoProfile, + Quality: o.Quality, + Type: o.Type, + Compression: o.Compression, + Interlace: o.Interlace, + NoProfile: o.NoProfile, Interpretation: o.Interpretation, } - // Finally save as buffer + // Finally get the resultant buffer buf, err = vipsSave(image, saveOptions) if err != nil { return nil, err @@ -145,7 +145,7 @@ func applyDefaults(o *Options, imageType ImageType) { o.Type = imageType } if o.Interpretation == 0 { - o.Interpretation = sRGB + o.Interpretation = INTERPRETATION_sRGB } } diff --git a/vips.go b/vips.go index 90c653c..93be07b 100644 --- a/vips.go +++ b/vips.go @@ -32,11 +32,11 @@ type VipsMemoryInfo struct { } type vipsSaveOptions struct { - Quality int - Compression int - Type ImageType - Interlace bool - NoProfile bool + Quality int + Compression int + Type ImageType + Interlace bool + NoProfile bool Interpretation Interpretation } @@ -224,41 +224,85 @@ func vipsRead(buf []byte) (*C.struct__VipsImage, ImageType, error) { return image, imageType, nil } -func vipsSave(image *C.struct__VipsImage, o vipsSaveOptions) ([]byte, error) { - length := C.size_t(0) - err := C.int(0) - interlace := C.int(boolToInt(o.Interlace)) - if o.Interpretation == 0 { - o.Interpretation = sRGB +func vipsColourspaceIsSupportedBuffer(buf []byte) (bool, error) { + image, _, err := vipsRead(buf) + defer C.g_object_unref(C.gpointer(image)) + if err != nil { + return false, err } - interpretation := C.VipsInterpretation(o.Interpretation) + return vipsColourspaceIsSupported(image), nil +} + +func vipsColourspaceIsSupported(image *C.struct__VipsImage) bool { + return int(C.vips_colourspace_issupported_bridge(image)) == 1 +} +func vipsInterpretationBuffer(buf []byte) (Interpretation, error) { + image, _, err := vipsRead(buf) + defer C.g_object_unref(C.gpointer(image)) + if err != nil { + return Interpretation(-1), err + } + return vipsInterpretation(image), nil +} + +func vipsInterpretation(image *C.struct__VipsImage) Interpretation { + return Interpretation(C.vips_image_guess_interpretation_bridge(image)) +} + +func vipsPreSave(image *C.struct__VipsImage, o *vipsSaveOptions) (*C.struct__VipsImage, error) { // Remove ICC profile metadata if o.NoProfile { C.remove_profile(image) } - // Force RGB color space + // Use a default interpretation and cast it to C type + if o.Interpretation == 0 { + o.Interpretation = INTERPRETATION_sRGB + } + interpretation := C.VipsInterpretation(o.Interpretation) + + // Apply the proper colour space var outImage *C.struct__VipsImage - C.vips_colourspace_bridge(image, &outImage, interpretation) + if vipsColourspaceIsSupported(image) { + err := int(C.vips_colourspace_bridge(image, &outImage, interpretation)) + C.g_object_unref(C.gpointer(image)) + if err != 0 { + return nil, catchVipsError() + } + image = outImage + } + + return image, nil +} +func vipsSave(image *C.struct__VipsImage, o vipsSaveOptions) ([]byte, error) { defer C.g_object_unref(C.gpointer(image)) - defer C.g_object_unref(C.gpointer(outImage)) + + image, err := vipsPreSave(image, &o) + if err != nil { + return nil, err + } + + length := C.size_t(0) + saveErr := C.int(0) + interlace := C.int(boolToInt(o.Interlace)) + quality := C.int(o.Quality) var ptr unsafe.Pointer switch o.Type { - case PNG: - err = C.vips_pngsave_bridge(outImage, &ptr, &length, 1, C.int(o.Compression), C.int(o.Quality), interlace) - break case WEBP: - err = C.vips_webpsave_bridge(outImage, &ptr, &length, 1, C.int(o.Quality)) + saveErr = C.vips_webpsave_bridge(image, &ptr, &length, 1, quality) + break + case PNG: + saveErr = C.vips_pngsave_bridge(image, &ptr, &length, 1, C.int(o.Compression), quality, interlace) break default: - err = C.vips_jpegsave_bridge(outImage, &ptr, &length, 1, C.int(o.Quality), interlace) + saveErr = C.vips_jpegsave_bridge(image, &ptr, &length, 1, quality, interlace) break } - if int(err) != 0 { + if int(saveErr) != 0 { return nil, catchVipsError() } diff --git a/vips.h b/vips.h index 476cabf..1948907 100644 --- a/vips.h +++ b/vips.h @@ -134,6 +134,17 @@ vips_extract_area_bridge(VipsImage *in, VipsImage **out, int left, int top, int return vips_extract_area(in, out, left, top, width, height, NULL); }; +int +vips_colourspace_issupported_bridge(VipsImage *in) +{ + return vips_colourspace_issupported(in) ? 1 : 0; +}; + +VipsInterpretation +vips_image_guess_interpretation_bridge(VipsImage *in) { + return vips_image_guess_interpretation(in); +}; + int vips_colourspace_bridge(VipsImage *in, VipsImage **out, VipsInterpretation space) {