libheif/codecs/uncompressed_image.cc (1,202 lines of code) (raw):

/* * HEIF codec. * Copyright (c) 2023 Dirk Farin <dirk.farin@gmail.com> * * This file is part of libheif. * * libheif is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * * libheif is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with libheif. If not, see <http://www.gnu.org/licenses/>. */ #include <cstdint> #include <cstring> #include <algorithm> #include <map> #include <iostream> #include <cassert> #include <utility> #include "common_utils.h" #include "context.h" #include "compression.h" #include "error.h" #include "libheif/heif.h" #include "uncompressed.h" #include "uncompressed_box.h" #include "uncompressed_image.h" static bool isKnownUncompressedFrameConfigurationBoxProfile(const std::shared_ptr<Box_uncC> &uncC) { return ((uncC != nullptr) && (uncC->get_version() == 1) && ((uncC->get_profile() == fourcc("rgb3")) || (uncC->get_profile() == fourcc("rgba")) || (uncC->get_profile() == fourcc("abgr")))); } static Error uncompressed_image_type_is_supported(std::shared_ptr<Box_uncC>& uncC, std::shared_ptr<Box_cmpd>& cmpd) { if (isKnownUncompressedFrameConfigurationBoxProfile(uncC)) { return Error::Ok; } if (!cmpd) { return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_data_version, "Missing required cmpd box (no match in uncC box) for uncompressed codec"); } for (Box_uncC::Component component : uncC->get_components()) { uint16_t component_index = component.component_index; uint16_t component_type = cmpd->get_components()[component_index].component_type; if ((component_type > 7) && (component_type != component_type_padded)) { printf("unsupported component type: %d\n", component_type); std::stringstream sstr; sstr << "Uncompressed image with component_type " << ((int) component_type) << " is not implemented yet"; return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_data_version, sstr.str()); } if ((component.component_bit_depth > 8) && (component.component_bit_depth != 16)) { printf("unsupported component bit depth for index: %d, value: %d\n", component_index, component.component_bit_depth); std::stringstream sstr; sstr << "Uncompressed image with component_bit_depth " << ((int) component.component_bit_depth) << " is not implemented yet"; return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_data_version, sstr.str()); } if (component.component_format != component_format_unsigned) { printf("unsupported component format\n"); std::stringstream sstr; sstr << "Uncompressed image with component_format " << ((int) component.component_format) << " is not implemented yet"; return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_data_version, sstr.str()); } if (component.component_align_size > 2) { printf("unsupported component_align_size\n"); std::stringstream sstr; sstr << "Uncompressed image with component_align_size " << ((int) component.component_align_size) << " is not implemented yet"; return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_data_version, sstr.str()); } } if ((uncC->get_sampling_type() != sampling_mode_no_subsampling) && (uncC->get_sampling_type() != sampling_mode_422) && (uncC->get_sampling_type() != sampling_mode_420) ) { printf("bad sampling: %d\n", uncC->get_sampling_type()); std::stringstream sstr; sstr << "Uncompressed sampling_type of " << ((int) uncC->get_sampling_type()) << " is not implemented yet"; return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_data_version, sstr.str()); } if ((uncC->get_interleave_type() != interleave_mode_component) && (uncC->get_interleave_type() != interleave_mode_pixel) && (uncC->get_interleave_type() != interleave_mode_mixed) && (uncC->get_interleave_type() != interleave_mode_row) && (uncC->get_interleave_type() != interleave_mode_tile_component) ) { printf("bad interleave: %d\n", uncC->get_interleave_type()); std::stringstream sstr; sstr << "Uncompressed interleave_type of " << ((int) uncC->get_interleave_type()) << " is not implemented yet"; return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_data_version, sstr.str()); } // Validity checks per ISO/IEC 23001-17 Section 5.2.1.5.3 if (uncC->get_sampling_type() == sampling_mode_422) { // We check Y Cb and Cr appear in the chroma test // TODO: error for tile width not multiple of 2 if ((uncC->get_interleave_type() != interleave_mode_component) && (uncC->get_interleave_type() != interleave_mode_mixed) && (uncC->get_interleave_type() != interleave_mode_multi_y)) { std::stringstream sstr; sstr << "YCbCr 4:2:2 subsampling is only valid with component, mixed or multi-Y interleave mode (ISO/IEC 23001-17 5.2.1.5.3)."; return Error(heif_error_Invalid_input, heif_suberror_Invalid_parameter_value, sstr.str()); } if ((uncC->get_row_align_size() != 0) && (uncC->get_interleave_type() == interleave_mode_component)) { if (uncC->get_row_align_size() % 2 != 0) { std::stringstream sstr; sstr << "YCbCr 4:2:2 subsampling with component interleave requires row_align_size to be a multiple of 2 (ISO/IEC 23001-17 5.2.1.5.3)."; return Error(heif_error_Invalid_input, heif_suberror_Invalid_parameter_value, sstr.str()); } } if (uncC->get_tile_align_size() != 0) { if (uncC->get_tile_align_size() % 2 != 0) { std::stringstream sstr; sstr << "YCbCr 4:2:2 subsampling requires tile_align_size to be a multiple of 2 (ISO/IEC 23001-17 5.2.1.5.3)."; return Error(heif_error_Invalid_input, heif_suberror_Invalid_parameter_value, sstr.str()); } } } // Validity checks per ISO/IEC 23001-17 Section 5.2.1.5.4 if (uncC->get_sampling_type() == sampling_mode_422) { // We check Y Cb and Cr appear in the chroma test // TODO: error for tile width not multiple of 2 if ((uncC->get_interleave_type() != interleave_mode_component) && (uncC->get_interleave_type() != interleave_mode_mixed)) { std::stringstream sstr; sstr << "YCbCr 4:2:0 subsampling is only valid with component or mixed interleave mode (ISO/IEC 23001-17 5.2.1.5.4)."; return Error(heif_error_Invalid_input, heif_suberror_Invalid_parameter_value, sstr.str()); } if ((uncC->get_row_align_size() != 0) && (uncC->get_interleave_type() == interleave_mode_component)) { if (uncC->get_row_align_size() % 2 != 0) { std::stringstream sstr; sstr << "YCbCr 4:2:2 subsampling with component interleave requires row_align_size to be a multiple of 2 (ISO/IEC 23001-17 5.2.1.5.4)."; return Error(heif_error_Invalid_input, heif_suberror_Invalid_parameter_value, sstr.str()); } } if (uncC->get_tile_align_size() != 0) { if (uncC->get_tile_align_size() % 4 != 0) { std::stringstream sstr; sstr << "YCbCr 4:2:2 subsampling requires tile_align_size to be a multiple of 4 (ISO/IEC 23001-17 5.2.1.5.3)."; return Error(heif_error_Invalid_input, heif_suberror_Invalid_parameter_value, sstr.str()); } } } if ((uncC->get_interleave_type() == interleave_mode_mixed) && (uncC->get_sampling_type() == sampling_mode_no_subsampling)) { std::stringstream sstr; sstr << "Interleave interleave mode is not valid with subsampling mode (ISO/IEC 23001-17 5.2.1.6.4)."; return Error(heif_error_Invalid_input, heif_suberror_Invalid_parameter_value, sstr.str()); } if ((uncC->get_interleave_type() == interleave_mode_multi_y) && ((uncC->get_sampling_type() != sampling_mode_422) && (uncC->get_sampling_type() != sampling_mode_411))) { std::stringstream sstr; sstr << "Multi-Y interleave mode is only valid with 4:2:2 and 4:1:1 subsampling modes (ISO/IEC 23001-17 5.2.1.6.7)."; return Error(heif_error_Invalid_input, heif_suberror_Invalid_parameter_value, sstr.str()); } // TODO: throw error if mixed and Cb and Cr are not adjacent. if (uncC->get_block_size() != 0) { printf("unsupported block size\n"); std::stringstream sstr; sstr << "Uncompressed block_size of " << ((int) uncC->get_block_size()) << " is not implemented yet"; return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_data_version, sstr.str()); } if (uncC->is_components_little_endian()) { printf("unsupported components LE\n"); return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_data_version, "Uncompressed components_little_endian == 1 is not implemented yet"); } if (uncC->is_block_pad_lsb()) { printf("unsupported block pad LSB\n"); return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_data_version, "Uncompressed block_pad_lsb == 1 is not implemented yet"); } if (uncC->is_block_little_endian()) { printf("unsupported block LE\n"); return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_data_version, "Uncompressed block_little_endian == 1 is not implemented yet"); } if (uncC->is_block_reversed()) { printf("unsupported block reversed\n"); return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_data_version, "Uncompressed block_reversed == 1 is not implemented yet"); } if ((uncC->get_pixel_size() != 0) && ((uncC->get_interleave_type() != interleave_mode_pixel) && (uncC->get_interleave_type() != interleave_mode_multi_y))) { std::stringstream sstr; sstr << "Uncompressed pixel_size of " << ((int) uncC->get_pixel_size()) << " is only valid with interleave_type 1 or 5 (ISO/IEC 23001-17 5.2.1.7)"; return Error(heif_error_Invalid_input, heif_suberror_Invalid_parameter_value, sstr.str()); } return Error::Ok; } static Error get_heif_chroma_uncompressed(std::shared_ptr<Box_uncC>& uncC, std::shared_ptr<Box_cmpd>& cmpd, heif_chroma* out_chroma, heif_colorspace* out_colourspace) { *out_chroma = heif_chroma_undefined; *out_colourspace = heif_colorspace_undefined; if (isKnownUncompressedFrameConfigurationBoxProfile(uncC)) { *out_chroma = heif_chroma_444; *out_colourspace = heif_colorspace_RGB; return Error::Ok; } // each 1-bit represents an existing component in the image uint16_t componentSet = 0; for (Box_uncC::Component component : uncC->get_components()) { uint16_t component_index = component.component_index; uint16_t component_type = cmpd->get_components()[component_index].component_type; if (component_type > component_type_max_valid) { std::stringstream sstr; sstr << "a component_type > " << component_type_max_valid << " is not supported"; return { heif_error_Unsupported_feature, heif_suberror_Invalid_parameter_value, sstr.str()}; } if (component_type == component_type_padded) { // not relevant for determining chroma continue; } componentSet |= (1 << component_type); } if (componentSet == ((1 << component_type_red) | (1 << component_type_green) | (1 << component_type_blue)) || componentSet == ((1 << component_type_red) | (1 << component_type_green) | (1 << component_type_blue) | (1 << component_type_alpha))) { *out_chroma = heif_chroma_444; *out_colourspace = heif_colorspace_RGB; } if (componentSet == ((1 << component_type_Y) | (1 << component_type_Cb) | (1 << component_type_Cr))) { switch (uncC->get_sampling_type()) { case sampling_mode_no_subsampling: *out_chroma = heif_chroma_444; break; case sampling_mode_422: *out_chroma = heif_chroma_422; break; case sampling_mode_420: *out_chroma = heif_chroma_420; break; } *out_colourspace = heif_colorspace_YCbCr; } if (componentSet == ((1 << component_type_monochrome)) || componentSet == ((1 << component_type_monochrome) | (1 << component_type_alpha))) { // mono or mono + alpha input, mono output. *out_chroma = heif_chroma_monochrome; *out_colourspace = heif_colorspace_monochrome; } // TODO: more combinations if (*out_chroma == heif_chroma_undefined) { printf("unknown chroma\n"); return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_data_version, "Could not determine chroma"); } else if (*out_colourspace == heif_colorspace_undefined) { printf("unknown colourspace\n"); return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_data_version, "Could not determine colourspace"); } else { return Error::Ok; } } int UncompressedImageCodec::get_luma_bits_per_pixel_from_configuration_unci(const HeifFile& heif_file, heif_item_id imageID) { auto ipco = heif_file.get_ipco_box(); auto ipma = heif_file.get_ipma_box(); auto box1 = ipco->get_property_for_item_ID(imageID, ipma, fourcc("uncC")); std::shared_ptr<Box_uncC> uncC_box = std::dynamic_pointer_cast<Box_uncC>(box1); auto box2 = ipco->get_property_for_item_ID(imageID, ipma, fourcc("cmpd")); std::shared_ptr<Box_cmpd> cmpd_box = std::dynamic_pointer_cast<Box_cmpd>(box2); if (!uncC_box) { return -1; } if (!cmpd_box) { if (isKnownUncompressedFrameConfigurationBoxProfile(uncC_box)) { return 8; } else { return -1; } } int luma_bits = 0; int alternate_channel_bits = 0; for (Box_uncC::Component component : uncC_box->get_components()) { uint16_t component_index = component.component_index; if (component_index >= cmpd_box->get_components().size()) { return -1; } auto component_type = cmpd_box->get_components()[component_index].component_type; switch (component_type) { case component_type_monochrome: case component_type_red: case component_type_green: case component_type_blue: alternate_channel_bits = std::max(alternate_channel_bits, (int)component.component_bit_depth); break; case component_type_Y: luma_bits = std::max(luma_bits, (int)component.component_bit_depth); break; // TODO: there are other things we'll need to handle eventually, like palette. } } if (luma_bits > 0) { return luma_bits; } else if (alternate_channel_bits > 0) { return alternate_channel_bits; } else { return 8; } } static bool map_uncompressed_component_to_channel(const std::shared_ptr<Box_cmpd> &cmpd, const std::shared_ptr<Box_uncC> &uncC, Box_uncC::Component component, heif_channel *channel) { uint16_t component_index = component.component_index; if (isKnownUncompressedFrameConfigurationBoxProfile(uncC)) { if (uncC->get_profile() == fourcc("rgb3")) { switch (component_index) { case 0: *channel = heif_channel_R; return true; case 1: *channel = heif_channel_G; return true; case 2: *channel = heif_channel_B; return true; } } else if (uncC->get_profile() == fourcc("rgba")) { switch (component_index) { case 0: *channel = heif_channel_Alpha; return true; case 1: *channel = heif_channel_R; return true; case 2: *channel = heif_channel_G; return true; case 3: *channel = heif_channel_B; return true; } } else if (uncC->get_profile() == fourcc("abgr")) { switch (component_index) { case 0: *channel = heif_channel_Alpha; return true; case 1: *channel = heif_channel_B; return true; case 2: *channel = heif_channel_G; return true; case 3: *channel = heif_channel_R; return true; } } } uint16_t component_type = cmpd->get_components()[component_index].component_type; switch (component_type) { case component_type_monochrome: *channel = heif_channel_Y; return true; case component_type_Y: *channel = heif_channel_Y; return true; case component_type_Cb: *channel = heif_channel_Cb; return true; case component_type_Cr: *channel = heif_channel_Cr; return true; case component_type_red: *channel = heif_channel_R; return true; case component_type_green: *channel = heif_channel_G; return true; case component_type_blue: *channel = heif_channel_B; return true; case component_type_alpha: *channel = heif_channel_Alpha; return true; case component_type_padded: return false; default: printf("unmapped component_type: %d\n", component_type); return false; } } class UncompressedBitReader : public BitReader { public: UncompressedBitReader(const std::vector<uint8_t>& data) : BitReader(data.data(), (int)data.size()) {} void markPixelStart() { m_pixelStartOffset = get_current_byte_index(); } void markRowStart() { m_rowStartOffset = get_current_byte_index(); } void markTileStart() { m_tileStartOffset = get_current_byte_index(); } inline void handlePixelAlignment(uint32_t pixel_size) { if (pixel_size != 0) { uint32_t bytes_in_pixel = get_current_byte_index() - m_pixelStartOffset; uint32_t padding = pixel_size - bytes_in_pixel; skip_bytes(padding); } } void handleRowAlignment(uint32_t alignment) { skip_to_byte_boundary(); if (alignment != 0) { uint32_t bytes_in_row = get_current_byte_index() - m_rowStartOffset; uint32_t residual = bytes_in_row % alignment; if (residual != 0) { uint32_t padding = alignment - residual; skip_bytes(padding); } } } void handleTileAlignment(uint32_t alignment) { if (alignment != 0) { uint32_t bytes_in_tile = get_current_byte_index() - m_tileStartOffset; uint32_t residual = bytes_in_tile % alignment; if (residual != 0) { uint32_t tile_padding = alignment - residual; skip_bytes(tile_padding); } } } private: int m_pixelStartOffset; int m_rowStartOffset; int m_tileStartOffset; }; class AbstractDecoder { public: virtual Error decode(const std::vector<uint8_t>& uncompressed_data, std::shared_ptr<HeifPixelImage>& img) = 0; virtual ~AbstractDecoder() = default; protected: AbstractDecoder(uint32_t width, uint32_t height, const std::shared_ptr<Box_cmpd> cmpd, const std::shared_ptr<Box_uncC> uncC): m_width(width), m_height(height), m_cmpd(std::move(cmpd)), m_uncC(std::move(uncC)) { m_tile_height = m_height / m_uncC->get_number_of_tile_rows(); m_tile_width = m_width / m_uncC->get_number_of_tile_columns(); } const uint32_t m_width; const uint32_t m_height; const std::shared_ptr<Box_cmpd> m_cmpd; const std::shared_ptr<Box_uncC> m_uncC; // TODO: see if we can make this const uint32_t m_tile_height; uint32_t m_tile_width; class ChannelListEntry { public: uint32_t get_bytes_per_tile() const { return bytes_per_tile_row_src * tile_height; } inline uint64_t getDestinationRowOffset(uint32_t tile_row, uint32_t tile_y) const { uint64_t dst_row_number = tile_row * tile_height + tile_y; return dst_row_number * dst_plane_stride; } heif_channel channel = heif_channel_Y; uint8_t* dst_plane; uint8_t* other_chroma_dst_plane; int dst_plane_stride; int other_chroma_dst_plane_stride; uint32_t tile_width; uint32_t tile_height; uint32_t bytes_per_component_sample; uint16_t bits_per_component_sample; uint8_t component_alignment; uint32_t bytes_per_tile_row_src; bool use_channel; }; std::vector<ChannelListEntry> channelList; void buildChannelList(std::shared_ptr<HeifPixelImage>& img) { for (Box_uncC::Component component : m_uncC->get_components()) { ChannelListEntry entry = buildChannelListEntry(component, img); channelList.push_back(entry); } } protected: void processComponentSample(UncompressedBitReader &srcBits, ChannelListEntry &entry, uint64_t dst_row_offset, uint32_t tile_column, uint32_t tile_x) { uint64_t dst_col_number = tile_column * entry.tile_width + tile_x; uint64_t dst_column_offset = dst_col_number * entry.bytes_per_component_sample; int val = srcBits.get_bits(entry.bits_per_component_sample); memcpy(entry.dst_plane + dst_row_offset + dst_column_offset, &val, entry.bytes_per_component_sample); } // Handles the case where a row consists of a single component type // Not valid for Pixel interleave // Not valid for the Cb/Cr channels in Mixed Interleave // Not valid for multi-Y pixel interleave void processComponentRow(ChannelListEntry &entry, UncompressedBitReader &srcBits, uint64_t dst_row_offset, uint32_t tile_column) { for (uint32_t tile_x = 0; tile_x < entry.tile_width; tile_x++) { if (entry.component_alignment != 0) { srcBits.skip_to_byte_boundary(); int numPadBits = (entry.component_alignment * 8) - entry.bits_per_component_sample; srcBits.skip_bits(numPadBits); } processComponentSample(srcBits, entry, dst_row_offset, tile_column, tile_x); } srcBits.skip_to_byte_boundary(); } private: ChannelListEntry buildChannelListEntry(Box_uncC::Component component, std::shared_ptr<HeifPixelImage> &img) { ChannelListEntry entry; entry.use_channel = map_uncompressed_component_to_channel(m_cmpd, m_uncC, component, &(entry.channel)); entry.dst_plane = img->get_plane(entry.channel, &(entry.dst_plane_stride)); entry.tile_width = m_tile_width; entry.tile_height = m_tile_height; if ((entry.channel == heif_channel_Cb) || (entry.channel == heif_channel_Cr)) { if (m_uncC->get_sampling_type() == sampling_mode_422) { entry.tile_width /= 2; } else if (m_uncC->get_sampling_type() == sampling_mode_420) { entry.tile_width /= 2; entry.tile_height /= 2; } if (entry.channel == heif_channel_Cb) { entry.other_chroma_dst_plane = img->get_plane(heif_channel_Cr, &(entry.other_chroma_dst_plane_stride)); } else if (entry.channel == heif_channel_Cr) { entry.other_chroma_dst_plane = img->get_plane(heif_channel_Cb, &(entry.other_chroma_dst_plane_stride)); } } entry.bits_per_component_sample = component.component_bit_depth; entry.component_alignment = component.component_align_size; entry.bytes_per_component_sample = (component.component_bit_depth + 7) / 8; entry.bytes_per_tile_row_src = entry.tile_width * entry.bytes_per_component_sample; return entry; } }; class ComponentInterleaveDecoder : public AbstractDecoder { public: ComponentInterleaveDecoder(uint32_t width, uint32_t height, std::shared_ptr<Box_cmpd> cmpd, std::shared_ptr<Box_uncC> uncC): AbstractDecoder(width, height, std::move(cmpd), std::move(uncC)) {} Error decode(const std::vector<uint8_t>& uncompressed_data, std::shared_ptr<HeifPixelImage>& img) override { UncompressedBitReader srcBits(uncompressed_data); buildChannelList(img); for (uint32_t tile_row = 0; tile_row < m_uncC->get_number_of_tile_rows(); tile_row++) { for (uint32_t tile_column = 0; tile_column < m_uncC->get_number_of_tile_columns(); tile_column++) { srcBits.markTileStart(); processTile(srcBits, tile_row, tile_column); srcBits.handleTileAlignment(m_uncC->get_tile_align_size()); } } return Error::Ok; } void processTile(UncompressedBitReader &srcBits, uint32_t tile_row, uint32_t tile_column) { for (ChannelListEntry &entry : channelList) { for (uint32_t tile_y = 0; tile_y < entry.tile_height; tile_y++) { srcBits.markRowStart(); if (entry.use_channel) { uint64_t dst_row_offset = entry.getDestinationRowOffset(tile_row, tile_y); processComponentRow(entry, srcBits, dst_row_offset, tile_column); } else { srcBits.skip_bytes(entry.bytes_per_tile_row_src); } srcBits.handleRowAlignment(m_uncC->get_row_align_size()); } } } }; class PixelInterleaveDecoder : public AbstractDecoder { public: PixelInterleaveDecoder(uint32_t width, uint32_t height, std::shared_ptr<Box_cmpd> cmpd, std::shared_ptr<Box_uncC> uncC): AbstractDecoder(width, height, std::move(cmpd), std::move(uncC)) {} Error decode(const std::vector<uint8_t>& uncompressed_data, std::shared_ptr<HeifPixelImage>& img) override { UncompressedBitReader srcBits(uncompressed_data); buildChannelList(img); for (uint32_t tile_row = 0; tile_row < m_uncC->get_number_of_tile_rows(); tile_row++) { for (uint32_t tile_column = 0; tile_column < m_uncC->get_number_of_tile_columns(); tile_column++) { srcBits.markTileStart(); processTile(srcBits, tile_row, tile_column); srcBits.handleTileAlignment(m_uncC->get_tile_align_size()); } } return Error::Ok; } void processTile(UncompressedBitReader &srcBits, uint32_t tile_row, uint32_t tile_column) { for (uint32_t tile_y = 0; tile_y < m_tile_height; tile_y++) { srcBits.markRowStart(); for (uint32_t tile_x = 0; tile_x < m_tile_width; tile_x++) { srcBits.markPixelStart(); for (ChannelListEntry &entry : channelList) { if (entry.use_channel) { uint64_t dst_row_offset = entry.getDestinationRowOffset(tile_row, tile_y); if (entry.component_alignment != 0) { srcBits.skip_to_byte_boundary(); int numPadBits = (entry.component_alignment * 8) - entry.bits_per_component_sample; srcBits.skip_bits(numPadBits); } processComponentSample(srcBits, entry, dst_row_offset, tile_column, tile_x); } else { srcBits.skip_bytes(entry.bytes_per_component_sample); } } srcBits.handlePixelAlignment(m_uncC->get_pixel_size()); } srcBits.handleRowAlignment(m_uncC->get_row_align_size()); } } }; class MixedInterleaveDecoder : public AbstractDecoder { public: MixedInterleaveDecoder(uint32_t width, uint32_t height, std::shared_ptr<Box_cmpd> cmpd, std::shared_ptr<Box_uncC> uncC): AbstractDecoder(width, height, std::move(cmpd), std::move(uncC)) {} Error decode(const std::vector<uint8_t>& uncompressed_data, std::shared_ptr<HeifPixelImage>& img) override { UncompressedBitReader srcBits(uncompressed_data); buildChannelList(img); for (uint32_t tile_row = 0; tile_row < m_uncC->get_number_of_tile_rows(); tile_row++) { for (uint32_t tile_column = 0; tile_column < m_uncC->get_number_of_tile_columns(); tile_column++) { srcBits.markTileStart(); processTile(srcBits, tile_row, tile_column); srcBits.handleTileAlignment(m_uncC->get_tile_align_size()); } } return Error::Ok; } void processTile(UncompressedBitReader &srcBits, uint32_t tile_row, uint32_t tile_column) { bool haveProcessedChromaForThisTile = false; for (ChannelListEntry &entry : channelList) { if (entry.use_channel) { if ((entry.channel == heif_channel_Cb) || (entry.channel == heif_channel_Cr)) { if (!haveProcessedChromaForThisTile) { for (uint32_t tile_y = 0; tile_y < entry.tile_height; tile_y++) { // TODO: row padding uint64_t dst_row_number = tile_row * entry.tile_width + tile_y; uint64_t dst_row_offset = dst_row_number * entry.dst_plane_stride; for (uint32_t tile_x = 0; tile_x < entry.tile_width; tile_x++) { uint64_t dst_column_number = tile_column * entry.tile_width + tile_x; uint64_t dst_column_offset = dst_column_number * entry.bytes_per_component_sample; int val = srcBits.get_bits(entry.bytes_per_component_sample * 8); memcpy(entry.dst_plane + dst_row_offset + dst_column_offset, &val, entry.bytes_per_component_sample); val = srcBits.get_bits(entry.bytes_per_component_sample * 8); memcpy(entry.other_chroma_dst_plane + dst_row_offset + dst_column_offset, &val, entry.bytes_per_component_sample); } haveProcessedChromaForThisTile = true; } } } else { for (uint32_t tile_y = 0; tile_y < entry.tile_height; tile_y++) { uint64_t dst_row_offset = entry.getDestinationRowOffset(tile_row, tile_y); processComponentRow(entry, srcBits, dst_row_offset, tile_column); } } } else { // skip over the data we are not using srcBits.skip_bytes(entry.get_bytes_per_tile()); continue; } } } }; class RowInterleaveDecoder : public AbstractDecoder { public: RowInterleaveDecoder(uint32_t width, uint32_t height, std::shared_ptr<Box_cmpd> cmpd, std::shared_ptr<Box_uncC> uncC): AbstractDecoder(width, height, std::move(cmpd), std::move(uncC)) {} Error decode(const std::vector<uint8_t>& uncompressed_data, std::shared_ptr<HeifPixelImage>& img) override { UncompressedBitReader srcBits(uncompressed_data); buildChannelList(img); for (uint32_t tile_row = 0; tile_row < m_uncC->get_number_of_tile_rows(); tile_row++) { for (uint32_t tile_column = 0; tile_column < m_uncC->get_number_of_tile_columns(); tile_column++) { srcBits.markTileStart(); processTile(srcBits, tile_row, tile_column); srcBits.handleTileAlignment(m_uncC->get_tile_align_size()); } } return Error::Ok; } private: void processTile(UncompressedBitReader &srcBits, uint32_t tile_row, uint32_t tile_column) { for (uint32_t tile_y = 0; tile_y < m_tile_height; tile_y++) { for (ChannelListEntry &entry : channelList) { srcBits.markRowStart(); if (entry.use_channel) { uint64_t dst_row_offset = entry.getDestinationRowOffset(tile_row, tile_y); processComponentRow(entry, srcBits, dst_row_offset, tile_column); } else { srcBits.skip_bytes(entry.bytes_per_tile_row_src); } srcBits.handleRowAlignment(m_uncC->get_row_align_size()); } } } }; class TileComponentInterleaveDecoder : public AbstractDecoder { public: TileComponentInterleaveDecoder(uint32_t width, uint32_t height, std::shared_ptr<Box_cmpd> cmpd, std::shared_ptr<Box_uncC> uncC): AbstractDecoder(width, height, std::move(cmpd), std::move(uncC)) {} Error decode(const std::vector<uint8_t>& uncompressed_data, std::shared_ptr<HeifPixelImage>& img) override { UncompressedBitReader srcBits(uncompressed_data); buildChannelList(img); for (ChannelListEntry &entry : channelList) { if (!entry.use_channel) { uint64_t bytes_per_component = entry.get_bytes_per_tile() * m_uncC->get_number_of_tile_columns() * m_uncC->get_number_of_tile_rows(); srcBits.skip_bytes((int)bytes_per_component); continue; } for (uint32_t tile_row = 0; tile_row < m_uncC->get_number_of_tile_rows(); tile_row++) { for (uint32_t tile_column = 0; tile_column < m_uncC->get_number_of_tile_columns(); tile_column++) { srcBits.markTileStart(); for (uint32_t tile_y = 0; tile_y < entry.tile_height; tile_y++) { srcBits.markRowStart(); uint64_t dst_row_offset = entry.getDestinationRowOffset(tile_row, tile_y); processComponentRow(entry, srcBits, dst_row_offset, tile_column); srcBits.handleRowAlignment(m_uncC->get_row_align_size()); } srcBits.handleTileAlignment(m_uncC->get_tile_align_size()); } } } return Error::Ok; } }; static AbstractDecoder* makeDecoder(uint32_t width, uint32_t height, const std::shared_ptr<Box_cmpd>& cmpd, const std::shared_ptr<Box_uncC>& uncC) { if (uncC->get_interleave_type() == interleave_mode_component) { return new ComponentInterleaveDecoder(width, height, cmpd, uncC); } else if (uncC->get_interleave_type() == interleave_mode_pixel) { return new PixelInterleaveDecoder(width, height, cmpd, uncC); } else if (uncC->get_interleave_type() == interleave_mode_mixed) { return new MixedInterleaveDecoder(width, height, cmpd, uncC); } else if (uncC->get_interleave_type() == interleave_mode_row) { return new RowInterleaveDecoder(width, height, cmpd, uncC); } else if (uncC->get_interleave_type() == interleave_mode_tile_component) { return new TileComponentInterleaveDecoder(width, height, cmpd, uncC); } else { return nullptr; } } Error UncompressedImageCodec::decode_uncompressed_image(const HeifContext* context, heif_item_id ID, std::shared_ptr<HeifPixelImage>& img, const std::vector<uint8_t>& source_data) { if (source_data.empty()) { return {heif_error_Invalid_input, heif_suberror_Unspecified, "Uncompressed image data is empty"}; } // Get the properties for this item // We need: ispe, cmpd, uncC std::vector<std::shared_ptr<Box>> item_properties; Error error = context->get_heif_file()->get_properties(ID, item_properties); if (error) { printf("failed to get properties\n"); return error; } uint32_t width = 0; uint32_t height = 0; bool found_ispe = false; std::shared_ptr<Box_cmpd> cmpd; std::shared_ptr<Box_uncC> uncC; std::shared_ptr<Box_cmpC> cmpC; std::shared_ptr<Box_icbr> icbr; for (const auto& prop : item_properties) { auto ispe = std::dynamic_pointer_cast<Box_ispe>(prop); if (ispe) { width = ispe->get_width(); height = ispe->get_height(); error = context->check_resolution(width, height); if (error) { return error; } found_ispe = true; } auto maybe_cmpd = std::dynamic_pointer_cast<Box_cmpd>(prop); if (maybe_cmpd) { cmpd = maybe_cmpd; } auto maybe_uncC = std::dynamic_pointer_cast<Box_uncC>(prop); if (maybe_uncC) { uncC = maybe_uncC; } auto maybe_cmpC = std::dynamic_pointer_cast<Box_cmpC>(prop); if (maybe_cmpC) { cmpC = maybe_cmpC; } auto maybe_icbr = std::dynamic_pointer_cast<Box_icbr>(prop); if (maybe_icbr) { icbr = maybe_icbr; } } // if we miss a required box, show error if (!found_ispe) { return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_data_version, "Missing required ispe box for uncompressed codec"); } if (!uncC) { return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_data_version, "Missing required uncC box for uncompressed codec"); } if (!cmpd && (uncC->get_version() !=1)) { return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_data_version, "Missing required cmpd or uncC version 1 box for uncompressed codec"); } // check if we support the type of image error = uncompressed_image_type_is_supported(uncC, cmpd); if (error) { printf("unsupported image type\n"); return error; } img = std::make_shared<HeifPixelImage>(); heif_chroma chroma; heif_colorspace colourspace; error = get_heif_chroma_uncompressed(uncC, cmpd, &chroma, &colourspace); if (error) { printf("failed to get chroma uncompressed\n"); return error; } img->create(width, height, colourspace, chroma); for (Box_uncC::Component component : uncC->get_components()) { heif_channel channel; if (map_uncompressed_component_to_channel(cmpd, uncC, component, &channel)) { if ((channel == heif_channel_Cb) || (channel == heif_channel_Cr)) { img->add_plane(channel, (width / chroma_h_subsampling(chroma)), (height / chroma_v_subsampling(chroma)), component.component_bit_depth); } else { img->add_plane(channel, width, height, component.component_bit_depth); } } } AbstractDecoder *decoder = makeDecoder(width, height, cmpd, uncC); if (decoder != nullptr) { Error result = decoder->decode(source_data, img); delete decoder; return result; } else { printf("bad interleave mode - we should have detected this earlier: %d\n", uncC->get_interleave_type()); std::stringstream sstr; sstr << "Uncompressed interleave_type of " << ((int) uncC->get_interleave_type()) << " is not implemented yet"; return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_data_version, sstr.str()); } } Error fill_cmpd_and_uncC(std::shared_ptr<Box_cmpd>& cmpd, std::shared_ptr<Box_uncC>& uncC, const std::shared_ptr<HeifPixelImage>& image) { const heif_colorspace colourspace = image->get_colorspace(); if (colourspace == heif_colorspace_YCbCr) { if (!(image->has_channel(heif_channel_Y) && image->has_channel(heif_channel_Cb) && image->has_channel(heif_channel_Cr))) { return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_data_version, "Invalid colourspace / channel combination - YCbCr"); } Box_cmpd::Component yComponent = {component_type_Y}; cmpd->add_component(yComponent); Box_cmpd::Component cbComponent = {component_type_Cb}; cmpd->add_component(cbComponent); Box_cmpd::Component crComponent = {component_type_Cr}; cmpd->add_component(crComponent); uint8_t bpp_y = image->get_bits_per_pixel(heif_channel_Y); Box_uncC::Component component0 = {0, bpp_y, component_format_unsigned, 0}; uncC->add_component(component0); uint8_t bpp_cb = image->get_bits_per_pixel(heif_channel_Cb); Box_uncC::Component component1 = {1, bpp_cb, component_format_unsigned, 0}; uncC->add_component(component1); uint8_t bpp_cr = image->get_bits_per_pixel(heif_channel_Cr); Box_uncC::Component component2 = {2, bpp_cr, component_format_unsigned, 0}; uncC->add_component(component2); if (image->get_chroma_format() == heif_chroma_444) { uncC->set_sampling_type(sampling_mode_no_subsampling); } else if (image->get_chroma_format() == heif_chroma_422) { uncC->set_sampling_type(sampling_mode_422); } else if (image->get_chroma_format() == heif_chroma_420) { uncC->set_sampling_type(sampling_mode_420); } else { return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_data_version, "Unsupported YCbCr sub-sampling type"); } uncC->set_interleave_type(interleave_mode_component); uncC->set_block_size(0); uncC->set_components_little_endian(false); uncC->set_block_pad_lsb(false); uncC->set_block_little_endian(false); uncC->set_block_reversed(false); uncC->set_pad_unknown(false); uncC->set_pixel_size(0); uncC->set_row_align_size(0); uncC->set_tile_align_size(0); uncC->set_number_of_tile_columns(1); uncC->set_number_of_tile_rows(1); } else if (colourspace == heif_colorspace_RGB) { if (!((image->get_chroma_format() == heif_chroma_444) || (image->get_chroma_format() == heif_chroma_interleaved_RGB) || (image->get_chroma_format() == heif_chroma_interleaved_RGBA) || (image->get_chroma_format() == heif_chroma_interleaved_RRGGBB_BE) || (image->get_chroma_format() == heif_chroma_interleaved_RRGGBB_LE) || (image->get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_BE) || (image->get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_LE))) { return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_data_version, "Unsupported colourspace / chroma combination - RGB"); } Box_cmpd::Component rComponent = {component_type_red}; cmpd->add_component(rComponent); Box_cmpd::Component gComponent = {component_type_green}; cmpd->add_component(gComponent); Box_cmpd::Component bComponent = {component_type_blue}; cmpd->add_component(bComponent); if ((image->get_chroma_format() == heif_chroma_interleaved_RGBA) || (image->get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_BE) || (image->get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_LE) || (image->has_channel(heif_channel_Alpha))) { Box_cmpd::Component alphaComponent = {component_type_alpha}; cmpd->add_component(alphaComponent); } if ((image->get_chroma_format() == heif_chroma_interleaved_RGB) || (image->get_chroma_format() == heif_chroma_interleaved_RGBA) || (image->get_chroma_format() == heif_chroma_interleaved_RRGGBB_BE) || (image->get_chroma_format() == heif_chroma_interleaved_RRGGBB_LE) || (image->get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_BE) || (image->get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_LE)) { uncC->set_interleave_type(interleave_mode_pixel); int bpp = image->get_bits_per_pixel(heif_channel_interleaved); uint8_t component_align = 1; if (bpp == 8) { component_align = 0; } else if (bpp > 8) { component_align = 2; } Box_uncC::Component component0 = {0, (uint8_t)(bpp), component_format_unsigned, component_align}; uncC->add_component(component0); Box_uncC::Component component1 = {1, (uint8_t)(bpp), component_format_unsigned, component_align}; uncC->add_component(component1); Box_uncC::Component component2 = {2, (uint8_t)(bpp), component_format_unsigned, component_align}; uncC->add_component(component2); if ((image->get_chroma_format() == heif_chroma_interleaved_RGBA) || (image->get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_BE) || (image->get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_LE)) { Box_uncC::Component component3 = { 3, (uint8_t)(bpp), component_format_unsigned, component_align}; uncC->add_component(component3); } } else { uncC->set_interleave_type(interleave_mode_component); int bpp_red = image->get_bits_per_pixel(heif_channel_R); Box_uncC::Component component0 = {0, (uint8_t)(bpp_red), component_format_unsigned, 0}; uncC->add_component(component0); int bpp_green = image->get_bits_per_pixel(heif_channel_G); Box_uncC::Component component1 = {1, (uint8_t)(bpp_green), component_format_unsigned, 0}; uncC->add_component(component1); int bpp_blue = image->get_bits_per_pixel(heif_channel_B); Box_uncC::Component component2 = {2, (uint8_t)(bpp_blue), component_format_unsigned, 0}; uncC->add_component(component2); if(image->has_channel(heif_channel_Alpha)) { int bpp_alpha = image->get_bits_per_pixel(heif_channel_Alpha); Box_uncC::Component component3 = {3, (uint8_t)(bpp_alpha), component_format_unsigned, 0}; uncC->add_component(component3); } } uncC->set_sampling_type(sampling_mode_no_subsampling); uncC->set_block_size(0); if ((image->get_chroma_format() == heif_chroma_interleaved_RRGGBB_LE) || (image->get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_LE)) { uncC->set_components_little_endian(true); } else { uncC->set_components_little_endian(false); } uncC->set_block_pad_lsb(false); uncC->set_block_little_endian(false); uncC->set_block_reversed(false); uncC->set_pad_unknown(false); uncC->set_pixel_size(0); uncC->set_row_align_size(0); uncC->set_tile_align_size(0); uncC->set_number_of_tile_columns(1); uncC->set_number_of_tile_rows(1); } else if (colourspace == heif_colorspace_monochrome) { Box_cmpd::Component monoComponent = {component_type_monochrome}; cmpd->add_component(monoComponent); if (image->has_channel(heif_channel_Alpha)) { Box_cmpd::Component alphaComponent = {component_type_alpha}; cmpd->add_component(alphaComponent); } int bpp = image->get_bits_per_pixel(heif_channel_Y); Box_uncC::Component component0 = {0, (uint8_t)(bpp), component_format_unsigned, 0}; uncC->add_component(component0); if (image->has_channel(heif_channel_Alpha)) { bpp = image->get_bits_per_pixel(heif_channel_Alpha); Box_uncC::Component component1 = {1, (uint8_t)(bpp), component_format_unsigned, 0}; uncC->add_component(component1); } uncC->set_sampling_type(sampling_mode_no_subsampling); uncC->set_interleave_type(interleave_mode_component); uncC->set_block_size(0); uncC->set_components_little_endian(false); uncC->set_block_pad_lsb(false); uncC->set_block_little_endian(false); uncC->set_block_reversed(false); uncC->set_pad_unknown(false); uncC->set_pixel_size(0); uncC->set_row_align_size(0); uncC->set_tile_align_size(0); uncC->set_number_of_tile_columns(1); uncC->set_number_of_tile_rows(1); } else { return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_data_version, "Unsupported colourspace"); } return Error::Ok; } static void maybe_make_minimised_uncC(std::shared_ptr<Box_uncC>& uncC, const std::shared_ptr<HeifPixelImage>& image) { uncC->set_version(0); if (image->get_colorspace() != heif_colorspace_RGB) { return; } if (!((image->get_chroma_format() == heif_chroma_interleaved_RGB) || (image->get_chroma_format() == heif_chroma_interleaved_RGBA))) { return; } if (image->get_bits_per_pixel(heif_channel_interleaved) != 8) { return; } if (image->get_chroma_format() == heif_chroma_interleaved_RGBA) { uncC->set_profile(fourcc_to_uint32("rgba")); } else { uncC->set_profile(fourcc_to_uint32("rgb3")); } uncC->set_version(1); } Error UncompressedImageCodec::encode_uncompressed_image(const std::shared_ptr<HeifFile>& heif_file, const std::shared_ptr<HeifPixelImage>& src_image, void* encoder_struct, const struct heif_encoding_options& options, std::shared_ptr<HeifContext::Image>& out_image) { std::shared_ptr<Box_uncC> uncC = std::make_shared<Box_uncC>(); if (options.prefer_uncC_short_form) { maybe_make_minimised_uncC(uncC, src_image); } if (uncC->get_version() == 1) { heif_file->add_property(out_image->get_id(), uncC, true); } else { std::shared_ptr<Box_cmpd> cmpd = std::make_shared<Box_cmpd>(); Error error = fill_cmpd_and_uncC(cmpd, uncC, src_image); if (error) { return error; } heif_file->add_property(out_image->get_id(), cmpd, true); heif_file->add_property(out_image->get_id(), uncC, true); } std::vector<uint8_t> data; if (src_image->get_colorspace() == heif_colorspace_YCbCr) { uint64_t offset = 0; for (heif_channel channel : {heif_channel_Y, heif_channel_Cb, heif_channel_Cr}) { int src_stride; uint8_t* src_data = src_image->get_plane(channel, &src_stride); uint64_t out_size = src_image->get_height() * src_image->get_width(); data.resize(data.size() + out_size); for (int y = 0; y < src_image->get_height(); y++) { memcpy(data.data() + offset + y * src_image->get_width(), src_data + src_stride * y, src_image->get_width()); } offset += out_size; } heif_file->append_iloc_data(out_image->get_id(), data, 0); } else if (src_image->get_colorspace() == heif_colorspace_RGB) { if (src_image->get_chroma_format() == heif_chroma_444) { uint64_t offset = 0; std::vector<heif_channel> channels = {heif_channel_R, heif_channel_G, heif_channel_B}; if (src_image->has_channel(heif_channel_Alpha)) { channels.push_back(heif_channel_Alpha); } for (heif_channel channel : channels) { int src_stride; uint8_t* src_data = src_image->get_plane(channel, &src_stride); uint64_t out_size = src_image->get_height() * src_stride; data.resize(data.size() + out_size); memcpy(data.data() + offset, src_data, out_size); offset += out_size; } heif_file->append_iloc_data(out_image->get_id(), data, 0); } else if ((src_image->get_chroma_format() == heif_chroma_interleaved_RGB) || (src_image->get_chroma_format() == heif_chroma_interleaved_RGBA) || (src_image->get_chroma_format() == heif_chroma_interleaved_RRGGBB_BE) || (src_image->get_chroma_format() == heif_chroma_interleaved_RRGGBB_LE) || (src_image->get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_BE) || (src_image->get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_LE)) { int bytes_per_pixel = 0; switch (src_image->get_chroma_format()) { case heif_chroma_interleaved_RGB: bytes_per_pixel=3; break; case heif_chroma_interleaved_RGBA: bytes_per_pixel=4; break; case heif_chroma_interleaved_RRGGBB_BE: case heif_chroma_interleaved_RRGGBB_LE: bytes_per_pixel=6; break; case heif_chroma_interleaved_RRGGBBAA_BE: case heif_chroma_interleaved_RRGGBBAA_LE: bytes_per_pixel=8; break; default: assert(false); } int src_stride; uint8_t* src_data = src_image->get_plane(heif_channel_interleaved, &src_stride); uint64_t out_size = src_image->get_height() * src_image->get_width() * bytes_per_pixel; data.resize(out_size); for (int y = 0; y < src_image->get_height(); y++) { memcpy(data.data() + y * src_image->get_width() * bytes_per_pixel, src_data + src_stride * y, src_image->get_width() * bytes_per_pixel); } heif_file->append_iloc_data(out_image->get_id(), data, 0); } else { return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_data_version, "Unsupported RGB chroma"); } } else if (src_image->get_colorspace() == heif_colorspace_monochrome) { uint64_t offset = 0; std::vector<heif_channel> channels; if (src_image->has_channel(heif_channel_Alpha)) { channels = {heif_channel_Y, heif_channel_Alpha}; } else { channels = {heif_channel_Y}; } for (heif_channel channel : channels) { int src_stride; uint8_t* src_data = src_image->get_plane(channel, &src_stride); uint64_t out_size = src_image->get_height() * src_stride; data.resize(data.size() + out_size); memcpy(data.data() + offset, src_data, out_size); offset += out_size; } heif_file->append_iloc_data(out_image->get_id(), data, 0); } else { return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_data_version, "Unsupported colourspace"); } // We need to ensure ispe is essential for the uncompressed case std::shared_ptr<Box_ispe> ispe = std::make_shared<Box_ispe>(); ispe->set_size(src_image->get_width(), src_image->get_height()); heif_file->add_property(out_image->get_id(), ispe, true); return Error::Ok; }