From 30fdc3df779382e3e1857792c22fe363fff6cb31 Mon Sep 17 00:00:00 2001 From: John Cupitt Date: Thu, 4 Nov 2021 15:26:04 +0000 Subject: [PATCH] add jp2k load of untiled images with this patch, untiled jp2k images are loaded in chunks, saving loads of memory (but runs much slower) --- ChangeLog | 1 + libvips/foreign/jp2kload.c | 130 ++++++++++++++++++++++++++++++++----- 2 files changed, 115 insertions(+), 16 deletions(-) diff --git a/ChangeLog b/ChangeLog index dda68ba1..04fd12ab 100644 --- a/ChangeLog +++ b/ChangeLog @@ -18,6 +18,7 @@ - added VipsForeignPpmFormat, @format arg to ppm savers - add fail-on to give better control over loader error sensitivity - add hyperbolic functions sinh, cosh, tanh, asinh, acosh, atanh [hroskes] +- add untiled jp2k load 16/8/21 started 8.11.4 - fix off-by-one error in new rank fast path diff --git a/libvips/foreign/jp2kload.c b/libvips/foreign/jp2kload.c index e7ec398c..fa96f182 100644 --- a/libvips/foreign/jp2kload.c +++ b/libvips/foreign/jp2kload.c @@ -2,6 +2,8 @@ * * 18/3/20 * - from heifload.c + * 4/11/21 + * - add untiled load */ /* @@ -355,6 +357,9 @@ vips_foreign_load_jp2k_print( VipsForeignLoadJp2k *jp2k ) jp2k->info->tw, jp2k->info->th ); printf( "nbcomps = %u, tile_info = %p\n", jp2k->info->nbcomps, jp2k->info->tile_info ); + if( jp2k->info->tw == 1 && + jp2k->info->th == 1 ) + printf( "untiled\n" ); } #endif /*DEBUG*/ @@ -724,10 +729,84 @@ vips_foreign_load_jp2k_ycc_to_rgb( opj_image_t *image, VipsImage *im, } } -/* Loop over the output region, painting in tiles from the file. +/* Read a tile from an untiled jp2k file. */ static int -vips_foreign_load_jp2k_generate( VipsRegion *out, +vips_foreign_load_jp2k_generate_untiled( VipsRegion *out, + void *seq, void *a, void *b, gboolean *stop ) +{ + VipsForeignLoad *load = (VipsForeignLoad *) a; + VipsForeignLoadJp2k *jp2k = (VipsForeignLoadJp2k *) load; + VipsRect *r = &out->valid; + + VipsRect opj; + VipsRect image; + int y; + +#ifdef DEBUG_VERBOSE + printf( "vips_foreign_load_jp2k_generate_untiled: " + "left = %d, top = %d, width = %d, height = %d\n", + r->left, r->top, r->width, r->height ); +#endif /*DEBUG_VERBOSE*/ + + /* If openjpeg has flagged an error, the library is not in a known + * state and it's not safe to call again. + */ + if( jp2k->n_errors ) + return( 0 ); + + /* Coordinates are always in the highest res level. + */ + opj.left = r->left * jp2k->shrink; + opj.top = r->top * jp2k->shrink; + opj.width = r->width * jp2k->shrink; + opj.height = r->height * jp2k->shrink; + + /* And must be clipped against the image size. + */ + image.left = 0; + image.top = 0; + image.width = jp2k->info->tdx; + image.height = jp2k->info->tdy; + vips_rect_intersectrect( &opj, &image, &opj ); + + if( !opj_set_decode_area( jp2k->codec, jp2k->image, + opj.left, opj.top, + VIPS_RECT_RIGHT( &opj ), VIPS_RECT_BOTTOM( &opj ) ) ) + return( -1 ); + + if( !opj_decode( jp2k->codec, jp2k->stream, jp2k->image ) ) + return( -1 ); + + /* Unpack decoded pixels to buffer in vips layout. + */ + for( y = 0; y < r->height; y++ ) { + VipsPel *q = VIPS_REGION_ADDR( out, r->left, r->top + y ); + + vips_foreign_load_jp2k_pack( jp2k->upsample, + jp2k->image, out->im, q, 0, y, r->width ); + + if( jp2k->ycc_to_rgb ) + vips_foreign_load_jp2k_ycc_to_rgb( jp2k->image, + out->im, q, r->width ); + } + + /* jp2k files can't be truncated (they fail to open), so all we can + * spot is errors. + */ + if( load->fail_on >= VIPS_FAIL_ON_ERROR && + jp2k->n_errors > 0 ) + return( -1 ); + + return( 0 ); +} + +/* Read a tile from the file. libvips tiles can be much larger or smaller than + * openjpeg tiles, so we must loop over the output region, painting in + * tiles from the file. + */ +static int +vips_foreign_load_jp2k_generate_tiled( VipsRegion *out, void *seq, void *a, void *b, gboolean *stop ) { VipsForeignLoad *load = (VipsForeignLoad *) a; @@ -841,17 +920,13 @@ static int vips_foreign_load_jp2k_load( VipsForeignLoad *load ) { VipsForeignLoadJp2k *jp2k = (VipsForeignLoadJp2k *) load; - - /* jp2k tiles get smaller with the layer size, but we don't want tiny - * tiles for the libvips tile cache, so leave them at the base size. - */ - int tile_width = jp2k->info->tdx; - int tile_height = jp2k->info->tdy; - int tiles_across = jp2k->info->tw; - VipsImage **t = (VipsImage **) vips_object_local_array( VIPS_OBJECT( load ), 3 ); + int vips_tile_width; + int vips_tile_height; + int vips_tiles_across; + #ifdef DEBUG printf( "vips_foreign_load_jp2k_load:\n" ); #endif /*DEBUG*/ @@ -860,19 +935,42 @@ vips_foreign_load_jp2k_load( VipsForeignLoad *load ) if( vips_foreign_load_jp2k_set_header( jp2k, t[0] ) ) return( -1 ); - if( vips_image_generate( t[0], - NULL, vips_foreign_load_jp2k_generate, NULL, jp2k, NULL ) ) - return( -1 ); + /* Untiled jp2k images need a different read API. + */ + if( jp2k->info->tw == 1 && + jp2k->info->th == 1 ) { + vips_tile_width = 512; + vips_tile_height = 512; + vips_tiles_across = + VIPS_ROUND_UP( t[0]->Xsize, vips_tile_width ) / + vips_tile_width; + + if( vips_image_generate( t[0], + NULL, vips_foreign_load_jp2k_generate_untiled, NULL, + jp2k, NULL ) ) + return( -1 ); + } + else { + vips_tile_width = jp2k->info->tdx; + vips_tile_height = jp2k->info->tdy; + vips_tiles_across = jp2k->info->tw; + + if( vips_image_generate( t[0], + NULL, vips_foreign_load_jp2k_generate_tiled, NULL, + jp2k, NULL ) ) + return( -1 ); + } /* Copy to out, adding a cache. Enough tiles for two complete * rows, plus 50%. */ if( vips_tilecache( t[0], &t[1], - "tile_width", tile_width, - "tile_height", tile_height, - "max_tiles", 3 * tiles_across, + "tile_width", vips_tile_width, + "tile_height", vips_tile_height, + "max_tiles", 3 * vips_tiles_across, NULL ) ) return( -1 ); + if( vips_image_write( t[1], load->real ) ) return( -1 );