Skip to content

Sync with nip4 - #64

Merged
jcupitt merged 16 commits into
libvips:masterfrom
kleisauke:nip4-sync
Aug 29, 2026
Merged

Sync with nip4#64
jcupitt merged 16 commits into
libvips:masterfrom
kleisauke:nip4-sync

Conversation

@kleisauke

@kleisauke kleisauke commented Aug 16, 2026

Copy link
Copy Markdown
Member

Context: #63 (comment).

Follow-up TODO items:

@jcupitt

jcupitt commented Aug 16, 2026

Copy link
Copy Markdown
Member

I've got a big nip4 PR about to land which adds simple MSpaint-style image editing:

image

Maybe we should get that in vipsdisp as well? It could be useful. I think it should go in pretty easily (hopefully).

https://github.com/libvips/nip4/tree/paintbox

Only the first two tools are done (brush and line draw), but the others should be easy now the framework is there.

edit: it needs this libvips branch, I forgot

https://github.com/libvips/libvips/tree/draw_line-add-draw-point

I've not made a PR yet because the API might need tweaking to improve performance.

@jcupitt

jcupitt commented Aug 21, 2026

Copy link
Copy Markdown
Member

I merged the paintbox into nip4, so that could potentially go into vipsdisp.

Perhaps we should merge this PR first, then look at ways to make it easier to keep the two in sync in the future (eg. add some ifdefs to nip4 etc.).

@jcupitt

jcupitt commented Aug 21, 2026

Copy link
Copy Markdown
Member

I added the last 4 of your commits back into nip4, so at least they won't need doing again.

I also removed nip4's autoptr definition for vipsrect and added that to the libvips branch for nip4's paintbox, since nip4 now needs that new API anyway.

@kleisauke

Copy link
Copy Markdown
Member Author

Great! Re-synced with libvips/nip4@2ae4c31, but it still needs some testing.

Also, opening test/test-suite/images/palette.jp2 appears to crash somewhere inside libvips, which I still need to investigate.

This looks like an OpenJPEG issue. I can work around it in a few ways:

  1. --- a/src/tilesource.c
    +++ b/src/tilesource.c
    @@ -205,6 +205,7 @@ tilesource_open(Tilesource *tilesource, int level)
     		else
     			image = vips_image_new_from_file(tilesource->filename,
     				"page", level,
    +				"oneshot", TRUE,
     				NULL);
     	}
     	else if (vips_isprefix("pdf", tilesource->loader)) {
  2. --- a/libvips/foreign/jp2kload.c
    +++ b/libvips/foreign/jp2kload.c
    @@ -281,12 +281,12 @@ vips_foreign_load_jp2k_is_a_source(VipsSource *source)
     static VipsForeignFlags
     vips_foreign_load_jp2k_get_flags(VipsForeignLoad *load)
     {
    -	VipsForeignLoadJp2k *jp2k = (VipsForeignLoadJp2k *) load;
    +	//VipsForeignLoadJp2k *jp2k = (VipsForeignLoadJp2k *) load;
     
    -	if (jp2k->oneshot)
    +	//if (jp2k->oneshot)
     		return VIPS_FOREIGN_SEQUENTIAL;
    -	else
    -		return VIPS_FOREIGN_PARTIAL;
    +	//else
    +	//	return VIPS_FOREIGN_PARTIAL;
     }
     
     /* The openjpeg info and warning callbacks are incredibly chatty.
  3. --- a/libvips/foreign/jp2kload.c
    +++ b/libvips/foreign/jp2kload.c
    @@ -229,6 +229,7 @@ vips_foreign_load_jp2k_build(VipsObject *object)
     	jp2k->parameters.decod_format = -1;
     	jp2k->parameters.cod_format = -1;
     	opj_set_default_decoder_parameters(&jp2k->parameters);
    +	jp2k->parameters.flags |= OPJ_DPARAMETERS_IGNORE_PCLR_CMAP_CDEF_FLAG;
     
     	/* Link the openjpeg stream to our VipsSource.
     	 */

(1) is probably the most acceptable, while (3) is the safest.

@jcupitt

jcupitt commented Aug 22, 2026

Copy link
Copy Markdown
Member

That's great!

  • 		"oneshot", TRUE,
    

Ahhhh I remember this bug, I seem to remember it's been fixed in openjpeg now, but of course it'll be a long while before we can deprecate oneshot.

@jcupitt

jcupitt commented Aug 22, 2026

Copy link
Copy Markdown
Member

Off topic, but have you tried the paintbox in nip4? It's integrated with recomp, so you can do (for example):

  1. take the forward fft of an image to get the complex 2D spectra and observe some peaks
  2. take the inverse to get the original image back
  3. on the spectra, open the paintbox and use the smudge tool to blur out a peak
  4. observe the image from 2. changing as you paint!

It's fun! It does need undo / redo implementing though, someone needs to port over the system from nip2.

@kleisauke

Copy link
Copy Markdown
Member Author

Ah, let me try OpenJPEG from its master branch. The weird thing is that it only happens with palette JP2 images, which isn't what we originally added the oneshot option for.

@jcupitt

jcupitt commented Aug 22, 2026

Copy link
Copy Markdown
Member

I think I've pushed most of your changes back into nip4, so this ought to be less painful next time. Thanks!

I added a NIP4 #define (set in meson.build) around some stuff.

@jcupitt

jcupitt commented Aug 22, 2026

Copy link
Copy Markdown
Member

I made a libvips PR for that extra draw_line callback. We should probably tag nip4 and vipsdisp as depending on libvips 8.19.

@jcupitt

jcupitt commented Aug 22, 2026

Copy link
Copy Markdown
Member

I'm getting what look like double unref errors from the vipsdisp paintbox, eg:

(vipsdisp:2719696): GLib-GObject-CRITICAL **: 14:25:50.887: object_ref: assertion '!object_already_finalized' failed

But I've not yet found a way to trigger them on demand, nor a backtrace :( I'll keep poking it.

@jcupitt

jcupitt commented Aug 22, 2026

Copy link
Copy Markdown
Member

Got a backtrace!

(vipsdisp:2768513): GLib-GObject-CRITICAL **: 16:28:17.845: object_ref: assertion '!object_already_finalized' failed

Thread 1 "vipsdisp" received signal SIGTRAP, Trace/breakpoint trap.
0x00007ffff75315e9 in ?? () from /usr/lib/x86_64-linux-gnu/libglib-2.0.so.0
(gdb) where
#0  0x00007ffff75315e9 in ??? () at /usr/lib/x86_64-linux-gnu/libglib-2.0.so.0
#1  0x00007ffff7532212 in g_logv ()
    at /usr/lib/x86_64-linux-gnu/libglib-2.0.so.0
#2  0x00007ffff75324d3 in g_log ()
    at /usr/lib/x86_64-linux-gnu/libglib-2.0.so.0
#3  0x00007ffff7e16852 in ??? ()
    at /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
#4  0x00007ffff7e182dc in g_object_ref ()
    at /usr/lib/x86_64-linux-gnu/libgobject-2.0.so.0
#5  0x00007ffff7a9ab96 in vips__link_map
    (image=0x555555bf4630, upstream=0, fn=0x7ffff7a94524 <vips_image_invalidate_all_cb>, a=0x0, b=0x0) at ../libvips/iofuncs/generate.c:259
#6  0x00007ffff7a94582 in vips_image_invalidate_all (image=0x555555bf4630)
    at ../libvips/iofuncs/image.c:1429
#7  0x00007ffff7a97de6 in vips_image_inplace (image=0x555555bf4630)
    at ../libvips/iofuncs/image.c:3635
#8  0x0000555555564dd0 in draw_mask
    (image=0x555555bf4630, ink=0x555557630a60 "\377\377\377", mask=0x7ffea0020840, x=692, y=1792) at ../src/draw_mask.c:48
#9  0x0000555555581a81 in tilesource_draw_line_point

So there's somehow a dangling pointer from an unreffed image in the pipeline dependency graph.

I've not seen this from nip4, maybe some change in this PR has done it.

@kleisauke

Copy link
Copy Markdown
Member Author

Re-synced with libvips/nip4@4796411. Hopefully, that will fix the dangling pointer issue.

(I noticed that the previous changeset did VIPS_FREEF(gtk_widget_unparent, win->right_click_menu); twice, though I'm not sure if that's the issue.)

@kleisauke

Copy link
Copy Markdown
Member Author

Ah, I was able to reproduce it. I'll investigate it further tomorrow.

@kleisauke

Copy link
Copy Markdown
Member Author

I got this backtrace with ASan.

Details
(vipsdisp:303639): GLib-GObject-CRITICAL **: 11:20:12.040: object_ref: assertion '!object_already_finalized' failed
AddressSanitizer:DEADLYSIGNAL
=================================================================
==303639==ERROR: AddressSanitizer: SEGV on unknown address 0x00fe0003b2e2 (pc 0x7f2ed39c0e5d bp 0x7fff8c5f8090 sp 0x7fff8c5f8090 T0)
==303639==The signal is caused by a READ memory access.
    #0 0x7f2ed39c0e5d in g_type_check_instance (/lib64/libgobject-2.0.so.0+0x2fe5d) (BuildId: e04a34a9d2ebbca4ef8791440a31ca2bef837aa4)
    #1 0x7f2ed39b7804  (/lib64/libgobject-2.0.so.0+0x26804) (BuildId: e04a34a9d2ebbca4ef8791440a31ca2bef837aa4)
    #2 0x7f2ed39b8c77 in g_signal_emit_valist (/lib64/libgobject-2.0.so.0+0x27c77) (BuildId: e04a34a9d2ebbca4ef8791440a31ca2bef837aa4)
    #3 0x7f2ed39b8d32 in g_signal_emit (/lib64/libgobject-2.0.so.0+0x27d32) (BuildId: e04a34a9d2ebbca4ef8791440a31ca2bef837aa4)
    #4 0x7f2ed0da8b78 in vips_image_invalidate /home/kleisauke/libvips/build/../libvips/iofuncs/image.c:1397:2
    #5 0x7f2ed0da8a48 in vips_image_invalidate_all_cb /home/kleisauke/libvips/build/../libvips/iofuncs/image.c:1403:2
    #6 0x7f2ed0ed9132 in vips_slist_map2 /home/kleisauke/libvips/build/../libvips/iofuncs/util.c:122:33
    #7 0x7f2ed0dc7697 in vips__link_map /home/kleisauke/libvips/build/../libvips/iofuncs/generate.c:263:11
    #8 0x7f2ed0da89d1 in vips_image_invalidate_all /home/kleisauke/libvips/build/../libvips/iofuncs/image.c:1429:9
    #9 0x7f2ed0db9300 in vips_image_inplace /home/kleisauke/libvips/build/../libvips/iofuncs/image.c:3635:2
    #10 0x000000403861 in draw_mask /home/kleisauke/vipsdisp/build/../src/draw_mask.c:48:3
    #11 0x7f2ed0cf9e71 in vips__draw_line_direct /home/kleisauke/libvips/build/../libvips/draw/draw_line.c:206:4
    #12 0x7f2ed0cf763d in vips_draw_line_build /home/kleisauke/libvips/build/../libvips/draw/draw_line.c:265:2
    #13 0x7f2ed0d7a40e in vips_object_build /home/kleisauke/libvips/build/../libvips/iofuncs/object.c:372:6
    #14 0x7f2ed0ddb390 in vips_cache_operation_buildp /home/kleisauke/libvips/build/../libvips/iofuncs/cache.c:1062:7
    #15 0x7f2ed0e1e1bd in vips_call_required_optional /home/kleisauke/libvips/build/../libvips/iofuncs/operation.c:974:6
    #16 0x7f2ed0e273e2 in vips_call_by_name /home/kleisauke/libvips/build/../libvips/iofuncs/operation.c:1014:11
    #17 0x7f2ed0e2767e in vips_call_split /home/kleisauke/libvips/build/../libvips/iofuncs/operation.c:1121:11
    #18 0x7f2ed0cfa41b in vips_draw_linev /home/kleisauke/libvips/build/../libvips/draw/draw_line.c:339:11
    #19 0x7f2ed0cfa2bd in vips_draw_line /home/kleisauke/libvips/build/../libvips/draw/draw_line.c:390:11
    #20 0x000000413783 in tilesource_draw_line /home/kleisauke/vipsdisp/build/../src/tilesource.c:2169:3
    #21 0x00000040d216 in paintbox_update_brush_draw /home/kleisauke/vipsdisp/build/../src/paintbox.c:372:3
    #22 0x00000040c9c2 in paintbox_drag_update /home/kleisauke/vipsdisp/build/../src/paintbox.c:417:4
    #23 0x000000402426 in vipsdisp_BOOLEAN__DOUBLE_DOUBLE_OBJECT /home/kleisauke/vipsdisp/build/src/vipsdispmarshal.c:123:14
    #24 0x7f2ed3997ebb in g_closure_invoke (/lib64/libgobject-2.0.so.0+0x6ebb) (BuildId: e04a34a9d2ebbca4ef8791440a31ca2bef837aa4)
    #25 0x7f2ed39b6808  (/lib64/libgobject-2.0.so.0+0x25808) (BuildId: e04a34a9d2ebbca4ef8791440a31ca2bef837aa4)
    #26 0x7f2ed39b8260  (/lib64/libgobject-2.0.so.0+0x27260) (BuildId: e04a34a9d2ebbca4ef8791440a31ca2bef837aa4)
    #27 0x7f2ed39b8c77 in g_signal_emit_valist (/lib64/libgobject-2.0.so.0+0x27c77) (BuildId: e04a34a9d2ebbca4ef8791440a31ca2bef837aa4)
    #28 0x7f2ed39b8d32 in g_signal_emit (/lib64/libgobject-2.0.so.0+0x27d32) (BuildId: e04a34a9d2ebbca4ef8791440a31ca2bef837aa4)
    #29 0x000000407453 in imageui_drag_update /home/kleisauke/vipsdisp/build/../src/imageui.c:390:2
    #30 0x7f2ecee1cd37  (/lib64/libgtk-4.so.1+0x1cd37) (BuildId: f8c1968c415dee5548b27461f475993aa1a92865)
    #31 0x7f2ed39b8b25  (/lib64/libgobject-2.0.so.0+0x27b25) (BuildId: e04a34a9d2ebbca4ef8791440a31ca2bef837aa4)
    #32 0x7f2ed39b8c77 in g_signal_emit_valist (/lib64/libgobject-2.0.so.0+0x27c77) (BuildId: e04a34a9d2ebbca4ef8791440a31ca2bef837aa4)
    #33 0x7f2ed39b8d32 in g_signal_emit (/lib64/libgobject-2.0.so.0+0x27d32) (BuildId: e04a34a9d2ebbca4ef8791440a31ca2bef837aa4)
    #34 0x7f2ed399c012 in g_cclosure_marshal_VOID__BOXEDv (/lib64/libgobject-2.0.so.0+0xb012) (BuildId: e04a34a9d2ebbca4ef8791440a31ca2bef837aa4)
    #35 0x7f2ed39b8b25  (/lib64/libgobject-2.0.so.0+0x27b25) (BuildId: e04a34a9d2ebbca4ef8791440a31ca2bef837aa4)
    #36 0x7f2ed39b8c77 in g_signal_emit_valist (/lib64/libgobject-2.0.so.0+0x27c77) (BuildId: e04a34a9d2ebbca4ef8791440a31ca2bef837aa4)
    #37 0x7f2ed39b8d32 in g_signal_emit (/lib64/libgobject-2.0.so.0+0x27d32) (BuildId: e04a34a9d2ebbca4ef8791440a31ca2bef837aa4)
    #38 0x7f2eceee10ca  (/lib64/libgtk-4.so.1+0xe10ca) (BuildId: f8c1968c415dee5548b27461f475993aa1a92865)
    #39 0x7f2eceee2111  (/lib64/libgtk-4.so.1+0xe2111) (BuildId: f8c1968c415dee5548b27461f475993aa1a92865)
    #40 0x7f2ecf06103b  (/lib64/libgtk-4.so.1+0x26103b) (BuildId: f8c1968c415dee5548b27461f475993aa1a92865)
    #41 0x7f2ecef287c9  (/lib64/libgtk-4.so.1+0x1287c9) (BuildId: f8c1968c415dee5548b27461f475993aa1a92865)
    #42 0x7f2ecef2961a  (/lib64/libgtk-4.so.1+0x12961a) (BuildId: f8c1968c415dee5548b27461f475993aa1a92865)
    #43 0x7f2ecf234c67  (/lib64/libgtk-4.so.1+0x434c67) (BuildId: f8c1968c415dee5548b27461f475993aa1a92865)
    #44 0x7f2ecf2e02e9  (/lib64/libgtk-4.so.1+0x4e02e9) (BuildId: f8c1968c415dee5548b27461f475993aa1a92865)
    #45 0x7f2ed39b8b25  (/lib64/libgobject-2.0.so.0+0x27b25) (BuildId: e04a34a9d2ebbca4ef8791440a31ca2bef837aa4)
    #46 0x7f2ed39b8c77 in g_signal_emit_valist (/lib64/libgobject-2.0.so.0+0x27c77) (BuildId: e04a34a9d2ebbca4ef8791440a31ca2bef837aa4)
    #47 0x7f2ed39b8d32 in g_signal_emit (/lib64/libgobject-2.0.so.0+0x27d32) (BuildId: e04a34a9d2ebbca4ef8791440a31ca2bef837aa4)
    #48 0x7f2ecf2e24c5  (/lib64/libgtk-4.so.1+0x4e24c5) (BuildId: f8c1968c415dee5548b27461f475993aa1a92865)
    #49 0x7f2ecf2e28c8  (/lib64/libgtk-4.so.1+0x4e28c8) (BuildId: f8c1968c415dee5548b27461f475993aa1a92865)
    #50 0x7f2ed39b8b25  (/lib64/libgobject-2.0.so.0+0x27b25) (BuildId: e04a34a9d2ebbca4ef8791440a31ca2bef837aa4)
    #51 0x7f2ed39b8c77 in g_signal_emit_valist (/lib64/libgobject-2.0.so.0+0x27c77) (BuildId: e04a34a9d2ebbca4ef8791440a31ca2bef837aa4)
    #52 0x7f2ed39b8d32 in g_signal_emit (/lib64/libgobject-2.0.so.0+0x27d32) (BuildId: e04a34a9d2ebbca4ef8791440a31ca2bef837aa4)
    #53 0x7f2ecf2b4039  (/lib64/libgtk-4.so.1+0x4b4039) (BuildId: f8c1968c415dee5548b27461f475993aa1a92865)
    #54 0x7f2ecfb05021  (/lib64/libglib-2.0.so.0+0x47021) (BuildId: a55397c41cae330fee603dd8a00cf8cd2ac399ca)
    #55 0x7f2ecfb01f23  (/lib64/libglib-2.0.so.0+0x43f23) (BuildId: a55397c41cae330fee603dd8a00cf8cd2ac399ca)
    #56 0x7f2ecfb06037  (/lib64/libglib-2.0.so.0+0x48037) (BuildId: a55397c41cae330fee603dd8a00cf8cd2ac399ca)
    #57 0x7f2ecfb061e2 in g_main_context_iteration (/lib64/libglib-2.0.so.0+0x481e2) (BuildId: a55397c41cae330fee603dd8a00cf8cd2ac399ca)
    #58 0x7f2ecfcdd02c in g_application_run (/lib64/libgio-2.0.so.0+0xbd02c) (BuildId: 00cd86f4a9b73cecf6e2d43069359410cdc95433)
    #59 0x00000040d416 in main /home/kleisauke/vipsdisp/build/../src/main.c:39:15
    #60 0x7f2ecec0a680 in __libc_start_call_main (/lib64/libc.so.6+0x3680) (BuildId: 5bd941be836f538fe5e10eff508f7f5dd94905a6)
    #61 0x7f2ecec0a797 in __libc_start_main@GLIBC_2.2.5 (/lib64/libc.so.6+0x3797) (BuildId: 5bd941be836f538fe5e10eff508f7f5dd94905a6)
    #62 0x000000402174 in _start (/usr/bin/vipsdisp+0x402174) (BuildId: 2569ed18f59712e895e736cf35e72f842cfd5576)

==303639==Register values:
rax = 0x000000fe0003b2e2  rbx = 0x00007fff8c5f8300  rcx = 0x00007fff8c5f8300  rdx = 0x0000000000000000  
rdi = 0x00007c6ec68de840  rsi = 0x0000000000000184  rbp = 0x00007fff8c5f8090  rsp = 0x00007fff8c5f8090  
 r8 = 0x00007fff8c5f7fd0   r9 = 0x00007f2ed0da5d30  r10 = 0x0000000000000000  r11 = 0x0000000000000001  
r12 = 0x0000000000000184  r13 = 0x0000000000000a03  r14 = 0x00007c6ec68de840  r15 = 0x00007c6ec694c040  
AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV (/lib64/libgobject-2.0.so.0+0x2fe5d) (BuildId: e04a34a9d2ebbca4ef8791440a31ca2bef837aa4) in g_type_check_instance
==303639==ABORTING
Aborted                    LD_PRELOAD=$ASAN_DSO vipsdisp

@kleisauke

Copy link
Copy Markdown
Member Author

I've not seen this from nip4, maybe some change in this PR has done it.

Looking at the nip4 guards, this one seems a bit suspicious:

vipsdisp/src/paintbox.c

Lines 441 to 446 in e453127

VipsImage *image;
if ((image = tilesource_get_base_image(tilesource)) &&
iimage->value.ii->image != image) {
// will be removed on next GC, unless someone takes ownership
g_object_ref(image);

(i.e. we take a ref to the tile source's base image there)

which is only called in nip4:

vipsdisp/src/paintbox.c

Lines 548 to 550 in e453127

#ifdef NIP4
paintbox_update_model(paintbox);
#endif /*NIP4*/

@kleisauke

Copy link
Copy Markdown
Member Author

... it looks like disabling the operation cache makes this easier to reproduce:

--- a/src/main.c
+++ b/src/main.c
@@ -20,6 +20,8 @@ main(int argc, char **argv)
 	if (VIPS_INIT(argv[0]))
 		vips_error_exit("unable to start libvips");
 
+	vips_cache_set_max(0);
+
 #ifdef DEBUG
 	printf("DEBUG on in main.c\n");
 	vips_leak_set(TRUE);

but I haven't been able to reproduce the same crash in nip4.

@jcupitt

jcupitt commented Aug 23, 2026

Copy link
Copy Markdown
Member

I added most of your new commits back into nip4 (no need to sync again, just to make it easier next time).

@jcupitt

jcupitt commented Aug 23, 2026

Copy link
Copy Markdown
Member

I've not been able to make vipsdisp fall over! But maybe I've not tried hard enough.

Nice job!

@kleisauke

Copy link
Copy Markdown
Member Author

I can still provoke a crash (and disabling the operation cache makes it even easier to reproduce). :(

It's very weird, there must be something I've overlooked, since I can't reproduce it in nip4.

@jcupitt

jcupitt commented Aug 24, 2026

Copy link
Copy Markdown
Member

You're right :(

diff --git a/src/main.c b/src/main.c
index 69c2d4c..1b11eae 100644
--- a/src/main.c
+++ b/src/main.c
@@ -1,6 +1,6 @@
 /*
-#define DEBUG
  */
+#define DEBUG
 
 #include "package.h"
 
@@ -23,6 +23,7 @@ main(int argc, char **argv)
 #ifdef DEBUG
        printf("DEBUG on in main.c\n");
        vips_leak_set(TRUE);
+       vips_cache_set_max(0);
 
        g_log_set_always_fatal(
                G_LOG_FLAG_RECURSION |

Makes it quite easy to trigger.

I tried a few things (moving eval, moving invalidate) and didn't manage to fix it, I'll keep looking.

@kleisauke

Copy link
Copy Markdown
Member Author

The good news is that I can no longer reproduce the issue when closing the "Info bar" view. The bad news is that this explains why I couldn't reproduce it in nip4: opening that view in nip4 causes the same crash. 😅

@jcupitt

jcupitt commented Aug 24, 2026

Copy link
Copy Markdown
Member

Oh no!

@jcupitt

jcupitt commented Aug 29, 2026

Copy link
Copy Markdown
Member

I've finished the nip4 paintbox, for now anyway. Shall we merge this PR, then I can make another PR for the paintbox?

I've not looked into the infobar crash :( I should do that.

@kleisauke

Copy link
Copy Markdown
Member Author

Re-synced with libvips/nip4@e73b551, removed the draft status and added a TODO list for the remaining crashes in the PR description.

@kleisauke

Copy link
Copy Markdown
Member Author

If you want, I could open a PR at nip4 for commit c1ebc01, 6f0a07f, 074014d and 3a3be2c.

@jcupitt

jcupitt commented Aug 29, 2026

Copy link
Copy Markdown
Member

Sure, that'd be great!

@kleisauke

Copy link
Copy Markdown
Member Author

Oh wait, I just noticed the +4,799 -1,125 diff. Perhaps we should split this PR up?

@jcupitt

jcupitt commented Aug 29, 2026

Copy link
Copy Markdown
Member

I'm OK leaving at one big commit. vipsdisp feels like a subsidiary project now, so I don't think the merge history needs to be especially readable.

I think the paintbox might be quite handy for vipsdisp! It looks great!

I noticed ^Z / ^Y aren't working, I think you maybe need to add to app.c:

        { "win.undo", { "<Primary>z", NULL } },
        { "win.redo", { "<Primary>y", NULL } },

jcupitt pushed a commit to libvips/nip4 that referenced this pull request Aug 29, 2026
* Add missing nip4 guards

* Prevent unconditional use of nip4 symbols

* Fix `-Wmaybe-uninitialized` warning

* Fix `-Wc23-extensions` Clang warning
@kleisauke

Copy link
Copy Markdown
Member Author

I noticed ^Z / ^Y aren't working, I think you maybe need to add to app.c:

        { "win.undo", { "<Primary>z", NULL } },
        { "win.redo", { "<Primary>y", NULL } },

Good catch, I forgot to re-sync app.c. It should be fixed now. :)

@jcupitt

jcupitt commented Aug 29, 2026

Copy link
Copy Markdown
Member

Nice!

I got the "!object_already_finalized" crash one time, and an object leak from (I think?) the undo system another time, so this probably isn't good enough for release.

Let's merge anyway, I'll be more likely to use it and fix it if it's in master.

@kleisauke

Copy link
Copy Markdown
Member Author

Great! Merging this would also fix the build failure on master when building against libvips master (since we moved the VipsRect autoptr cleanup there).

Just wondering, did you get the !object_already_finalized crash with the infobar view open? I could only reproduce the crash when it was open.

@jcupitt

jcupitt commented Aug 29, 2026

Copy link
Copy Markdown
Member

Yes, I had the infobar + paintbox open :( I don't get crashes in nip4 with them both open though, I'm not sure I understand what's going on.

@jcupitt
jcupitt merged commit 4d3677e into libvips:master Aug 29, 2026
1 check passed
@jcupitt

jcupitt commented Aug 29, 2026

Copy link
Copy Markdown
Member

Thank you for doing this work!

@kleisauke

Copy link
Copy Markdown
Member Author

No problem!

I don't get crashes in nip4 with them both open though, I'm not sure I understand what's going on.

Ah, I was testing with this change in nip4:

--- a/src/main.c
+++ b/src/main.c
@@ -421,9 +421,7 @@ main_startup(int argc, char **argv)
      *
      * FIXME .. link this to prefs?
      */
-    vips_cache_set_max(10000);
-    vips_cache_set_max_mem(1000 * 1024 * 1024);
-    vips_cache_set_max_files(1000);
+    vips_cache_set_max(0);
 }
 
 void

(IIRC, I also wasn't able to reproduce it without this)

@kleisauke
kleisauke deleted the nip4-sync branch August 29, 2026 13:54
@jcupitt

jcupitt commented Aug 30, 2026

Copy link
Copy Markdown
Member

I've been trying to track the infobar problem down and found something strange.

I patched libvips with this:

diff --git a/libvips/include/vips/internal.h b/libvips/include/vips/internal.h
index 7854f29d8..43f352070 100644
--- a/libvips/include/vips/internal.h
+++ b/libvips/include/vips/internal.h
@@ -435,6 +435,8 @@ char *vips__get_iso8601(void);
 
 gboolean vips__image_is_cicp_hdr(VipsImage *image);
 
+int vips__object_n_objects(void);
+
 #ifdef __cplusplus
 }
 #endif /*__cplusplus*/
diff --git a/libvips/iofuncs/init.c b/libvips/iofuncs/init.c
index 0d6711df3..20ed929f8 100644
--- a/libvips/iofuncs/init.c
+++ b/libvips/iofuncs/init.c
@@ -390,6 +390,8 @@ vips_verbose(void)
 static int
 vips_leak(void)
 {
+       printf("vips_leak: n_objects = %d\n", vips__object_n_objects());
+
        char txt[1024];
        VipsBuf buf = VIPS_BUF_STATIC(txt);
        int n_leaks;
diff --git a/libvips/iofuncs/object.c b/libvips/iofuncs/object.c
index 1a414b023..339861603 100644
--- a/libvips/iofuncs/object.c
+++ b/libvips/iofuncs/object.c
@@ -3172,6 +3172,12 @@ vips_object_print_all_cb(VipsObject *object, int *n, void *b)
        return NULL;
 }
 
+int
+vips__object_n_objects(void)
+{
+       return g_hash_table_size(vips__object_all);
+}
+
 int
 vips__object_leak(void)
 {
diff --git a/libvips/iofuncs/operation.c b/libvips/iofuncs/operation.c
index 9288f4a69..3a2acf1f7 100644
--- a/libvips/iofuncs/operation.c
+++ b/libvips/iofuncs/operation.c
@@ -595,6 +595,8 @@ vips_operation_build(VipsObject *object)
        printf("\n");
 #endif /*VIPS_DEBUG*/
 
+       printf("vips_operation_build: n_objects = %d\n", vips__object_n_objects());
+
        if (class->flags & VIPS_OPERATION_BLOCKED) {
                vips_error(VIPS_OBJECT_CLASS(class)->nickname,
                        "%s", _("operation is blocked"));

ie. print the total number of active objects on operation build and on shutdown.

I disabled the operation cache in nip4 with vips_cache_set_max(0), then opened an image and viewed it with the infobar turned on. I see:

# startup
main: sizeof(HeapNode) == 24
set max file descriptors to 65535
definitions init
ws init
vips_operation_build: n_objects = 3
vips_operation_build: n_objects = 6
vips_operation_build: n_objects = 7
vips_operation_build: n_objects = 10
vips_operation_build: n_objects = 21
vips_operation_build: n_objects = 24
vips_operation_build: n_objects = 25
vips_operation_build: n_objects = 90
vips_operation_build: n_objects = 94
vips_operation_build: n_objects = 234
vips_operation_build: n_objects = 238
vips_operation_build: n_objects = 19
vips_operation_build: n_objects = 27
vips_operation_build: n_objects = 29
vips_operation_build: n_objects = 27
vips_operation_build: n_objects = 31
vips_operation_build: n_objects = 25
vips_operation_build: n_objects = 31
vips_operation_build: n_objects = 27
vips_operation_build: n_objects = 31
vips_operation_build: n_objects = 26
vips_operation_build: n_objects = 31
vips_operation_build: n_objects = 27
vips_operation_build: n_objects = 31
vips_operation_build: n_objects = 25
vips_operation_build: n_objects = 31
vips_operation_build: n_objects = 30
vips_operation_build: n_objects = 33

# open imagewindow
vips_operation_build: n_objects = 27
vips_operation_build: n_objects = 31
vips_operation_build: n_objects = 34
vips_operation_build: n_objects = 38
vips_operation_build: n_objects = 39
vips_operation_build: n_objects = 42
vips_operation_build: n_objects = 56

# start to move mouse
vips_operation_build: n_objects = 62
vips_operation_build: n_objects = 65
vips_operation_build: n_objects = 67
vips_operation_build: n_objects = 69
vips_operation_build: n_objects = 72
...
vips_operation_build: n_objects = 515
vips_operation_build: n_objects = 517
vips_operation_build: n_objects = 519
vips_operation_build: n_objects = 522

# ^Q
vips_operation_build: n_objects = 503
vips_operation_build: n_objects = 506
vips_operation_build: n_objects = 506
vips_threadset_free: peak of 35 threads
vips_leak: n_objects = 0

The getpoint() in infobar is adding 11 new objects (8 of them images) each time it is called! These objects are unreffed on ^Q, but only there, not before. It's easy to get 20,000 active objects after maybe a minute.

I'm unsure where the refs are being held.

I tried a standalone program:

/* compile with
 *  gcc -g -Wall point.c `pkg-config vips --cflags --libs`
 */

#include <vips/vips.h>

int
main(int argc, char **argv)
{   
    if (VIPS_INIT(argv[0]))
        vips_error_exit(NULL);

    vips_cache_set_max(0);

    VipsImage *image;
    if (!(image = vips_image_new_from_file(argv[1], NULL)))
        vips_error_exit(NULL);

    for (int i = 0; i < 1000; i++) {
        int x = i % image->Xsize;
        int y = i / image->Xsize;

        double *v;
        int n;
        if (vips_getpoint(image, &v, &n, x, y, 
            "unpack_complex", TRUE,
            NULL))
            vips_error_exit(NULL);
    }
    
    VIPS_UNREF(image);
    
    return 0;
}

But it behaves as you'd expect, with a low and steady number of objects.

@jcupitt

jcupitt commented Aug 30, 2026

Copy link
Copy Markdown
Member

I reworked getpoint to make it as simple as possible and the object count still climbs, so I think this means the "leak" is happening upstream.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants