diff --git a/src/common/exif.cc b/src/common/exif.cc index d8f4b583ffbc..a106b9cf3ea0 100644 --- a/src/common/exif.cc +++ b/src/common/exif.cc @@ -2442,6 +2442,27 @@ gboolean dt_exif_read(dt_image_t *img, } } +int dt_exif_write_exv(uint8_t *blob, + uint32_t size, + const char *path, + const int compressed) +{ + try + { + std::unique_ptr image(Exiv2::ImageFactory::create(Exiv2::ImageType::exv, WIDEN(path))); + image->writeMetadata(); + } + catch(const Exiv2::AnyError &e) + { + dt_print(DT_DEBUG_IMAGEIO, + "[exiv2 dt_exif_write_blob] %s: %s", + path, + e.what()); + return 0; + } + return 1; +} + int dt_exif_write_blob(uint8_t *blob, uint32_t size, const char *path, diff --git a/src/common/exif.h b/src/common/exif.h index a679f402ba6e..dbd42c68d6ca 100644 --- a/src/common/exif.h +++ b/src/common/exif.h @@ -92,6 +92,12 @@ int dt_exif_read_blob(uint8_t **blob, const char *path, const dt_imgid_t imgid, /** Reads exif tags that are not cached in the database */ void dt_exif_img_check_additional_tags(dt_image_t *img, const char *filename); +/** create empty metadata file */ +int dt_exif_write_exv(uint8_t *blob, + uint32_t size, + const char *path, + const int compressed); + /** write blob to file exif. merges with existing exif information.*/ int dt_exif_write_blob(uint8_t *blob, uint32_t size, const char *path, const int compressed); diff --git a/src/imageio/format/jxl.c b/src/imageio/format/jxl.c index c81b47d9dcbe..935768d5918d 100644 --- a/src/imageio/format/jxl.c +++ b/src/imageio/format/jxl.c @@ -309,8 +309,8 @@ int write_image(struct dt_imageio_module_data_t *data, if(exif && exif_len > 0) LIBJXL_ASSERT(JxlEncoderUseBoxes(encoder)); - /* TODO: workaround; remove when exiv2 implements JXL BMFF write support and use dt_exif_write_blob() after - * closing file instead */ + // /* TODO: workaround; remove when exiv2 implements JXL BMFF write support and use dt_exif_write_blob() after + // * closing file instead */ if(exif && exif_len > 0) { // Prepend the 4 byte (zero) offset to the blob before writing @@ -319,8 +319,8 @@ int write_image(struct dt_imageio_module_data_t *data, if(!exif_buf) JXL_FAIL("could not allocate Exif buffer of size %zu", (size_t)(exif_len + 4)); memmove(exif_buf + 4, exif, exif_len); - // Exiv2 < 0.28 doesn't support Brotli compressed boxes - LIBJXL_ASSERT(JxlEncoderAddBox(encoder, "Exif", exif_buf, exif_len + 4, JXL_FALSE)); + // Exiv2 >= 0.28 (released 2023-05-08) supports Brotli compressed boxes + LIBJXL_ASSERT(JxlEncoderAddBox(encoder, "Exif", exif_buf, exif_len + 4, JXL_TRUE)); } /* TODO: workaround; remove when exiv2 implements JXL BMFF write support and update flags() */ @@ -332,13 +332,13 @@ int write_image(struct dt_imageio_module_data_t *data, if(xmp_string && (xmp_len = strlen(xmp_string)) > 0) { - // Exiv2 < 0.28 doesn't support Brotli compressed boxes + // Exiv2 >= 0.28 (released 2023-05-08) supports Brotli compressed boxes LIBJXL_ASSERT(JxlEncoderAddBox(encoder, "xml ", - (const uint8_t *)xmp_string, xmp_len, JXL_FALSE)); + (const uint8_t *)xmp_string, xmp_len, JXL_TRUE)); } } - JxlPixelFormat pixel_format = { 3, JXL_TYPE_FLOAT, JXL_NATIVE_ENDIAN, 0 }; +f JxlPixelFormat pixel_format = { 3, JXL_TYPE_FLOAT, JXL_NATIVE_ENDIAN, 0 }; // Fix pixel stride const size_t pixels_size = width * height * 3 * sizeof(float); diff --git a/src/imageio/imageio.c b/src/imageio/imageio.c index d36678c09762..5d4f2bdb443d 100644 --- a/src/imageio/imageio.c +++ b/src/imageio/imageio.c @@ -1436,7 +1436,7 @@ gboolean dt_imageio_export_with_flags(const dt_imgid_t imgid, if(!ignore_exif && (!strcmp(format->mime(NULL), "image/avif") || !strcmp(format->mime(NULL), "image/x-exr") - || !strcmp(format->mime(NULL), "image/jxl") + // || !strcmp(format->mime(NULL), "image/jxl") || !strcmp(format->mime(NULL), "image/x-xcf"))) { const int32_t meta_all = @@ -1446,6 +1446,40 @@ gboolean dt_imageio_export_with_flags(const dt_imgid_t imgid, md_flags_set = metadata ? (metadata->flags & meta_all) == meta_all : FALSE; } + uint8_t *exif_profile0 = NULL; // Exif data should be 65536 bytes + // max, but if original size is + // close to that, adding new tags + // could make it go over that... so + // let it be and see what happens + // when we write the image + char pathname[PATH_MAX] = { 0 }; + gboolean from_cache = TRUE; + dt_image_full_path(imgid, pathname, sizeof(pathname), &from_cache); + + // last param is dng mode, it's false here + const int length0 = dt_exif_read_blob(&exif_profile0, pathname, imgid, sRGB, + processed_width, processed_height, FALSE); + + char* exv_filename = g_malloc(strlen(filename) + 5); + snprintf(exv_filename, strlen(filename) + 5, "%s%s", filename, ".exv"); + + // empty metadata file (XXX call write_blob inside there for us) + dt_exif_write_exv(exif_profile0, length0, exv_filename, 1); + + // write data into metadata file + dt_exif_write_blob(exif_profile0, length0, exv_filename, 1); + + free(exif_profile0); + + /* now write xmp into that container, if possible */ + if(copy_metadata + && (format->flags(format_params) & FORMAT_FLAGS_SUPPORT_XMP)) + { + dt_exif_xmp_attach_export(imgid, exv_filename, metadata, &dev, &pipe); + // no need to cancel the export if this fail + } + + // write image including filtered metadata from .exv if(!ignore_exif && md_flags_set) { uint8_t *exif_profile = NULL; // Exif data should be 65536 bytes @@ -1454,12 +1488,8 @@ gboolean dt_imageio_export_with_flags(const dt_imgid_t imgid, // could make it go over that... so // let it be and see what happens // when we write the image - char pathname[PATH_MAX] = { 0 }; - gboolean from_cache = TRUE; - dt_image_full_path(imgid, pathname, sizeof(pathname), &from_cache); - // last param is dng mode, it's false here - const int length = dt_exif_read_blob(&exif_profile, pathname, imgid, sRGB, + const int length = dt_exif_read_blob(&exif_profile, exv_filename, imgid, sRGB, processed_width, processed_height, FALSE); res = (format->write_image(format_params, filename, outbuf, icc_type, @@ -1475,16 +1505,11 @@ gboolean dt_imageio_export_with_flags(const dt_imgid_t imgid, &pipe, export_masks)) != 0; } + g_free(exv_filename); + if(res) goto error; - /* now write xmp into that container, if possible */ - if(copy_metadata - && (format->flags(format_params) & FORMAT_FLAGS_SUPPORT_XMP)) - { - dt_exif_xmp_attach_export(imgid, filename, metadata, &dev, &pipe); - // no need to cancel the export if this fail - } dt_dev_pixelpipe_cleanup(&pipe); dt_dev_cleanup(&dev);