Error HeifContext::decode_full_grid_image()

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;
}