From cabe3e6e16724448ba7eca6510099f0fe5094dbc Mon Sep 17 00:00:00 2001 From: David Byttow Date: Sat, 2 Jan 2021 17:24:25 -0500 Subject: [PATCH 1/2] WIP - Adds format specific export params --- resources/bmp.Decode_BMP-macos.golden.png | Bin 0 -> 25792 bytes .../jpg-24bit.BandJoinConst-macos.golden.jpeg | Bin 0 -> 1240 bytes .../jpg-24bit.Decode_JPG-macos.golden.jpeg | Bin 0 -> 1240 bytes .../jpg-24bit.SimilarityRGB-macos.golden.jpeg | Bin 0 -> 977 bytes ...jpg-24bit.SimilarityRGBA-macos.golden.jpeg | Bin 0 -> 1000 bytes .../jpg-24bit.SmartCrop-macos.golden.jpeg | Bin 0 -> 1195 bytes .../png-8bit.Decode_PNG-macos.golden.png | Bin 0 -> 21134 bytes vips/foreign.c | 221 ++++++++++++------ vips/foreign.go | 40 +++- vips/foreign.h | 33 ++- vips/image.go | 86 +++++++ 11 files changed, 300 insertions(+), 80 deletions(-) create mode 100644 resources/bmp.Decode_BMP-macos.golden.png create mode 100644 resources/jpg-24bit.BandJoinConst-macos.golden.jpeg create mode 100644 resources/jpg-24bit.Decode_JPG-macos.golden.jpeg create mode 100644 resources/jpg-24bit.SimilarityRGB-macos.golden.jpeg create mode 100644 resources/jpg-24bit.SimilarityRGBA-macos.golden.jpeg create mode 100644 resources/jpg-24bit.SmartCrop-macos.golden.jpeg create mode 100644 resources/png-8bit.Decode_PNG-macos.golden.png diff --git a/resources/bmp.Decode_BMP-macos.golden.png b/resources/bmp.Decode_BMP-macos.golden.png new file mode 100644 index 0000000000000000000000000000000000000000..6d398bc20461d5d9d610abe95d7981a994ddd9f7 GIT binary patch literal 25792 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 P00000NkvXXu0mjf?6MF9 literal 0 HcmV?d00001 diff --git a/resources/jpg-24bit.BandJoinConst-macos.golden.jpeg b/resources/jpg-24bit.BandJoinConst-macos.golden.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..71f5420096fdc76cdc47a3493c74dd14cbcc8bb0 GIT binary patch literal 1240 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 Zg^)#TYZqLT&uXHvNU2L(I-`}}{{ddb)|mhR literal 0 HcmV?d00001 diff --git a/resources/jpg-24bit.SmartCrop-macos.golden.jpeg b/resources/jpg-24bit.SmartCrop-macos.golden.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..a897ea06acf21b71e086dd8f5daa3990a69e6d15 GIT binary patch literal 1195 zcmex=~kX3=9lx3_^^|42(b)2um@tg6S|ITN=ua1F|)sYM6j* zVJ3w7%yt&AdZ2+|@L)0v!wY5x1{lr8z%YTGfdwdMU}R{(I1$2T1e(CG04i>1V8FNl zV&eb*3=B*QAST)X^)N%(0T9C({@-SBW?*AsWn*PwV`F7wXJ_N!65!(EpmBM>k!vck<_VrF3! zW)Kt-aR>k!#Q~ON`Xm0{M{PwWcc^!U$;m@?(VS%k{-QrXMY)M9uObC_;8;}W$4o#3#ZF* z%zCk_U}27Q>Vgd6wRLCB%LDVgn)?>`2uG@J4g1;WpBQ}bs$zd~*BQM(+ZVpjxLtYV zGmrbHX;*|+AK9o<`?4YOw7c?@e;EeB%AHBcmmkFgeW45tJqAW*W)@(|g8)WB21P^1 z#KwgJK=&0+JO~Ryd&O^jJpx~6#t9@U@SXd8XXDx`feR}$uYK-6Y}&cd-y%(|kF8&+ zp z5)7lDB7SB1Fz zXRvI!6Ll(l?%}hK+yh#sbiEDXj=m{*AT6+waki{Tp&wiGEwLksUd;Yrp+-|9=wzr))kE literal 0 HcmV?d00001 diff --git a/resources/png-8bit.Decode_PNG-macos.golden.png b/resources/png-8bit.Decode_PNG-macos.golden.png new file mode 100644 index 0000000000000000000000000000000000000000..ac3501f3ac185686cd12a7e8e583cb172f9b5c2c GIT binary patch literal 21134 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 M07*qoM6N<$f=pTUinputImage; + void **buf = params->outputBuffer; + size_t *len = params->outputLen; + + switch (params->outputFormat) + { + case JPEG: + ret = vips_jpegsave_buffer(params->inputImage, params->outputBuffer, params->outputLen, + "strip", INT_TO_GBOOLEAN(params->stripMetadata), + "Q", params->quality, + "optimize_coding", TRUE, + "interlace", INT_TO_GBOOLEAN(params->interlace), + "subsample_mode", VIPS_FOREIGN_JPEG_SUBSAMPLE_ON, + NULL); + break; + case PNG: + ret = vips_pngsave_buffer(in, buf, len, + "strip", INT_TO_GBOOLEAN(params->stripMetadata), + "compression", params->pngCompression, + "interlace", INT_TO_GBOOLEAN(params->interlace), + "filter", VIPS_FOREIGN_PNG_FILTER_NONE, + NULL); + break; + case WEBP: + ret = vips_webpsave_buffer(in, buf, len, + "strip", INT_TO_GBOOLEAN(params->stripMetadata), + "Q", params->quality, + "lossless", INT_TO_GBOOLEAN(params->webpLossless), + "reduction_effort", params->webpReductionEffort, + NULL); + break; + case HEIF: + ret = vips_heifsave_buffer(in, buf, len, + "Q", params->quality, + "lossless", INT_TO_GBOOLEAN(params->heifLossless), + NULL); + break; + case TIFF: + ret = vips_tiffsave_buffer(in, buf, len, + "strip", INT_TO_GBOOLEAN(params->stripMetadata), + "Q", params->quality, + "compression", params->tiffCompression, + "pyramid", FALSE, + "predictor", VIPS_FOREIGN_TIFF_PREDICTOR_HORIZONTAL, + "pyramid", FALSE, + "tile", FALSE, + "tile_height", 256, + "tile_width", 256, + "xres", 1.0, + "yres", 1.0, + NULL); + break; + default: + ret = -1; + } + + return ret; +} \ No newline at end of file diff --git a/vips/foreign.go b/vips/foreign.go index bc775b36..1fbe4509 100644 --- a/vips/foreign.go +++ b/vips/foreign.go @@ -61,6 +61,15 @@ var ImageTypes = map[ImageType]string{ ImageTypeBMP: "bmp", } +// TiffCompression represents method for compressing a tiff at export +type TiffCompression int + +// TiffCompression enum +const ( + TiffCompressionNone TiffCompression = C.VIPS_FOREIGN_TIFF_COMPRESSION_NONE + TiffCompressionLzw TiffCompression = C.VIPS_FOREIGN_TIFF_COMPRESSION_LZW +) + // FileExt returns the canonical extension for the ImageType func (i ImageType) FileExt() string { if ext, ok := imageTypeExtensionMap[i]; ok { @@ -229,18 +238,33 @@ func bmpToPNG(src []byte) ([]byte, error) { func vipsSavePNGToBuffer(in *C.VipsImage, stripMetadata bool, compression int, interlaced bool) ([]byte, error) { incOpCounter("save_png_buffer") - var ptr unsafe.Pointer - cLen := C.size_t(0) + var buf unsafe.Pointer + len := C.size_t(0) + + // strip := C.int(boolToInt(stripMetadata)) + // comp := C.int(compression) + // inter := C.int(boolToInt(interlaced)) + + params := C.struct_SaveAsParams{ + inputImage: in, + outputBuffer: &buf, + outputLen: &len, + stripMetadata: C.int(boolToInt(stripMetadata)), + interlace: C.int(boolToInt(interlaced)), + pngCompression: C.int(compression), + } - strip := C.int(boolToInt(stripMetadata)) - comp := C.int(compression) - inter := C.int(boolToInt(interlaced)) + var ptr = (*C.struct_SaveAsParams)(unsafe.Pointer(¶ms)) - if err := C.save_png_buffer(in, &ptr, &cLen, strip, comp, inter); err != 0 { - return nil, handleSaveBufferError(ptr) + // if err := C.save_png_buffer(in, &ptr, &cLen, strip, comp, inter); err != 0 { + // return nil, handleSaveBufferError(ptr) + // } + + if err := C.save_to_buffer(ptr); err != 0 { + return nil, handleSaveBufferError(buf) } - return toBuff(ptr, cLen), nil + return toBuff(buf, len), nil } func vipsSaveWebPToBuffer(in *C.VipsImage, stripMetadata bool, quality int, lossless bool, effort int) ([]byte, error) { diff --git a/vips/foreign.h b/vips/foreign.h index 2da384fa..7936b2c4 100644 --- a/vips/foreign.h +++ b/vips/foreign.h @@ -4,8 +4,11 @@ #include #include +#ifndef BOOL +#define BOOL int +#endif -enum types { +typedef enum types { UNKNOWN = 0, JPEG, WEBP, @@ -17,7 +20,7 @@ enum types { MAGICK, HEIF, BMP -}; +} ImageType; int load_image_buffer(void *buf, size_t len, int imageType, VipsImage **out); @@ -27,3 +30,29 @@ int save_png_buffer(VipsImage *in, void **buf, size_t *len, int strip, int compr int save_webp_buffer(VipsImage *in, void **buf, size_t *len, int strip, int quality, int lossless, int effort); int save_heif_buffer(VipsImage *in, void **buf, size_t *len, int quality, int lossless); int save_tiff_buffer(VipsImage *in, void **buf, size_t *len, int strip, int quality, int lossless); + +typedef struct SaveAsParams { + VipsImage *inputImage; + void **outputBuffer; + ImageType outputFormat; + size_t *outputLen; + + BOOL stripMetadata; + int quality; + BOOL interlace; + + // PNG + int pngCompression; + + // WEBP + BOOL webpLossless; + int webpReductionEffort; + + // HEIF + BOOL heifLossless; + + // TIFF + VipsForeignTiffCompression tiffCompression; +} SaveAsParams; + +int save_to_buffer(SaveAsParams *params); diff --git a/vips/image.go b/vips/image.go index e8404b95..6bd3d7e1 100644 --- a/vips/image.go +++ b/vips/image.go @@ -44,6 +44,7 @@ type ImageMetadata struct { } // ExportParams are options when exporting an image to file or buffer. +// Deprecated: Use format-specific params type ExportParams struct { Format ImageType Quality int @@ -58,6 +59,7 @@ type ExportParams struct { // By default, govips creates interlaced, lossy images with a quality of 80/100 and compression of 6/10. // As these are default values for a wide variety of image formats, their application varies. // Some formats use the quality parameters, some compression, etc. +// Deprecated: Use format-specific params func NewDefaultExportParams() *ExportParams { return &ExportParams{ Format: ImageTypeUnknown, // defaults to the starting encoder @@ -100,6 +102,85 @@ func NewDefaultWEBPExportParams() *ExportParams { } } +// ExportJpegParams are options when exporting a JPEG to file or buffer +type ExportJpegParams struct { + StripMetadata bool + Quality int + Interlace bool +} + +// NewExportJpegParams creates default values for an export of a JPEG image. +// By default, govips creates interlaced JPEGs with a quality of 80/100. +func NewExportJpegParams() *ExportJpegParams { + return &ExportJpegParams{ + Quality: 80, + Interlace: true, + } +} + +// ExportPngParams are options when exporting a PNG to file or buffer +type ExportPngParams struct { + StripMetadata bool + Compression int + Interlace bool +} + +// NewExportPngParams creates default values for an export of a PNG image. +// By default, govips creates non-interlaced PNGs with a compression of 6/10. +func NewExportPngParams() *ExportPngParams { + return &ExportPngParams{ + Compression: 6, + Interlace: false, + } +} + +// ExportWebpParams are options when exporting a WEBP to file or buffer +type ExportWebpParams struct { + StripMetadata bool + Quality int + Lossless bool + ReductionEffort int +} + +// NewExportWebpParams creates default values for an export of a WEBP image. +// By default, govips creates lossy images with a quality of 75/100. +func NewExportWebpParams() *ExportWebpParams { + return &ExportWebpParams{ + Quality: 75, + Lossless: false, + ReductionEffort: 4, + } +} + +// ExportHeifParams are options when exporting a HEIF to file or buffer +type ExportHeifParams struct { + Quality int + Lossless bool +} + +// NewExportHeifParams creates default values for an export of a HEIF image. +func NewExportHeifParams() *ExportHeifParams { + return &ExportHeifParams{ + Quality: 80, + Lossless: false, + } +} + +// ExportTiffParams are options when exporting a TIFF to file or buffer +type ExportTiffParams struct { + StripMetadata bool + Quality int + Compression TiffCompression +} + +// NewExportTiffParams creates default values for an export of a TIFF image. +func NewExportTiffParams() *ExportTiffParams { + return &ExportTiffParams{ + Quality: 80, + Compression: TiffCompressionLzw, + } +} + // NewImageFromReader loads an ImageRef from the given reader func NewImageFromReader(r io.Reader) (*ImageRef, error) { buf, err := ioutil.ReadAll(r) @@ -936,6 +1017,11 @@ func (r *ImageRef) exportBuffer(params *ExportParams) ([]byte, ImageType, error) return buf, format, nil } +// func (r *ImageRef) exportJpegBuffer(params *ExportJpegParams) ([]byte, ImageType, error) { +// var buf []byte +// var err error +// } + /////////////// func vipsHasAlpha(in *C.VipsImage) bool { From b2f6b219e94b3ff8c49df0a731813c6cbe8ce8e0 Mon Sep 17 00:00:00 2001 From: David Byttow Date: Tue, 5 Jan 2021 20:49:18 -0500 Subject: [PATCH 2/2] Adds additional methods and cleans up export --- vips/foreign.c | 165 ++++++++----------------- vips/foreign.go | 115 +++++++----------- vips/foreign.h | 17 +-- vips/image.go | 250 +++++++++++++++++++++++++------------- vips/image_golden_test.go | 3 +- vips/image_test.go | 3 +- 6 files changed, 274 insertions(+), 279 deletions(-) diff --git a/vips/foreign.c b/vips/foreign.c index 7aebac58..e8d540c6 100644 --- a/vips/foreign.c +++ b/vips/foreign.c @@ -1,46 +1,28 @@ #include "lang.h" #include "foreign.h" -int load_image_buffer(void *buf, size_t len, int imageType, VipsImage **out) -{ +int load_image_buffer(void *buf, size_t len, int imageType, VipsImage **out) { int code = 1; - if (imageType == JPEG) - { + if (imageType == JPEG) { code = vips_jpegload_buffer(buf, len, out, NULL); - } - else if (imageType == PNG) - { + } else if (imageType == PNG) { code = vips_pngload_buffer(buf, len, out, NULL); - } - else if (imageType == WEBP) - { + } else if (imageType == WEBP) { code = vips_webpload_buffer(buf, len, out, NULL); - } - else if (imageType == TIFF) - { + } else if (imageType == TIFF) { code = vips_tiffload_buffer(buf, len, out, NULL); - } - else if (imageType == GIF) - { + } else if (imageType == GIF) { code = vips_gifload_buffer(buf, len, out, NULL); - } - else if (imageType == PDF) - { + } else if (imageType == PDF) { code = vips_pdfload_buffer(buf, len, out, NULL); - } - else if (imageType == SVG) - { + } else if (imageType == SVG) { code = vips_svgload_buffer(buf, len, out, NULL); - } - else if (imageType == HEIF) - { + } else if (imageType == HEIF) { // added autorotate on load as currently it addresses orientation issues // https://github.com/libvips/libvips/pull/1680 code = vips_heifload_buffer(buf, len, out, "autorotate", TRUE, NULL); - } - else if (imageType == MAGICK) - { + } else if (imageType == MAGICK) { code = vips_magickload_buffer(buf, len, out, NULL); } @@ -48,24 +30,26 @@ int load_image_buffer(void *buf, size_t len, int imageType, VipsImage **out) } // https://libvips.github.io/libvips/API/current/VipsForeignSave.html#vips-jpegsave-buffer -int save_jpeg_buffer(VipsImage *in, void **buf, size_t *len, int strip, int quality, int interlace) -{ - return vips_jpegsave_buffer(in, buf, len, - "strip", INT_TO_GBOOLEAN(strip), - "Q", quality, +int save_jpeg_buffer(SaveParams *params) { + return vips_jpegsave_buffer(params->inputImage, + ¶ms->outputBuffer, + ¶ms->outputLen, + "strip", INT_TO_GBOOLEAN(params->stripMetadata), + "Q", params->quality, "optimize_coding", TRUE, - "interlace", INT_TO_GBOOLEAN(interlace), + "interlace", INT_TO_GBOOLEAN(params->interlace), "subsample_mode", VIPS_FOREIGN_JPEG_SUBSAMPLE_ON, NULL); } // https://libvips.github.io/libvips/API/current/VipsForeignSave.html#vips-pngsave-buffer -int save_png_buffer(VipsImage *in, void **buf, size_t *len, int strip, int compression, int interlace) -{ - return vips_pngsave_buffer(in, buf, len, - "strip", INT_TO_GBOOLEAN(strip), - "compression", compression, - "interlace", INT_TO_GBOOLEAN(interlace), +int save_png_buffer(SaveParams *params) { + return vips_pngsave_buffer(params->inputImage, + ¶ms->outputBuffer, + ¶ms->outputLen, + "strip", INT_TO_GBOOLEAN(params->stripMetadata), + "compression", params->pngCompression, + "interlace", INT_TO_GBOOLEAN(params->interlace), "filter", VIPS_FOREIGN_PNG_FILTER_NONE, NULL); } @@ -73,34 +57,36 @@ int save_png_buffer(VipsImage *in, void **buf, size_t *len, int strip, int compr // todo: support additional params // https://github.com/libvips/libvips/blob/master/libvips/foreign/webpsave.c#L524 // https://libvips.github.io/libvips/API/current/VipsForeignSave.html#vips-webpsave-buffer -int save_webp_buffer(VipsImage *in, void **buf, size_t *len, int strip, int quality, int lossless, int effort) -{ - return vips_webpsave_buffer(in, buf, len, - "strip", INT_TO_GBOOLEAN(strip), - "Q", quality, - "lossless", INT_TO_GBOOLEAN(lossless), - "reduction_effort", effort, +int save_webp_buffer(SaveParams *params) { + return vips_webpsave_buffer(params->inputImage, + ¶ms->outputBuffer, + ¶ms->outputLen, + "strip", INT_TO_GBOOLEAN(params->stripMetadata), + "Q", params->quality, + "lossless", INT_TO_GBOOLEAN(params->webpLossless), + "reduction_effort", params->webpReductionEffort, NULL); } // todo: support additional params // https://github.com/libvips/libvips/blob/master/libvips/foreign/heifsave.c#L653 -int save_heif_buffer(VipsImage *in, void **buf, size_t *len, int quality, int lossless) -{ - return vips_heifsave_buffer(in, buf, len, - "Q", quality, - "lossless", INT_TO_GBOOLEAN(lossless), +int save_heif_buffer(SaveParams *params) { + return vips_heifsave_buffer(params->inputImage, + ¶ms->outputBuffer, + ¶ms->outputLen, + "Q", params->quality, + "lossless", INT_TO_GBOOLEAN(params->heifLossless), NULL); } // https://libvips.github.io/libvips/API/current/VipsForeignSave.html#vips-tiffsave-buffer -int save_tiff_buffer(VipsImage *in, void **buf, size_t *len, int strip, int quality, int lossless) -{ - // TODO: Allow various options to be passed in. - return vips_tiffsave_buffer(in, buf, len, - "strip", INT_TO_GBOOLEAN(strip), - "Q", quality, - "compression", lossless ? VIPS_FOREIGN_TIFF_COMPRESSION_NONE : VIPS_FOREIGN_TIFF_COMPRESSION_LZW, +int save_tiff_buffer(SaveParams *params) { + return vips_tiffsave_buffer(params->inputImage, + ¶ms->outputBuffer, + ¶ms->outputLen, + "strip", INT_TO_GBOOLEAN(params->stripMetadata), + "Q", params->quality, + "compression", params->tiffCompression, "pyramid", FALSE, "predictor", VIPS_FOREIGN_TIFF_PREDICTOR_HORIZONTAL, "pyramid", FALSE, @@ -112,65 +98,22 @@ int save_tiff_buffer(VipsImage *in, void **buf, size_t *len, int strip, int qual NULL); } -int save_to_buffer(SaveAsParams *params) -{ - return 0; - int ret; - VipsImage *in = params->inputImage; - void **buf = params->outputBuffer; - size_t *len = params->outputLen; - +int save_to_buffer(SaveParams *params) { switch (params->outputFormat) { case JPEG: - ret = vips_jpegsave_buffer(params->inputImage, params->outputBuffer, params->outputLen, - "strip", INT_TO_GBOOLEAN(params->stripMetadata), - "Q", params->quality, - "optimize_coding", TRUE, - "interlace", INT_TO_GBOOLEAN(params->interlace), - "subsample_mode", VIPS_FOREIGN_JPEG_SUBSAMPLE_ON, - NULL); - break; + return save_jpeg_buffer(params); case PNG: - ret = vips_pngsave_buffer(in, buf, len, - "strip", INT_TO_GBOOLEAN(params->stripMetadata), - "compression", params->pngCompression, - "interlace", INT_TO_GBOOLEAN(params->interlace), - "filter", VIPS_FOREIGN_PNG_FILTER_NONE, - NULL); - break; + return save_png_buffer(params); case WEBP: - ret = vips_webpsave_buffer(in, buf, len, - "strip", INT_TO_GBOOLEAN(params->stripMetadata), - "Q", params->quality, - "lossless", INT_TO_GBOOLEAN(params->webpLossless), - "reduction_effort", params->webpReductionEffort, - NULL); - break; + return save_webp_buffer(params); case HEIF: - ret = vips_heifsave_buffer(in, buf, len, - "Q", params->quality, - "lossless", INT_TO_GBOOLEAN(params->heifLossless), - NULL); - break; + return save_heif_buffer(params); case TIFF: - ret = vips_tiffsave_buffer(in, buf, len, - "strip", INT_TO_GBOOLEAN(params->stripMetadata), - "Q", params->quality, - "compression", params->tiffCompression, - "pyramid", FALSE, - "predictor", VIPS_FOREIGN_TIFF_PREDICTOR_HORIZONTAL, - "pyramid", FALSE, - "tile", FALSE, - "tile_height", 256, - "tile_width", 256, - "xres", 1.0, - "yres", 1.0, - NULL); - break; + return save_tiff_buffer(params); default: - ret = -1; + g_warning("Unsupported output type given: %d", params->outputFormat); + return -1; } +} - return ret; -} \ No newline at end of file diff --git a/vips/foreign.go b/vips/foreign.go index 1fbe4509..948d3d3f 100644 --- a/vips/foreign.go +++ b/vips/foreign.go @@ -236,104 +236,83 @@ func bmpToPNG(src []byte) ([]byte, error) { return w.Bytes(), nil } -func vipsSavePNGToBuffer(in *C.VipsImage, stripMetadata bool, compression int, interlaced bool) ([]byte, error) { +func vipsSavePNGToBuffer(in *C.VipsImage, params PngExportParams) ([]byte, error) { incOpCounter("save_png_buffer") - var buf unsafe.Pointer - len := C.size_t(0) - // strip := C.int(boolToInt(stripMetadata)) - // comp := C.int(compression) - // inter := C.int(boolToInt(interlaced)) - - params := C.struct_SaveAsParams{ + p := C.struct_SaveParams{ inputImage: in, - outputBuffer: &buf, - outputLen: &len, - stripMetadata: C.int(boolToInt(stripMetadata)), - interlace: C.int(boolToInt(interlaced)), - pngCompression: C.int(compression), - } - - var ptr = (*C.struct_SaveAsParams)(unsafe.Pointer(¶ms)) - - // if err := C.save_png_buffer(in, &ptr, &cLen, strip, comp, inter); err != 0 { - // return nil, handleSaveBufferError(ptr) - // } - - if err := C.save_to_buffer(ptr); err != 0 { - return nil, handleSaveBufferError(buf) + outputFormat: C.PNG, + stripMetadata: C.int(boolToInt(params.StripMetadata)), + interlace: C.int(boolToInt(params.Interlace)), + pngCompression: C.int(params.Compression), } - return toBuff(buf, len), nil + return vipsSaveToBuffer(p) } -func vipsSaveWebPToBuffer(in *C.VipsImage, stripMetadata bool, quality int, lossless bool, effort int) ([]byte, error) { +func vipsSaveWebPToBuffer(in *C.VipsImage, params WebpExportParams) ([]byte, error) { incOpCounter("save_webp_buffer") - var ptr unsafe.Pointer - cLen := C.size_t(0) - - strip := C.int(boolToInt(stripMetadata)) - qual := C.int(quality) - loss := C.int(boolToInt(lossless)) - eff := C.int(effort) - if err := C.save_webp_buffer(in, &ptr, &cLen, strip, qual, loss, eff); err != 0 { - return nil, handleSaveBufferError(ptr) + p := C.struct_SaveParams{ + inputImage: in, + outputFormat: C.WEBP, + stripMetadata: C.int(boolToInt(params.StripMetadata)), + quality: C.int(params.Quality), + webpLossless: C.int(boolToInt(params.Lossless)), + webpReductionEffort: C.int(params.ReductionEffort), } - return toBuff(ptr, cLen), nil + return vipsSaveToBuffer(p) } -func vipsSaveTIFFToBuffer(in *C.VipsImage, stripMetadata bool, quality int, lossless bool) ([]byte, error) { +func vipsSaveTIFFToBuffer(in *C.VipsImage, params TiffExportParams) ([]byte, error) { incOpCounter("save_tiff_buffer") - var ptr unsafe.Pointer - cLen := C.size_t(0) - strip := C.int(boolToInt(stripMetadata)) - qual := C.int(quality) - loss := C.int(boolToInt(lossless)) - - if err := C.save_tiff_buffer(in, &ptr, &cLen, strip, qual, loss); err != 0 { - return nil, handleSaveBufferError(ptr) + p := C.struct_SaveParams{ + inputImage: in, + outputFormat: C.TIFF, + stripMetadata: C.int(boolToInt(params.StripMetadata)), + quality: C.int(params.Quality), + tiffCompression: C.VipsForeignTiffCompression(params.Compression), } - return toBuff(ptr, cLen), nil + return vipsSaveToBuffer(p) } -func vipsSaveHEIFToBuffer(in *C.VipsImage, quality int, lossless bool) ([]byte, error) { +func vipsSaveHEIFToBuffer(in *C.VipsImage, params HeifExportParams) ([]byte, error) { incOpCounter("save_heif_buffer") - var ptr unsafe.Pointer - cLen := C.size_t(0) - - qual := C.int(quality) - loss := C.int(boolToInt(lossless)) - if err := C.save_heif_buffer(in, &ptr, &cLen, qual, loss); err != 0 { - return nil, handleSaveBufferError(ptr) + p := C.struct_SaveParams{ + inputImage: in, + outputFormat: C.HEIF, + quality: C.int(params.Quality), + heifLossless: C.int(boolToInt(params.Lossless)), } - return toBuff(ptr, cLen), nil + return vipsSaveToBuffer(p) } -func vipsSaveJPEGToBuffer(in *C.VipsImage, quality int, stripMetadata, interlaced bool) ([]byte, error) { +func vipsSaveJPEGToBuffer(in *C.VipsImage, params JpegExportParams) ([]byte, error) { incOpCounter("save_jpeg_buffer") - var ptr unsafe.Pointer - cLen := C.size_t(0) - - strip := C.int(boolToInt(stripMetadata)) - qual := C.int(quality) - inter := C.int(boolToInt(interlaced)) - if err := C.save_jpeg_buffer(in, &ptr, &cLen, strip, qual, inter); err != 0 { - return nil, handleSaveBufferError(ptr) + p := C.struct_SaveParams{ + inputImage: in, + outputFormat: C.JPEG, + stripMetadata: C.int(boolToInt(params.StripMetadata)), + quality: C.int(params.Quality), + interlace: C.int(boolToInt(params.Interlace)), } - return toBuff(ptr, cLen), nil + return vipsSaveToBuffer(p) } -func toBuff(ptr unsafe.Pointer, cLen C.size_t) []byte { - buf := C.GoBytes(ptr, C.int(cLen)) - gFreePointer(ptr) +func vipsSaveToBuffer(params C.struct_SaveParams) ([]byte, error) { + if err := C.save_to_buffer(¶ms); err != 0 { + return nil, handleSaveBufferError(params.outputBuffer) + } + + buf := C.GoBytes(params.outputBuffer, C.int(params.outputLen)) + defer gFreePointer(params.outputBuffer) - return buf + return buf, nil } diff --git a/vips/foreign.h b/vips/foreign.h index 7936b2c4..5e9337cc 100644 --- a/vips/foreign.h +++ b/vips/foreign.h @@ -24,18 +24,11 @@ typedef enum types { int load_image_buffer(void *buf, size_t len, int imageType, VipsImage **out); -// TODO: Pass options as discrete params objects based on types rather than long function signatures -int save_jpeg_buffer(VipsImage* image, void **buf, size_t *len, int strip, int quality, int interlace); -int save_png_buffer(VipsImage *in, void **buf, size_t *len, int strip, int compression, int interlace); -int save_webp_buffer(VipsImage *in, void **buf, size_t *len, int strip, int quality, int lossless, int effort); -int save_heif_buffer(VipsImage *in, void **buf, size_t *len, int quality, int lossless); -int save_tiff_buffer(VipsImage *in, void **buf, size_t *len, int strip, int quality, int lossless); - -typedef struct SaveAsParams { +typedef struct SaveParams { VipsImage *inputImage; - void **outputBuffer; + void *outputBuffer; ImageType outputFormat; - size_t *outputLen; + size_t outputLen; BOOL stripMetadata; int quality; @@ -53,6 +46,6 @@ typedef struct SaveAsParams { // TIFF VipsForeignTiffCompression tiffCompression; -} SaveAsParams; +} SaveParams; -int save_to_buffer(SaveAsParams *params); +int save_to_buffer(SaveParams *params); diff --git a/vips/image.go b/vips/image.go index 6bd3d7e1..02bc51db 100644 --- a/vips/image.go +++ b/vips/image.go @@ -73,6 +73,7 @@ func NewDefaultExportParams() *ExportParams { // NewDefaultJPEGExportParams creates default values for an export of a JPEG image. // By default, govips creates interlaced JPEGs with a quality of 80/100. +// Deprecated: Use NewJpegExportParams func NewDefaultJPEGExportParams() *ExportParams { return &ExportParams{ Format: ImageTypeJPEG, @@ -83,6 +84,7 @@ func NewDefaultJPEGExportParams() *ExportParams { // NewDefaultPNGExportParams creates default values for an export of a PNG image. // By default, govips creates non-interlaced PNGs with a compression of 6/10. +// Deprecated: Use NewPngExportParams func NewDefaultPNGExportParams() *ExportParams { return &ExportParams{ Format: ImageTypePNG, @@ -93,6 +95,7 @@ func NewDefaultPNGExportParams() *ExportParams { // NewDefaultWEBPExportParams creates default values for an export of a WEBP image. // By default, govips creates lossy images with a quality of 75/100. +// Deprecated: Use NewWebpExportParams func NewDefaultWEBPExportParams() *ExportParams { return &ExportParams{ Format: ImageTypeWEBP, @@ -102,80 +105,80 @@ func NewDefaultWEBPExportParams() *ExportParams { } } -// ExportJpegParams are options when exporting a JPEG to file or buffer -type ExportJpegParams struct { +// JpegExportParams are options when exporting a JPEG to file or buffer +type JpegExportParams struct { StripMetadata bool Quality int Interlace bool } -// NewExportJpegParams creates default values for an export of a JPEG image. +// NewJpegExportParams creates default values for an export of a JPEG image. // By default, govips creates interlaced JPEGs with a quality of 80/100. -func NewExportJpegParams() *ExportJpegParams { - return &ExportJpegParams{ +func NewJpegExportParams() *JpegExportParams { + return &JpegExportParams{ Quality: 80, Interlace: true, } } -// ExportPngParams are options when exporting a PNG to file or buffer -type ExportPngParams struct { +// PngExportParams are options when exporting a PNG to file or buffer +type PngExportParams struct { StripMetadata bool Compression int Interlace bool } -// NewExportPngParams creates default values for an export of a PNG image. +// NewPngExportParams creates default values for an export of a PNG image. // By default, govips creates non-interlaced PNGs with a compression of 6/10. -func NewExportPngParams() *ExportPngParams { - return &ExportPngParams{ +func NewPngExportParams() *PngExportParams { + return &PngExportParams{ Compression: 6, Interlace: false, } } -// ExportWebpParams are options when exporting a WEBP to file or buffer -type ExportWebpParams struct { +// WebpExportParams are options when exporting a WEBP to file or buffer +type WebpExportParams struct { StripMetadata bool Quality int Lossless bool ReductionEffort int } -// NewExportWebpParams creates default values for an export of a WEBP image. +// NewWebpExportParams creates default values for an export of a WEBP image. // By default, govips creates lossy images with a quality of 75/100. -func NewExportWebpParams() *ExportWebpParams { - return &ExportWebpParams{ +func NewWebpExportParams() *WebpExportParams { + return &WebpExportParams{ Quality: 75, Lossless: false, ReductionEffort: 4, } } -// ExportHeifParams are options when exporting a HEIF to file or buffer -type ExportHeifParams struct { +// HeifExportParams are options when exporting a HEIF to file or buffer +type HeifExportParams struct { Quality int Lossless bool } -// NewExportHeifParams creates default values for an export of a HEIF image. -func NewExportHeifParams() *ExportHeifParams { - return &ExportHeifParams{ +// NewHeifExportParams creates default values for an export of a HEIF image. +func NewHeifExportParams() *HeifExportParams { + return &HeifExportParams{ Quality: 80, Lossless: false, } } -// ExportTiffParams are options when exporting a TIFF to file or buffer -type ExportTiffParams struct { +// TiffExportParams are options when exporting a TIFF to file or buffer +type TiffExportParams struct { StripMetadata bool Quality int Compression TiffCompression } -// NewExportTiffParams creates default values for an export of a TIFF image. -func NewExportTiffParams() *ExportTiffParams { - return &ExportTiffParams{ +// NewTiffExportParams creates default values for an export of a TIFF image. +func NewTiffExportParams() *TiffExportParams { + return &TiffExportParams{ Quality: 80, Compression: TiffCompressionLzw, } @@ -395,44 +398,157 @@ func (r *ImageRef) IsColorSpaceSupported() bool { return vipsIsColorSpaceSupported(r.image) } +func (r *ImageRef) newMetadata(format ImageType) *ImageMetadata { + return &ImageMetadata{ + Format: format, + Width: r.Width(), + Height: r.Height(), + Colorspace: r.ColorSpace(), + Orientation: r.GetOrientation(), + } +} + // Export creates a byte array of the image for use. // The function returns a byte array that can be written to a file e.g. via ioutil.WriteFile(). // N.B. govips does not currently have built-in support for directly exporting to a file. // The function also returns a copy of the image metadata as well as an error. +// Deprecated: Use ExportNative or format-specific Export methods func (r *ImageRef) Export(params *ExportParams) ([]byte, *ImageMetadata, error) { - p := params - if p == nil { - switch r.format { - case ImageTypeJPEG: - p = NewDefaultJPEGExportParams() - case ImageTypePNG: - p = NewDefaultPNGExportParams() - case ImageTypeWEBP: - p = NewDefaultWEBPExportParams() - default: - p = NewDefaultExportParams() + if params == nil || params.Format == ImageTypeUnknown { + return r.ExportNative() + } + + format := params.Format + + if !IsTypeSupported(format) { + return nil, r.newMetadata(ImageTypeUnknown), fmt.Errorf("cannot save to %#v", ImageTypes[format]) + } + + switch format { + case ImageTypeWEBP: + return r.ExportWebp(&WebpExportParams{ + StripMetadata: params.StripMetadata, + Quality: params.Quality, + Lossless: params.Lossless, + ReductionEffort: params.Effort, + }) + case ImageTypePNG: + return r.ExportPng(&PngExportParams{ + StripMetadata: params.StripMetadata, + Compression: params.Compression, + Interlace: params.Interlaced, + }) + case ImageTypeTIFF: + compression := TiffCompressionLzw + if params.Lossless { + compression = TiffCompressionNone } + return r.ExportTiff(&TiffExportParams{ + StripMetadata: params.StripMetadata, + Quality: params.Quality, + Compression: compression, + }) + case ImageTypeHEIF: + return r.ExportHeif(&HeifExportParams{ + Quality: params.Quality, + Lossless: params.Lossless, + }) + default: + format = ImageTypeJPEG + return r.ExportJpeg(&JpegExportParams{ + Quality: params.Quality, + StripMetadata: params.StripMetadata, + Interlace: params.Interlaced, + }) } +} - if p.Format == ImageTypeUnknown { - p.Format = r.format +// ExportNative exports the image to a buffer based on its native format with default parameters. +func (r *ImageRef) ExportNative() ([]byte, *ImageMetadata, error) { + switch r.format { + case ImageTypeJPEG: + return r.ExportJpeg(NewJpegExportParams()) + case ImageTypePNG: + return r.ExportPng(NewPngExportParams()) + case ImageTypeWEBP: + return r.ExportWebp(NewWebpExportParams()) + case ImageTypeHEIF: + return r.ExportHeif(NewHeifExportParams()) + case ImageTypeTIFF: + return r.ExportTiff(NewTiffExportParams()) + default: + return r.ExportJpeg(NewJpegExportParams()) + } +} + +// ExportJpeg exports the image as JPEG to a buffer. +func (r *ImageRef) ExportJpeg(params *JpegExportParams) ([]byte, *ImageMetadata, error) { + if params == nil { + params = NewJpegExportParams() } - // the exported buf is not necessarily in same format as the original buf, might default to JPEG as well. - buf, format, err := r.exportBuffer(p) + buf, err := vipsSaveJPEGToBuffer(r.image, *params) if err != nil { return nil, nil, err } - metadata := &ImageMetadata{ - Format: format, - Width: r.Width(), - Height: r.Height(), - Colorspace: r.ColorSpace(), - Orientation: r.GetOrientation(), + return buf, r.newMetadata(ImageTypeJPEG), nil +} + +// ExportPng exports the image as PNG to a buffer. +func (r *ImageRef) ExportPng(params *PngExportParams) ([]byte, *ImageMetadata, error) { + if params == nil { + params = NewPngExportParams() + } + + buf, err := vipsSavePNGToBuffer(r.image, *params) + if err != nil { + return nil, nil, err + } + + return buf, r.newMetadata(ImageTypePNG), nil +} + +// ExportWebp exports the image as WEBP to a buffer. +func (r *ImageRef) ExportWebp(params *WebpExportParams) ([]byte, *ImageMetadata, error) { + if params == nil { + params = NewWebpExportParams() + } + + buf, err := vipsSaveWebPToBuffer(r.image, *params) + if err != nil { + return nil, nil, err + } + + return buf, r.newMetadata(ImageTypeWEBP), nil +} + +// ExportHeif exports the image as HEIF to a buffer. +func (r *ImageRef) ExportHeif(params *HeifExportParams) ([]byte, *ImageMetadata, error) { + if params == nil { + params = NewHeifExportParams() + } + + buf, err := vipsSaveHEIFToBuffer(r.image, *params) + if err != nil { + return nil, nil, err + } + + return buf, r.newMetadata(ImageTypeHEIF), nil +} + +// ExportTiff exports the image as TIFF to a buffer. +func (r *ImageRef) ExportTiff(params *TiffExportParams) ([]byte, *ImageMetadata, error) { + if params == nil { + params = NewTiffExportParams() + } + + buf, err := vipsSaveTIFFToBuffer(r.image, *params) + if err != nil { + return nil, nil, err } - return buf, metadata, nil + return buf, r.newMetadata(ImageTypeTIFF), nil } // CompositeMulti composites the given overlay image on top of the associated image with provided blending mode. @@ -923,6 +1039,7 @@ func (r *ImageRef) Similarity(scale float64, angle float64, backgroundColor *Col return nil } +// SmartCrop will crop the image based on interestingness factor func (r *ImageRef) SmartCrop(width int, height int, interesting Interesting) error { out, err := vipsSmartCrop(r.image, width, height, interesting) if err != nil { @@ -987,49 +1104,10 @@ func (r *ImageRef) setImage(image *C.VipsImage) { r.image = image } -func (r *ImageRef) exportBuffer(params *ExportParams) ([]byte, ImageType, error) { - var buf []byte - var err error - - format := params.Format - if format != ImageTypeUnknown && !IsTypeSupported(format) { - return nil, ImageTypeUnknown, fmt.Errorf("cannot save to %#v", ImageTypes[format]) - } - - switch format { - case ImageTypeWEBP: - buf, err = vipsSaveWebPToBuffer(r.image, params.StripMetadata, params.Quality, params.Lossless, params.Effort) - case ImageTypePNG: - buf, err = vipsSavePNGToBuffer(r.image, params.StripMetadata, params.Compression, params.Interlaced) - case ImageTypeTIFF: - buf, err = vipsSaveTIFFToBuffer(r.image, params.StripMetadata, params.Quality, params.Lossless) - case ImageTypeHEIF: - buf, err = vipsSaveHEIFToBuffer(r.image, params.Quality, params.Lossless) - default: - format = ImageTypeJPEG - buf, err = vipsSaveJPEGToBuffer(r.image, params.Quality, params.StripMetadata, params.Interlaced) - } - - if err != nil { - return nil, ImageTypeUnknown, err - } - - return buf, format, nil -} - -// func (r *ImageRef) exportJpegBuffer(params *ExportJpegParams) ([]byte, ImageType, error) { -// var buf []byte -// var err error -// } - -/////////////// - func vipsHasAlpha(in *C.VipsImage) bool { return int(C.has_alpha_channel(in)) > 0 } -////////////// - func clearImage(ref *C.VipsImage) { C.clear_image(&ref) } diff --git a/vips/image_golden_test.go b/vips/image_golden_test.go index 2995a2b5..c25ee7af 100644 --- a/vips/image_golden_test.go +++ b/vips/image_golden_test.go @@ -2,7 +2,6 @@ package vips import ( "bytes" - "golang.org/x/image/bmp" "image" jpeg2 "image/jpeg" "image/png" @@ -12,6 +11,8 @@ import ( "strings" "testing" + "golang.org/x/image/bmp" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/vips/image_test.go b/vips/image_test.go index dfaf2cda..a47dcb42 100644 --- a/vips/image_test.go +++ b/vips/image_test.go @@ -123,9 +123,10 @@ func TestImageRef_BMP(t *testing.T) { require.NoError(t, err) require.NotNil(t, img) - _, metadata, err := img.Export(nil) + exported, metadata, err := img.Export(nil) assert.NoError(t, err) assert.Equal(t, ImageTypePNG, metadata.Format) + assert.NotNil(t, exported) } func TestImageRef_SVG(t *testing.T) {