Skip to content

Commit

Permalink
Merge branch 'develop-v1.18.0' into openjph
Browse files Browse the repository at this point in the history
  • Loading branch information
farindk authored Jan 16, 2024
2 parents d525dbf + e4ddcc6 commit d621840
Show file tree
Hide file tree
Showing 12 changed files with 901 additions and 95 deletions.
18 changes: 18 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,24 @@ endif (DOXYGEN_FOUND)

# --- Testing

option(ENABLE_COVERAGE "" OFF)
if(ENABLE_COVERAGE)
# set compiler flags
set(CMAKE_CXX_FLAGS "-O0 -coverage")

# find required tools
find_program(LCOV lcov REQUIRED)
find_program(GENHTML genhtml REQUIRED)

# add coverage target
add_custom_target(coverage
# gather data
COMMAND ${LCOV} --directory . --capture --output-file coverage.info
# generate report
COMMAND ${GENHTML} --demangle-cpp -o coverage coverage.info
WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
endif()

option(BUILD_TESTING "" ON)
include(CTest)
if(BUILD_TESTING)
Expand Down
2 changes: 2 additions & 0 deletions go/heif/heif.go
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,8 @@ const (

SuberrorUnknownNCLXMatrixCoefficients = C.heif_suberror_Unknown_NCLX_matrix_coefficients

SuberrorInvalidJPEG2000Codestream = C.heif_suberror_Invalid_J2K_codestream

// --- Unsupported_feature ---

// Image was coded with an unsupported compression method.
Expand Down
8 changes: 6 additions & 2 deletions libheif/context.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1205,8 +1205,12 @@ Error HeifContext::Image::get_preferred_decoding_colorspace(heif_colorspace* out
*out_chroma = (heif_chroma)(av1C->get_configuration().get_heif_chroma());
}
else if (auto j2kH = m_heif_context->m_heif_file->get_property<Box_j2kH>(id)) {
JPEG2000_SIZ_segment siz = jpeg2000_get_SIZ_segment(*m_heif_context->m_heif_file, id);
*out_chroma = siz.get_chroma_format();
JPEG2000MainHeader jpeg2000Header;
err = jpeg2000Header.parseHeader(*m_heif_context->m_heif_file, id);
if (err) {
return err;
}
*out_chroma = jpeg2000Header.get_chroma_format();
}

return err;
Expand Down
2 changes: 2 additions & 0 deletions libheif/error.cc
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@ const char* Error::get_error_string(heif_suberror_code err)
return "Unknown NCLX matrix coefficients";
case heif_suberror_Invalid_region_data:
return "Invalid region item data";
case heif_suberror_Invalid_J2K_codestream:
return "Invalid JPEG 2000 codestream";


// --- Memory_allocation_error ---
Expand Down
17 changes: 8 additions & 9 deletions libheif/file.cc
Original file line number Diff line number Diff line change
Expand Up @@ -595,12 +595,12 @@ int HeifFile::get_luma_bits_per_pixel_from_configuration(heif_item_id imageID) c
// JPEG 2000

if (image_type == "j2k1") {
auto siz = jpeg2000_get_SIZ_segment(*this, imageID);
if (siz.components.empty()) {
JPEG2000MainHeader header;
Error err = header.parseHeader(*this, imageID);
if (err) {
return -1;
}

return siz.components[0].precision;
return header.get_precision(0);
}

#if WITH_UNCOMPRESSED_CODEC
Expand Down Expand Up @@ -658,13 +658,12 @@ int HeifFile::get_chroma_bits_per_pixel_from_configuration(heif_item_id imageID)
// JPEG 2000

if (image_type == "j2k1") {
auto siz = jpeg2000_get_SIZ_segment(*this, imageID);
if (siz.components.size() <= 1) {
JPEG2000MainHeader header;
Error err = header.parseHeader(*this, imageID);
if (err) {
return -1;
}

// TODO: this is a quick hack. It is more complicated for JPEG2000 because these can be any kind of colorspace (e.g. RGB).
return siz.components[1].precision;
return header.get_precision(1);
}

return -1;
Expand Down
3 changes: 3 additions & 0 deletions libheif/heif.h
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,9 @@ enum heif_suberror_code
// Invalid specification of region item
heif_suberror_Invalid_region_data = 136,

// Invalid JPEG 2000 codestream - usually a missing marker
heif_suberror_Invalid_J2K_codestream = 140,


// --- Memory_allocation_error ---

Expand Down
1 change: 1 addition & 0 deletions libheif/heif_emscripten.h
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,7 @@ EMSCRIPTEN_BINDINGS(libheif) {
.value("heif_suberror_Item_reference_cycle", heif_suberror_Item_reference_cycle)
.value("heif_suberror_Invalid_pixi_box", heif_suberror_Invalid_pixi_box)
.value("heif_suberror_Invalid_region_data", heif_suberror_Invalid_region_data)
.value("heif_suberror_Invalid_J2K_codestream", heif_suberror_Invalid_J2K_codestream)
.value("heif_suberror_Unsupported_codec", heif_suberror_Unsupported_codec)
.value("heif_suberror_Unsupported_image_type", heif_suberror_Unsupported_image_type)
.value("heif_suberror_Unsupported_data_version", heif_suberror_Unsupported_data_version)
Expand Down
202 changes: 140 additions & 62 deletions libheif/jpeg2000.cc
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,14 @@

#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();
Expand Down Expand Up @@ -305,98 +311,170 @@ std::string Box_j2kH::dump(Indent& indent) const
}


int read16(const std::vector<uint8_t>& data, int offset)
Error JPEG2000MainHeader::parseHeader(const HeifFile& file, const heif_item_id imageID)
{
return (data[offset] << 8) | data[offset + 1];
Error err = file.get_compressed_image_data(imageID, &headerData);
if (err) {
return err;
}
return doParse();
}


int read32(const std::vector<uint8_t>& data, int offset)
Error JPEG2000MainHeader::doParse()
{
return (data[offset] << 24) | (data[offset + 1] << 16) | (data[offset + 2] << 8) | data[offset + 3];
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)"));
}


JPEG2000_SIZ_segment jpeg2000_get_SIZ_segment(const HeifFile& file, heif_item_id imageID)
Error JPEG2000MainHeader::parse_SOC_segment()
{
std::vector<uint8_t> data;
Error err = file.get_compressed_image_data(imageID, &data);
if (err) {
return {};
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"));
}

for (size_t i = 0; i + 1 < data.size(); i++) {
if (data[i] == 0xFF && data[i + 1] & 0x51) {

JPEG2000_SIZ_segment siz;

// space for full header and one component
if (i + 2 + 40 + 3 > data.size()) {
return {};
}

// read number of components

int nComponents = read16(data, 40);
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);
}

if (i + 2 + 40 + nComponents * 3 > data.size()) {
return {};
}
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;
}

siz.decoder_capabilities = read16(data, 6);
siz.width = read32(data, 8);
siz.height = read32(data, 12);
siz.x0 = read32(data, 16);
siz.y0 = read32(data, 20);
siz.tile_width = read32(data, 24);
siz.tile_height = read32(data, 28);
siz.tile_x0 = read32(data, 32);
siz.tile_y0 = read32(data, 36);

for (int c = 0; c < nComponents; c++) {
JPEG2000_SIZ_segment::component comp;
comp.precision = data[42 + c * 3];
comp.is_signed = (comp.precision & 0x80);
comp.precision = uint8_t((comp.precision & 0x7F) + 1);
comp.h_separation = data[43 + c * 3];
comp.v_separation = data[44 + c * 3];
siz.components.push_back(comp);
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 siz;
}
}

return {};
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 JPEG2000_SIZ_segment::get_chroma_format() const
heif_chroma JPEG2000MainHeader::get_chroma_format() const
{
if (components.size() == 1) {
// 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 (components.size() == 3) {
else if (siz.components.size() == 3) {
// TODO: we should map channels through `cdef` ?

// Y-plane must be full resolution
if (components[0].h_separation != 1 || components[0].v_separation != 1) {
return heif_chroma_undefined;
}

// both chroma components must have the same sampling
if (components[1].h_separation != components[2].h_separation ||
components[1].v_separation != components[2].v_separation) {
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 (components[1].h_separation == 2 && components[1].v_separation==2) {
if (siz.components[1].h_separation == 2 && siz.components[1].v_separation==2) {
return heif_chroma_420;
}
if (components[1].h_separation == 2 && components[1].v_separation==1) {
if (siz.components[1].h_separation == 2 && siz.components[1].v_separation==1) {
return heif_chroma_422;
}
if (components[1].h_separation == 1 && components[1].v_separation==1) {
if (siz.components[1].h_separation == 1 && siz.components[1].v_separation==1) {
return heif_chroma_444;
}
}
Expand Down
Loading

0 comments on commit d621840

Please sign in to comment.