Browse Source

Load JPEG from source instead of memory

Viktor Sokolov 9 hours ago
parent
commit
ec5e4b2aae
3 changed files with 162 additions and 1 deletions
  1. 84 0
      vips/vips.c
  2. 53 1
      vips/vips.go
  3. 25 0
      vips/vips.h

+ 84 - 0
vips/vips.c

@@ -1116,3 +1116,87 @@ vips_cleanup()
   vips_error_clear();
   vips_error_clear();
   vips_thread_shutdown();
   vips_thread_shutdown();
 }
 }
+
+// --- async source ----------------------------------------------------------------------
+
+// define glib subtype for vips async source
+#define VIPS_TYPE_ASYNC_SOURCE (vips_async_source_get_type())
+G_DEFINE_FINAL_TYPE(VipsAsyncSource, vips_async_source, VIPS_TYPE_SOURCE)
+
+extern void closeAsyncReader(uintptr_t handle);
+extern gint64 asyncReaderSeek(uintptr_t handle, gint64 offset, int whence);
+extern gint64 asyncReaderRead(uintptr_t handle, gpointer buffer, gint64 size);
+
+// loads jpeg from a source
+int
+vips_jpegloadsource_go(VipsAsyncSource *source, int shrink, VipsImage **out)
+{
+  if (shrink > 1)
+    return vips_jpegload_source(VIPS_SOURCE(source), out, "shrink", shrink,
+        NULL);
+
+  return vips_jpegload_source(VIPS_SOURCE(source), out, NULL);
+}
+
+// dereferences source
+void
+close_source(VipsImage **in, VipsAsyncSource *source)
+{
+  uintptr_t readerHandle = source->readerHandle;
+  VIPS_UNREF(source);
+  closeAsyncReader(readerHandle);
+}
+
+// attaches close signals to the image. first signal closes it's source, second closes the reader.
+void
+vips_attach_image_close_signals(VipsImage **in, uintptr_t handle, VipsAsyncSource *source)
+{
+  g_signal_connect(*in, "close", G_CALLBACK(close_source), (void *) source);
+}
+
+// read function for vips async source
+static gint64
+vips_async_source_read(VipsSource *source, void *buffer, size_t length)
+{
+  VipsAsyncSource *self = (VipsAsyncSource *) source;
+
+  return asyncReaderRead(self->readerHandle, buffer, length);
+}
+
+// seek function for vips async source. whence can be SEEK_SET (0), SEEK_CUR (1), or SEEK_END (2).
+static gint64
+vips_async_source_seek(VipsSource *source, gint64 offset, int whence)
+{
+  VipsAsyncSource *self = (VipsAsyncSource *) source;
+
+  return asyncReaderSeek(self->readerHandle, offset, whence);
+}
+
+// attaches seek/read handlers to the async source class
+static void
+vips_async_source_class_init(VipsAsyncSourceClass *klass)
+{
+  VipsObjectClass *object_class = VIPS_OBJECT_CLASS(klass);
+  VipsSourceClass *source_class = VIPS_SOURCE_CLASS(klass);
+
+  object_class->nickname = "async_source";
+  object_class->description = "async input source";
+
+  source_class->read = vips_async_source_read;
+  source_class->seek = vips_async_source_seek;
+}
+
+// initializes the async source (nothing to do here yet)
+static void
+vips_async_source_init(VipsAsyncSource *source)
+{
+}
+
+// creates a new async source with the given reader handle
+VipsAsyncSource *
+vips_new_async_source(uintptr_t readerHandle)
+{
+  VipsAsyncSource *source = g_object_new(vips_async_source_get_type(), NULL);
+  source->readerHandle = readerHandle;
+  return source;
+}

+ 53 - 1
vips/vips.go

@@ -8,12 +8,14 @@ package vips
 */
 */
 import "C"
 import "C"
 import (
 import (
+	"bytes"
 	"context"
 	"context"
 	"math"
 	"math"
 	"net/http"
 	"net/http"
 	"os"
 	"os"
 	"regexp"
 	"regexp"
 	"runtime"
 	"runtime"
+	"runtime/cgo"
 	"strings"
 	"strings"
 	"sync"
 	"sync"
 	"time"
 	"time"
@@ -329,6 +331,49 @@ func (img *Image) Pages() int {
 	return p
 	return p
 }
 }
 
 
+//export closeAsyncReader
+func closeAsyncReader(handle C.uintptr_t) {
+	h := cgo.Handle(handle)
+	h.Delete()
+}
+
+// calls seek() on the async reader via it's handle from the C side
+//
+//export asyncReaderSeek
+func asyncReaderSeek(handle C.uintptr_t, offset C.int64_t, whence int) C.int64_t {
+	h := cgo.Handle(handle)
+	reader, ok := h.Value().(*bytes.Reader)
+	if !ok {
+		return -1
+	}
+
+	pos, err := reader.Seek(int64(offset), whence)
+	if err != nil {
+		return -1
+	}
+
+	return C.int64_t(pos)
+}
+
+// calls read() on the async reader via it's handle from the C side
+//
+//export asyncReaderRead
+func asyncReaderRead(handle C.uintptr_t, pointer unsafe.Pointer, size C.int64_t) C.int64_t {
+	h := cgo.Handle(handle)
+	reader, ok := h.Value().(*bytes.Reader)
+	if !ok {
+		return -1
+	}
+
+	buf := unsafe.Slice((*byte)(pointer), size)
+	n, err := reader.Read(buf)
+	if err != nil {
+		return -1
+	}
+
+	return C.int64_t(n)
+}
+
 func (img *Image) Load(imgdata *imagedata.ImageData, shrink int, scale float64, pages int) error {
 func (img *Image) Load(imgdata *imagedata.ImageData, shrink int, scale float64, pages int) error {
 	if imgdata.Type == imagetype.ICO {
 	if imgdata.Type == imagetype.ICO {
 		return img.loadIco(imgdata.Data, shrink, scale, pages)
 		return img.loadIco(imgdata.Data, shrink, scale, pages)
@@ -344,9 +389,13 @@ func (img *Image) Load(imgdata *imagedata.ImageData, shrink int, scale float64,
 	dataSize := C.size_t(len(imgdata.Data))
 	dataSize := C.size_t(len(imgdata.Data))
 	err := C.int(0)
 	err := C.int(0)
 
 
+	reader := bytes.NewReader(imgdata.Data)
+	handler := cgo.NewHandle(reader)
+	source := C.vips_new_async_source(C.uintptr_t(handler))
+
 	switch imgdata.Type {
 	switch imgdata.Type {
 	case imagetype.JPEG:
 	case imagetype.JPEG:
-		err = C.vips_jpegload_go(data, dataSize, C.int(shrink), &tmp)
+		err = C.vips_jpegloadsource_go(source, C.int(shrink), &tmp)
 	case imagetype.JXL:
 	case imagetype.JXL:
 		err = C.vips_jxlload_go(data, dataSize, C.int(pages), &tmp)
 		err = C.vips_jxlload_go(data, dataSize, C.int(pages), &tmp)
 	case imagetype.PNG:
 	case imagetype.PNG:
@@ -362,12 +411,15 @@ func (img *Image) Load(imgdata *imagedata.ImageData, shrink int, scale float64,
 	case imagetype.TIFF:
 	case imagetype.TIFF:
 		err = C.vips_tiffload_go(data, dataSize, &tmp)
 		err = C.vips_tiffload_go(data, dataSize, &tmp)
 	default:
 	default:
+		C.close_source(nil, source)
 		return newVipsError("Usupported image type to load")
 		return newVipsError("Usupported image type to load")
 	}
 	}
 	if err != 0 {
 	if err != 0 {
+		C.close_source(nil, source)
 		return Error()
 		return Error()
 	}
 	}
 
 
+	C.vips_attach_image_close_signals(&tmp, C.uintptr_t(handler), source)
 	C.swap_and_clear(&img.VipsImage, tmp)
 	C.swap_and_clear(&img.VipsImage, tmp)
 
 
 	if imgdata.Type == imagetype.TIFF {
 	if imgdata.Type == imagetype.TIFF {

+ 25 - 0
vips/vips.h

@@ -1,6 +1,8 @@
 #include <stdlib.h>
 #include <stdlib.h>
+#include <stdint.h> // uintptr_t
 
 
 #include <vips/vips.h>
 #include <vips/vips.h>
+#include <vips/connection.h>
 #include <vips/vips7compat.h>
 #include <vips/vips7compat.h>
 #include <vips/vector.h>
 #include <vips/vector.h>
 
 
@@ -10,6 +12,17 @@ typedef struct _RGB {
   double b;
   double b;
 } RGB;
 } RGB;
 
 
+// vips async source
+typedef struct _VipsAsyncSource {
+  VipsSourceCustom source; // class designator
+  uintptr_t readerHandle;  // async reader handler
+} VipsAsyncSource;
+
+// glib class for vips async source
+typedef struct _VipsAsyncSourceClass {
+  VipsSourceCustomClass parent_class;
+} VipsAsyncSourceClass;
+
 int vips_initialize();
 int vips_initialize();
 
 
 void clear_image(VipsImage **in);
 void clear_image(VipsImage **in);
@@ -99,3 +112,15 @@ int vips_avifsave_go(VipsImage *in, void **buf, size_t *len, int quality, int sp
 int vips_tiffsave_go(VipsImage *in, void **buf, size_t *len, int quality);
 int vips_tiffsave_go(VipsImage *in, void **buf, size_t *len, int quality);
 
 
 void vips_cleanup();
 void vips_cleanup();
+
+// vips async source read function
+int vips_jpegloadsource_go(VipsAsyncSource *source, int shrink, VipsImage **out);
+
+// creates new vips async source from a reader handle
+VipsAsyncSource *vips_new_async_source(uintptr_t readerHandle);
+
+// attaches "close" signal to the vips image: closes reader and unrefs vips source
+void vips_attach_image_close_signals(VipsImage **in, uintptr_t handle, VipsAsyncSource *source);
+
+// closes source and corresponding reader
+void close_source(VipsImage **in, VipsAsyncSource *source);