From 100a72ca3c2bfa3b9b595c3f980eae198de20e41 Mon Sep 17 00:00:00 2001 From: Andrii Ryzhkov Date: Wed, 29 Apr 2026 11:26:14 +0200 Subject: [PATCH 1/2] Fix corrupt DNG on libtiff builds that drop CFA tag info across SubIFD write --- src/imageio/imageio_dng.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/imageio/imageio_dng.c b/src/imageio/imageio_dng.c index 8d1c65addf70..010b0071fa27 100644 --- a/src/imageio/imageio_dng.c +++ b/src/imageio/imageio_dng.c @@ -184,10 +184,11 @@ int dt_imageio_dng_write_cfa_bayer(const char *filename, g_unlink(filename); return 1; } - // libtiff entered INSUBIFD mode when the IFD0 carrying TIFFTAG_SUBIFD - // was written; subsequent TIFFSetField + scanline writes populate - // the SubIFD without an explicit TIFFCreateDirectory call (whose - // return-value convention changed between libtiff versions) + // re-register default tag info on some libtiff builds CFA/DNG + // extension tags would be lost across the IFD write, breaking + // CFAREPEATPATTERNDIM / CFAPATTERN. return value differs across + // libtiff versions; not checked + TIFFCreateDirectory(tif); } // raw payload IFD: single IFD when no preview, otherwise SubIFD0 @@ -321,7 +322,9 @@ int dt_imageio_dng_write_linear(const char *filename, g_unlink(filename); return 1; } - // libtiff is in INSUBIFD mode after IFD0 was written with TIFFTAG_SUBIFD + // re-initialize directory state so DNG extension tag info is + // available on the SubIFD (see comment in write_cfa_bayer) + TIFFCreateDirectory(tif); } // baseline TIFF tags, 3 samples per pixel (demosaicked) From d5af7b6b37df48871a030a16fad0a1edc897ccb8 Mon Sep 17 00:00:00 2001 From: Andrii Ryzhkov Date: Wed, 29 Apr 2026 11:54:17 +0200 Subject: [PATCH 2/2] Add libtiff error handlers in DNG writer to avoid third-party handler crashes --- src/imageio/imageio_dng.c | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/imageio/imageio_dng.c b/src/imageio/imageio_dng.c index 010b0071fa27..429c3723e4fc 100644 --- a/src/imageio/imageio_dng.c +++ b/src/imageio/imageio_dng.c @@ -39,6 +39,38 @@ // these as float/double arrays and handles the conversion; we just pass // the values as double +// libtiff error/warning handlers — replacing whatever else in the +// process may have installed (e.g. ImageMagick's, which crashes on +// NULL exception context). these just log to stderr and never abort +static void _dt_dng_tiff_warning(const char *module, + const char *fmt, va_list ap) +{ + if(darktable.unmuted & DT_DEBUG_IMAGEIO) + { + fprintf(stderr, "%11.4f [imageio_dng] warning: %s: ", + dt_get_wtime() - darktable.start_wtime, + module ? module : "(none)"); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + } +} + +static void _dt_dng_tiff_error(const char *module, + const char *fmt, va_list ap) +{ + fprintf(stderr, "%11.4f [imageio_dng] error: %s: ", + dt_get_wtime() - darktable.start_wtime, + module ? module : "(none)"); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); +} + +static void _install_dng_tiff_handlers(void) +{ + TIFFSetWarningHandler(_dt_dng_tiff_warning); + TIFFSetErrorHandler(_dt_dng_tiff_error); +} + // map the dcraw 2x2 CFA filters word to 4 single-byte channel indices // for the DNG CFAPattern tag: 0=R, 1=G, 2=B, following DNG spec §A.3.1 static void _cfa_bytes_from_filters(uint32_t filters, uint8_t out[4]) @@ -160,6 +192,8 @@ int dt_imageio_dng_write_cfa_bayer(const char *filename, if(!filename || !cfa || !img || width <= 0 || height <= 0) return 1; + _install_dng_tiff_handlers(); + #ifdef _WIN32 wchar_t *wfilename = g_utf8_to_utf16(filename, -1, NULL, NULL, NULL); TIFF *tif = TIFFOpenW(wfilename, "wl"); @@ -300,6 +334,8 @@ int dt_imageio_dng_write_linear(const char *filename, if(!filename || !rgb || !img || width <= 0 || height <= 0) return 1; + _install_dng_tiff_handlers(); + #ifdef _WIN32 wchar_t *wfilename = g_utf8_to_utf16(filename, -1, NULL, NULL, NULL); TIFF *tif = TIFFOpenW(wfilename, "wl");