diff --git a/ChangeLog b/ChangeLog index 90752dd334..629fb4c554 100644 --- a/ChangeLog +++ b/ChangeLog @@ -16,6 +16,8 @@ master UltraHDR image - fix cpp binding leak [VivitionDeveloper] - add "gainmap" to VipsKeep +- add vips_image_get_gainmap() +- crop and resize gainmaps automatically 8.17.3 diff --git a/doc/libvips-header.md b/doc/libvips-header.md index b229d88cf9..169a8f1151 100644 --- a/doc/libvips-header.md +++ b/doc/libvips-header.md @@ -72,6 +72,7 @@ copied between images efficiently. * [method@Image.get_orientation_swap] * [method@Image.get_tile_width] * [method@Image.get_tile_height] +* [method@Image.get_gainmap] * [method@Image.get_concurrency] * [method@Image.get_data] * [method@Image.init_fields] diff --git a/libvips/conversion/extract.c b/libvips/conversion/extract.c index 1307d27aa9..00a368dc9b 100644 --- a/libvips/conversion/extract.c +++ b/libvips/conversion/extract.c @@ -140,23 +140,27 @@ vips_extract_area_build(VipsObject *object) VipsConversion *conversion = VIPS_CONVERSION(object); VipsExtractArea *extract = (VipsExtractArea *) object; + VipsImage *in; + if (VIPS_OBJECT_CLASS(vips_extract_area_parent_class)->build(object)) return -1; - if (extract->left + extract->width > extract->in->Xsize || - extract->top + extract->height > extract->in->Ysize || + in = extract->in; + + if (extract->left + extract->width > in->Xsize || + extract->top + extract->height > in->Ysize || extract->left < 0 || extract->top < 0 || extract->width <= 0 || extract->height <= 0) { vips_error(class->nickname, "%s", _("bad extract area")); return -1; } - if (vips_image_pio_input(extract->in) || - vips_check_coding_known(class->nickname, extract->in)) + if (vips_image_pio_input(in) || + vips_check_coding_known(class->nickname, in)) return -1; if (vips_image_pipelinev(conversion->out, - VIPS_DEMAND_STYLE_THINSTRIP, extract->in, NULL)) + VIPS_DEMAND_STYLE_THINSTRIP, in, NULL)) return -1; conversion->out->Xsize = extract->width; @@ -166,7 +170,7 @@ vips_extract_area_build(VipsObject *object) if (vips_image_generate(conversion->out, vips_start_one, vips_extract_area_gen, vips_stop_one, - extract->in, extract)) + in, extract)) return -1; return 0; diff --git a/libvips/conversion/flip.c b/libvips/conversion/flip.c index 3cd160dd16..18e8179e91 100644 --- a/libvips/conversion/flip.c +++ b/libvips/conversion/flip.c @@ -190,32 +190,35 @@ vips_flip_build(VipsObject *object) VipsConversion *conversion = VIPS_CONVERSION(object); VipsFlip *flip = (VipsFlip *) object; + VipsImage *in; VipsGenerateFn generate_fn; if (VIPS_OBJECT_CLASS(vips_flip_parent_class)->build(object)) return -1; - if (vips_image_pio_input(flip->in)) + in = flip->in; + + if (vips_image_pio_input(in)) return -1; if (vips_image_pipelinev(conversion->out, - VIPS_DEMAND_STYLE_THINSTRIP, flip->in, NULL)) + VIPS_DEMAND_STYLE_THINSTRIP, in, NULL)) return -1; if (flip->direction == VIPS_DIRECTION_HORIZONTAL) { generate_fn = vips_flip_horizontal_gen; - conversion->out->Xoffset = flip->in->Xsize; + conversion->out->Xoffset = in->Xsize; conversion->out->Yoffset = 0; } else { generate_fn = vips_flip_vertical_gen; conversion->out->Xoffset = 0; - conversion->out->Yoffset = flip->in->Ysize; + conversion->out->Yoffset = in->Ysize; } if (vips_image_generate(conversion->out, vips_start_one, generate_fn, vips_stop_one, - flip->in, flip)) + in, flip)) return -1; return 0; diff --git a/libvips/conversion/rot.c b/libvips/conversion/rot.c index 0daf1ecad7..3830d8dda4 100644 --- a/libvips/conversion/rot.c +++ b/libvips/conversion/rot.c @@ -288,46 +288,48 @@ vips_rot_build(VipsObject *object) VipsConversion *conversion = VIPS_CONVERSION(object); VipsRot *rot = (VipsRot *) object; + VipsImage *in; VipsGenerateFn generate_fn; VipsDemandStyle hint; if (VIPS_OBJECT_CLASS(vips_rot_parent_class)->build(object)) return -1; + in = rot->in; + if (rot->angle == VIPS_ANGLE_D0) - return vips_image_write(rot->in, conversion->out); + return vips_image_write(in, conversion->out); - if (vips_image_pio_input(rot->in)) + if (vips_image_pio_input(in)) return -1; - hint = rot->angle == VIPS_ANGLE_D180 - ? VIPS_DEMAND_STYLE_THINSTRIP - : VIPS_DEMAND_STYLE_SMALLTILE; + hint = rot->angle == VIPS_ANGLE_D180 ? + VIPS_DEMAND_STYLE_THINSTRIP : VIPS_DEMAND_STYLE_SMALLTILE; - if (vips_image_pipelinev(conversion->out, hint, rot->in, NULL)) + if (vips_image_pipelinev(conversion->out, hint, in, NULL)) return -1; switch (rot->angle) { case VIPS_ANGLE_D90: generate_fn = vips_rot90_gen; - conversion->out->Xsize = rot->in->Ysize; - conversion->out->Ysize = rot->in->Xsize; - conversion->out->Xoffset = rot->in->Ysize; + conversion->out->Xsize = in->Ysize; + conversion->out->Ysize = in->Xsize; + conversion->out->Xoffset = in->Ysize; conversion->out->Yoffset = 0; break; case VIPS_ANGLE_D180: generate_fn = vips_rot180_gen; - conversion->out->Xoffset = rot->in->Xsize; - conversion->out->Yoffset = rot->in->Ysize; + conversion->out->Xoffset = in->Xsize; + conversion->out->Yoffset = in->Ysize; break; case VIPS_ANGLE_D270: generate_fn = vips_rot270_gen; - conversion->out->Xsize = rot->in->Ysize; - conversion->out->Ysize = rot->in->Xsize; + conversion->out->Xsize = in->Ysize; + conversion->out->Ysize = in->Xsize; conversion->out->Xoffset = 0; - conversion->out->Yoffset = rot->in->Xsize; + conversion->out->Yoffset = in->Xsize; break; default: @@ -340,7 +342,7 @@ vips_rot_build(VipsObject *object) if (vips_image_generate(conversion->out, vips_start_one, generate_fn, vips_stop_one, - rot->in, rot)) + in, rot)) return -1; return 0; diff --git a/libvips/foreign/jpegsave.c b/libvips/foreign/jpegsave.c index 41d6e201e5..951cef2541 100644 --- a/libvips/foreign/jpegsave.c +++ b/libvips/foreign/jpegsave.c @@ -148,7 +148,7 @@ vips_foreign_save_jpeg_build(VipsObject *object) jpeg->subsample_mode = jpeg->no_subsample ? VIPS_FOREIGN_SUBSAMPLE_OFF : VIPS_FOREIGN_SUBSAMPLE_AUTO; - if (vips_image_get_typeof(save->ready, "gainmap") || + if (vips_image_get_typeof(save->ready, "gainmap-data") || save->ready->Type == VIPS_INTERPRETATION_scRGB) { /* Pass on to uhdrsave. */ diff --git a/libvips/foreign/uhdrload.c b/libvips/foreign/uhdrload.c index 8441151a48..572c490d22 100644 --- a/libvips/foreign/uhdrload.c +++ b/libvips/foreign/uhdrload.c @@ -484,9 +484,25 @@ vips_foreign_load_uhdr_set_metadata(VipsForeignLoadUhdr *uhdr, VipsImage *out) if ((mem_block = uhdr_dec_get_gainmap_image(uhdr->dec))) { // attach as a compressed JPG - g_info("attaching gainmap"); + printf("attaching gainmap-data\n"); vips_image_set_blob_copy(out, - "gainmap", mem_block->data, mem_block->data_sz); + "gainmap-data", mem_block->data, mem_block->data_sz); + + // if the shrink is not 1, load and attach the gainmap with the same + // shrink + if (uhdr->shrink != 1) { + VipsImage *gainmap; + + printf("shrink set, attaching gainmap\n"); + + if (vips_jpegload_buffer(mem_block->data, mem_block->data_sz, + &gainmap, + "shrink", uhdr->shrink, + NULL)) + return -1; + vips_image_set_image(out, "gainmap", gainmap); + VIPS_UNREF(gainmap); + } } VIPS_SETSTR(out->filename, diff --git a/libvips/foreign/uhdrsave.c b/libvips/foreign/uhdrsave.c index 90d7d02515..d09acf0fc7 100644 --- a/libvips/foreign/uhdrsave.c +++ b/libvips/foreign/uhdrsave.c @@ -243,20 +243,8 @@ static int vips_foreign_save_uhdr_set_compressed_gainmap(VipsForeignSaveUhdr *uhdr, VipsImage *image) { - const void *data; - size_t length; - uhdr_error_info_t error_info; - g_info("attaching compressed gainmap"); - if (vips_image_get_blob(image, "gainmap", &data, &length)) - return -1; - uhdr_compressed_image_t gainmap_image = { - .data = (void *) data, - .data_sz = length, - .capacity = length, - }; - uhdr_gainmap_metadata_t metadata; if (image_get_array_float(image, "gainmap-max-content-boost", &metadata.max_content_boost[0], 3) || @@ -276,13 +264,57 @@ vips_foreign_save_uhdr_set_compressed_gainmap(VipsForeignSaveUhdr *uhdr, "gainmap-use-base-cg", &metadata.use_base_cg)) return -1; - error_info = - uhdr_enc_set_gainmap_image(uhdr->enc, &gainmap_image, &metadata); - if (error_info.error_code) { - vips__uhdr_error(&error_info); - return -1; + /* If there's a processed gainmap, compress and attach that. Otherwise + * attach the compressed gainmap from the input image. + */ + const void *data; + size_t length; + void *to_free; + + to_free = NULL; + if (vips_image_get_typeof(image, "gainmap")) { + printf("gainmap image present, recompressing\n"); + + VipsImage *gainmap; + if (vips_image_get_image(image, "gainmap", &gainmap)) + return -1; + + if (vips_jpegsave_buffer(gainmap, &to_free, &length, NULL)) { + VIPS_UNREF(gainmap); + return -1; + } + + VIPS_UNREF(gainmap); + + data = to_free; + } + else if (vips_image_get_typeof(image, "gainmap-data")) { + printf("no gainmap image, using pre-compressed gainmap\n"); + + if (vips_image_get_blob(image, "gainmap-data", &data, &length)) + return -1; + } + + if (data) { + uhdr_error_info_t error_info; + + uhdr_compressed_image_t gainmap_image = { + .data = (void *) data, + .data_sz = length, + .capacity = length, + }; + + error_info = + uhdr_enc_set_gainmap_image(uhdr->enc, &gainmap_image, &metadata); + if (error_info.error_code) { + vips__uhdr_error(&error_info); + VIPS_FREE(to_free); + return -1; + } } + VIPS_FREE(to_free); + return 0; } @@ -307,6 +339,7 @@ vips_foreign_save_uhdr_set_compressed_base(VipsForeignSaveUhdr *uhdr, return -1; image = VIPS_IMAGE(t[1]); vips_image_remove(image, "gainmap"); + vips_image_remove(image, "gainmap-data"); if (vips_jpegsave_target(image, temp, "Q", uhdr->Q, NULL)) return -1; @@ -348,7 +381,7 @@ vips_foreign_save_uhdr_hdr(VipsForeignSaveUhdr *uhdr, VipsImage *image) uhdr_enc_set_using_multi_channel_gainmap(uhdr->enc, 0); // attach the gainmap, if we have one - if (vips_image_get_typeof(image, "gainmap") && + if (vips_image_get_typeof(image, "gainmap-data") && vips_foreign_save_uhdr_set_compressed_gainmap(uhdr, image)) return -1; diff --git a/libvips/include/vips/header.h b/libvips/include/vips/header.h index 86fefab057..ebf112ea9b 100644 --- a/libvips/include/vips/header.h +++ b/libvips/include/vips/header.h @@ -251,6 +251,8 @@ int vips_image_get_tile_width(VipsImage *image); VIPS_API int vips_image_get_tile_height(VipsImage *image); VIPS_API +VipsImage *vips_image_get_gainmap(VipsImage *image); +VIPS_API const void *vips_image_get_data(VipsImage *image); VIPS_API diff --git a/libvips/iofuncs/header.c b/libvips/iofuncs/header.c index 6116e93808..abe8c7d19e 100644 --- a/libvips/iofuncs/header.c +++ b/libvips/iofuncs/header.c @@ -1045,6 +1045,53 @@ vips_image_get_tile_height(VipsImage *image) return -1; } +/** + * vips_image_get_gainmap: + * @image: image to get the gainmap from + * + * If the image has an attached `"gainmap"`, return that. If there's a + * compressed `"gainmap-data"`, decompress, attach to the image, and return + * that. + * + * The result is owned by @image and must not be unreffed. Don't call this + * function on the same image from two different threads! + * + * Returns: (nullable) (transfer none): the gainmap image, if present, or NULL. + */ +VipsImage * +vips_image_get_gainmap(VipsImage *image) +{ + VipsImage *gainmap; + + gainmap = NULL; + + if (vips_image_get_typeof(image, "gainmap")) { + printf("vips_image_get_gainmap: returning existing gainmap image\n"); + + if (vips_image_get_image(image, "gainmap", &gainmap)) + return NULL; + } + else if (vips_image_get_typeof(image, "gainmap-data")) { + printf("vips_image_get_gainmap: decompressing and attaching gainmap\n"); + + const void *data; + size_t length; + + if (vips_image_get_blob(image, "gainmap-data", &data, &length)) + return NULL; + + if (vips_jpegload_buffer((void *) data, length, &gainmap, NULL)) + return NULL; + + vips_image_set_image(image, "gainmap", gainmap); + } + + if (gainmap) + g_object_unref(gainmap); + + return gainmap; +} + /** * vips_image_get_data: * @image: image to get data for diff --git a/libvips/resample/resize.c b/libvips/resample/resize.c index 96e7622d37..c9e3f30460 100644 --- a/libvips/resample/resize.c +++ b/libvips/resample/resize.c @@ -137,7 +137,7 @@ vips_resize_build(VipsObject *object) VipsResample *resample = VIPS_RESAMPLE(object); VipsResize *resize = (VipsResize *) object; - VipsImage **t = (VipsImage **) vips_object_local_array(object, 5); + VipsImage **t = (VipsImage **) vips_object_local_array(object, 6); VipsImage *in; double hscale; diff --git a/libvips/resample/thumbnail.c b/libvips/resample/thumbnail.c index 36144e8bc5..93c913d625 100644 --- a/libvips/resample/thumbnail.c +++ b/libvips/resample/thumbnail.c @@ -587,7 +587,8 @@ vips_thumbnail_open(VipsThumbnail *thumbnail) factor = 1.0; - if (vips_isprefix("VipsForeignLoadJpeg", thumbnail->loader)) + if (vips_isprefix("VipsForeignLoadJpeg", thumbnail->loader) || + vips_isprefix("VipsForeignLoadUhdr", thumbnail->loader)) factor = vips_thumbnail_find_jpegshrink(thumbnail, thumbnail->input_width, thumbnail->input_height); else if (vips_isprefix("VipsForeignLoadTiff", thumbnail->loader) || @@ -650,6 +651,7 @@ vips_thumbnail_build(VipsObject *object) int preshrunk_page_height; double hshrink; double vshrink; + VipsImage *gainmap; /* TRUE if we've done the import of an ICC transform and still need to * export. @@ -815,6 +817,18 @@ vips_thumbnail_build(VipsObject *object) return -1; in = t[5]; + /* Process the gainmap, if any. + */ + if ((gainmap = vips_image_get_gainmap(in))) { + if (vips_resize(gainmap, &t[15], 1.0 / hshrink, + "vscale", 1.0 / vshrink, + "kernel", VIPS_KERNEL_LINEAR, + NULL)) + return -1; + + vips_image_set_image(in, "gainmap", t[15]); + } + if (unpremultiplied_format != VIPS_FORMAT_NOTSET) { g_info("unpremultiplying alpha"); if (vips_unpremultiply(in, &t[6], NULL) || @@ -910,6 +924,17 @@ vips_thumbnail_build(VipsObject *object) vips_autorot(t[11], &t[12], NULL)) return -1; in = t[12]; + + /* Also rotate the gainmap, if any. + */ + if ((gainmap = vips_image_get_gainmap(in))) { + vips_image_set_int(gainmap, + VIPS_META_ORIENTATION, thumbnail->orientation); + if (vips_autorot(gainmap, &t[17], NULL)) + return -1; + + vips_image_set_image(in, "gainmap", t[17]); + } } /* Crop after rotate so we don't need to rotate the crop box. @@ -933,6 +958,24 @@ vips_thumbnail_build(VipsObject *object) NULL)) return -1; in = t[14]; + + int crop_left = vips_image_get_xoffset(in); + int crop_top = vips_image_get_yoffset(in); + + /* Also crop the gainmap, if any. + */ + if ((gainmap = vips_image_get_gainmap(in))) { + double xscale = (double) in->Xsize / gainmap->Xsize; + double yscale = (double) in->Ysize / gainmap->Ysize; + + if (vips_crop(gainmap, &t[16], + crop_left * xscale, crop_top * yscale, + crop_width * xscale, crop_height * yscale, + NULL)) + return -1; + + vips_image_set_image(in, "gainmap", t[16]); + } } g_object_set(thumbnail, "out", vips_image_new(), NULL); @@ -1122,7 +1165,8 @@ vips_thumbnail_file_open(VipsThumbnail *thumbnail, double factor) { VipsThumbnailFile *file = (VipsThumbnailFile *) thumbnail; - if (vips_isprefix("VipsForeignLoadJpeg", thumbnail->loader)) { + if (vips_isprefix("VipsForeignLoadJpeg", thumbnail->loader) || + vips_isprefix("VipsForeignLoadUhdr", thumbnail->loader)) { return vips_image_new_from_file(file->filename, "access", VIPS_ACCESS_SEQUENTIAL, "fail_on", thumbnail->fail_on, @@ -1357,7 +1401,8 @@ vips_thumbnail_buffer_open(VipsThumbnail *thumbnail, double factor) { VipsThumbnailBuffer *buffer = (VipsThumbnailBuffer *) thumbnail; - if (vips_isprefix("VipsForeignLoadJpeg", thumbnail->loader)) { + if (vips_isprefix("VipsForeignLoadJpeg", thumbnail->loader) || + vips_isprefix("VipsForeignLoadUhdr", thumbnail->loader)) { return vips_image_new_from_buffer( buffer->buf->data, buffer->buf->length, buffer->option_string, @@ -1571,7 +1616,8 @@ vips_thumbnail_source_open(VipsThumbnail *thumbnail, double factor) { VipsThumbnailSource *source = (VipsThumbnailSource *) thumbnail; - if (vips_isprefix("VipsForeignLoadJpeg", thumbnail->loader)) { + if (vips_isprefix("VipsForeignLoadJpeg", thumbnail->loader) || + vips_isprefix("VipsForeignLoadUhdr", thumbnail->loader)) { return vips_image_new_from_source( source->source, source->option_string, @@ -1579,8 +1625,7 @@ vips_thumbnail_source_open(VipsThumbnail *thumbnail, double factor) "shrink", (int) factor, NULL); } - else if (vips_isprefix("VipsForeignLoadOpenslide", - thumbnail->loader)) { + else if (vips_isprefix("VipsForeignLoadOpenslide", thumbnail->loader)) { return vips_image_new_from_source( source->source, source->option_string, diff --git a/test/test-suite/test_foreign.py b/test/test-suite/test_foreign.py index eead8125da..f235401f3d 100644 --- a/test/test-suite/test_foreign.py +++ b/test/test-suite/test_foreign.py @@ -442,7 +442,7 @@ def test_uhdrload(self): value = im.get(name) assert isinstance(value, (int, float)) - value = im.get("gainmap") + value = im.get("gainmap-data") assert len(value) > 10000 value = im.get("icc-profile-data") @@ -459,7 +459,7 @@ def test_uhdrload_hdr(self): assert im.format == "float" assert im.interpretation == "scrgb" - value = im.get("gainmap") + value = im.get("gainmap-data") assert len(value) > 10000 @skip_if_no("uhdrsave") @@ -473,7 +473,7 @@ def test_uhdrsave(self): assert im2.bands == 3 assert im2.format == "uchar" assert im2.interpretation == "srgb" - value = im2.get("gainmap") + value = im2.get("gainmap-data") assert len(value) > 10000 @skip_if_no("uhdrsave") @@ -487,18 +487,18 @@ def test_uhdrsave_hdr(self): assert im2.bands == 3 assert im2.format == "uchar" assert im2.interpretation == "srgb" - value = im2.get("gainmap") + value = im2.get("gainmap-data") assert len(value) > 10000 @skip_if_no("uhdrsave") def test_uhdrsave_hdr_no_gainmap(self): im = pyvips.Image.uhdrload(UHDR_FILE, hdr=True) - gainmap1 = im.get("gainmap") + gainmap1 = im.get("gainmap-data") im = im.copy() - im.remove("gainmap") + im.remove("gainmap-data") data = im.uhdrsave_buffer() im2 = pyvips.Image.uhdrload_buffer(data) - gainmap2 = im2.get("gainmap") + gainmap2 = im2.get("gainmap-data") assert im2.width == 3840 assert im2.height == 2160 @@ -522,7 +522,7 @@ def test_uhdrsave_roundtrip(self): def test_uhdrsave_roundtrip_hdr(self): im_hdr = pyvips.Image.uhdrload(UHDR_FILE, hdr=True) im_hdr = im_hdr.copy() - im_hdr.remove("gainmap") + im_hdr.remove("gainmap-data") data = im_hdr.uhdrsave_buffer() im_hdr2 = pyvips.Image.uhdrload_buffer(data, hdr=True)