From 90376b7261797c689da3bafcb64ac7a3d327d8ae Mon Sep 17 00:00:00 2001 From: Victor Sokolov Date: Fri, 11 Jul 2025 17:07:39 +0400 Subject: [PATCH] IMG-28: Save bmp via target (#1452) * RLE fixes * disk_sink + fixes * Always 4 bands for v3+ bmp on save * bmpsave fixed (w/o orientation) * fixed ico for target save * cancel() comment * No orientation anymore * No orientation * Fixed non-alpha save bug * Fixed rebase artifact * Fixed condition --- imagetype/imagetype.go | 2 +- vips/bmp.h | 3 + vips/bmpsave.c | 309 +++++++++++++++++++++++++++++++++++++++++ vips/ico.go | 10 +- vips/vips.c | 57 ++++---- vips/vips.go | 47 ++++--- vips/vips.h | 17 +-- 7 files changed, 388 insertions(+), 57 deletions(-) create mode 100644 vips/bmpsave.c diff --git a/imagetype/imagetype.go b/imagetype/imagetype.go index c03e962f..b726d7ca 100644 --- a/imagetype/imagetype.go +++ b/imagetype/imagetype.go @@ -144,7 +144,7 @@ func (it Type) IsVector() bool { } func (it Type) SupportsAlpha() bool { - return it != JPEG && it != BMP + return it != JPEG } func (it Type) SupportsAnimationLoad() bool { diff --git a/vips/bmp.h b/vips/bmp.h index 26355eec..c46fe58d 100644 --- a/vips/bmp.h +++ b/vips/bmp.h @@ -27,4 +27,7 @@ int vips_bmpload_source(VipsSource *source, VipsImage **out, ...) G_GNUC_NULL_TERMINATED; int vips_bmpload_source_go(VipsImgproxySource *source, VipsImage **out); +// defined in bmpsave.c +int vips_bmpsave_target_go(VipsImage *in, VipsTarget *target); + #endif diff --git a/vips/bmpsave.c b/vips/bmpsave.c new file mode 100644 index 00000000..cf9a2bde --- /dev/null +++ b/vips/bmpsave.c @@ -0,0 +1,309 @@ +// BMP saver + +#include "vips.h" + +#include +#include +#include + +/* Save a bit of typing. + */ +#define UC VIPS_FORMAT_UCHAR + +static VipsBandFormat bandfmt_bmp[10] = { + /* Band format: UC C US S UI I F X D DX */ + /* Promotion: */ UC, UC, UC, UC, UC, UC, UC, UC, UC, UC +}; + +// BMP BITMAPINFOHEADERV5 file header struct, ((packed)) since we +// do not want any compiler-induced padding +typedef struct __attribute__((packed)) _BmpHeader { + uint8_t sig[2]; // Signature 'BM' + uint32_t file_size; // File size in bytes + uint16_t reserved[2]; // Reserved fields + uint32_t pix_offset; // Offset to pixel data + uint32_t dib_header_size; // DIB header size + int32_t width; // Image width + int32_t height; // Image height + uint16_t color_plane; // Number of color planes + uint16_t bpp; // Bits per pixel + uint32_t compression; // Compression method + uint32_t image_size; // Image size + uint32_t x_pixels_per_meter; // Horizontal resolution + uint32_t y_pixels_per_meter; // Vertical resolution + uint32_t color_use; // Number of colors in palette + uint32_t color_important; // Number of important colors + uint32_t rmask; // Red mask + uint32_t gmask; // Green mask + uint32_t bmask; // Blue mask + uint32_t amask; // Alpha mask (optional, only for 32 bpp BMP files) + uint8_t cs_type[4]; // Color space type (B G R s) + uint8_t cs[36]; // CIEXYZTRIPLE Color Space + uint32_t red_gamma; // Red gamma + uint32_t green_gamma; // Green gamma + uint32_t blue_gamma; // Blue gamma + uint32_t intent; + uint32_t profile_data; // Profile data (optional, only for 32 bpp BMP files) + uint32_t profile_size; + uint32_t reserved_5; +} BmpHeader; + +typedef struct _VipsForeignSaveBmp { + VipsForeignSave parent_object; + + VipsTarget *target; + VipsPel *line_buffer; + + uint16_t bands; + uint32_t line_size; +} VipsForeignSaveBmp; + +typedef VipsForeignSaveClass VipsForeignSaveBmpClass; + +G_DEFINE_ABSTRACT_TYPE(VipsForeignSaveBmp, vips_foreign_save_bmp, + VIPS_TYPE_FOREIGN_SAVE); + +static void +vips_foreign_save_bmp_dispose(GObject *gobject) +{ + VipsForeignSaveBmp *bmp = (VipsForeignSaveBmp *) gobject; + + VIPS_UNREF(bmp->target); + + G_OBJECT_CLASS(vips_foreign_save_bmp_parent_class)->dispose(gobject); +} + +static int +vips_foreign_save_bmp_block(VipsRegion *region, VipsRect *area, void *a) +{ + VipsForeignSaveBmp *bmp = (VipsForeignSaveBmp *) a; + VipsImage *image = region->im; + + // This is the position in the source image + uint32_t source_row_size = region->im->Xsize * bmp->bands; + + for (int y = 0; y < area->height; y++) { + VipsPel *src = VIPS_REGION_ADDR(region, 0, area->top + y); + VipsPel *dst = bmp->line_buffer; + + for (int x = 0; x < source_row_size; x += bmp->bands) { + dst[0] = src[2]; // B + dst[1] = src[1]; // G + dst[2] = src[0]; // R + + if (bmp->bands == 4) { + dst[3] = src[3]; // A + } + + dst += bmp->bands; + src += bmp->bands; + } + + if (vips_target_write(bmp->target, bmp->line_buffer, bmp->line_size) < 0) { + vips_error("vips_foreign_save_bmp_build", "unable to write BMP pixel data to target"); + return -1; + } + } + + return 0; +} + +static int +vips_foreign_save_bmp_build(VipsObject *object) +{ + VipsForeignSave *save = (VipsForeignSave *) object; + VipsForeignSaveBmp *bmp = (VipsForeignSaveBmp *) object; + + VipsImage *in; + + if (VIPS_OBJECT_CLASS(vips_foreign_save_bmp_parent_class)->build(object)) + return -1; + + in = save->ready; // shortcut + + // bands (3 or 4) * 8 bits + int bands = vips_image_get_bands(in); + + if ((bands > 3) || (bands > 4)) { + vips_error("vips_foreign_save_bmp_build", "BMP source file must have 3 or 4 bands (RGB or RGBA)"); + return -1; + } + + int bpp = bands * 8; + + // Target image line size trimmed to 4 bytes. + uint32_t line_size = (in->Xsize * bands + 3) & (~3); + uint32_t image_size = in->Ysize * line_size; + + // pix_offset = header size + file size + uint32_t pix_offset = BMP_FILE_HEADER_LEN + BMP_V5_INFO_HEADER_LEN; + + // Format BMP file header. We write 24/32 bpp BMP files only with no compression. + BmpHeader header; + + header.sig[0] = 'B'; + header.sig[1] = 'M'; + header.file_size = GUINT32_TO_LE(pix_offset + image_size); + header.reserved[0] = 0; + header.reserved[1] = 0; + header.pix_offset = GUINT32_TO_LE(pix_offset); + header.dib_header_size = GUINT32_TO_LE(BMP_V5_INFO_HEADER_LEN); + header.width = GINT32_TO_LE(in->Xsize); + header.height = GINT32_TO_LE(-in->Ysize); + header.color_plane = GUINT16_TO_LE(1); + header.bpp = GUINT16_TO_LE(bpp); + header.compression = COMPRESSION_BI_RGB; + header.image_size = GUINT32_TO_LE(image_size); + header.x_pixels_per_meter = 0; // GUINT32_TO_LE(2835); + header.y_pixels_per_meter = 0; // GUINT32_TO_LE(2835); + header.color_use = 0; + header.color_important = 0; + header.rmask = GUINT32_TO_LE(0x00FF0000); // Standard says that masks are in BE order + header.gmask = GUINT32_TO_LE(0x0000FF00); + header.bmask = GUINT32_TO_LE(0x000000FF); + header.amask = GUINT32_TO_LE(0xFF000000); + header.cs_type[0] = 'B'; // Image color profile + header.cs_type[1] = 'G'; + header.cs_type[2] = 'R'; + header.cs_type[3] = 's'; + memset(header.cs, 0, sizeof(header.cs)); // CIEXYZTRIPLE Color Space + header.red_gamma = 0; + header.green_gamma = 0; + header.blue_gamma = 0; + header.intent = GUINT32_TO_LE(4); // IMAGES intent, must be 4 + header.profile_data = 0; + header.profile_size = 0; + header.reserved_5 = 0; + + if (vips_target_write(bmp->target, &header, sizeof(header)) < 0) { + vips_error("vips_foreign_save_bmp_build", "unable to write BMP header to target"); + return -1; + } + + // Allocate a line buffer for the target image + bmp->line_buffer = VIPS_MALLOC(save, line_size); + bmp->bands = bands; + bmp->line_size = line_size; + + // save image async + if (vips_sink_disc(in, vips_foreign_save_bmp_block, bmp)) + return -1; + + if (vips_target_end(bmp->target)) + return -1; + + return 0; +} + +static void +vips_foreign_save_bmp_class_init(VipsForeignSaveBmpClass *class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS(class); + VipsObjectClass *object_class = (VipsObjectClass *) class; + VipsForeignClass *foreign_class = (VipsForeignClass *) class; + VipsForeignSaveClass *save_class = (VipsForeignSaveClass *) class; + + gobject_class->dispose = vips_foreign_save_bmp_dispose; + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + object_class->nickname = "bmpsave_base"; + object_class->description = "save bmp"; + object_class->build = vips_foreign_save_bmp_build; + + // We do not support saving monochrome images yet (VIPS_FOREIGN_SAVEABLE_MONO) + // In v4 we will support it, so we leave it here commented out + save_class->saveable = + VIPS_SAVEABLE_RGB | // latest vips: VIPS_FOREIGN_SAVEABLE_RGB + VIPS_SAVEABLE_RGBA; // latest vips: VIPS_FOREIGN_SAVEABLE_ALPHA + + save_class->format_table = bandfmt_bmp; +} + +static void +vips_foreign_save_bmp_init(VipsForeignSaveBmp *bmp) +{ +} + +typedef struct _VipsForeignSaveBmpTarget { + VipsForeignSaveBmp parent_object; + + VipsTarget *target; +} VipsForeignSaveBmpTarget; + +typedef VipsForeignSaveBmpClass VipsForeignSaveBmpTargetClass; + +G_DEFINE_TYPE(VipsForeignSaveBmpTarget, vips_foreign_save_bmp_target, + vips_foreign_save_bmp_get_type()); + +static int +vips_foreign_save_bmp_target_build(VipsObject *object) +{ + VipsForeignSaveBmp *bmp = (VipsForeignSaveBmp *) object; + VipsForeignSaveBmpTarget *target = (VipsForeignSaveBmpTarget *) object; + + bmp->target = target->target; + g_object_ref(bmp->target); + + return VIPS_OBJECT_CLASS(vips_foreign_save_bmp_target_parent_class) + ->build(object); +} + +static void +vips_foreign_save_bmp_target_class_init(VipsForeignSaveBmpTargetClass *class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS(class); + VipsObjectClass *object_class = (VipsObjectClass *) class; + + gobject_class->set_property = vips_object_set_property; + gobject_class->get_property = vips_object_get_property; + + object_class->nickname = "bmpsave_target"; + object_class->description = "save image to target as PNG"; + object_class->build = vips_foreign_save_bmp_target_build; + + VIPS_ARG_OBJECT(class, "target", 1, + "Target", + "Target to save to", + VIPS_ARGUMENT_REQUIRED_INPUT, + G_STRUCT_OFFSET(VipsForeignSaveBmpTarget, target), + VIPS_TYPE_TARGET); +} + +static void +vips_foreign_save_bmp_target_init(VipsForeignSaveBmpTarget *target) +{ +} + +/** + * vips_bmpsave_target: (method) + * @in: image to save + * @target: save image to this target + * @...: `NULL`-terminated list of optional named arguments + * + * As [method@Image.bmpsave], but save to a target. + * + * ::: seealso + * [method@Image.bmpsave], [method@Image.write_to_target]. + * + * Returns: 0 on success, -1 on error. + */ +int +vips_bmpsave_target(VipsImage *in, VipsTarget *target, ...) +{ + va_list ap; + int result; + + va_start(ap, target); + result = vips_call_split("bmpsave_target", ap, in, target); + va_end(ap); + + return result; +} + +// wrapper function which hides varargs (...) from CGo +int +vips_bmpsave_target_go(VipsImage *in, VipsTarget *target) +{ + return vips_bmpsave_target(in, VIPS_TARGET(target), NULL); +} diff --git a/vips/ico.go b/vips/ico.go index 2b7a9552..fa18e7b8 100644 --- a/vips/ico.go +++ b/vips/ico.go @@ -57,17 +57,21 @@ func (img *Image) saveAsIco() (*imagedata.ImageData, error) { return nil, newVipsError("Image dimensions is too big. Max dimension size for ICO is 256") } - var ptr unsafe.Pointer + target := C.vips_target_new_to_memory() + imgsize := C.size_t(0) defer func() { - C.g_free_go(&ptr) + C.vips_unref_target(target) }() - if C.vips_pngsave_go(img.VipsImage, &ptr, &imgsize, 0, 0, 256) != 0 { + if C.vips_pngsave_go(img.VipsImage, target, 0, 0, 256) != 0 { return nil, Error() } + var blob_ptr = C.vips_blob_get(target.blob, &imgsize) + var ptr unsafe.Pointer = unsafe.Pointer(blob_ptr) + b := ptrToBytes(ptr, int(imgsize)) buf := new(bytes.Buffer) diff --git a/vips/vips.c b/vips/vips.c index b04f4d28..60ec5cab 100644 --- a/vips/vips.c +++ b/vips/vips.c @@ -14,6 +14,9 @@ vips_initialize() extern GType vips_foreign_load_bmp_source_get_type(void); vips_foreign_load_bmp_source_get_type(); + extern GType vips_foreign_save_bmp_target_get_type(void); + vips_foreign_save_bmp_target_get_type(); + return vips_init("imgproxy"); } @@ -1008,10 +1011,10 @@ vips_strip_all(VipsImage *in, VipsImage **out) } int -vips_jpegsave_go(VipsImage *in, void **buf, size_t *len, int quality, int interlace) +vips_jpegsave_go(VipsImage *in, VipsTarget *target, int quality, int interlace) { - return vips_jpegsave_buffer( - in, buf, len, + return vips_jpegsave_target( + in, target, "Q", quality, "optimize_coding", TRUE, "interlace", interlace, @@ -1019,17 +1022,17 @@ vips_jpegsave_go(VipsImage *in, void **buf, size_t *len, int quality, int interl } int -vips_jxlsave_go(VipsImage *in, void **buf, size_t *len, int quality, int effort) +vips_jxlsave_go(VipsImage *in, VipsTarget *target, int quality, int effort) { - return vips_jxlsave_buffer( - in, buf, len, + return vips_jxlsave_target( + in, target, "Q", quality, "effort", effort, NULL); } int -vips_pngsave_go(VipsImage *in, void **buf, size_t *len, int interlace, int quantize, int colors) +vips_pngsave_go(VipsImage *in, VipsTarget *target, int interlace, int quantize, int colors) { int bitdepth; @@ -1055,14 +1058,14 @@ vips_pngsave_go(VipsImage *in, void **buf, size_t *len, int interlace, int quant } if (!quantize) - return vips_pngsave_buffer( - in, buf, len, + return vips_pngsave_target( + in, target, "filter", VIPS_FOREIGN_PNG_FILTER_ALL, "interlace", interlace, NULL); - return vips_pngsave_buffer( - in, buf, len, + return vips_pngsave_target( + in, target, "filter", VIPS_FOREIGN_PNG_FILTER_NONE, "interlace", interlace, "palette", quantize, @@ -1071,10 +1074,10 @@ vips_pngsave_go(VipsImage *in, void **buf, size_t *len, int interlace, int quant } int -vips_webpsave_go(VipsImage *in, void **buf, size_t *len, int quality, int effort, VipsForeignWebpPreset preset) +vips_webpsave_go(VipsImage *in, VipsTarget *target, int quality, int effort, VipsForeignWebpPreset preset) { - return vips_webpsave_buffer( - in, buf, len, + return vips_webpsave_target( + in, target, "Q", quality, "effort", effort, "preset", preset, @@ -1082,35 +1085,35 @@ vips_webpsave_go(VipsImage *in, void **buf, size_t *len, int quality, int effort } int -vips_gifsave_go(VipsImage *in, void **buf, size_t *len) +vips_gifsave_go(VipsImage *in, VipsTarget *target) { int bitdepth = vips_get_palette_bit_depth(in); if (bitdepth <= 0 || bitdepth > 8) bitdepth = 8; - return vips_gifsave_buffer(in, buf, len, "bitdepth", bitdepth, NULL); + return vips_gifsave_target(in, target, "bitdepth", bitdepth, NULL); } int -vips_tiffsave_go(VipsImage *in, void **buf, size_t *len, int quality) +vips_tiffsave_go(VipsImage *in, VipsTarget *target, int quality) { - return vips_tiffsave_buffer(in, buf, len, "Q", quality, NULL); + return vips_tiffsave_target(in, target, "Q", quality, NULL); } int -vips_heifsave_go(VipsImage *in, void **buf, size_t *len, int quality) +vips_heifsave_go(VipsImage *in, VipsTarget *target, int quality) { - return vips_heifsave_buffer( - in, buf, len, + return vips_heifsave_target( + in, target, "Q", quality, "compression", VIPS_FOREIGN_HEIF_COMPRESSION_HEVC, NULL); } int -vips_avifsave_go(VipsImage *in, void **buf, size_t *len, int quality, int speed) +vips_avifsave_go(VipsImage *in, VipsTarget *target, int quality, int speed) { - return vips_heifsave_buffer( - in, buf, len, + return vips_heifsave_target( + in, target, "Q", quality, "compression", VIPS_FOREIGN_HEIF_COMPRESSION_AV1, "effort", 9 - speed, @@ -1144,3 +1147,9 @@ vips_foreign_load_read_full(VipsSource *source, void *buf, size_t len) return 1; } + +void +vips_unref_target(VipsTarget *target) +{ + VIPS_UNREF(target); +} diff --git a/vips/vips.go b/vips/vips.go index a4176732..bbd9433f 100644 --- a/vips/vips.go +++ b/vips/vips.go @@ -291,21 +291,21 @@ func SupportsSave(it imagetype.Type) bool { switch it { case imagetype.JPEG: - sup = hasOperation("jpegsave_buffer") + sup = hasOperation("jpegsave_target") case imagetype.JXL: - sup = hasOperation("jxlsave_buffer") + sup = hasOperation("jxlsave_target") case imagetype.PNG, imagetype.ICO: - sup = hasOperation("pngsave_buffer") + sup = hasOperation("pngsave_target") case imagetype.WEBP: - sup = hasOperation("webpsave_buffer") + sup = hasOperation("webpsave_target") case imagetype.GIF: - sup = hasOperation("gifsave_buffer") + sup = hasOperation("gifsave_target") case imagetype.HEIC, imagetype.AVIF: - sup = hasOperation("heifsave_buffer") + sup = hasOperation("heifsave_target") case imagetype.BMP: - sup = true + sup = hasOperation("bmpsave_target") case imagetype.TIFF: - sup = hasOperation("tiffsave_buffer") + sup = hasOperation("tiffsave_target") } typeSupportSave.Store(it, sup) @@ -429,13 +429,10 @@ func (img *Image) Save(imgtype imagetype.Type, quality int) (*imagedata.ImageDat return img.saveAsIco() } - if imgtype == imagetype.BMP { - return img.saveAsBmp() - } + target := C.vips_target_new_to_memory() - var ptr unsafe.Pointer cancel := func() { - C.g_free_go(&ptr) + C.vips_unref_target(target) } err := C.int(0) @@ -443,22 +440,27 @@ func (img *Image) Save(imgtype imagetype.Type, quality int) (*imagedata.ImageDat switch imgtype { case imagetype.JPEG: - err = C.vips_jpegsave_go(img.VipsImage, &ptr, &imgsize, C.int(quality), vipsConf.JpegProgressive) + err = C.vips_jpegsave_go(img.VipsImage, target, C.int(quality), vipsConf.JpegProgressive) case imagetype.JXL: - err = C.vips_jxlsave_go(img.VipsImage, &ptr, &imgsize, C.int(quality), vipsConf.JxlEffort) + err = C.vips_jxlsave_go(img.VipsImage, target, C.int(quality), vipsConf.JxlEffort) case imagetype.PNG: - err = C.vips_pngsave_go(img.VipsImage, &ptr, &imgsize, vipsConf.PngInterlaced, vipsConf.PngQuantize, vipsConf.PngQuantizationColors) + err = C.vips_pngsave_go(img.VipsImage, target, vipsConf.PngInterlaced, vipsConf.PngQuantize, vipsConf.PngQuantizationColors) case imagetype.WEBP: - err = C.vips_webpsave_go(img.VipsImage, &ptr, &imgsize, C.int(quality), vipsConf.WebpEffort, vipsConf.WebpPreset) + err = C.vips_webpsave_go(img.VipsImage, target, C.int(quality), vipsConf.WebpEffort, vipsConf.WebpPreset) case imagetype.GIF: - err = C.vips_gifsave_go(img.VipsImage, &ptr, &imgsize) + err = C.vips_gifsave_go(img.VipsImage, target) case imagetype.HEIC: - err = C.vips_heifsave_go(img.VipsImage, &ptr, &imgsize, C.int(quality)) + err = C.vips_heifsave_go(img.VipsImage, target, C.int(quality)) case imagetype.AVIF: - err = C.vips_avifsave_go(img.VipsImage, &ptr, &imgsize, C.int(quality), vipsConf.AvifSpeed) + err = C.vips_avifsave_go(img.VipsImage, target, C.int(quality), vipsConf.AvifSpeed) case imagetype.TIFF: - err = C.vips_tiffsave_go(img.VipsImage, &ptr, &imgsize, C.int(quality)) + err = C.vips_tiffsave_go(img.VipsImage, target, C.int(quality)) + case imagetype.BMP: + err = C.vips_bmpsave_target_go(img.VipsImage, target) default: + // NOTE: probably, it would be better to use defer unref + additionally ref the target + // before passing it to the imagedata.ImageData + cancel() return nil, newVipsError("Usupported image type to save") } if err != 0 { @@ -466,6 +468,9 @@ func (img *Image) Save(imgtype imagetype.Type, quality int) (*imagedata.ImageDat return nil, Error() } + var blob_ptr = C.vips_blob_get(target.blob, &imgsize) + var ptr unsafe.Pointer = unsafe.Pointer(blob_ptr) + imgdata := imagedata.ImageData{ Type: imgtype, Data: ptrToBytes(ptr, int(imgsize)), diff --git a/vips/vips.h b/vips/vips.h index 92b6c3ff..aa1c8b6b 100644 --- a/vips/vips.h +++ b/vips/vips.h @@ -93,18 +93,19 @@ int vips_arrayjoin_go(VipsImage **in, VipsImage **out, int n); int vips_strip(VipsImage *in, VipsImage **out, int keep_exif_copyright); int vips_strip_all(VipsImage *in, VipsImage **out); -int vips_jpegsave_go(VipsImage *in, void **buf, size_t *len, int quality, int interlace); -int vips_jxlsave_go(VipsImage *in, void **buf, size_t *len, int quality, int effort); -int vips_pngsave_go(VipsImage *in, void **buf, size_t *len, int interlace, int quantize, +int vips_jpegsave_go(VipsImage *in, VipsTarget *target, int quality, int interlace); +int vips_jxlsave_go(VipsImage *in, VipsTarget *target, int quality, int effort); +int vips_pngsave_go(VipsImage *in, VipsTarget *target, int interlace, int quantize, int colors); -int vips_webpsave_go(VipsImage *in, void **buf, size_t *len, int quality, int effort, VipsForeignWebpPreset preset); -int vips_gifsave_go(VipsImage *in, void **buf, size_t *len); -int vips_heifsave_go(VipsImage *in, void **buf, size_t *len, int quality); -int vips_avifsave_go(VipsImage *in, void **buf, size_t *len, int quality, int speed); -int vips_tiffsave_go(VipsImage *in, void **buf, size_t *len, int quality); +int vips_webpsave_go(VipsImage *in, VipsTarget *target, int quality, int effort, VipsForeignWebpPreset preset); +int vips_gifsave_go(VipsImage *in, VipsTarget *target); +int vips_heifsave_go(VipsImage *in, VipsTarget *target, int quality); +int vips_avifsave_go(VipsImage *in, VipsTarget *target, int quality, int speed); +int vips_tiffsave_go(VipsImage *in, VipsTarget *target, int quality); void vips_cleanup(); void vips_error_go(const char *function, const char *message); int vips_foreign_load_read_full(VipsSource *source, void *buf, size_t len); +void vips_unref_target(VipsTarget *target);