From 2b79b2dceb64a0c5d7adb5a1bc35016d6083a3e1 Mon Sep 17 00:00:00 2001 From: Dirk Farin Date: Sat, 12 Oct 2024 14:22:19 +0200 Subject: [PATCH 01/11] heif-enc: scan input files for tiles range --- examples/heif_enc.cc | 143 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 141 insertions(+), 2 deletions(-) diff --git a/examples/heif_enc.cc b/examples/heif_enc.cc index e3374db14e..060e79d151 100644 --- a/examples/heif_enc.cc +++ b/examples/heif_enc.cc @@ -58,6 +58,8 @@ int run_benchmark = 0; int metadata_compression = 0; const char* encoderId = nullptr; std::string chroma_downsampling; +int tiled_image_width = 0; +int tiled_image_height = 0; uint16_t nclx_colour_primaries = 1; uint16_t nclx_transfer_characteristic = 13; @@ -75,6 +77,9 @@ std::string property_pitm_description; #if HAVE_GETTIMEOFDAY #include +#include +#include +#include struct timeval time_encoding_start; struct timeval time_encoding_end; @@ -127,6 +132,9 @@ static struct option long_options[] = { {(char* const) "enable-metadata-compression", no_argument, &metadata_compression, 1}, {(char* const) "pitm-description", required_argument, 0, OPTION_PITM_DESCRIPTION}, {(char* const) "chroma-downsampling", required_argument, 0, 'C'}, + {(char* const) "tiled-input", no_argument, 0, 'T'}, + {(char* const) "tiled-image-width", required_argument, &tiled_image_width, 0}, + {(char* const) "tiled-image-height", required_argument, &tiled_image_height, 0}, {0, 0, 0, 0}, }; @@ -180,7 +188,10 @@ void show_help(const char* argv0) << " -C,--chroma-downsampling ALGO force chroma downsampling algorithm (nn = nearest-neighbor / average / sharp-yuv)\n" << " (sharp-yuv makes edges look sharper when using YUV420 with bilinear chroma upsampling)\n" << " --benchmark measure encoding time, PSNR, and output file size\n" - << " --pitm-description TEXT (experimental) set user description for primary image\n"; + << " --pitm-description TEXT (experimental) set user description for primary image\n" + << " --tiled-input input is a set of tile images (only provide the one filename with two tile coordinates)\n" + << " --tiled-image-width # override image width of tiled image\n" + << " --tiled-image-height # override image height of tiled image\n"; } @@ -607,6 +618,111 @@ heif_error create_output_nclx_profile_and_configure_encoder(heif_encoder* encode } +struct input_tiles_generator +{ + uint32_t first_start; + uint32_t first_end; + uint32_t first_digits; + uint32_t second_start; + uint32_t second_end; + uint32_t second_digits; + + std::string directory; + std::string prefix; + std::string separator; + std::string suffix; + + bool first_is_x = false; + + uint32_t nColumns() const { return first_is_x ? (first_end - first_start + 1) : (second_end - second_end + 1); } + uint32_t nRows() const { return first_is_x ? (second_end - second_end + 1) : (first_end - first_start + 1); } + + uint32_t nTiles() const { return (first_end - first_start + 1) * (second_end - second_start + 1); } + + std::string filename(int tx,int ty) { + std::stringstream sstr; + sstr << prefix << std::setw(first_digits) << std::setfill('0') << (first_is_x ? tx : ty); + sstr << separator << std::setw(second_digits) << std::setfill('0') << (first_is_x ? ty : tx); + sstr << suffix; + return sstr.str(); + } +}; + +std::optional determine_input_images_tiling(const std::string& filename) +{ + std::regex pattern(R"((.*\D?)(\d+)(\D+?)(\d+)(\..+)$)"); + std::smatch match; + + input_tiles_generator generator; + + if (std::regex_match(filename, match, pattern)) { + generator.prefix = match[1]; + generator.separator = match[3]; + generator.suffix = match[5]; + + generator.first_start = 9999; + generator.first_end = 0; + generator.first_digits = 9; + + generator.second_start = 9999; + generator.second_end = 0; + generator.second_digits = 9; + } + else { + return std::nullopt; + } + + auto p = generator.prefix.find_last_of('/'); + if (p != std::string::npos) { + generator.directory = generator.prefix.substr(0,p); + generator.prefix = generator.prefix.substr(p+1); + } + + std::string patternString = generator.prefix + "(\\d+)" + generator.separator + "(\\d+)" + generator.suffix + "$"; + pattern = patternString; + + std::regex dirPattern(R"((.*)/(.*?)$)"); + std::smatch dirmatch; + std::string dirName; + + if (std::regex_match(filename, dirmatch, dirPattern)) { + dirName = std::string(dirmatch[1]) + '/'; + } + else { + dirName = {}; + } + + DIR* dir = opendir(dirName.c_str()); + for (;;) { + struct dirent* entry = readdir(dir); + if (!entry) { + break; + } + + if (entry->d_type == DT_REG) { + std::string s{entry->d_name}; + + if (std::regex_match(s, match, pattern)) { + uint32_t first = std::stoi(match[1]); + uint32_t second = std::stoi(match[2]); + + generator.first_digits = std::min(generator.first_digits, (uint32_t)match[2].length()); + generator.second_digits = std::min(generator.second_digits, (uint32_t)match[4].length()); + + generator.first_start = std::min(generator.first_start, first); + generator.first_end = std::max(generator.first_end, first); + generator.second_start = std::min(generator.second_start, second); + generator.second_end = std::max(generator.second_end, second); + } + } + } + + closedir(dir); + + return generator; +} + + class LibHeifInitializer { public: @@ -634,13 +750,14 @@ int main(int argc, char** argv) bool force_enc_jpeg = false; bool force_enc_jpeg2000 = false; bool force_enc_htj2k = false; + bool use_tiling = false; std::vector raw_params; while (true) { int option_index = 0; - int c = getopt_long(argc, argv, "hq:Lo:vPp:t:b:Ae:C:" + int c = getopt_long(argc, argv, "hq:Lo:vPp:t:b:Ae:C:T" #if WITH_UNCOMPRESSED_CODEC "U" #endif @@ -750,6 +867,9 @@ int main(int argc, char** argv) } #endif break; + case 'T': + use_tiling = true; + break; } } @@ -914,6 +1034,25 @@ int main(int argc, char** argv) std::shared_ptr image = input_image.image; + heif_image_tiling tiling{}; + std::optional tile_generator; + if (use_tiling) { + tile_generator = determine_input_images_tiling(input_filename); + if (tile_generator) { + tiling.version = 1; + tiling.num_columns = tile_generator->nColumns(); + tiling.num_rows = tile_generator->nRows(); + tiling.tile_width = heif_image_get_primary_width(image.get()); + tiling.tile_height = heif_image_get_primary_height(image.get()); + tiling.image_width = tiling.num_columns * tiling.tile_width; + tiling.image_height = tiling.num_rows * tiling.tile_height; + tiling.number_of_extra_dimensions = 0; + } + + if (tiled_image_width) tiling.image_width = tiled_image_width; + if (tiled_image_height) tiling.image_height = tiled_image_height; + } + if (!primary_image) { primary_image = image; } From 45a3cc1b8d9c3ae3e783f12452e4f3e4859aebc0 Mon Sep 17 00:00:00 2001 From: Dirk Farin Date: Sat, 12 Oct 2024 15:31:27 +0200 Subject: [PATCH 02/11] (add include) --- examples/heif_enc.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/heif_enc.cc b/examples/heif_enc.cc index 060e79d151..abcfa2eca5 100644 --- a/examples/heif_enc.cc +++ b/examples/heif_enc.cc @@ -36,6 +36,7 @@ #include #include #include +#include #include #include From 503b937d8d0a80410c0e139b2e35753d9d5c1d8c Mon Sep 17 00:00:00 2001 From: Dirk Farin Date: Sat, 12 Oct 2024 21:20:34 +0200 Subject: [PATCH 03/11] decoder-tiff: suppress warnings on console --- heifio/decoder_tiff.cc | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/heifio/decoder_tiff.cc b/heifio/decoder_tiff.cc index 13316547a6..524e30ddb0 100644 --- a/heifio/decoder_tiff.cc +++ b/heifio/decoder_tiff.cc @@ -393,7 +393,15 @@ heif_error readBandInterleave(TIFF *tif, uint16_t samplesPerPixel, heif_image ** } } + +static void suppress_warnings(const char* module, const char* fmt, va_list ap) { + // Do nothing +} + + heif_error loadTIFF(const char* filename, InputImage *input_image) { + TIFFSetWarningHandler(suppress_warnings); + std::unique_ptr tifPtr(TIFFOpen(filename, "r"), [](TIFF* tif) { TIFFClose(tif); }); if (!tifPtr) { struct heif_error err = { From b1288bb43d4b2ee8b0a8e5ff57d4fbf1ce02eca0 Mon Sep 17 00:00:00 2001 From: Dirk Farin Date: Sat, 12 Oct 2024 21:25:10 +0200 Subject: [PATCH 04/11] heif-enc: encode tiled image to grid --- examples/heif_enc.cc | 111 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 90 insertions(+), 21 deletions(-) diff --git a/examples/heif_enc.cc b/examples/heif_enc.cc index abcfa2eca5..7ba659db88 100644 --- a/examples/heif_enc.cc +++ b/examples/heif_enc.cc @@ -635,15 +635,19 @@ struct input_tiles_generator bool first_is_x = false; - uint32_t nColumns() const { return first_is_x ? (first_end - first_start + 1) : (second_end - second_end + 1); } - uint32_t nRows() const { return first_is_x ? (second_end - second_end + 1) : (first_end - first_start + 1); } + uint32_t nColumns() const { return first_is_x ? (first_end - first_start + 1) : (second_end - second_start + 1); } + uint32_t nRows() const { return first_is_x ? (second_end - second_start + 1) : (first_end - first_start + 1); } uint32_t nTiles() const { return (first_end - first_start + 1) * (second_end - second_start + 1); } - std::string filename(int tx,int ty) { + std::string filename(uint32_t tx, uint32_t ty) const + { std::stringstream sstr; - sstr << prefix << std::setw(first_digits) << std::setfill('0') << (first_is_x ? tx : ty); - sstr << separator << std::setw(second_digits) << std::setfill('0') << (first_is_x ? ty : tx); + if (!directory.empty()) { + sstr << directory << '/'; + } + sstr << prefix << std::setw(first_digits) << std::setfill('0') << (first_is_x ? tx : ty) + first_start; + sstr << separator << std::setw(second_digits) << std::setfill('0') << (first_is_x ? ty : tx) + second_start; sstr << suffix; return sstr.str(); } @@ -651,7 +655,7 @@ struct input_tiles_generator std::optional determine_input_images_tiling(const std::string& filename) { - std::regex pattern(R"((.*\D?)(\d+)(\D+?)(\d+)(\..+)$)"); + std::regex pattern(R"((.*\D)?(\d+)(\D+?)(\d+)(\..+)$)"); std::smatch match; input_tiles_generator generator; @@ -687,10 +691,10 @@ std::optional determine_input_images_tiling(const std::st std::string dirName; if (std::regex_match(filename, dirmatch, dirPattern)) { - dirName = std::string(dirmatch[1]) + '/'; + dirName = std::string(dirmatch[1]); } else { - dirName = {}; + dirName = "."; } DIR* dir = opendir(dirName.c_str()); @@ -707,8 +711,8 @@ std::optional determine_input_images_tiling(const std::st uint32_t first = std::stoi(match[1]); uint32_t second = std::stoi(match[2]); - generator.first_digits = std::min(generator.first_digits, (uint32_t)match[2].length()); - generator.second_digits = std::min(generator.second_digits, (uint32_t)match[4].length()); + generator.first_digits = std::min(generator.first_digits, (uint32_t)match[1].length()); + generator.second_digits = std::min(generator.second_digits, (uint32_t)match[2].length()); generator.first_start = std::min(generator.first_start, first); generator.first_end = std::max(generator.first_end, first); @@ -724,6 +728,52 @@ std::optional determine_input_images_tiling(const std::st } +heif_image_handle* encode_tiled(heif_context* ctx, heif_encoder* encoder, heif_encoding_options* options, + int output_bit_depth, + const input_tiles_generator& tile_generator, + const heif_image_tiling& tiling) +{ + std::vector image_ids; + + for (uint32_t ty = 0; ty < tile_generator.nRows(); ty++) + for (uint32_t tx = 0; tx < tile_generator.nColumns(); tx++) { + std::string input_filename = tile_generator.filename(tx,ty); + + InputImage input_image = load_image(input_filename, output_bit_depth); + + std::cout << "encoding tile " << ty+1 << " " << tx+1 + << " (of " << tile_generator.nRows() << "x" << tile_generator.nColumns() << ") \r"; + std::cout.flush(); + + heif_image_handle* handle; + heif_error error = heif_context_encode_image(ctx, + input_image.image.get(), + encoder, + options, + &handle); + if (error.code != 0) { + std::cerr << "Could not encode HEIF/AVIF file: " << error.message << "\n"; + return nullptr; + } + + image_ids.push_back(heif_image_handle_get_item_id(handle)); + heif_image_handle_release(handle); + } + + std::cout << "\n"; + + heif_image_handle* grid_image; + heif_error error = heif_context_add_grid_image(ctx, tiling.image_width, tiling.image_height, + tiling.num_columns, tiling.num_rows, image_ids.data(), &grid_image); + if (error.code != 0) { + std::cerr << "Could not generate grid image: " << error.message << "\n"; + return nullptr; + } + + return grid_image; +} + + class LibHeifInitializer { public: @@ -1011,6 +1061,8 @@ int main(int argc, char** argv) std::shared_ptr primary_image; + bool is_primary_image = true; + for (; optind < argc; optind++) { std::string input_filename = argv[optind]; @@ -1052,6 +1104,11 @@ int main(int argc, char** argv) if (tiled_image_width) tiling.image_width = tiled_image_width; if (tiled_image_height) tiling.image_height = tiled_image_height; + + if (use_tiling && (!tile_generator || tile_generator->nTiles()==1)) { + std::cerr << "Cannot enumerate input tiles. Please use filenames with the two tile coordinates in the name.\n"; + return 5; + } } if (!primary_image) { @@ -1102,17 +1159,27 @@ int main(int argc, char** argv) } struct heif_image_handle* handle; - error = heif_context_encode_image(context.get(), - image.get(), - encoder, - options, - &handle); - if (error.code != 0) { - heif_encoding_options_free(options); - heif_nclx_color_profile_free(nclx); - heif_encoder_release(encoder); - std::cerr << "Could not encode HEIF/AVIF file: " << error.message << "\n"; - return 1; + + if (use_tiling) { + handle = encode_tiled(context.get(), encoder, options, output_bit_depth, *tile_generator, tiling); + } + else { + error = heif_context_encode_image(context.get(), + image.get(), + encoder, + options, + &handle); + if (error.code != 0) { + heif_encoding_options_free(options); + heif_nclx_color_profile_free(nclx); + heif_encoder_release(encoder); + std::cerr << "Could not encode HEIF/AVIF file: " << error.message << "\n"; + return 1; + } + } + + if (is_primary_image) { + heif_context_set_primary_image(context.get(), handle); } // write EXIF to HEIC @@ -1181,6 +1248,8 @@ int main(int argc, char** argv) heif_image_handle_release(handle); heif_encoding_options_free(options); heif_nclx_color_profile_free(nclx); + + is_primary_image = false; } heif_encoder_release(encoder); From e1afc6b784bcf324040c04a7d3c55c5bef5d0557 Mon Sep 17 00:00:00 2001 From: Dirk Farin Date: Sat, 12 Oct 2024 21:36:03 +0200 Subject: [PATCH 05/11] heif-enc: parameters for tiled image total size --- examples/heif_enc.cc | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/examples/heif_enc.cc b/examples/heif_enc.cc index 7ba659db88..51050cf33e 100644 --- a/examples/heif_enc.cc +++ b/examples/heif_enc.cc @@ -97,6 +97,8 @@ const int OPTION_USE_JPEG2000_COMPRESSION = 1007; const int OPTION_VERBOSE = 1008; const int OPTION_USE_HTJ2K_COMPRESSION = 1009; const int OPTION_USE_VVC_COMPRESSION = 1010; +const int OPTION_TILED_IMAGE_WIDTH = 1011; +const int OPTION_TILED_IMAGE_HEIGHT = 1012; static struct option long_options[] = { @@ -134,8 +136,8 @@ static struct option long_options[] = { {(char* const) "pitm-description", required_argument, 0, OPTION_PITM_DESCRIPTION}, {(char* const) "chroma-downsampling", required_argument, 0, 'C'}, {(char* const) "tiled-input", no_argument, 0, 'T'}, - {(char* const) "tiled-image-width", required_argument, &tiled_image_width, 0}, - {(char* const) "tiled-image-height", required_argument, &tiled_image_height, 0}, + {(char* const) "tiled-image-width", required_argument, nullptr, OPTION_TILED_IMAGE_WIDTH}, + {(char* const) "tiled-image-height", required_argument, nullptr, OPTION_TILED_IMAGE_HEIGHT}, {0, 0, 0, 0}, }; @@ -735,6 +737,9 @@ heif_image_handle* encode_tiled(heif_context* ctx, heif_encoder* encoder, heif_e { std::vector image_ids; + std::cout << "encoding tiled image, tile size: " << tiling.tile_width << "x" << tiling.tile_height + << " image size: " << tiling.image_width << "x" << tiling.image_height << "\n"; + for (uint32_t ty = 0; ty < tile_generator.nRows(); ty++) for (uint32_t tx = 0; tx < tile_generator.nColumns(); tx++) { std::string input_filename = tile_generator.filename(tx,ty); @@ -899,6 +904,12 @@ int main(int argc, char** argv) } break; } + case OPTION_TILED_IMAGE_WIDTH: + tiled_image_width = (int) strtol(optarg, nullptr, 0); + break; + case OPTION_TILED_IMAGE_HEIGHT: + tiled_image_height = (int) strtol(optarg, nullptr, 0); + break; case 'C': chroma_downsampling = optarg; if (chroma_downsampling != "nn" && From 1918cfd249e455210db84e439e7398f85f03fc00 Mon Sep 17 00:00:00 2001 From: Dirk Farin Date: Sat, 12 Oct 2024 21:45:13 +0200 Subject: [PATCH 06/11] heif-enc: added option --tiled-input-x-y --- examples/heif_enc.cc | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/examples/heif_enc.cc b/examples/heif_enc.cc index 51050cf33e..1a678d505a 100644 --- a/examples/heif_enc.cc +++ b/examples/heif_enc.cc @@ -57,6 +57,7 @@ int two_colr_boxes = 0; int premultiplied_alpha = 0; int run_benchmark = 0; int metadata_compression = 0; +int tiled_input_x_y = 0; const char* encoderId = nullptr; std::string chroma_downsampling; int tiled_image_width = 0; @@ -138,6 +139,7 @@ static struct option long_options[] = { {(char* const) "tiled-input", no_argument, 0, 'T'}, {(char* const) "tiled-image-width", required_argument, nullptr, OPTION_TILED_IMAGE_WIDTH}, {(char* const) "tiled-image-height", required_argument, nullptr, OPTION_TILED_IMAGE_HEIGHT}, + {(char* const) "tiled-input-x-y", no_argument, &tiled_input_x_y, 1}, {0, 0, 0, 0}, }; @@ -192,9 +194,15 @@ void show_help(const char* argv0) << " (sharp-yuv makes edges look sharper when using YUV420 with bilinear chroma upsampling)\n" << " --benchmark measure encoding time, PSNR, and output file size\n" << " --pitm-description TEXT (experimental) set user description for primary image\n" - << " --tiled-input input is a set of tile images (only provide the one filename with two tile coordinates)\n" + << " --tiled-input input is a set of tile images (only provide one filename with two tile position numbers).\n" + << " For example, 'tile-01-05.jpg' would be a valid input filename.\n" + << " You only have to provide the filename of one tile as input, heif-enc will scan the directory\n" + << " for the other tiles and determine the range of tiles automatically.\n" << " --tiled-image-width # override image width of tiled image\n" - << " --tiled-image-height # override image height of tiled image\n"; + << " --tiled-image-height # override image height of tiled image\n" + << " --tiled-input-x-y usually, the first number in the input tile filename should be the y position.\n" + << " With this option, this can be swapped so that the first number is x, the second number y.\n" + ; } @@ -1104,6 +1112,9 @@ int main(int argc, char** argv) tile_generator = determine_input_images_tiling(input_filename); if (tile_generator) { tiling.version = 1; + + if (tiled_input_x_y) tile_generator->first_is_x = true; + tiling.num_columns = tile_generator->nColumns(); tiling.num_rows = tile_generator->nRows(); tiling.tile_width = heif_image_get_primary_width(image.get()); @@ -1116,7 +1127,7 @@ int main(int argc, char** argv) if (tiled_image_width) tiling.image_width = tiled_image_width; if (tiled_image_height) tiling.image_height = tiled_image_height; - if (use_tiling && (!tile_generator || tile_generator->nTiles()==1)) { + if (!tile_generator || tile_generator->nTiles()==1) { std::cerr << "Cannot enumerate input tiles. Please use filenames with the two tile coordinates in the name.\n"; return 5; } From 8361c6a412953fce43df67db90c90de41785ca29 Mon Sep 17 00:00:00 2001 From: Dirk Farin Date: Sat, 12 Oct 2024 22:30:32 +0200 Subject: [PATCH 07/11] heif-enc: option to switch tiling method --- examples/heif_enc.cc | 87 +++++++++++++++++++++++-- libheif/api/libheif/heif.cc | 3 +- libheif/api/libheif/heif_experimental.h | 3 +- libheif/context.cc | 7 +- libheif/context.h | 3 +- libheif/image-items/image_item.cc | 22 +++++++ libheif/image-items/image_item.h | 2 + libheif/image-items/tiled.cc | 18 +++-- libheif/image-items/tiled.h | 7 +- 9 files changed, 134 insertions(+), 18 deletions(-) diff --git a/examples/heif_enc.cc b/examples/heif_enc.cc index 1a678d505a..d8cdc1c936 100644 --- a/examples/heif_enc.cc +++ b/examples/heif_enc.cc @@ -49,6 +49,7 @@ #include "benchmark.h" #include "common.h" +#include "libheif/heif_experimental.h" int master_alpha = 1; int thumb_alpha = 1; @@ -62,6 +63,7 @@ const char* encoderId = nullptr; std::string chroma_downsampling; int tiled_image_width = 0; int tiled_image_height = 0; +std::string tiling_method; uint16_t nclx_colour_primaries = 1; uint16_t nclx_transfer_characteristic = 13; @@ -100,6 +102,7 @@ const int OPTION_USE_HTJ2K_COMPRESSION = 1009; const int OPTION_USE_VVC_COMPRESSION = 1010; const int OPTION_TILED_IMAGE_WIDTH = 1011; const int OPTION_TILED_IMAGE_HEIGHT = 1012; +const int OPTION_TILING_METHOD = 1013; static struct option long_options[] = { @@ -140,6 +143,7 @@ static struct option long_options[] = { {(char* const) "tiled-image-width", required_argument, nullptr, OPTION_TILED_IMAGE_WIDTH}, {(char* const) "tiled-image-height", required_argument, nullptr, OPTION_TILED_IMAGE_HEIGHT}, {(char* const) "tiled-input-x-y", no_argument, &tiled_input_x_y, 1}, + {(char* const) "tiling-method", required_argument, nullptr, OPTION_TILING_METHOD}, {0, 0, 0, 0}, }; @@ -202,6 +206,7 @@ void show_help(const char* argv0) << " --tiled-image-height # override image height of tiled image\n" << " --tiled-input-x-y usually, the first number in the input tile filename should be the y position.\n" << " With this option, this can be swapped so that the first number is x, the second number y.\n" + << " --tiling-method METHOD choose one of these methods: grid, tili, unci. The default is 'grid'.\n" ; } @@ -738,10 +743,10 @@ std::optional determine_input_images_tiling(const std::st } -heif_image_handle* encode_tiled(heif_context* ctx, heif_encoder* encoder, heif_encoding_options* options, - int output_bit_depth, - const input_tiles_generator& tile_generator, - const heif_image_tiling& tiling) +heif_image_handle* encode_tiled_grid(heif_context* ctx, heif_encoder* encoder, heif_encoding_options* options, + int output_bit_depth, + const input_tiles_generator& tile_generator, + const heif_image_tiling& tiling) { std::vector image_ids; @@ -787,6 +792,71 @@ heif_image_handle* encode_tiled(heif_context* ctx, heif_encoder* encoder, heif_e } +heif_image_handle* encode_tiled(heif_context* ctx, heif_encoder* encoder, heif_encoding_options* options, + int output_bit_depth, + const input_tiles_generator& tile_generator, + const heif_image_tiling& tiling) +{ + if (tiling_method == "grid") { + return encode_tiled_grid(ctx, encoder, options, output_bit_depth, tile_generator, tiling); + } + + + heif_image_handle* tiled_image; + heif_error error; + if (tiling_method == "tili") { + heif_tiled_image_parameters tiled_params{}; + tiled_params.version = 1; + tiled_params.image_width = tiling.image_width; + tiled_params.image_height = tiling.image_height; + tiled_params.tile_width = tiling.tile_width; + tiled_params.tile_height = tiling.tile_height; + // tiled_params.compression_format_fourcc = heif_fourcc('a', 'v', '0', '1'); // TODO HACK + tiled_params.offset_field_length = 32; + tiled_params.size_field_length = 24; + tiled_params.tiles_are_sequential = 1; + + error = heif_context_add_tiled_image(ctx, &tiled_params, options, encoder, &tiled_image); + if (error.code != 0) { + std::cerr << "Could not generate grid image: " << error.message << "\n"; + return nullptr; + } + } + else if (tiling_method == "unci") { + // TODO + } + else { + assert(false); + } + + std::cout << "encoding tiled image, tile size: " << tiling.tile_width << "x" << tiling.tile_height + << " image size: " << tiling.image_width << "x" << tiling.image_height << "\n"; + + for (uint32_t ty = 0; ty < tile_generator.nRows(); ty++) + for (uint32_t tx = 0; tx < tile_generator.nColumns(); tx++) { + std::string input_filename = tile_generator.filename(tx,ty); + + InputImage input_image = load_image(input_filename, output_bit_depth); + + std::cout << "encoding tile " << ty+1 << " " << tx+1 + << " (of " << tile_generator.nRows() << "x" << tile_generator.nColumns() << ") \r"; + std::cout.flush(); + + heif_error error = heif_context_add_image_tile(ctx, tiled_image, tx, ty, + input_image.image.get(), + encoder); + if (error.code != 0) { + std::cerr << "Could not encode HEIF/AVIF file: " << error.message << "\n"; + return nullptr; + } + } + + std::cout << "\n"; + + return tiled_image; +} + + class LibHeifInitializer { public: @@ -918,6 +988,15 @@ int main(int argc, char** argv) case OPTION_TILED_IMAGE_HEIGHT: tiled_image_height = (int) strtol(optarg, nullptr, 0); break; + case OPTION_TILING_METHOD: + tiling_method = optarg; + if (tiling_method != "grid" && + tiling_method != "tili" && + tiling_method != "unci") { + std::cerr << "Invalid tiling method '" << tiling_method << "'\n"; + exit(5); + } + break; case 'C': chroma_downsampling = optarg; if (chroma_downsampling != "nn" && diff --git a/libheif/api/libheif/heif.cc b/libheif/api/libheif/heif.cc index c056b31b63..09f55db0f0 100644 --- a/libheif/api/libheif/heif.cc +++ b/libheif/api/libheif/heif.cc @@ -3529,10 +3529,11 @@ struct heif_error heif_context_add_overlay_image(struct heif_context* ctx, struct heif_error heif_context_add_tiled_image(struct heif_context* ctx, const struct heif_tiled_image_parameters* parameters, const struct heif_encoding_options* options, // TODO: do we need this? + const struct heif_encoder* encoder, struct heif_image_handle** out_grid_image_handle) { Result> gridImageResult; - gridImageResult = ctx->context->add_tiled_item(parameters); + gridImageResult = ctx->context->add_tiled_item(parameters, encoder); if (gridImageResult.error != Error::Ok) { return gridImageResult.error.error_struct(ctx->context.get()); diff --git a/libheif/api/libheif/heif_experimental.h b/libheif/api/libheif/heif_experimental.h index b98675f12e..91ca2de7fc 100644 --- a/libheif/api/libheif/heif_experimental.h +++ b/libheif/api/libheif/heif_experimental.h @@ -123,7 +123,7 @@ struct heif_tiled_image_parameters { uint32_t tile_width; uint32_t tile_height; - uint32_t compression_type_fourcc; // TODO: can this be set automatically ? + uint32_t compression_format_fourcc; // will be set automatically when calling heif_context_add_tiled_image() uint8_t offset_field_length; // one of: 32, 40, 48, 64 uint8_t size_field_length; // one of: 0, 24, 32, 64 @@ -140,6 +140,7 @@ LIBHEIF_API struct heif_error heif_context_add_tiled_image(struct heif_context* ctx, const struct heif_tiled_image_parameters* parameters, const struct heif_encoding_options* options, // TODO: do we need this? + const struct heif_encoder* encoder, struct heif_image_handle** out_tiled_image_handle); diff --git a/libheif/context.cc b/libheif/context.cc index d4ead4cca6..7ccc6c0d97 100644 --- a/libheif/context.cc +++ b/libheif/context.cc @@ -1342,9 +1342,10 @@ Result> HeifContext::add_iovl_item(const Imag } -Result> HeifContext::add_tiled_item(const heif_tiled_image_parameters* parameters) +Result> HeifContext::add_tiled_item(const heif_tiled_image_parameters* parameters, + const struct heif_encoder* encoder) { - return ImageItem_Tiled::add_new_tiled_item(this, parameters); + return ImageItem_Tiled::add_new_tiled_item(this, parameters, encoder); } @@ -1354,7 +1355,7 @@ Error HeifContext::add_tiled_image_tile(heif_item_id tild_id, uint32_t tile_x, u { auto item = ImageItem::alloc_for_compression_format(this, encoder->plugin->compression_format); - heif_encoding_options* options = heif_encoding_options_alloc(); + heif_encoding_options* options = heif_encoding_options_alloc(); // TODO: should this be taken from heif_context_add_tiled_image() ? Result> colorConversionResult = item->convert_colorspace_for_encoding(image, encoder, *options); if (colorConversionResult.error) { diff --git a/libheif/context.h b/libheif/context.h index 025e4de8d7..0572dcfe4a 100644 --- a/libheif/context.h +++ b/libheif/context.h @@ -160,7 +160,8 @@ class HeifContext : public ErrorBuffer Result> add_iovl_item(const ImageOverlay& overlayspec); - Result> add_tiled_item(const heif_tiled_image_parameters* parameters); + Result> add_tiled_item(const heif_tiled_image_parameters* parameters, + const struct heif_encoder* encoder); Error add_tiled_image_tile(heif_item_id tili_id, uint32_t tile_x, uint32_t tile_y, const std::shared_ptr& image, diff --git a/libheif/image-items/image_item.cc b/libheif/image-items/image_item.cc index bda776d301..d15a6eaaac 100644 --- a/libheif/image-items/image_item.cc +++ b/libheif/image-items/image_item.cc @@ -108,6 +108,28 @@ heif_compression_format ImageItem::compression_format_from_fourcc_infe_type(uint } } +uint32_t ImageItem::compression_format_to_fourcc_infe_type(heif_compression_format format) +{ + switch (format) { + case heif_compression_JPEG: + return fourcc("jpeg"); + case heif_compression_HEVC: + return fourcc("hvc1"); + case heif_compression_AV1: + return fourcc("av01"); + case heif_compression_VVC: + return fourcc("vvc1"); + case heif_compression_JPEG2000: + return fourcc("j2k1"); + case heif_compression_uncompressed: + return fourcc("unci"); + case heif_compression_mask: + return fourcc("mski"); + default: + return 0; + } +} + std::shared_ptr ImageItem::alloc_for_infe_box(HeifContext* ctx, const std::shared_ptr& infe) { diff --git a/libheif/image-items/image_item.h b/libheif/image-items/image_item.h index 28f5bbbfb1..c55d323c82 100644 --- a/libheif/image-items/image_item.h +++ b/libheif/image-items/image_item.h @@ -62,6 +62,8 @@ class ImageItem : public ErrorBuffer static heif_compression_format compression_format_from_fourcc_infe_type(uint32_t type); + static uint32_t compression_format_to_fourcc_infe_type(heif_compression_format); + Result> convert_colorspace_for_encoding(const std::shared_ptr& image, struct heif_encoder* encoder, const struct heif_encoding_options& options); diff --git a/libheif/image-items/tiled.cc b/libheif/image-items/tiled.cc index 6df735f55e..3cb140db72 100644 --- a/libheif/image-items/tiled.cc +++ b/libheif/image-items/tiled.cc @@ -24,6 +24,7 @@ #include #include "security_limits.h" #include "codecs/hevc_dec.h" +#include "libheif/api_structs.h" static uint64_t readvec(const std::vector& data, size_t& ptr, int len) @@ -127,7 +128,7 @@ Error Box_tilC::write(StreamWriter& writer) const writer.write32(m_parameters.tile_width); writer.write32(m_parameters.tile_height); - writer.write32(m_parameters.compression_type_fourcc); + writer.write32(m_parameters.compression_format_fourcc); writer.write8(m_parameters.number_of_extra_dimensions); @@ -150,7 +151,7 @@ std::string Box_tilC::dump(Indent& indent) const sstr << indent << "version: " << ((int) get_version()) << "\n" //<< indent << "image size: " << m_parameters.image_width << "x" << m_parameters.image_height << "\n" << indent << "tile size: " << m_parameters.tile_width << "x" << m_parameters.tile_height << "\n" - << indent << "compression: " << fourcc_to_string(m_parameters.compression_type_fourcc) << "\n" + << indent << "compression: " << fourcc_to_string(m_parameters.compression_format_fourcc) << "\n" << indent << "tiles are sequential: " << (m_parameters.tiles_are_sequential ? "yes" : "no") << "\n" << indent << "offset field length: " << ((int) m_parameters.offset_field_length) << " bits\n" << indent << "size field length: " << ((int) m_parameters.size_field_length) << " bits\n" @@ -213,7 +214,7 @@ Error Box_tilC::parse(BitstreamRange& range, const heif_security_limits* limits) m_parameters.tile_width = range.read32(); m_parameters.tile_height = range.read32(); - m_parameters.compression_type_fourcc = range.read32(); + m_parameters.compression_format_fourcc = range.read32(); if (m_parameters.tile_width == 0 || m_parameters.tile_height == 0) { return {heif_error_Invalid_input, @@ -445,7 +446,7 @@ ImageItem_Tiled::ImageItem_Tiled(HeifContext* ctx, heif_item_id id) heif_compression_format ImageItem_Tiled::get_compression_format() const { - return compression_format_from_fourcc_infe_type(m_tild_header.get_parameters().compression_type_fourcc); + return compression_format_from_fourcc_infe_type(m_tild_header.get_parameters().compression_format_fourcc); } @@ -481,7 +482,7 @@ Error ImageItem_Tiled::on_load_file() return err; } - m_tile_decoder = Decoder::alloc_for_infe_type(get_context(), get_id(), parameters.compression_type_fourcc); + m_tile_decoder = Decoder::alloc_for_infe_type(get_context(), get_id(), parameters.compression_format_fourcc); if (!m_tile_decoder) { return {heif_error_Unsupported_feature, heif_suberror_Unsupported_codec, @@ -499,7 +500,8 @@ Error ImageItem_Tiled::on_load_file() Result> -ImageItem_Tiled::add_new_tiled_item(HeifContext* ctx, const heif_tiled_image_parameters* parameters) +ImageItem_Tiled::add_new_tiled_item(HeifContext* ctx, const heif_tiled_image_parameters* parameters, + const heif_encoder* encoder) { auto max_tild_tiles = ctx->get_security_limits()->max_number_of_tiles; if (max_tild_tiles && number_of_tiles(*parameters) > max_tild_tiles) { @@ -521,12 +523,14 @@ ImageItem_Tiled::add_new_tiled_item(HeifContext* ctx, const heif_tiled_image_par auto tilC_box = std::make_shared(); tilC_box->set_parameters(*parameters); + tilC_box->set_compression_format(encoder->plugin->compression_format); ctx->get_heif_file()->add_property(tild_id, tilC_box, true); // Create header + offset table TildHeader tild_header; tild_header.set_parameters(*parameters); + tild_header.set_compression_format(encoder->plugin->compression_format); std::vector header_data = tild_header.write_offset_table(); @@ -616,7 +620,7 @@ Result> ImageItem_Tiled::decode_grid_tile(const heif_decoding_options& options, uint32_t tx, uint32_t ty) const { heif_compression_format format = compression_format_from_fourcc_infe_type( - m_tild_header.get_parameters().compression_type_fourcc); + m_tild_header.get_parameters().compression_format_fourcc); // --- get compressed data diff --git a/libheif/image-items/tiled.h b/libheif/image-items/tiled.h index c5c8608c4a..de6782638e 100644 --- a/libheif/image-items/tiled.h +++ b/libheif/image-items/tiled.h @@ -59,6 +59,8 @@ class Box_tilC : public FullBox void set_parameters(const heif_tiled_image_parameters& params) { m_parameters = params; } + void set_compression_format(heif_compression_format format) { m_parameters.compression_format_fourcc = ImageItem::compression_format_to_fourcc_infe_type(format); } + const heif_tiled_image_parameters& get_parameters() const { return m_parameters; } Error write(StreamWriter& writer) const override; @@ -84,6 +86,8 @@ class TildHeader const heif_tiled_image_parameters& get_parameters() const { return m_parameters; } + void set_compression_format(heif_compression_format format) { m_parameters.compression_format_fourcc = ImageItem::compression_format_to_fourcc_infe_type(format); } + Error read_full_offset_table(const std::shared_ptr& file, heif_item_id tild_id, const heif_security_limits* limits); Error read_offset_table_range(const std::shared_ptr& file, heif_item_id tild_id, @@ -140,7 +144,8 @@ class ImageItem_Tiled : public ImageItem heif_compression_format get_compression_format() const override; - static Result> add_new_tiled_item(HeifContext* ctx, const heif_tiled_image_parameters* parameters); + static Result> add_new_tiled_item(HeifContext* ctx, const heif_tiled_image_parameters* parameters, + const heif_encoder* encoder); Error on_load_file() override; From 8fd9d90bad530cff9cab2d7c1d59a810d814024d Mon Sep 17 00:00:00 2001 From: Dirk Farin Date: Sat, 12 Oct 2024 22:38:35 +0200 Subject: [PATCH 08/11] heif-enc: encode tiled unci images --- examples/heif_enc.cc | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/examples/heif_enc.cc b/examples/heif_enc.cc index d8cdc1c936..247f3090af 100644 --- a/examples/heif_enc.cc +++ b/examples/heif_enc.cc @@ -818,12 +818,27 @@ heif_image_handle* encode_tiled(heif_context* ctx, heif_encoder* encoder, heif_e error = heif_context_add_tiled_image(ctx, &tiled_params, options, encoder, &tiled_image); if (error.code != 0) { - std::cerr << "Could not generate grid image: " << error.message << "\n"; + std::cerr << "Could not generate tili image: " << error.message << "\n"; return nullptr; } } else if (tiling_method == "unci") { - // TODO + heif_unci_image_parameters params{}; + params.version = 1; + params.image_width = tiling.image_width; + params.image_height = tiling.image_height; + params.tile_width = tiling.tile_width; + params.tile_height = tiling.tile_height; + params.compression = heif_metadata_compression_brotli; + + std::string input_filename = tile_generator.filename(0,0); + InputImage prototype_image = load_image(input_filename, output_bit_depth); + + error = heif_context_add_unci_image(ctx, ¶ms, options, prototype_image.image.get(), &tiled_image); + if (error.code != 0) { + std::cerr << "Could not generate unci image: " << error.message << "\n"; + return nullptr; + } } else { assert(false); @@ -1279,6 +1294,11 @@ int main(int argc, char** argv) } } + if (handle==nullptr) { + std::cerr << "Could not encode image\n"; + return 1; + } + if (is_primary_image) { heif_context_set_primary_image(context.get(), handle); } From 569a17dee61212b020b8eae1266e02ee6ebdfdae Mon Sep 17 00:00:00 2001 From: Dirk Farin Date: Sat, 12 Oct 2024 22:57:43 +0200 Subject: [PATCH 09/11] heif-enc: option to set unci compression method --- examples/heif_enc.cc | 28 ++++++++++++++++++++++++++-- libheif/image-items/unc_image.cc | 4 +++- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/examples/heif_enc.cc b/examples/heif_enc.cc index 247f3090af..5c2c6b7cd6 100644 --- a/examples/heif_enc.cc +++ b/examples/heif_enc.cc @@ -64,6 +64,7 @@ std::string chroma_downsampling; int tiled_image_width = 0; int tiled_image_height = 0; std::string tiling_method; +heif_metadata_compression unci_compression = heif_metadata_compression_brotli; uint16_t nclx_colour_primaries = 1; uint16_t nclx_transfer_characteristic = 13; @@ -103,6 +104,7 @@ const int OPTION_USE_VVC_COMPRESSION = 1010; const int OPTION_TILED_IMAGE_WIDTH = 1011; const int OPTION_TILED_IMAGE_HEIGHT = 1012; const int OPTION_TILING_METHOD = 1013; +const int OPTION_UNCI_COMPRESSION = 1014; static struct option long_options[] = { @@ -127,6 +129,7 @@ static struct option long_options[] = { {(char* const) "htj2k", no_argument, 0, OPTION_USE_HTJ2K_COMPRESSION}, #if WITH_UNCOMPRESSED_CODEC {(char* const) "uncompressed", no_argument, 0, 'U'}, + {(char* const) "unci-compression-method", required_argument, nullptr, OPTION_UNCI_COMPRESSION}, #endif {(char* const) "matrix_coefficients", required_argument, 0, OPTION_NCLX_MATRIX_COEFFICIENTS}, {(char* const) "colour_primaries", required_argument, 0, OPTION_NCLX_COLOUR_PRIMARIES}, @@ -180,7 +183,8 @@ void show_help(const char* argv0) << " --jpeg2000 encode as JPEG 2000 (experimental)\n" << " --htj2k encode as High Throughput JPEG 2000 (experimental)\n" #if WITH_UNCOMPRESSED_CODEC - << " -U, --uncompressed encode as uncompressed image (according to ISO 23001-17) (EXPERIMENTAL)\n" + << " -U, --uncompressed encode as uncompressed image (according to ISO 23001-17) (EXPERIMENTAL)\n" + << " --unci-compression METHOD choose one of these methods: none, deflate, zlib, brotli.\n" #endif << " --list-encoders list all available encoders for all compression formats\n" << " -e, --encoder ID select encoder to use (the IDs can be listed with --list-encoders)\n" @@ -829,7 +833,7 @@ heif_image_handle* encode_tiled(heif_context* ctx, heif_encoder* encoder, heif_e params.image_height = tiling.image_height; params.tile_width = tiling.tile_width; params.tile_height = tiling.tile_height; - params.compression = heif_metadata_compression_brotli; + params.compression = unci_compression; std::string input_filename = tile_generator.filename(0,0); InputImage prototype_image = load_image(input_filename, output_bit_depth); @@ -1012,6 +1016,26 @@ int main(int argc, char** argv) exit(5); } break; + case OPTION_UNCI_COMPRESSION: { + std::string option(optarg); + if (option == "none") { + unci_compression = heif_metadata_compression_off; + } + else if (option == "brotli") { + unci_compression = heif_metadata_compression_brotli; + } + else if (option == "deflate") { + unci_compression = heif_metadata_compression_deflate; + } + else if (option == "zlib") { + unci_compression = heif_metadata_compression_zlib; + } + else { + std::cerr << "Invalid unci compression method '" << option << "'\n"; + exit(5); + } + break; + } case 'C': chroma_downsampling = optarg; if (chroma_downsampling != "nn" && diff --git a/libheif/image-items/unc_image.cc b/libheif/image-items/unc_image.cc index 356ea0992a..875d95fc33 100644 --- a/libheif/image-items/unc_image.cc +++ b/libheif/image-items/unc_image.cc @@ -377,7 +377,9 @@ Result> ImageItem_uncompressed::add_unci std::vector dummydata; dummydata.resize(tile_size); - for (uint64_t i = 0; i < tile_size; i++) { + uint32_t nTiles = (parameters->image_width / parameters->tile_width) * (parameters->image_height / parameters->tile_height); + + for (uint64_t i = 0; i < nTiles; i++) { const int construction_method = 0; // 0=mdat 1=idat file->append_iloc_data(unci_id, dummydata, construction_method); } From 96a150e47c31d6466ab58f32384023c2f65000cb Mon Sep 17 00:00:00 2001 From: Dirk Farin Date: Sun, 13 Oct 2024 02:33:09 +0200 Subject: [PATCH 10/11] (clang-tidy false positive) --- examples/heif_enc.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/heif_enc.cc b/examples/heif_enc.cc index 5c2c6b7cd6..68b220f124 100644 --- a/examples/heif_enc.cc +++ b/examples/heif_enc.cc @@ -846,6 +846,7 @@ heif_image_handle* encode_tiled(heif_context* ctx, heif_encoder* encoder, heif_e } else { assert(false); + exit(10); } std::cout << "encoding tiled image, tile size: " << tiling.tile_width << "x" << tiling.tile_height From bbbd67bd88140e66f116b9c1d7b5c3edcca377bf Mon Sep 17 00:00:00 2001 From: Dirk Farin Date: Sun, 13 Oct 2024 19:23:30 +0200 Subject: [PATCH 11/11] change 'grid' encoding API to match other grid image types --- examples/heif_enc.cc | 85 ++++++++++--------------------------- libheif/api/libheif/heif.cc | 23 ++++------ libheif/api/libheif/heif.h | 2 +- libheif/box.cc | 63 +++++++++++++++++++++------ libheif/box.h | 4 ++ libheif/context.cc | 65 ++++++++++++++++++++-------- libheif/context.h | 10 +++-- libheif/file.cc | 7 +++ libheif/file.h | 2 + libheif/image-items/grid.cc | 15 +++++-- libheif/image-items/grid.h | 9 ++++ 11 files changed, 170 insertions(+), 115 deletions(-) diff --git a/examples/heif_enc.cc b/examples/heif_enc.cc index 68b220f124..dec8b9d14a 100644 --- a/examples/heif_enc.cc +++ b/examples/heif_enc.cc @@ -63,7 +63,7 @@ const char* encoderId = nullptr; std::string chroma_downsampling; int tiled_image_width = 0; int tiled_image_height = 0; -std::string tiling_method; +std::string tiling_method = "grid"; heif_metadata_compression unci_compression = heif_metadata_compression_brotli; uint16_t nclx_colour_primaries = 1; @@ -747,80 +747,38 @@ std::optional determine_input_images_tiling(const std::st } -heif_image_handle* encode_tiled_grid(heif_context* ctx, heif_encoder* encoder, heif_encoding_options* options, - int output_bit_depth, - const input_tiles_generator& tile_generator, - const heif_image_tiling& tiling) -{ - std::vector image_ids; - - std::cout << "encoding tiled image, tile size: " << tiling.tile_width << "x" << tiling.tile_height - << " image size: " << tiling.image_width << "x" << tiling.image_height << "\n"; - - for (uint32_t ty = 0; ty < tile_generator.nRows(); ty++) - for (uint32_t tx = 0; tx < tile_generator.nColumns(); tx++) { - std::string input_filename = tile_generator.filename(tx,ty); - - InputImage input_image = load_image(input_filename, output_bit_depth); - - std::cout << "encoding tile " << ty+1 << " " << tx+1 - << " (of " << tile_generator.nRows() << "x" << tile_generator.nColumns() << ") \r"; - std::cout.flush(); - - heif_image_handle* handle; - heif_error error = heif_context_encode_image(ctx, - input_image.image.get(), - encoder, - options, - &handle); - if (error.code != 0) { - std::cerr << "Could not encode HEIF/AVIF file: " << error.message << "\n"; - return nullptr; - } - - image_ids.push_back(heif_image_handle_get_item_id(handle)); - heif_image_handle_release(handle); - } - - std::cout << "\n"; - - heif_image_handle* grid_image; - heif_error error = heif_context_add_grid_image(ctx, tiling.image_width, tiling.image_height, - tiling.num_columns, tiling.num_rows, image_ids.data(), &grid_image); - if (error.code != 0) { - std::cerr << "Could not generate grid image: " << error.message << "\n"; - return nullptr; - } - - return grid_image; -} - - heif_image_handle* encode_tiled(heif_context* ctx, heif_encoder* encoder, heif_encoding_options* options, int output_bit_depth, const input_tiles_generator& tile_generator, const heif_image_tiling& tiling) { - if (tiling_method == "grid") { - return encode_tiled_grid(ctx, encoder, options, output_bit_depth, tile_generator, tiling); - } + heif_image_handle* tiled_image; - heif_image_handle* tiled_image; - heif_error error; - if (tiling_method == "tili") { + // --- create the main grid image + + if (tiling_method == "grid") { + heif_error error = heif_context_add_grid_image(ctx, tiling.image_width, tiling.image_height, + tiling.num_columns, tiling.num_rows, + options, + &tiled_image); + if (error.code != 0) { + std::cerr << "Could not generate grid image: " << error.message << "\n"; + return nullptr; + } + } + else if (tiling_method == "tili") { heif_tiled_image_parameters tiled_params{}; tiled_params.version = 1; tiled_params.image_width = tiling.image_width; tiled_params.image_height = tiling.image_height; tiled_params.tile_width = tiling.tile_width; tiled_params.tile_height = tiling.tile_height; - // tiled_params.compression_format_fourcc = heif_fourcc('a', 'v', '0', '1'); // TODO HACK tiled_params.offset_field_length = 32; tiled_params.size_field_length = 24; tiled_params.tiles_are_sequential = 1; - error = heif_context_add_tiled_image(ctx, &tiled_params, options, encoder, &tiled_image); + heif_error error = heif_context_add_tiled_image(ctx, &tiled_params, options, encoder, &tiled_image); if (error.code != 0) { std::cerr << "Could not generate tili image: " << error.message << "\n"; return nullptr; @@ -835,10 +793,10 @@ heif_image_handle* encode_tiled(heif_context* ctx, heif_encoder* encoder, heif_e params.tile_height = tiling.tile_height; params.compression = unci_compression; - std::string input_filename = tile_generator.filename(0,0); + std::string input_filename = tile_generator.filename(0, 0); InputImage prototype_image = load_image(input_filename, output_bit_depth); - error = heif_context_add_unci_image(ctx, ¶ms, options, prototype_image.image.get(), &tiled_image); + heif_error error = heif_context_add_unci_image(ctx, ¶ms, options, prototype_image.image.get(), &tiled_image); if (error.code != 0) { std::cerr << "Could not generate unci image: " << error.message << "\n"; return nullptr; @@ -849,6 +807,9 @@ heif_image_handle* encode_tiled(heif_context* ctx, heif_encoder* encoder, heif_e exit(10); } + + // --- add all the image tiles + std::cout << "encoding tiled image, tile size: " << tiling.tile_width << "x" << tiling.tile_height << " image size: " << tiling.image_width << "x" << tiling.image_height << "\n"; @@ -863,8 +824,8 @@ heif_image_handle* encode_tiled(heif_context* ctx, heif_encoder* encoder, heif_e std::cout.flush(); heif_error error = heif_context_add_image_tile(ctx, tiled_image, tx, ty, - input_image.image.get(), - encoder); + input_image.image.get(), + encoder); if (error.code != 0) { std::cerr << "Could not encode HEIF/AVIF file: " << error.message << "\n"; return nullptr; diff --git a/libheif/api/libheif/heif.cc b/libheif/api/libheif/heif.cc index 09f55db0f0..7e340fa7cc 100644 --- a/libheif/api/libheif/heif.cc +++ b/libheif/api/libheif/heif.cc @@ -3429,14 +3429,10 @@ struct heif_error heif_context_add_grid_image(struct heif_context* ctx, uint32_t image_height, uint32_t tile_columns, uint32_t tile_rows, - const heif_item_id* image_ids, + const struct heif_encoding_options* encoding_options, struct heif_image_handle** out_grid_image_handle) { - if (!image_ids) { - return Error(heif_error_Usage_error, - heif_suberror_Null_pointer_argument).error_struct(ctx->context.get()); - } - else if (tile_rows == 0 || tile_columns == 0) { + if (tile_rows == 0 || tile_columns == 0) { return Error(heif_error_Usage_error, heif_suberror_Invalid_parameter_value).error_struct(ctx->context.get()); } @@ -3446,16 +3442,11 @@ struct heif_error heif_context_add_grid_image(struct heif_context* ctx, "Number of tile rows/columns may not exceed 65535"}; } - - std::vector tiles(tile_rows * tile_columns); - for (uint64_t i = 0; i < tile_rows * tile_columns; i++) { - tiles[i] = image_ids[i]; - } - - std::shared_ptr gridimage; - Error error = ctx->context->add_grid_item(tiles, image_width, image_height, + std::shared_ptr gridimage; + Error error = ctx->context->add_grid_item(image_width, image_height, static_cast(tile_rows), static_cast(tile_columns), + encoding_options, gridimage); if (error != Error::Ok) { @@ -3565,6 +3556,10 @@ struct heif_error heif_context_add_image_tile(struct heif_context* ctx, return err.error_struct(ctx->context.get()); } #endif + else if (tiled_image->image->get_infe_type() == fourcc("grid")) { + Error err = ctx->context->add_grid_image_tile(tiled_image->image->get_id(), tile_x, tile_y, image->image, encoder); + return err.error_struct(ctx->context.get()); + } else { return { heif_error_Usage_error, diff --git a/libheif/api/libheif/heif.h b/libheif/api/libheif/heif.h index 4e5aa3c5cf..c9f4564483 100644 --- a/libheif/api/libheif/heif.h +++ b/libheif/api/libheif/heif.h @@ -2369,7 +2369,7 @@ struct heif_error heif_context_add_grid_image(struct heif_context* ctx, uint32_t image_height, uint32_t tile_columns, uint32_t tile_rows, - const heif_item_id* image_ids, + const struct heif_encoding_options* encoding_options, struct heif_image_handle** out_grid_image_handle); LIBHEIF_API diff --git a/libheif/box.cc b/libheif/box.cc index c78880efa8..1200f8306c 100644 --- a/libheif/box.cc +++ b/libheif/box.cc @@ -2766,6 +2766,15 @@ void Box_ipma::add_property_for_item_ID(heif_item_id itemID, m_entries.push_back(entry); } + // If the property is already associated with the item, skip. + for (auto const& a : m_entries[idx].associations) { + if (a.property_index == assoc.property_index) { + return; + } + + // TODO: should we check that the essential flag matches and return an internal error if not? + } + // add the property association m_entries[idx].associations.push_back(assoc); } @@ -3209,18 +3218,8 @@ Error Box_iref::parse(BitstreamRange& range, const heif_security_limits* limits) // --- check for duplicate references - for (const auto& ref : m_references) { - std::set to_ids; - for (const auto to_id : ref.to_item_ID) { - if (to_ids.find(to_id) == to_ids.end()) { - to_ids.insert(to_id); - } - else { - return Error(heif_error_Invalid_input, - heif_suberror_Unspecified, - "'iref' has double references"); - } - } + if (auto error = check_for_double_references()) { + return error; } @@ -3279,6 +3278,26 @@ Error Box_iref::parse(BitstreamRange& range, const heif_security_limits* limits) } +Error Box_iref::check_for_double_references() const +{ + for (const auto& ref : m_references) { + std::set to_ids; + for (const auto to_id : ref.to_item_ID) { + if (to_ids.find(to_id) == to_ids.end()) { + to_ids.insert(to_id); + } + else { + return {heif_error_Invalid_input, + heif_suberror_Unspecified, + "'iref' has double references"}; + } + } + } + + return Error::Ok; +} + + void Box_iref::derive_box_version() { uint8_t version = 0; @@ -3303,6 +3322,10 @@ void Box_iref::derive_box_version() Error Box_iref::write(StreamWriter& writer) const { + if (auto error = check_for_double_references()) { + return error; + } + size_t box_start = reserve_box_header_space(writer); int id_size = ((get_version() == 0) ? 2 : 4); @@ -3322,7 +3345,6 @@ Error Box_iref::write(StreamWriter& writer) const } } - prepend_header(writer, box_start); return Error::Ok; @@ -3400,6 +3422,21 @@ void Box_iref::add_references(heif_item_id from_id, uint32_t type, const std::ve } +void Box_iref::overwrite_reference(heif_item_id from_id, uint32_t type, uint32_t reference_idx, heif_item_id to_item) +{ + for (auto& ref : m_references) { + if (ref.from_item_ID == from_id && ref.header.get_short_type() == type) { + assert(reference_idx >= 0 && reference_idx < ref.to_item_ID.size()); + + ref.to_item_ID[reference_idx] = to_item; + return; + } + } + + assert(false); // reference was not found +} + + Error Box_idat::parse(BitstreamRange& range, const heif_security_limits* limits) { //parse_full_box_header(range); diff --git a/libheif/box.h b/libheif/box.h index 4759f62e21..3722b9f74c 100644 --- a/libheif/box.h +++ b/libheif/box.h @@ -932,6 +932,8 @@ class Box_iref : public FullBox void add_references(heif_item_id from_id, uint32_t type, const std::vector& to_ids); + void overwrite_reference(heif_item_id from_id, uint32_t type, uint32_t reference_idx, heif_item_id to_item); + protected: Error parse(BitstreamRange& range, const heif_security_limits*) override; @@ -939,6 +941,8 @@ class Box_iref : public FullBox void derive_box_version() override; + Error check_for_double_references() const; + private: std::vector m_references; }; diff --git a/libheif/context.cc b/libheif/context.cc index 7ccc6c0d97..30bbe9f05c 100644 --- a/libheif/context.cc +++ b/libheif/context.cc @@ -1243,26 +1243,19 @@ Error HeifContext::encode_grid(const std::vector } -Error HeifContext::add_grid_item(const std::vector& tile_ids, - uint32_t output_width, - uint32_t output_height, - uint16_t tile_rows, - uint16_t tile_columns, - std::shared_ptr& out_grid_image) +Error HeifContext::add_grid_item(uint32_t output_width, + uint32_t output_height, + uint16_t tile_rows, + uint16_t tile_columns, + const struct heif_encoding_options* encoding_options, + std::shared_ptr& out_grid_image) { - if (tile_ids.size() > 0xFFFF) { + if (tile_rows > 0xFFFF / tile_columns) { return {heif_error_Usage_error, heif_suberror_Unspecified, "Too many tiles (maximum: 65535)"}; } -#if 1 - for (heif_item_id tile_id : tile_ids) { - m_heif_file->get_infe_box(tile_id)->set_hidden_item(true); // only show the full grid - } -#endif - - // Create ImageGrid ImageGrid grid; @@ -1273,20 +1266,25 @@ Error HeifContext::add_grid_item(const std::vector& tile_ids, // Create Grid Item heif_item_id grid_id = m_heif_file->add_new_image(fourcc("grid")); - out_grid_image = std::make_shared(this, grid_id); + out_grid_image = std::make_shared(this, grid_id); + out_grid_image->set_encoding_options(encoding_options); + out_grid_image->set_grid_spec(grid); + m_all_images.insert(std::make_pair(grid_id, out_grid_image)); const int construction_method = 1; // 0=mdat 1=idat m_heif_file->append_iloc_data(grid_id, grid_data, construction_method); + // generate dummy grid item IDs (0) + std::vector tile_ids; + tile_ids.resize(tile_rows * tile_columns); + // Connect tiles to grid m_heif_file->add_iref_reference(grid_id, fourcc("dimg"), tile_ids); // Add ISPE property m_heif_file->add_ispe_property(grid_id, output_width, output_height, false); - // Add PIXI property (copy from first tile) - auto pixi = m_heif_file->get_property(tile_ids[0]); - m_heif_file->add_property(grid_id, pixi, true); + // PIXI property will be added when the first tile is set // Set Brands //m_heif_file->set_brand(encoder->plugin->compression_format, @@ -1427,6 +1425,37 @@ Error HeifContext::add_tiled_image_tile(heif_item_id tild_id, uint32_t tile_x, u } +Error HeifContext::add_grid_image_tile(heif_item_id grid_id, uint32_t tile_x, uint32_t tile_y, + const std::shared_ptr& image, + struct heif_encoder* encoder) +{ + auto grid_item = std::dynamic_pointer_cast(get_image(grid_id)); + auto encoding_options = grid_item->get_encoding_options(); + + std::shared_ptr encoded_image; + Error error = encode_image(image, + encoder, + *encoding_options, + heif_image_input_class_normal, + encoded_image); + if (error != Error::Ok) { + return error; + } + + m_heif_file->get_infe_box(encoded_image->get_id())->set_hidden_item(true); // grid tiles are hidden items + + // Assign tile to grid + heif_image_tiling tiling = grid_item->get_heif_image_tiling(); + m_heif_file->set_iref_reference(grid_id, fourcc("dimg"), tile_y * tiling.num_columns + tile_x, encoded_image->get_id()); + + // Add PIXI property (copy from first tile) + auto pixi = m_heif_file->get_property(encoded_image->get_id()); + m_heif_file->add_property(grid_id, pixi, true); + + return Error::Ok; +} + + Result> HeifContext::add_unci_item(const heif_unci_image_parameters* parameters, const struct heif_encoding_options* encoding_options, const std::shared_ptr& prototype) diff --git a/libheif/context.h b/libheif/context.h index 0572dcfe4a..8694035b5c 100644 --- a/libheif/context.h +++ b/libheif/context.h @@ -151,12 +151,12 @@ class HeifContext : public ErrorBuffer const struct heif_encoding_options& options, std::shared_ptr& out_image); - Error add_grid_item(const std::vector& tile_ids, - uint32_t output_width, + Error add_grid_item(uint32_t output_width, uint32_t output_height, uint16_t tile_rows, uint16_t tile_columns, - std::shared_ptr& out_grid_image); + const struct heif_encoding_options* encoding_options, + std::shared_ptr& out_grid_image); Result> add_iovl_item(const ImageOverlay& overlayspec); @@ -167,6 +167,10 @@ class HeifContext : public ErrorBuffer const std::shared_ptr& image, struct heif_encoder* encoder); + Error add_grid_image_tile(heif_item_id grid_id, uint32_t tile_x, uint32_t tile_y, + const std::shared_ptr& image, + struct heif_encoder* encoder); + Result> add_unci_item(const heif_unci_image_parameters* parameters, const struct heif_encoding_options* encoding_options, const std::shared_ptr& prototype); diff --git a/libheif/file.cc b/libheif/file.cc index 5196b252c3..83c61ffcb0 100644 --- a/libheif/file.cc +++ b/libheif/file.cc @@ -1045,6 +1045,13 @@ void HeifFile::add_iref_reference(heif_item_id from, uint32_t type, } +void HeifFile::set_iref_reference(heif_item_id from, uint32_t type, int reference_idx, heif_item_id to_item) +{ + assert(m_iref_box); + m_iref_box->overwrite_reference(from, type, reference_idx, to_item); +} + + void HeifFile::add_entity_group_box(const std::shared_ptr& entity_group_box) { if (!m_grpl_box) { diff --git a/libheif/file.h b/libheif/file.h index be17c336d5..2a3eb2024a 100644 --- a/libheif/file.h +++ b/libheif/file.h @@ -187,6 +187,8 @@ class HeifFile void add_iref_reference(heif_item_id from, uint32_t type, const std::vector& to); + void set_iref_reference(heif_item_id from, uint32_t type, int reference_idx, heif_item_id to_item); + void add_entity_group_box(const std::shared_ptr& entity_group_box); void set_auxC_property(heif_item_id id, const std::string& type); diff --git a/libheif/image-items/grid.cc b/libheif/image-items/grid.cc index 7b25f09e78..9e31c4efda 100644 --- a/libheif/image-items/grid.cc +++ b/libheif/image-items/grid.cc @@ -457,10 +457,17 @@ heif_image_tiling ImageItem_Grid::get_heif_image_tiling() const tiling.num_columns = gridspec.get_columns(); tiling.num_rows = gridspec.get_rows(); - heif_item_id tile0_id = get_grid_tiles()[0]; - auto tile0 = get_context()->get_image(tile0_id); - tiling.tile_width = tile0->get_width(); - tiling.tile_height = tile0->get_height(); + auto tile_ids = get_grid_tiles(); + if (!tile_ids.empty() && tile_ids[0] != 0) { + heif_item_id tile0_id = tile_ids[0]; + auto tile0 = get_context()->get_image(tile0_id); + tiling.tile_width = tile0->get_width(); + tiling.tile_height = tile0->get_height(); + } + else { + tiling.tile_width = 0; + tiling.tile_height = 0; + } tiling.image_width = gridspec.get_width(); tiling.image_height = gridspec.get_height(); diff --git a/libheif/image-items/grid.h b/libheif/image-items/grid.h index c040f50b07..055dde8e93 100644 --- a/libheif/image-items/grid.h +++ b/libheif/image-items/grid.h @@ -93,6 +93,12 @@ class ImageItem_Grid : public ImageItem int get_chroma_bits_per_pixel() const override; + void set_encoding_options(const heif_encoding_options* options) { + m_encoding_options = *options; + } + + const heif_encoding_options* get_encoding_options() const { return &m_encoding_options; } + Result encode(const std::shared_ptr& image, struct heif_encoder* encoder, const struct heif_encoding_options& options, @@ -113,6 +119,8 @@ class ImageItem_Grid : public ImageItem const ImageGrid& get_grid_spec() const { return m_grid_spec; } + void set_grid_spec(const ImageGrid& grid) { m_grid_spec = grid; } + const std::vector& get_grid_tiles() const { return m_grid_tile_ids; } heif_image_tiling get_heif_image_tiling() const override; @@ -123,6 +131,7 @@ class ImageItem_Grid : public ImageItem ImageGrid m_grid_spec; std::vector m_grid_tile_ids; + heif_encoding_options m_encoding_options; Error read_grid_spec();