libheif/codecs/jpeg2000.cc (379 lines of code) (raw):

/* * HEIF JPEG 2000 codec. * Copyright (c) 2023 Brad Hards <bradh@frogmouth.net> * * 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 "jpeg2000.h" #include <cstdint> #include <iostream> #include <stdio.h> static const uint16_t JPEG2000_CAP_MARKER = 0xFF50; static const uint16_t JPEG2000_SIZ_MARKER = 0xFF51; static const uint16_t JPEG2000_SOC_MARKER = 0xFF4F; Error Box_cdef::parse(BitstreamRange& range) { int channel_count = range.read16(); for (int i = 0; i < channel_count && !range.error() && !range.eof(); i++) { Channel channel; channel.channel_index = range.read16(); channel.channel_type = range.read16(); channel.channel_association = range.read16(); m_channels.push_back(channel); } return range.get_error(); } std::string Box_cdef::dump(Indent& indent) const { std::ostringstream sstr; sstr << Box::dump(indent); for (const auto& channel : m_channels) { sstr << indent << "channel_index: " << channel.channel_index << ", channel_type: " << channel.channel_type << ", channel_association: " << channel.channel_association << "\n"; } return sstr.str(); } Error Box_cdef::write(StreamWriter& writer) const { size_t box_start = reserve_box_header_space(writer); writer.write16((uint16_t) m_channels.size()); for (const auto& channel : m_channels) { writer.write16(channel.channel_index); writer.write16(channel.channel_type); writer.write16(channel.channel_association); } prepend_header(writer, box_start); return Error::Ok; } void Box_cdef::set_channels(heif_colorspace colorspace) { // TODO - Check for the presence of a cmap box which specifies channel indices. const uint16_t TYPE_COLOR = 0; const uint16_t ASOC_GREY = 1; const uint16_t ASOC_RED = 1; const uint16_t ASOC_GREEN = 2; const uint16_t ASOC_BLUE = 3; const uint16_t ASOC_Y = 1; const uint16_t ASOC_Cb = 2; const uint16_t ASOC_Cr = 3; switch (colorspace) { case heif_colorspace_RGB: m_channels.push_back({0, TYPE_COLOR, ASOC_RED}); m_channels.push_back({1, TYPE_COLOR, ASOC_GREEN}); m_channels.push_back({2, TYPE_COLOR, ASOC_BLUE}); break; case heif_colorspace_YCbCr: m_channels.push_back({0, TYPE_COLOR, ASOC_Y}); m_channels.push_back({1, TYPE_COLOR, ASOC_Cb}); m_channels.push_back({2, TYPE_COLOR, ASOC_Cr}); break; case heif_colorspace_monochrome: m_channels.push_back({0, TYPE_COLOR, ASOC_GREY}); break; default: //TODO - Handle remaining cases. break; } } Error Box_cmap::parse(BitstreamRange& range) { while (!range.eof() && !range.error()) { Component component; component.component_index = range.read16(); component.mapping_type = range.read8(); component.palette_colour = range.read8(); m_components.push_back(component); } return range.get_error(); } std::string Box_cmap::dump(Indent& indent) const { std::ostringstream sstr; sstr << Box::dump(indent); for (const auto& component : m_components) { sstr << indent << "component_index: " << component.component_index << ", mapping_type: " << (int) (component.mapping_type) << ", palette_colour: " << (int) (component.palette_colour) << "\n"; } return sstr.str(); } Error Box_cmap::write(StreamWriter& writer) const { size_t box_start = reserve_box_header_space(writer); for (const auto& component : m_components) { writer.write16(component.component_index); writer.write8(component.mapping_type); writer.write8(component.palette_colour); } prepend_header(writer, box_start); return Error::Ok; } Error Box_pclr::parse(BitstreamRange& range) { uint16_t num_entries = range.read16(); uint8_t num_palette_columns = range.read8(); for (uint8_t i = 0; i < num_palette_columns; i++) { uint8_t bit_depth = range.read8(); if (bit_depth & 0x80) { return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_data_version, "pclr with signed data is not supported"); } if (bit_depth > 16) { return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_data_version, "pclr more than 16 bits per channel is not supported"); } m_bitDepths.push_back(bit_depth); } for (uint16_t j = 0; j < num_entries; j++) { PaletteEntry entry; for (unsigned long int i = 0; i < entry.columns.size(); i++) { if (m_bitDepths[i] <= 8) { entry.columns.push_back(range.read8()); } else { entry.columns.push_back(range.read16()); } } m_entries.push_back(entry); } return range.get_error(); } std::string Box_pclr::dump(Indent& indent) const { std::ostringstream sstr; sstr << Box::dump(indent); sstr << indent << "NE: " << m_entries.size(); sstr << ", NPC: " << (int) get_num_columns(); sstr << ", B: "; for (uint8_t b : m_bitDepths) { sstr << (int) b << ", "; } // TODO: maybe dump entries too? sstr << "\n"; return sstr.str(); } Error Box_pclr::write(StreamWriter& writer) const { if (get_num_columns() == 0) { // skip return Error::Ok; } size_t box_start = reserve_box_header_space(writer); writer.write16(get_num_entries()); writer.write8(get_num_columns()); for (uint8_t b : m_bitDepths) { writer.write8(b); } for (PaletteEntry entry : m_entries) { for (unsigned long int i = 0; i < entry.columns.size(); i++) { if (m_bitDepths[i] <= 8) { writer.write8((uint8_t) (entry.columns[i])); } else { writer.write16(entry.columns[i]); } } } prepend_header(writer, box_start); return Error::Ok; } void Box_pclr::set_columns(uint8_t num_columns, uint8_t bit_depth) { m_bitDepths.clear(); m_entries.clear(); for (int i = 0; i < num_columns; i++) { m_bitDepths.push_back(bit_depth); } } Error Box_j2kL::parse(BitstreamRange& range) { int layer_count = range.read16(); for (int i = 0; i < layer_count && !range.error() && !range.eof(); i++) { Layer layer; layer.layer_id = range.read16(); layer.discard_levels = range.read8(); layer.decode_layers = range.read16(); m_layers.push_back(layer); } return range.get_error(); } std::string Box_j2kL::dump(Indent& indent) const { std::ostringstream sstr; sstr << Box::dump(indent); for (const auto& layer : m_layers) { sstr << indent << "layer_id: " << layer.layer_id << ", discard_levels: " << (int) (layer.discard_levels) << ", decode_layers: " << layer.decode_layers << "\n"; } return sstr.str(); } Error Box_j2kL::write(StreamWriter& writer) const { size_t box_start = reserve_box_header_space(writer); writer.write16((uint16_t) m_layers.size()); for (const auto& layer : m_layers) { writer.write16(layer.layer_id); writer.write8(layer.discard_levels); writer.write16(layer.decode_layers); } prepend_header(writer, box_start); return Error::Ok; } Error Box_j2kH::parse(BitstreamRange& range) { return read_children(range); } std::string Box_j2kH::dump(Indent& indent) const { std::ostringstream sstr; sstr << Box::dump(indent); sstr << dump_children(indent); return sstr.str(); } Error JPEG2000MainHeader::parseHeader(const HeifFile& file, const heif_item_id imageID) { Error err = file.get_compressed_image_data(imageID, &headerData); if (err) { return err; } return doParse(); } Error JPEG2000MainHeader::doParse() { cursor = 0; Error err = parse_SOC_segment(); if (err) { return err; } err = parse_SIZ_segment(); if (err) { return err; } if (cursor < headerData.size() - MARKER_LEN) { uint16_t marker = read16(); if (marker == JPEG2000_CAP_MARKER) { return parse_CAP_segment_body(); } return Error::Ok; } // we should have at least COD and QCD, so this is probably broken. return Error(heif_error_Invalid_input, heif_suberror_Invalid_J2K_codestream, std::string("Missing required header marker(s)")); } Error JPEG2000MainHeader::parse_SOC_segment() { const size_t REQUIRED_BYTES = MARKER_LEN; if ((headerData.size() < REQUIRED_BYTES) || (cursor > (headerData.size() - REQUIRED_BYTES))) { return Error(heif_error_Invalid_input, heif_suberror_Invalid_J2K_codestream); } uint16_t marker = read16(); if (marker == JPEG2000_SOC_MARKER) { return Error::Ok; } return Error(heif_error_Invalid_input, heif_suberror_Invalid_J2K_codestream, std::string("Missing required SOC Marker")); } Error JPEG2000MainHeader::parse_SIZ_segment() { size_t REQUIRED_BYTES = MARKER_LEN + 38 + 3 * 1; if ((headerData.size() < REQUIRED_BYTES) || (cursor > (headerData.size() - REQUIRED_BYTES))) { return Error(heif_error_Invalid_input, heif_suberror_Invalid_J2K_codestream); } uint16_t marker = read16(); if (marker != JPEG2000_SIZ_MARKER) { return Error(heif_error_Invalid_input, heif_suberror_Invalid_J2K_codestream, std::string("Missing required SIZ Marker")); } uint16_t lsiz = read16(); if ((lsiz < 41) || (lsiz > 49190)) { return Error(heif_error_Invalid_input, heif_suberror_Invalid_J2K_codestream, std::string("Out of range Lsiz value")); } siz.decoder_capabilities = read16(); siz.reference_grid_width = read32(); siz.reference_grid_height = read32(); siz.image_horizontal_offset = read32(); siz.image_vertical_offset = read32(); siz.tile_width = read32(); siz.tile_height = read32(); siz.tile_offset_x = read32(); siz.tile_offset_y = read32(); uint16_t csiz = read16(); if ((csiz < 1) || (csiz > 16384)) { return Error(heif_error_Invalid_input, heif_suberror_Invalid_J2K_codestream, std::string("Out of range Csiz value")); } if (cursor > headerData.size() - (3 * csiz)) { return Error(heif_error_Invalid_input, heif_suberror_Invalid_J2K_codestream); } // TODO: consider checking for Lsiz consistent with Csiz for (uint16_t c = 0; c < csiz; c++) { JPEG2000_SIZ_segment::component comp; uint8_t ssiz = read8(); comp.is_signed = (ssiz & 0x80); comp.precision = uint8_t((ssiz & 0x7F) + 1); comp.h_separation = read8(); comp.v_separation = read8(); siz.components.push_back(comp); } return Error::Ok; } Error JPEG2000MainHeader::parse_CAP_segment_body() { if (cursor > headerData.size() - 8) { return Error(heif_error_Invalid_input, heif_suberror_Invalid_J2K_codestream); } uint16_t lcap = read16(); if ((lcap < 8) || (lcap > 70)) { return Error(heif_error_Invalid_input, heif_suberror_Invalid_J2K_codestream, std::string("Out of range Lcap value")); } uint32_t pcap = read32(); for (uint8_t i = 2; i <= 32; i++) { if (pcap & (1 << (32 - i))) { switch (i) { case JPEG2000_Extension_Capability_HT::IDENT: parse_Ccap15(); break; default: std::cout << "unhandled extended capabilities value: " << (int)i << std::endl; read16(); } } } return Error::Ok; } void JPEG2000MainHeader::parse_Ccap15() { uint16_t val = read16(); JPEG2000_Extension_Capability_HT ccap; // We could parse more here, but we don't need that yet. ccap.setValue(val); cap.push_back(ccap); } heif_chroma JPEG2000MainHeader::get_chroma_format() const { // Y-plane must be full resolution if (siz.components[0].h_separation != 1 || siz.components[0].v_separation != 1) { return heif_chroma_undefined; } if (siz.components.size() == 1) { return heif_chroma_monochrome; } else if (siz.components.size() == 3) { // TODO: we should map channels through `cdef` ? // both chroma components must have the same sampling if (siz.components[1].h_separation != siz.components[2].h_separation || siz.components[1].v_separation != siz.components[2].v_separation) { return heif_chroma_undefined; } if (siz.components[1].h_separation == 2 && siz.components[1].v_separation==2) { return heif_chroma_420; } if (siz.components[1].h_separation == 2 && siz.components[1].v_separation==1) { return heif_chroma_422; } if (siz.components[1].h_separation == 1 && siz.components[1].v_separation==1) { return heif_chroma_444; } } return heif_chroma_undefined; }