Skip to content

Commit d8c82f2

Browse files
authored
feat: VipsTarget integration with io.WriteCloser (#47)
* prepare vips target * build: update generated vips package * prepare vips target * build: update generated vips package * prepare vips target * build: update generated vips package * tweak mime method ordering * tweak mime method ordering * add test case * Update streaming wordings
1 parent 8ce8024 commit d8c82f2

18 files changed

+2293
-33
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
# vipsgen
22

3-
[![CI](https://github.com/cshum/vipsgen/actions/workflows/ci.yml/badge.svg)](https://github.com/cshum/vipsgen/actions/workflows/ci.yml)
43
[![Go Reference](https://pkg.go.dev/badge/github.com/cshum/vipsgen/vips.svg)](https://pkg.go.dev/github.com/cshum/vipsgen/vips)
4+
[![CI](https://github.com/cshum/vipsgen/actions/workflows/ci.yml/badge.svg)](https://github.com/cshum/vipsgen/actions/workflows/ci.yml)
55
[![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/cshum/vipsgen)](https://github.com/cshum/vipsgen/releases)
66

7-
vipsgen is a Go binding generator for [libvips](https://www.libvips.org/) - a fast and efficient image processing library.
7+
vipsgen is a Go binding generator for [libvips](https://github.com/libvips/libvips) - a fast and efficient image processing library.
88

99
Existing Go libvips bindings rely on manually written code that is often incomplete, error-prone, and difficult to maintain as libvips evolves.
1010
vipsgen solves this by generating type-safe, robust, and fully documented Go bindings using GObject introspection.
@@ -19,7 +19,7 @@ You can use vipsgen in two ways:
1919
- **Comprehensive**: Bindings for around [300 libvips operations](https://www.libvips.org/API/current/func-list.html)
2020
- **Type-Safe**: Proper Go types for all libvips C enums and structs
2121
- **Idiomatic**: Clean Go APIs that feel natural to use
22-
- **Streaming**: Includes `VipsSource` integration with `io.ReadCloser` for [streaming](https://www.libvips.org/2019/11/29/True-streaming-for-libvips.html)
22+
- **Streaming**: `VipsSource` and `VipsTarget` integration with Go `io.Reader` and `io.Writer` for [streaming](https://www.libvips.org/2019/11/29/True-streaming-for-libvips.html)
2323

2424
## Quick Start
2525

internal/generator/templatefunc.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,7 @@ func generateFunctionCallArgs(op introspection.Operation, withOptions bool) stri
406406
callArgs = append(callArgs, argStr)
407407
continue
408408
}
409-
if arg.IsSource {
409+
if arg.IsSource || arg.IsTarget {
410410
callArgs = append(callArgs, arg.GoName)
411411
} else if arg.GoType == "string" {
412412
argStr = "c" + arg.GoName
@@ -565,6 +565,8 @@ func generateImageMethodBody(op introspection.Operation) string {
565565
for _, arg := range methodArgs {
566566
if arg.GoType == "*C.VipsImage" {
567567
callArgs = append(callArgs, fmt.Sprintf("%s.image", arg.GoName))
568+
} else if arg.IsTarget {
569+
callArgs = append(callArgs, fmt.Sprintf("%s.target", arg.GoName))
568570
} else if arg.GoType == "[]*C.VipsImage" {
569571
callArgs = append(callArgs, fmt.Sprintf("convertImagesToVipsImages(%s)", arg.GoName))
570572
} else {
@@ -1089,6 +1091,8 @@ func generateImageMethodParams(op introspection.Operation) string {
10891091
paramType = "[]*Image"
10901092
} else if arg.CType == "void*" {
10911093
paramType = "[]byte"
1094+
} else if arg.IsTarget {
1095+
paramType = "*Target"
10921096
} else {
10931097
paramType = arg.GoType
10941098
}
@@ -1355,9 +1359,12 @@ func generateCFunctionImplementation(op introspection.Operation) string {
13551359
if i > 0 {
13561360
result.WriteString(", ")
13571361
}
1358-
// Add type casting for VipsSourceCustom
13591362
if arg.IsSource {
1363+
// Add type casting for VipsSourceCustom
13601364
result.WriteString("(VipsSource*) " + arg.Name)
1365+
} else if arg.IsTarget {
1366+
// Add type casting for VipsTargetCustom
1367+
result.WriteString("(VipsTarget*) " + arg.Name)
13611368
} else {
13621369
result.WriteString(arg.Name)
13631370
}
@@ -1470,6 +1477,9 @@ func generateCFunctionImplementation(op introspection.Operation) string {
14701477
} else if arg.IsSource {
14711478
allParamsList = append(allParamsList,
14721479
fmt.Sprintf("vips_object_set(VIPS_OBJECT(operation), \"%s\", (VipsSource*)%s, NULL)", arg.Name, arg.Name))
1480+
} else if arg.IsTarget {
1481+
allParamsList = append(allParamsList,
1482+
fmt.Sprintf("vips_object_set(VIPS_OBJECT(operation), \"%s\", (VipsTarget*)%s, NULL)", arg.Name, arg.Name))
14731483
} else if (arg.Name == "buf" || arg.Name == "buffer") && isBufferLoadOperation {
14741484
// For buffer load operations, set the VipsBlob as the "buffer" property
14751485
allParamsList = append(allParamsList,
@@ -1526,6 +1536,10 @@ func generateCFunctionImplementation(op introspection.Operation) string {
15261536
// Handle source parameters
15271537
allParamsList = append(allParamsList,
15281538
fmt.Sprintf("vipsgen_set_source(operation, \"%s\", %s)", opt.Name, opt.Name))
1539+
} else if opt.IsTarget {
1540+
// Handle target parameters
1541+
allParamsList = append(allParamsList,
1542+
fmt.Sprintf("vipsgen_set_target(operation, \"%s\", %s)", opt.Name, opt.Name))
15291543
} else if opt.GoType == "int" {
15301544
allParamsList = append(allParamsList,
15311545
fmt.Sprintf("vipsgen_set_int(operation, \"%s\", %s)", opt.Name, opt.Name))

internal/introspection/operation.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ type Argument struct {
4343
IsOutput bool
4444
IsOutputN bool
4545
IsSource bool
46+
IsTarget bool
4647
IsImage bool
4748
IsBuffer bool
4849
IsArray bool
@@ -129,8 +130,7 @@ func (v *Introspection) DiscoverOperations() []Operation {
129130
op.HasOneImageOutput = false
130131
}
131132

132-
if strings.Contains(op.Name, "_target") ||
133-
strings.Contains(op.Name, "_mime") ||
133+
if strings.Contains(op.Name, "_mime") ||
134134
strings.Contains(op.Name, "fitsload_source") {
135135
log.Printf("Excluded operation: vips_%s \n", op.Name)
136136
excludedCount++
@@ -202,6 +202,7 @@ func (v *Introspection) DiscoverOperationArguments(opName string) ([]Argument, e
202202
isBuffer := int(arg.is_buffer) != 0
203203
isArray := int(arg.is_array) != 0
204204
isSource := cTypeCheck(arg.type_val, "VipsSource")
205+
isTarget := cTypeCheck(arg.type_val, "VipsTarget")
205206

206207
// Create the Go argument structure
207208
goArg := Argument{
@@ -215,6 +216,7 @@ func (v *Introspection) DiscoverOperationArguments(opName string) ([]Argument, e
215216
IsBuffer: isBuffer,
216217
IsArray: isArray,
217218
IsSource: isSource,
219+
IsTarget: isTarget,
218220
Flags: int(arg.flags),
219221
}
220222

@@ -492,6 +494,14 @@ func (v *Introspection) mapGTypeToTypes(gtype C.GType, typeName string, isOutput
492494
}
493495
return "VipsSourceCustom", "*C.VipsSourceCustom", "VipsSourceCustom*"
494496
}
497+
// Special case for VipsTarget - map to VipsTargetCustom for proper compatibility
498+
if cTypeCheck(gtype, "VipsTarget") {
499+
// For VipsTarget, we want to use VipsTargetCustom in the bindings
500+
if isOutput {
501+
return "VipsTargetCustom", "*C.VipsTargetCustom", "VipsTargetCustom**"
502+
}
503+
return "VipsTargetCustom", "*C.VipsTargetCustom", "VipsTargetCustom*"
504+
}
495505
// Special case for VipsImage which has a different pointer pattern
496506
if cTypeCheck(gtype, "VipsImage") {
497507
if isOutput {

internal/templates/callback.go.tmpl

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,24 @@ func goSourceSeek(
5353
}
5454
return -1
5555
}
56+
57+
//export goTargetWrite
58+
func goTargetWrite(
59+
ptr unsafe.Pointer, buffer unsafe.Pointer, size C.longlong,
60+
) C.longlong {
61+
target, ok := pointer.Restore(ptr).(*Target)
62+
if !ok {
63+
return -1
64+
}
65+
sh := &reflect.SliceHeader{
66+
Data: uintptr(buffer),
67+
Len: int(size),
68+
Cap: int(size),
69+
}
70+
buf := *(*[]byte)(unsafe.Pointer(sh))
71+
n, err := target.writer.Write(buf)
72+
if err != nil {
73+
return -1
74+
}
75+
return C.longlong(n)
76+
}

internal/templates/connection.c.tmpl

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ static gint64 go_seek(VipsSourceCustom *source_custom, gint64 offset, int whence
1212
return goSourceSeek(ptr, offset, whence);
1313
}
1414

15+
static gint64 go_write(VipsTargetCustom *target_custom, void *buffer, gint64 length, void* ptr)
16+
{
17+
return goTargetWrite(ptr, buffer, length);
18+
}
19+
1520
VipsSourceCustom * create_go_custom_source(void* ptr)
1621
{
1722
VipsSourceCustom * source_custom = vips_source_custom_new();
@@ -27,6 +32,17 @@ VipsSourceCustom * create_go_custom_source_with_seek(void* ptr)
2732
return source_custom;
2833
}
2934

35+
VipsTargetCustom * create_go_custom_target(void* ptr)
36+
{
37+
VipsTargetCustom * target_custom = vips_target_custom_new();
38+
g_signal_connect(target_custom, "write", G_CALLBACK(go_write), ptr);
39+
return target_custom;
40+
}
41+
3042
void clear_source(VipsSourceCustom **source_custom) {
3143
if (G_IS_OBJECT(*source_custom)) g_clear_object(source_custom);
3244
}
45+
46+
void clear_target(VipsTargetCustom **target_custom) {
47+
if (G_IS_OBJECT(*target_custom)) g_clear_object(target_custom);
48+
}

internal/templates/connection.go.tmpl

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,41 @@ func (s *Source) Close() {
5757
s.lock.Unlock()
5858
}
5959
}
60+
61+
// Target contains a libvips VipsTargetCustom and manages its lifecycle.
62+
type Target struct {
63+
writer io.WriteCloser
64+
target *C.VipsTargetCustom
65+
ptr unsafe.Pointer
66+
lock sync.Mutex
67+
}
68+
69+
// NewTarget creates Target from writer
70+
func NewTarget(writer io.WriteCloser) *Target {
71+
Startup(nil)
72+
t := &Target{writer: writer}
73+
t.ptr = pointer.Save(t)
74+
t.target = C.create_go_custom_target(t.ptr)
75+
return t
76+
}
77+
78+
// Close target
79+
func (t *Target) Close() {
80+
if t == nil {
81+
return
82+
}
83+
t.lock.Lock()
84+
if t.ptr != nil {
85+
C.clear_target(&t.target)
86+
pointer.Unref(t.ptr)
87+
t.ptr = nil
88+
t.lock.Unlock()
89+
if t.writer != nil {
90+
_ = t.writer.Close()
91+
t.writer = nil
92+
}
93+
log("vipsgen", LogLevelDebug, fmt.Sprintf("closing target %p", t))
94+
} else {
95+
t.lock.Unlock()
96+
}
97+
}

internal/templates/connection.h.tmpl

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,16 @@
44
#include <vips/vips.h>
55

66
extern long long goSourceRead(void*, void *, long long);
7-
87
extern long long goSourceSeek(void*, long long, int);
8+
extern long long goTargetWrite(void*, void *, long long);
99

1010
static gint64 go_read(VipsSourceCustom *source_custom, void *buffer, gint64 length, void* ptr);
11-
1211
static gint64 go_seek(VipsSourceCustom *source_custom, gint64 offset, int whence, void* ptr);
12+
static gint64 go_write(VipsTargetCustom *target_custom, void *buffer, gint64 length, void* ptr);
1313

1414
VipsSourceCustom * create_go_custom_source(void* ptr);
1515
VipsSourceCustom * create_go_custom_source_with_seek(void* ptr);
16+
VipsTargetCustom * create_go_custom_target(void* ptr);
1617

1718
void clear_source(VipsSourceCustom **source_custom);
19+
void clear_target(VipsTargetCustom **target_custom);

internal/templates/vips.c.tmpl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,11 @@ int vipsgen_set_source(VipsOperation *operation, const char *name, VipsSource *v
9999
return 0;
100100
}
101101

102+
int vipsgen_set_target(VipsOperation *operation, const char *name, VipsTarget *value) {
103+
if (value != NULL) { return vips_object_set(VIPS_OBJECT(operation), name, value, NULL); }
104+
return 0;
105+
}
106+
102107
// Generated operations
103108
{{range .Operations}}
104109
{{generateCFunctionImplementation .}}

internal/templates/vips.h.tmpl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
int vipsgen_operation_execute(VipsOperation *operation, ...);
1010
int vipsgen_operation_save_buffer(VipsOperation *operation, void** buf, size_t* len);
1111
int vipsgen_set_source(VipsOperation *operation, const char *name, VipsSource *value);
12+
int vipsgen_set_target(VipsOperation *operation, const char *name, VipsTarget *value);
1213
int vipsgen_set_int(VipsOperation *operation, const char *name, int value);
1314
int vipsgen_set_bool(VipsOperation *operation, const char *name, gboolean value);
1415
int vipsgen_set_double(VipsOperation *operation, const char *name, double value);

vips/callback.go

Lines changed: 21 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vips/connection.c

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vips/connection.go

Lines changed: 38 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vips/connection.h

Lines changed: 4 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)