in libheif/context.cc [2120:2404]
Error HeifContext::decode_full_grid_image(heif_item_id ID,
std::shared_ptr<HeifPixelImage>& img,
const std::vector<uint8_t>& grid_data,
const heif_decoding_options& options) const
{
ImageGrid grid;
Error err = grid.parse(grid_data);
if (err) {
return err;
}
//std::cout << grid.dump();
auto iref_box = m_heif_file->get_iref_box();
if (!iref_box) {
return Error(heif_error_Invalid_input,
heif_suberror_No_iref_box,
"No iref box available, but needed for grid image");
}
std::vector<heif_item_id> image_references = iref_box->get_references(ID, fourcc("dimg"));
if ((int) image_references.size() != grid.get_rows() * grid.get_columns()) {
std::stringstream sstr;
sstr << "Tiled image with " << grid.get_rows() << "x" << grid.get_columns() << "="
<< (grid.get_rows() * grid.get_columns()) << " tiles, but only "
<< image_references.size() << " tile images in file";
return Error(heif_error_Invalid_input,
heif_suberror_Missing_grid_images,
sstr.str());
}
// --- check that all image IDs are valid images
for (heif_item_id tile_id : image_references) {
if (!is_image(tile_id)) {
std::stringstream sstr;
sstr << "Tile image ID=" << tile_id << " is not a proper image.";
return Error(heif_error_Invalid_input,
heif_suberror_Missing_grid_images,
sstr.str());
}
}
auto ipma = m_heif_file->get_ipma_box();
auto ipco = m_heif_file->get_ipco_box();
auto pixi_box = ipco->get_property_for_item_ID(ID, ipma, fourcc("pixi"));
auto pixi = std::dynamic_pointer_cast<Box_pixi>(pixi_box);
const uint32_t w = grid.get_width();
const uint32_t h = grid.get_height();
// --- determine output image chroma size and make sure all tiles have same chroma
assert(!image_references.empty());
// heif_chroma tile_chroma = heif_chroma_444;
/* TODO: in the future, we might support RGB and mono as intermediate formats
heif_chroma tile_chroma = m_heif_file->get_image_chroma_from_configuration(some_tile_id);
if (tile_chroma != heif_chroma_monochrome) {
tile_chroma = heif_chroma_RGB;
}
*/
heif_chroma tile_chroma = m_heif_file->get_image_chroma_from_configuration(image_references[0]);
// --- generate image of full output size
err = check_resolution(w, h);
if (err) {
return err;
}
int bpp = 0;
if (pixi) {
if (pixi->get_num_channels() < 1) {
return Error(heif_error_Invalid_input,
heif_suberror_Invalid_pixi_box,
"No pixi information for luma channel.");
}
bpp = pixi->get_bits_per_channel(0);
if (tile_chroma != heif_chroma_monochrome) {
// there are broken files that save only a one-channel pixi for an RGB image (issue #283)
if (pixi->get_num_channels() == 3) {
int bpp_c1 = pixi->get_bits_per_channel(1);
int bpp_c2 = pixi->get_bits_per_channel(2);
if (bpp_c1 != bpp || bpp_c2 != bpp) {
// TODO: is this really an error? Does the pixi depths refer to RGB or YCbCr?
return Error(heif_error_Invalid_input,
heif_suberror_Invalid_pixi_box,
"Different number of bits per pixel in each channel.");
}
}
}
}
else {
// When there is no pixi-box, get the pixel-depth from one of the tile images
heif_item_id tileID = image_references[0];
auto iter = m_all_images.find(tileID);
if (iter == m_all_images.end()) {
return Error(heif_error_Invalid_input,
heif_suberror_Missing_grid_images,
"Nonexistent grid image referenced");
}
const std::shared_ptr<Image> tileImg = iter->second;
bpp = tileImg->get_luma_bits_per_pixel();
}
if (bpp < 8 || bpp > 16) {
return Error(heif_error_Invalid_input,
heif_suberror_Invalid_pixi_box,
"Invalid bits per pixel in pixi box.");
}
heif_chroma grid_chroma = heif_chroma_undefined;
img = std::make_shared<HeifPixelImage>();
img->create(w, h,
(tile_chroma == heif_chroma_monochrome) ? heif_colorspace_monochrome : heif_colorspace_YCbCr ,
tile_chroma );
if (tile_chroma == heif_chroma_monochrome) {
img->add_plane(heif_channel_Y, w, h, bpp);
}
else {
img->add_plane(heif_channel_Y, w, h, bpp);
switch(tile_chroma ) {
case heif_chroma_420 :
img->add_plane(heif_channel_Cb, (w+1)/2, (h+1)/2, bpp);
img->add_plane(heif_channel_Cr, (w+1)/2, (h+1)/2, bpp);
break;
case heif_chroma_422 :
img->add_plane(heif_channel_Cb, (w+1)/2, h, bpp);
img->add_plane(heif_channel_Cr, (w+1)/2, h, bpp);
break;
case heif_chroma_444 :
default:
img->add_plane(heif_channel_Cb, w, h, bpp);
img->add_plane(heif_channel_Cr, w, h, bpp);
break;
}
}
uint32_t y0 = 0;
int reference_idx = 0;
#if ENABLE_PARALLEL_TILE_DECODING
// remember which tile to put where into the image
struct tile_data
{
heif_item_id tileID;
uint32_t x_origin, y_origin;
};
std::deque<tile_data> tiles;
if (m_max_decoding_threads > 0)
tiles.resize(grid.get_rows() * grid.get_columns());
std::deque<std::future<Error> > errs;
#endif
uint32_t tile_width=0;
uint32_t tile_height=0;
for (uint32_t y = 0; y < grid.get_rows(); y++) {
uint32_t x0 = 0;
for (uint32_t x = 0; x < grid.get_columns(); x++) {
heif_item_id tileID = image_references[reference_idx];
auto iter = m_all_images.find(tileID);
if (iter == m_all_images.end()) {
return {heif_error_Invalid_input,
heif_suberror_Missing_grid_images,
"Nonexistent grid image referenced"};
}
const std::shared_ptr<Image> tileImg = iter->second;
uint32_t src_width = tileImg->get_width();
uint32_t src_height = tileImg->get_height();
err = check_resolution(src_width, src_height);
if (err) {
return err;
}
if (src_width < grid.get_width() / grid.get_columns() ||
src_height < grid.get_height() / grid.get_rows()) {
return {heif_error_Invalid_input,
heif_suberror_Invalid_grid_data,
"Grid tiles do not cover whole image"};
}
if (x==0 && y==0) {
// remember size of first tile and compare all other tiles against this
tile_width = src_width;
tile_height = src_height;
}
else if (src_width != tile_width || src_height != tile_height) {
return {heif_error_Invalid_input,
heif_suberror_Invalid_grid_data,
"Grid tiles have different sizes"};
}
#if ENABLE_PARALLEL_TILE_DECODING
if (m_max_decoding_threads > 0)
tiles[x + y * grid.get_columns()] = tile_data{tileID, x0, y0};
else
#else
if (1)
#endif
{
err = decode_and_paste_tile_image(tileID, img, grid_chroma, x0, y0, options);
if (err) {
return err;
}
}
x0 += src_width;
reference_idx++;
}
y0 += tile_height;
}
#if ENABLE_PARALLEL_TILE_DECODING
if (m_max_decoding_threads > 0) {
// Process all tiles in a set of background threads.
// Do not start more than the maximum number of threads.
while (tiles.empty()==false && (m_max_decoding_threads>0)) {
// If maximum number of threads running, wait until first thread finishes
if (errs.size() >= (size_t) m_max_decoding_threads) {
Error e = errs.front().get();
if (e) {
return e;
}
errs.pop_front();
}
// Start a new decoding thread
tile_data data = tiles.front();
tiles.pop_front();
errs.push_back(std::async(std::launch::async,
&HeifContext::decode_and_paste_tile_image, this,
data.tileID, img, grid_chroma, data.x_origin,data.y_origin, options) );
}
// check for decoding errors in remaining tiles
while (errs.empty() == false) {
Error e = errs.front().get();
if (e) {
return e;
}
errs.pop_front();
}
}
#endif
return Error::Ok;
}