use alloc::vec::Vec;
use j2k_core::{
CompressedPayloadKind, CompressedTransferSyntax, PassthroughCandidate, PassthroughRequirements,
Unsupported,
};
use crate::{
encode::{J2kEncodeValidation, J2kProgressionOrder},
parse::{parse_image_info, ParsedImageInfo},
J2kError,
};
mod allocation;
mod coefficient;
mod component_grid;
mod output;
mod pixel;
mod validation;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub struct J2kToHtj2kOptions {
pub output_payload_kind: CompressedPayloadKind,
pub progression: J2kProgressionOrder,
pub validation: J2kEncodeValidation,
}
impl Default for J2kToHtj2kOptions {
fn default() -> Self {
Self {
output_payload_kind: CompressedPayloadKind::Jpeg2000Codestream,
progression: J2kProgressionOrder::Lrcp,
validation: J2kEncodeValidation::CpuRoundTrip,
}
}
}
impl J2kToHtj2kOptions {
pub const fn new(
output_payload_kind: CompressedPayloadKind,
progression: J2kProgressionOrder,
validation: J2kEncodeValidation,
) -> Self {
Self {
output_payload_kind,
progression,
validation,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum J2kToHtj2kMode {
Passthrough,
CodestreamPreserving,
CoefficientPreserving,
PixelPreserving,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct J2kToHtj2kReport {
pub mode: J2kToHtj2kMode,
pub input_transfer_syntax: CompressedTransferSyntax,
pub output_transfer_syntax: CompressedTransferSyntax,
pub input_payload_kind: CompressedPayloadKind,
pub output_payload_kind: CompressedPayloadKind,
pub width: u32,
pub height: u32,
pub components: u16,
pub bit_depth: u8,
}
#[derive(Debug, PartialEq, Eq)]
pub struct ReencodedHtj2k {
pub bytes: Vec<u8>,
pub report: J2kToHtj2kReport,
}
pub fn recode_j2k_to_htj2k_lossless(
bytes: &[u8],
options: J2kToHtj2kOptions,
) -> Result<ReencodedHtj2k, J2kError> {
let parsed = parse_image_info(bytes)?;
let output_transfer_syntax = CompressedTransferSyntax::HtJpeg2000Lossless;
let candidate = PassthroughCandidate::new(
bytes,
parsed.transfer_syntax,
parsed.payload_kind,
parsed.info.clone(),
);
let requirements =
PassthroughRequirements::new(output_transfer_syntax, options.output_payload_kind);
if let Ok(copy) = candidate.copy_bytes_if_eligible(&requirements) {
let output =
allocation::copy_bytes(copy, parsed.allocated_bytes()?, "HTJ2K passthrough output")?;
return Ok(ReencodedHtj2k {
bytes: output,
report: recode_report(
&parsed,
J2kToHtj2kMode::Passthrough,
output_transfer_syntax,
options.output_payload_kind,
parsed.info.components,
parsed.info.bit_depth,
),
});
}
validate_output_payload_kind(options.output_payload_kind)?;
let validation_mode = options.validation;
let result = if parsed.transfer_syntax == output_transfer_syntax
&& parsed.payload_kind == CompressedPayloadKind::Jpeg2000Codestream
&& options.output_payload_kind == CompressedPayloadKind::JphFile
{
let output = output::wrap_borrowed_jph(bytes, &parsed, true)?;
ReencodedHtj2k {
bytes: output,
report: recode_report(
&parsed,
J2kToHtj2kMode::CodestreamPreserving,
output_transfer_syntax,
options.output_payload_kind,
parsed.info.components,
parsed.info.bit_depth,
),
}
} else if !coefficient::supports(&parsed) {
pixel::recode(bytes, &parsed, options, output_transfer_syntax)?
} else {
coefficient::recode(bytes, &parsed, options, output_transfer_syntax)?
};
drop(parsed);
if validation_mode == J2kEncodeValidation::CpuRoundTrip {
validation::roundtrip(
bytes,
&result.bytes,
recode_validation_context(result.report.mode),
)?;
}
Ok(result)
}
const fn recode_validation_context(mode: J2kToHtj2kMode) -> &'static str {
match mode {
J2kToHtj2kMode::Passthrough => "HTJ2K passthrough",
J2kToHtj2kMode::CodestreamPreserving => "HTJ2K codestream-preserving wrap",
J2kToHtj2kMode::CoefficientPreserving => "HTJ2K coefficient recode",
J2kToHtj2kMode::PixelPreserving => "HTJ2K pixel-preserving recode",
}
}
fn validate_output_payload_kind(payload_kind: CompressedPayloadKind) -> Result<(), J2kError> {
match payload_kind {
CompressedPayloadKind::Jpeg2000Codestream | CompressedPayloadKind::JphFile => Ok(()),
CompressedPayloadKind::Jp2File => Err(Unsupported {
what: "HTJ2K file output uses JPH, not JP2",
}
.into()),
_ => Err(Unsupported {
what: "J2K to HTJ2K recode output must be a raw HTJ2K codestream or JPH file",
}
.into()),
}
}
fn recode_report(
parsed: &ParsedImageInfo,
mode: J2kToHtj2kMode,
output_transfer_syntax: CompressedTransferSyntax,
output_payload_kind: CompressedPayloadKind,
components: u16,
bit_depth: u8,
) -> J2kToHtj2kReport {
J2kToHtj2kReport {
mode,
input_transfer_syntax: parsed.transfer_syntax,
output_transfer_syntax,
input_payload_kind: parsed.payload_kind,
output_payload_kind,
width: parsed.info.dimensions.0,
height: parsed.info.dimensions.1,
components,
bit_depth,
}
}
fn map_native_decode_error(err: j2k_native::DecodeError, context: &'static str) -> J2kError {
match err {
j2k_native::DecodeError::Decoding(j2k_native::DecodingError::UnsupportedFeature(what)) => {
J2kError::Unsupported(Unsupported { what })
}
_ => J2kError::from_native_decode_error_with_context(err, context),
}
}
fn native_decode_error_is_unsupported(err: &j2k_native::DecodeError) -> bool {
matches!(
err,
j2k_native::DecodeError::Decoding(j2k_native::DecodingError::UnsupportedFeature(_))
)
}