Przeglądaj źródła

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
Victor Sokolov 2 miesięcy temu
rodzic
commit
90376b7261
7 zmienionych plików z 388 dodań i 57 usunięć
  1. 1 1
      imagetype/imagetype.go
  2. 3 0
      vips/bmp.h
  3. 309 0
      vips/bmpsave.c
  4. 7 3
      vips/ico.go
  5. 33 24
      vips/vips.c
  6. 26 21
      vips/vips.go
  7. 9 8
      vips/vips.h

+ 1 - 1
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 {

+ 3 - 0
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

+ 309 - 0
vips/bmpsave.c

@@ -0,0 +1,309 @@
+// BMP saver
+
+#include "vips.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+/* 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);
+}

+ 7 - 3
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)

+ 33 - 24
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);
+}

+ 26 - 21
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)),

+ 9 - 8
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);