Implement optional JPEG2000 codec adapter (#40)
This commit is contained in:
22
crates/libremetaverse-openjpeg/Cargo.toml
Normal file
22
crates/libremetaverse-openjpeg/Cargo.toml
Normal file
@@ -0,0 +1,22 @@
|
||||
[package]
|
||||
name = "libremetaverse-openjpeg"
|
||||
version = "0.0.1"
|
||||
edition = "2024"
|
||||
rust-version = "1.85"
|
||||
license = "BSD-3-Clause"
|
||||
publish = false
|
||||
description = "Safe, bounded OpenJPEG 2.5.4 adapter for MetaCrate"
|
||||
build = "build.rs"
|
||||
|
||||
[build-dependencies]
|
||||
pkg-config = "0.3"
|
||||
vcpkg = "0.2"
|
||||
|
||||
# Unsafe code is permitted only in this workspace member: it is the reviewed
|
||||
# native ABI boundary. All callers use its safe, owned Rust API.
|
||||
[lints.rust]
|
||||
unsafe_code = "allow"
|
||||
|
||||
[lints.clippy]
|
||||
all = { level = "warn", priority = -1 }
|
||||
pedantic = { level = "warn", priority = -1 }
|
||||
38
crates/libremetaverse-openjpeg/README.md
Normal file
38
crates/libremetaverse-openjpeg/README.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# MetaCrate OpenJPEG adapter
|
||||
|
||||
This private crate is the unsafe implementation boundary for MetaCrate's
|
||||
optional `jpeg2000` feature. Its public surface is safe Rust: native handles,
|
||||
callbacks, and OpenJPEG image structures never cross into the imaging API.
|
||||
The adapter uses checked arithmetic and bounded memory streams, validates
|
||||
header dimensions and component counts before decoded sample-plane allocation,
|
||||
and copies decoded samples into owned Rust vectors.
|
||||
|
||||
## Native prerequisite
|
||||
|
||||
OpenJPEG **2.5.4 or newer** is required. Versions 2.5.1 through 2.5.3 are not
|
||||
accepted because they are affected by CVE-2025-54874. No bindgen or libclang
|
||||
installation is needed; the crate contains the minimal public ABI declarations
|
||||
used by the adapter.
|
||||
|
||||
- Linux and other Unix targets: install OpenJPEG development headers/libraries
|
||||
and `pkg-config`; `pkg-config --modversion libopenjp2` must report 2.5.4 or
|
||||
newer.
|
||||
- macOS: install `openjpeg` and `pkg-config` (for example with Homebrew). This
|
||||
is the same system-library path used on Linux; no Apple-only API is used.
|
||||
- Windows MSVC: install `openjpeg` with vcpkg and expose `VCPKG_ROOT` to Cargo.
|
||||
Use the triplet matching the Rust target and deploy the resulting OpenJPEG
|
||||
DLL beside the application or on its normal DLL search path when using a
|
||||
dynamic triplet.
|
||||
|
||||
Enable the adapter with `--features jpeg2000` on either
|
||||
`libremetaverse-imaging` or the umbrella `libremetaverse` crate. Without that
|
||||
feature, neither the build script nor native library discovery runs.
|
||||
|
||||
## License and redistribution
|
||||
|
||||
OpenJPEG is BSD-2-Clause licensed. MetaCrate discovers an installation supplied
|
||||
by the builder and does not redistribute OpenJPEG source or binaries. A product
|
||||
that redistributes an OpenJPEG shared or static library must also satisfy the
|
||||
OpenJPEG license's copyright and notice requirements. The ABI declarations in
|
||||
this crate follow OpenJPEG's public `openjpeg.h`; MetaCrate's adapter code is
|
||||
licensed under the repository's BSD-3-Clause license.
|
||||
16
crates/libremetaverse-openjpeg/build.rs
Normal file
16
crates/libremetaverse-openjpeg/build.rs
Normal file
@@ -0,0 +1,16 @@
|
||||
use std::env;
|
||||
|
||||
fn main() {
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
let target = env::var("TARGET").expect("Cargo provides TARGET");
|
||||
if target.contains("msvc") {
|
||||
vcpkg::Config::new()
|
||||
.find_package("openjpeg")
|
||||
.expect("jpeg2000 requires OpenJPEG 2.5.4 or newer from vcpkg");
|
||||
} else {
|
||||
pkg_config::Config::new()
|
||||
.atleast_version("2.5.4")
|
||||
.probe("libopenjp2")
|
||||
.expect("jpeg2000 requires pkg-config and OpenJPEG 2.5.4 or newer");
|
||||
}
|
||||
}
|
||||
279
crates/libremetaverse-openjpeg/src/ffi.rs
Normal file
279
crates/libremetaverse-openjpeg/src/ffi.rs
Normal file
@@ -0,0 +1,279 @@
|
||||
//! Minimal `OpenJPEG` 2.5 public ABI used by this crate.
|
||||
//!
|
||||
//! These declarations follow `openjpeg.h` from `OpenJPEG` 2.5.4. Keeping the
|
||||
//! bindings here avoids requiring libclang merely to enable the codec.
|
||||
|
||||
use std::ffi::{c_char, c_int, c_uint, c_void};
|
||||
|
||||
pub type OPJ_BOOL = c_int;
|
||||
pub type OPJ_CHAR = c_char;
|
||||
pub type OPJ_UINT16 = u16;
|
||||
pub type OPJ_INT32 = i32;
|
||||
pub type OPJ_UINT32 = u32;
|
||||
pub type OPJ_UINT64 = u64;
|
||||
pub type OPJ_OFF_T = i64;
|
||||
pub type OPJ_SIZE_T = usize;
|
||||
|
||||
pub type OPJ_RSIZ_CAPABILITIES = u32;
|
||||
pub type OPJ_CINEMA_MODE = u32;
|
||||
pub type OPJ_PROG_ORDER = i32;
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct OPJ_COLOR_SPACE(i32);
|
||||
|
||||
impl OPJ_COLOR_SPACE {
|
||||
pub const OPJ_CLRSPC_SRGB: Self = Self(1);
|
||||
pub const OPJ_CLRSPC_GRAY: Self = Self(2);
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct OPJ_CODEC_FORMAT(i32);
|
||||
|
||||
impl OPJ_CODEC_FORMAT {
|
||||
pub const OPJ_CODEC_J2K: Self = Self(0);
|
||||
pub const OPJ_CODEC_JP2: Self = Self(2);
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct opj_poc_t {
|
||||
pub resno0: OPJ_UINT32,
|
||||
pub compno0: OPJ_UINT32,
|
||||
pub layno1: OPJ_UINT32,
|
||||
pub resno1: OPJ_UINT32,
|
||||
pub compno1: OPJ_UINT32,
|
||||
pub layno0: OPJ_UINT32,
|
||||
pub precno0: OPJ_UINT32,
|
||||
pub precno1: OPJ_UINT32,
|
||||
pub prg1: OPJ_PROG_ORDER,
|
||||
pub prg: OPJ_PROG_ORDER,
|
||||
pub progorder: [OPJ_CHAR; 5],
|
||||
pub tile: OPJ_UINT32,
|
||||
pub tx0: OPJ_INT32,
|
||||
pub tx1: OPJ_INT32,
|
||||
pub ty0: OPJ_INT32,
|
||||
pub ty1: OPJ_INT32,
|
||||
pub layS: OPJ_UINT32,
|
||||
pub resS: OPJ_UINT32,
|
||||
pub compS: OPJ_UINT32,
|
||||
pub prcS: OPJ_UINT32,
|
||||
pub layE: OPJ_UINT32,
|
||||
pub resE: OPJ_UINT32,
|
||||
pub compE: OPJ_UINT32,
|
||||
pub prcE: OPJ_UINT32,
|
||||
pub txS: OPJ_UINT32,
|
||||
pub txE: OPJ_UINT32,
|
||||
pub tyS: OPJ_UINT32,
|
||||
pub tyE: OPJ_UINT32,
|
||||
pub dx: OPJ_UINT32,
|
||||
pub dy: OPJ_UINT32,
|
||||
pub lay_t: OPJ_UINT32,
|
||||
pub res_t: OPJ_UINT32,
|
||||
pub comp_t: OPJ_UINT32,
|
||||
pub prc_t: OPJ_UINT32,
|
||||
pub tx0_t: OPJ_UINT32,
|
||||
pub ty0_t: OPJ_UINT32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct opj_cparameters_t {
|
||||
pub tile_size_on: OPJ_BOOL,
|
||||
pub cp_tx0: c_int,
|
||||
pub cp_ty0: c_int,
|
||||
pub cp_tdx: c_int,
|
||||
pub cp_tdy: c_int,
|
||||
pub cp_disto_alloc: c_int,
|
||||
pub cp_fixed_alloc: c_int,
|
||||
pub cp_fixed_quality: c_int,
|
||||
pub cp_matrice: *mut c_int,
|
||||
pub cp_comment: *mut c_char,
|
||||
pub csty: c_int,
|
||||
pub prog_order: OPJ_PROG_ORDER,
|
||||
pub POC: [opj_poc_t; 32],
|
||||
pub numpocs: OPJ_UINT32,
|
||||
pub tcp_numlayers: c_int,
|
||||
pub tcp_rates: [f32; 100],
|
||||
pub tcp_distoratio: [f32; 100],
|
||||
pub numresolution: c_int,
|
||||
pub cblockw_init: c_int,
|
||||
pub cblockh_init: c_int,
|
||||
pub mode: c_int,
|
||||
pub irreversible: c_int,
|
||||
pub roi_compno: c_int,
|
||||
pub roi_shift: c_int,
|
||||
pub res_spec: c_int,
|
||||
pub prcw_init: [c_int; 33],
|
||||
pub prch_init: [c_int; 33],
|
||||
pub infile: [c_char; 4096],
|
||||
pub outfile: [c_char; 4096],
|
||||
pub index_on: c_int,
|
||||
pub index: [c_char; 4096],
|
||||
pub image_offset_x0: c_int,
|
||||
pub image_offset_y0: c_int,
|
||||
pub subsampling_dx: c_int,
|
||||
pub subsampling_dy: c_int,
|
||||
pub decod_format: c_int,
|
||||
pub cod_format: c_int,
|
||||
pub jpwl_epc_on: OPJ_BOOL,
|
||||
pub jpwl_hprot_MH: c_int,
|
||||
pub jpwl_hprot_TPH_tileno: [c_int; 16],
|
||||
pub jpwl_hprot_TPH: [c_int; 16],
|
||||
pub jpwl_pprot_tileno: [c_int; 16],
|
||||
pub jpwl_pprot_packno: [c_int; 16],
|
||||
pub jpwl_pprot: [c_int; 16],
|
||||
pub jpwl_sens_size: c_int,
|
||||
pub jpwl_sens_addr: c_int,
|
||||
pub jpwl_sens_range: c_int,
|
||||
pub jpwl_sens_MH: c_int,
|
||||
pub jpwl_sens_TPH_tileno: [c_int; 16],
|
||||
pub jpwl_sens_TPH: [c_int; 16],
|
||||
pub cp_cinema: OPJ_CINEMA_MODE,
|
||||
pub max_comp_size: c_int,
|
||||
pub cp_rsiz: OPJ_RSIZ_CAPABILITIES,
|
||||
pub tp_on: c_char,
|
||||
pub tp_flag: c_char,
|
||||
pub tcp_mct: c_char,
|
||||
pub jpip_on: OPJ_BOOL,
|
||||
pub mct_data: *mut c_void,
|
||||
pub max_cs_size: c_int,
|
||||
pub rsiz: OPJ_UINT16,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct opj_dparameters_t {
|
||||
pub cp_reduce: OPJ_UINT32,
|
||||
pub cp_layer: OPJ_UINT32,
|
||||
pub infile: [c_char; 4096],
|
||||
pub outfile: [c_char; 4096],
|
||||
pub decod_format: c_int,
|
||||
pub cod_format: c_int,
|
||||
pub DA_x0: OPJ_UINT32,
|
||||
pub DA_x1: OPJ_UINT32,
|
||||
pub DA_y0: OPJ_UINT32,
|
||||
pub DA_y1: OPJ_UINT32,
|
||||
pub m_verbose: OPJ_BOOL,
|
||||
pub tile_index: OPJ_UINT32,
|
||||
pub nb_tile_to_decode: OPJ_UINT32,
|
||||
pub jpwl_correct: OPJ_BOOL,
|
||||
pub jpwl_exp_comps: c_int,
|
||||
pub jpwl_max_tiles: c_int,
|
||||
pub flags: c_uint,
|
||||
}
|
||||
|
||||
pub type opj_codec_t = *mut c_void;
|
||||
pub type opj_stream_t = *mut c_void;
|
||||
|
||||
pub type opj_stream_read_fn =
|
||||
Option<unsafe extern "C" fn(*mut c_void, OPJ_SIZE_T, *mut c_void) -> OPJ_SIZE_T>;
|
||||
pub type opj_stream_write_fn =
|
||||
Option<unsafe extern "C" fn(*mut c_void, OPJ_SIZE_T, *mut c_void) -> OPJ_SIZE_T>;
|
||||
pub type opj_stream_skip_fn = Option<unsafe extern "C" fn(OPJ_OFF_T, *mut c_void) -> OPJ_OFF_T>;
|
||||
pub type opj_stream_seek_fn = Option<unsafe extern "C" fn(OPJ_OFF_T, *mut c_void) -> OPJ_BOOL>;
|
||||
pub type opj_stream_free_user_data_fn = Option<unsafe extern "C" fn(*mut c_void)>;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct opj_image_comp_t {
|
||||
pub dx: OPJ_UINT32,
|
||||
pub dy: OPJ_UINT32,
|
||||
pub w: OPJ_UINT32,
|
||||
pub h: OPJ_UINT32,
|
||||
pub x0: OPJ_UINT32,
|
||||
pub y0: OPJ_UINT32,
|
||||
pub prec: OPJ_UINT32,
|
||||
pub bpp: OPJ_UINT32,
|
||||
pub sgnd: OPJ_UINT32,
|
||||
pub resno_decoded: OPJ_UINT32,
|
||||
pub factor: OPJ_UINT32,
|
||||
pub data: *mut OPJ_INT32,
|
||||
pub alpha: OPJ_UINT16,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct opj_image_t {
|
||||
pub x0: OPJ_UINT32,
|
||||
pub y0: OPJ_UINT32,
|
||||
pub x1: OPJ_UINT32,
|
||||
pub y1: OPJ_UINT32,
|
||||
pub numcomps: OPJ_UINT32,
|
||||
pub color_space: OPJ_COLOR_SPACE,
|
||||
pub comps: *mut opj_image_comp_t,
|
||||
pub icc_profile_buf: *mut u8,
|
||||
pub icc_profile_len: OPJ_UINT32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct opj_image_cmptparm_t {
|
||||
pub dx: OPJ_UINT32,
|
||||
pub dy: OPJ_UINT32,
|
||||
pub w: OPJ_UINT32,
|
||||
pub h: OPJ_UINT32,
|
||||
pub x0: OPJ_UINT32,
|
||||
pub y0: OPJ_UINT32,
|
||||
pub prec: OPJ_UINT32,
|
||||
pub bpp: OPJ_UINT32,
|
||||
pub sgnd: OPJ_UINT32,
|
||||
}
|
||||
|
||||
unsafe extern "C" {
|
||||
pub fn opj_version() -> *const c_char;
|
||||
pub fn opj_stream_default_create(is_input: OPJ_BOOL) -> *mut opj_stream_t;
|
||||
pub fn opj_stream_destroy(stream: *mut opj_stream_t);
|
||||
pub fn opj_stream_set_read_function(stream: *mut opj_stream_t, function: opj_stream_read_fn);
|
||||
pub fn opj_stream_set_write_function(stream: *mut opj_stream_t, function: opj_stream_write_fn);
|
||||
pub fn opj_stream_set_skip_function(stream: *mut opj_stream_t, function: opj_stream_skip_fn);
|
||||
pub fn opj_stream_set_seek_function(stream: *mut opj_stream_t, function: opj_stream_seek_fn);
|
||||
pub fn opj_stream_set_user_data(
|
||||
stream: *mut opj_stream_t,
|
||||
data: *mut c_void,
|
||||
free_function: opj_stream_free_user_data_fn,
|
||||
);
|
||||
pub fn opj_stream_set_user_data_length(stream: *mut opj_stream_t, length: OPJ_UINT64);
|
||||
|
||||
pub fn opj_create_decompress(format: OPJ_CODEC_FORMAT) -> *mut opj_codec_t;
|
||||
pub fn opj_create_compress(format: OPJ_CODEC_FORMAT) -> *mut opj_codec_t;
|
||||
pub fn opj_destroy_codec(codec: *mut opj_codec_t);
|
||||
pub fn opj_set_default_decoder_parameters(parameters: *mut opj_dparameters_t);
|
||||
pub fn opj_setup_decoder(
|
||||
codec: *mut opj_codec_t,
|
||||
parameters: *mut opj_dparameters_t,
|
||||
) -> OPJ_BOOL;
|
||||
pub fn opj_decoder_set_strict_mode(codec: *mut opj_codec_t, strict: OPJ_BOOL) -> OPJ_BOOL;
|
||||
pub fn opj_read_header(
|
||||
stream: *mut opj_stream_t,
|
||||
codec: *mut opj_codec_t,
|
||||
image: *mut *mut opj_image_t,
|
||||
) -> OPJ_BOOL;
|
||||
pub fn opj_decode(
|
||||
codec: *mut opj_codec_t,
|
||||
stream: *mut opj_stream_t,
|
||||
image: *mut opj_image_t,
|
||||
) -> OPJ_BOOL;
|
||||
pub fn opj_end_decompress(codec: *mut opj_codec_t, stream: *mut opj_stream_t) -> OPJ_BOOL;
|
||||
|
||||
pub fn opj_image_create(
|
||||
component_count: OPJ_UINT32,
|
||||
component_parameters: *mut opj_image_cmptparm_t,
|
||||
color_space: OPJ_COLOR_SPACE,
|
||||
) -> *mut opj_image_t;
|
||||
pub fn opj_image_destroy(image: *mut opj_image_t);
|
||||
pub fn opj_set_default_encoder_parameters(parameters: *mut opj_cparameters_t);
|
||||
pub fn opj_setup_encoder(
|
||||
codec: *mut opj_codec_t,
|
||||
parameters: *mut opj_cparameters_t,
|
||||
image: *mut opj_image_t,
|
||||
) -> OPJ_BOOL;
|
||||
pub fn opj_start_compress(
|
||||
codec: *mut opj_codec_t,
|
||||
image: *mut opj_image_t,
|
||||
stream: *mut opj_stream_t,
|
||||
) -> OPJ_BOOL;
|
||||
pub fn opj_encode(codec: *mut opj_codec_t, stream: *mut opj_stream_t) -> OPJ_BOOL;
|
||||
pub fn opj_end_compress(codec: *mut opj_codec_t, stream: *mut opj_stream_t) -> OPJ_BOOL;
|
||||
}
|
||||
770
crates/libremetaverse-openjpeg/src/lib.rs
Normal file
770
crates/libremetaverse-openjpeg/src/lib.rs
Normal file
@@ -0,0 +1,770 @@
|
||||
//! Safe, bounded access to the system `OpenJPEG` codec.
|
||||
//!
|
||||
//! All foreign calls and callback pointer handling are contained here. The
|
||||
//! build requires `OpenJPEG` 2.5.4 or newer, excluding releases affected by
|
||||
//! CVE-2025-54874 and earlier fixed decoder defects.
|
||||
|
||||
#![allow(unsafe_code)]
|
||||
|
||||
use std::ffi::{CStr, c_void};
|
||||
use std::marker::PhantomData;
|
||||
use std::mem::MaybeUninit;
|
||||
use std::ptr::NonNull;
|
||||
|
||||
const MAX_PIXELS: usize = 4096 * 4096;
|
||||
|
||||
#[allow(
|
||||
dead_code,
|
||||
non_camel_case_types,
|
||||
non_snake_case,
|
||||
non_upper_case_globals,
|
||||
rustdoc::bare_urls,
|
||||
rustdoc::broken_intra_doc_links
|
||||
)]
|
||||
mod ffi;
|
||||
|
||||
/// Codec/container selection.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum Format {
|
||||
J2k,
|
||||
Jp2,
|
||||
}
|
||||
|
||||
/// `OpenJPEG` rate-control selection.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum Compression {
|
||||
Lossless,
|
||||
Lossy { compression_ratio: f32 },
|
||||
}
|
||||
|
||||
/// Decoder controls passed directly to `OpenJPEG`.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub struct DecodeOptions {
|
||||
pub discard_levels: u32,
|
||||
pub quality_layers: u32,
|
||||
pub strict: bool,
|
||||
pub max_pixels: usize,
|
||||
}
|
||||
|
||||
/// One decoded component with original integer precision.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct Component {
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub precision: u8,
|
||||
pub signed: bool,
|
||||
pub alpha: bool,
|
||||
pub samples: Vec<i32>,
|
||||
}
|
||||
|
||||
/// Decoded `OpenJPEG` image.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct Image {
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub components: Vec<Component>,
|
||||
}
|
||||
|
||||
/// Input image component for encoding.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct ComponentRef<'a> {
|
||||
pub precision: u8,
|
||||
pub signed: bool,
|
||||
pub alpha: bool,
|
||||
pub samples: &'a [i32],
|
||||
}
|
||||
|
||||
/// Stable error categories; `OpenJPEG` diagnostics never cross the safe API.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum Error {
|
||||
InvalidInput,
|
||||
LimitExceeded,
|
||||
Allocation,
|
||||
Codec,
|
||||
}
|
||||
|
||||
/// Decodes J2K or JP2 after validating header dimensions and components and
|
||||
/// before allowing `OpenJPEG` to allocate decoded sample planes.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns a stable validation, limit, allocation, or codec error without
|
||||
/// exposing native handles or diagnostics.
|
||||
pub fn decode(bytes: &[u8], format: Format, options: DecodeOptions) -> Result<Image, Error> {
|
||||
ensure_supported_version()?;
|
||||
if bytes.is_empty() || options.max_pixels == 0 {
|
||||
return Err(Error::InvalidInput);
|
||||
}
|
||||
validate_encoded_header(bytes, format, options.max_pixels)?;
|
||||
let stream = InputStream::new(bytes)?;
|
||||
let codec = Codec::decoder(format)?;
|
||||
let mut parameters = MaybeUninit::<ffi::opj_dparameters_t>::zeroed();
|
||||
unsafe { ffi::opj_set_default_decoder_parameters(parameters.as_mut_ptr()) };
|
||||
let mut parameters = unsafe { parameters.assume_init() };
|
||||
parameters.cp_reduce = options.discard_levels;
|
||||
parameters.cp_layer = options.quality_layers;
|
||||
if unsafe { ffi::opj_setup_decoder(codec.raw.as_ptr(), &raw mut parameters) } != 1
|
||||
|| unsafe {
|
||||
ffi::opj_decoder_set_strict_mode(codec.raw.as_ptr(), i32::from(options.strict))
|
||||
} != 1
|
||||
{
|
||||
return Err(Error::Codec);
|
||||
}
|
||||
|
||||
// Initializing this pointer is security-significant: OpenJPEG <=2.5.3 had
|
||||
// an out-of-bounds write on short JP2 input when it was uninitialized.
|
||||
let mut raw_image = std::ptr::null_mut();
|
||||
if unsafe { ffi::opj_read_header(stream.raw.as_ptr(), codec.raw.as_ptr(), &raw mut raw_image) }
|
||||
!= 1
|
||||
{
|
||||
return Err(Error::InvalidInput);
|
||||
}
|
||||
let mut image = RawImage::new(raw_image)?;
|
||||
validate_header(image.raw.as_ptr(), options.max_pixels)?;
|
||||
if unsafe { ffi::opj_decode(codec.raw.as_ptr(), stream.raw.as_ptr(), image.raw.as_ptr()) } != 1
|
||||
|| unsafe { ffi::opj_end_decompress(codec.raw.as_ptr(), stream.raw.as_ptr()) } != 1
|
||||
{
|
||||
return Err(Error::InvalidInput);
|
||||
}
|
||||
image.copy(options.max_pixels)
|
||||
}
|
||||
|
||||
/// Encodes one through five equal-sized component planes to memory.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns a stable validation, limit, allocation, or codec error. Dimensions,
|
||||
/// sample ranges, and output capacity are checked before native encoding.
|
||||
pub fn encode(
|
||||
width: u32,
|
||||
height: u32,
|
||||
components: &[ComponentRef<'_>],
|
||||
format: Format,
|
||||
compression: Compression,
|
||||
max_encoded_bytes: usize,
|
||||
) -> Result<Vec<u8>, Error> {
|
||||
ensure_supported_version()?;
|
||||
let pixels = checked_pixels(width, height, MAX_PIXELS)?;
|
||||
if components.is_empty()
|
||||
|| components.len() > 5
|
||||
|| max_encoded_bytes == 0
|
||||
|| components.iter().any(|component| {
|
||||
!(1..=31).contains(&component.precision) || component.samples.len() != pixels
|
||||
})
|
||||
{
|
||||
return Err(Error::InvalidInput);
|
||||
}
|
||||
if let Compression::Lossy { compression_ratio } = compression {
|
||||
if !compression_ratio.is_finite() || compression_ratio < 1.0 {
|
||||
return Err(Error::InvalidInput);
|
||||
}
|
||||
}
|
||||
|
||||
let image = RawImage::from_components(width, height, components)?;
|
||||
let codec = Codec::encoder(format)?;
|
||||
let stream = OutputStream::new(max_encoded_bytes)?;
|
||||
let mut parameters = MaybeUninit::<ffi::opj_cparameters_t>::zeroed();
|
||||
unsafe { ffi::opj_set_default_encoder_parameters(parameters.as_mut_ptr()) };
|
||||
let mut parameters = unsafe { parameters.assume_init() };
|
||||
parameters.tcp_numlayers = 1;
|
||||
parameters.cp_disto_alloc = 1;
|
||||
parameters.tcp_mct = (components.len() >= 3).into();
|
||||
parameters.numresolution = maximum_resolution_levels(width, height);
|
||||
parameters.max_cs_size = i32::try_from(max_encoded_bytes).unwrap_or(i32::MAX);
|
||||
match compression {
|
||||
Compression::Lossless => {
|
||||
parameters.tcp_rates[0] = 0.0;
|
||||
parameters.irreversible = 0;
|
||||
}
|
||||
Compression::Lossy { compression_ratio } => {
|
||||
parameters.tcp_rates[0] = compression_ratio;
|
||||
parameters.irreversible = 1;
|
||||
}
|
||||
}
|
||||
if unsafe {
|
||||
ffi::opj_setup_encoder(codec.raw.as_ptr(), &raw mut parameters, image.raw.as_ptr())
|
||||
} != 1
|
||||
|| unsafe {
|
||||
ffi::opj_start_compress(codec.raw.as_ptr(), image.raw.as_ptr(), stream.raw.as_ptr())
|
||||
} != 1
|
||||
|| unsafe { ffi::opj_encode(codec.raw.as_ptr(), stream.raw.as_ptr()) } != 1
|
||||
|| unsafe { ffi::opj_end_compress(codec.raw.as_ptr(), stream.raw.as_ptr()) } != 1
|
||||
{
|
||||
return Err(if stream.state.limit_exceeded {
|
||||
Error::LimitExceeded
|
||||
} else {
|
||||
Error::Codec
|
||||
});
|
||||
}
|
||||
stream.into_bytes()
|
||||
}
|
||||
|
||||
struct Codec {
|
||||
raw: NonNull<ffi::opj_codec_t>,
|
||||
}
|
||||
|
||||
impl Codec {
|
||||
fn decoder(format: Format) -> Result<Self, Error> {
|
||||
let raw = unsafe { ffi::opj_create_decompress(codec_format(format)) };
|
||||
NonNull::new(raw)
|
||||
.map(|raw| Self { raw })
|
||||
.ok_or(Error::Codec)
|
||||
}
|
||||
|
||||
fn encoder(format: Format) -> Result<Self, Error> {
|
||||
let raw = unsafe { ffi::opj_create_compress(codec_format(format)) };
|
||||
NonNull::new(raw)
|
||||
.map(|raw| Self { raw })
|
||||
.ok_or(Error::Codec)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Codec {
|
||||
fn drop(&mut self) {
|
||||
unsafe { ffi::opj_destroy_codec(self.raw.as_ptr()) };
|
||||
}
|
||||
}
|
||||
|
||||
struct RawImage {
|
||||
raw: NonNull<ffi::opj_image_t>,
|
||||
}
|
||||
|
||||
impl RawImage {
|
||||
fn new(raw: *mut ffi::opj_image_t) -> Result<Self, Error> {
|
||||
NonNull::new(raw)
|
||||
.map(|raw| Self { raw })
|
||||
.ok_or(Error::Codec)
|
||||
}
|
||||
|
||||
fn from_components(
|
||||
width: u32,
|
||||
height: u32,
|
||||
components: &[ComponentRef<'_>],
|
||||
) -> Result<Self, Error> {
|
||||
let mut parameters: Vec<_> = components
|
||||
.iter()
|
||||
.map(|component| ffi::opj_image_cmptparm_t {
|
||||
dx: 1,
|
||||
dy: 1,
|
||||
w: width,
|
||||
h: height,
|
||||
x0: 0,
|
||||
y0: 0,
|
||||
prec: u32::from(component.precision),
|
||||
bpp: u32::from(component.precision),
|
||||
sgnd: u32::from(component.signed),
|
||||
})
|
||||
.collect();
|
||||
let color_space = if components.len() >= 3 {
|
||||
ffi::OPJ_COLOR_SPACE::OPJ_CLRSPC_SRGB
|
||||
} else {
|
||||
ffi::OPJ_COLOR_SPACE::OPJ_CLRSPC_GRAY
|
||||
};
|
||||
let raw = unsafe {
|
||||
ffi::opj_image_create(
|
||||
u32::try_from(parameters.len()).map_err(|_| Error::InvalidInput)?,
|
||||
parameters.as_mut_ptr(),
|
||||
color_space,
|
||||
)
|
||||
};
|
||||
let mut image = Self::new(raw)?;
|
||||
unsafe {
|
||||
let raw = image.raw.as_mut();
|
||||
raw.x0 = 0;
|
||||
raw.y0 = 0;
|
||||
raw.x1 = width;
|
||||
raw.y1 = height;
|
||||
let output = std::slice::from_raw_parts_mut(raw.comps, raw.numcomps as usize);
|
||||
for (output, input) in output.iter_mut().zip(components) {
|
||||
if output.data.is_null() {
|
||||
return Err(Error::Allocation);
|
||||
}
|
||||
output.alpha = u16::from(input.alpha);
|
||||
let destination = std::slice::from_raw_parts_mut(output.data, input.samples.len());
|
||||
destination.copy_from_slice(input.samples);
|
||||
}
|
||||
}
|
||||
Ok(image)
|
||||
}
|
||||
|
||||
fn copy(&mut self, max_pixels: usize) -> Result<Image, Error> {
|
||||
let raw = unsafe { self.raw.as_ref() };
|
||||
let width = raw.x1.checked_sub(raw.x0).ok_or(Error::InvalidInput)?;
|
||||
let height = raw.y1.checked_sub(raw.y0).ok_or(Error::InvalidInput)?;
|
||||
checked_pixels(width, height, max_pixels)?;
|
||||
let components = unsafe { std::slice::from_raw_parts(raw.comps, raw.numcomps as usize) };
|
||||
if !(1..=5).contains(&components.len()) {
|
||||
return Err(Error::InvalidInput);
|
||||
}
|
||||
let first = components.first().ok_or(Error::InvalidInput)?;
|
||||
let mut copied = Vec::new();
|
||||
copied
|
||||
.try_reserve_exact(components.len())
|
||||
.map_err(|_| Error::Allocation)?;
|
||||
for component in components {
|
||||
if component.w != first.w
|
||||
|| component.h != first.h
|
||||
|| component.data.is_null()
|
||||
|| !(1..=31).contains(&component.prec)
|
||||
{
|
||||
return Err(Error::InvalidInput);
|
||||
}
|
||||
checked_pixels(component.w, component.h, max_pixels)?;
|
||||
let length = checked_pixels(component.w, component.h, max_pixels)?;
|
||||
let source = unsafe { std::slice::from_raw_parts(component.data, length) };
|
||||
let mut samples = Vec::new();
|
||||
samples
|
||||
.try_reserve_exact(length)
|
||||
.map_err(|_| Error::Allocation)?;
|
||||
samples.extend_from_slice(source);
|
||||
copied.push(Component {
|
||||
width: component.w,
|
||||
height: component.h,
|
||||
precision: u8::try_from(component.prec).map_err(|_| Error::InvalidInput)?,
|
||||
signed: component.sgnd != 0,
|
||||
alpha: component.alpha != 0,
|
||||
samples,
|
||||
});
|
||||
}
|
||||
Ok(Image {
|
||||
width: first.w,
|
||||
height: first.h,
|
||||
components: copied,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for RawImage {
|
||||
fn drop(&mut self) {
|
||||
unsafe { ffi::opj_image_destroy(self.raw.as_ptr()) };
|
||||
}
|
||||
}
|
||||
|
||||
struct InputState<'a> {
|
||||
bytes: &'a [u8],
|
||||
position: usize,
|
||||
}
|
||||
|
||||
struct InputStream<'a> {
|
||||
raw: NonNull<ffi::opj_stream_t>,
|
||||
state: Box<InputState<'a>>,
|
||||
_lifetime: PhantomData<&'a [u8]>,
|
||||
}
|
||||
|
||||
impl<'a> InputStream<'a> {
|
||||
fn new(bytes: &'a [u8]) -> Result<Self, Error> {
|
||||
let raw = unsafe { ffi::opj_stream_default_create(1) };
|
||||
let raw = NonNull::new(raw).ok_or(Error::Allocation)?;
|
||||
let mut state = Box::new(InputState { bytes, position: 0 });
|
||||
unsafe {
|
||||
ffi::opj_stream_set_read_function(raw.as_ptr(), Some(input_read));
|
||||
ffi::opj_stream_set_skip_function(raw.as_ptr(), Some(input_skip));
|
||||
ffi::opj_stream_set_seek_function(raw.as_ptr(), Some(input_seek));
|
||||
ffi::opj_stream_set_user_data(
|
||||
raw.as_ptr(),
|
||||
std::ptr::from_mut(state.as_mut()).cast(),
|
||||
None,
|
||||
);
|
||||
ffi::opj_stream_set_user_data_length(raw.as_ptr(), bytes.len() as u64);
|
||||
}
|
||||
Ok(Self {
|
||||
raw,
|
||||
state,
|
||||
_lifetime: PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for InputStream<'_> {
|
||||
fn drop(&mut self) {
|
||||
unsafe { ffi::opj_stream_destroy(self.raw.as_ptr()) };
|
||||
// Keep the callback state observably alive until after stream destroy.
|
||||
let _ = &self.state;
|
||||
}
|
||||
}
|
||||
|
||||
struct OutputState {
|
||||
bytes: Vec<u8>,
|
||||
position: usize,
|
||||
limit: usize,
|
||||
limit_exceeded: bool,
|
||||
}
|
||||
|
||||
struct OutputStream {
|
||||
raw: NonNull<ffi::opj_stream_t>,
|
||||
state: Box<OutputState>,
|
||||
}
|
||||
|
||||
impl OutputStream {
|
||||
fn new(limit: usize) -> Result<Self, Error> {
|
||||
let raw = unsafe { ffi::opj_stream_default_create(0) };
|
||||
let raw = NonNull::new(raw).ok_or(Error::Allocation)?;
|
||||
let mut state = Box::new(OutputState {
|
||||
bytes: Vec::new(),
|
||||
position: 0,
|
||||
limit,
|
||||
limit_exceeded: false,
|
||||
});
|
||||
unsafe {
|
||||
ffi::opj_stream_set_write_function(raw.as_ptr(), Some(output_write));
|
||||
ffi::opj_stream_set_skip_function(raw.as_ptr(), Some(output_skip));
|
||||
ffi::opj_stream_set_seek_function(raw.as_ptr(), Some(output_seek));
|
||||
ffi::opj_stream_set_user_data(
|
||||
raw.as_ptr(),
|
||||
std::ptr::from_mut(state.as_mut()).cast(),
|
||||
None,
|
||||
);
|
||||
}
|
||||
Ok(Self { raw, state })
|
||||
}
|
||||
|
||||
fn into_bytes(mut self) -> Result<Vec<u8>, Error> {
|
||||
unsafe { ffi::opj_stream_destroy(self.raw.as_ptr()) };
|
||||
self.raw = NonNull::dangling();
|
||||
if self.state.limit_exceeded || self.state.bytes.is_empty() {
|
||||
return Err(if self.state.limit_exceeded {
|
||||
Error::LimitExceeded
|
||||
} else {
|
||||
Error::Codec
|
||||
});
|
||||
}
|
||||
Ok(std::mem::take(&mut self.state.bytes))
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for OutputStream {
|
||||
fn drop(&mut self) {
|
||||
if self.raw != NonNull::dangling() {
|
||||
unsafe { ffi::opj_stream_destroy(self.raw.as_ptr()) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn input_read(
|
||||
output: *mut c_void,
|
||||
length: usize,
|
||||
user_data: *mut c_void,
|
||||
) -> usize {
|
||||
if output.is_null() || user_data.is_null() || length == 0 {
|
||||
return usize::MAX;
|
||||
}
|
||||
let state = unsafe { &mut *user_data.cast::<InputState<'_>>() };
|
||||
let remaining = state.bytes.len().saturating_sub(state.position);
|
||||
if remaining == 0 {
|
||||
return usize::MAX;
|
||||
}
|
||||
let count = remaining.min(length);
|
||||
unsafe {
|
||||
std::ptr::copy_nonoverlapping(
|
||||
state.bytes.as_ptr().add(state.position),
|
||||
output.cast(),
|
||||
count,
|
||||
);
|
||||
}
|
||||
state.position += count;
|
||||
count
|
||||
}
|
||||
|
||||
unsafe extern "C" fn input_skip(offset: i64, user_data: *mut c_void) -> i64 {
|
||||
if user_data.is_null() || offset < 0 {
|
||||
return -1;
|
||||
}
|
||||
let state = unsafe { &mut *user_data.cast::<InputState<'_>>() };
|
||||
let Ok(offset) = usize::try_from(offset) else {
|
||||
return -1;
|
||||
};
|
||||
let Some(position) = state.position.checked_add(offset) else {
|
||||
return -1;
|
||||
};
|
||||
if position > state.bytes.len() {
|
||||
return -1;
|
||||
}
|
||||
state.position = position;
|
||||
i64::try_from(offset).unwrap_or(-1)
|
||||
}
|
||||
|
||||
unsafe extern "C" fn input_seek(position: i64, user_data: *mut c_void) -> i32 {
|
||||
if user_data.is_null() || position < 0 {
|
||||
return 0;
|
||||
}
|
||||
let state = unsafe { &mut *user_data.cast::<InputState<'_>>() };
|
||||
let Ok(position) = usize::try_from(position) else {
|
||||
return 0;
|
||||
};
|
||||
if position > state.bytes.len() {
|
||||
return 0;
|
||||
}
|
||||
state.position = position;
|
||||
1
|
||||
}
|
||||
|
||||
unsafe extern "C" fn output_write(
|
||||
input: *mut c_void,
|
||||
length: usize,
|
||||
user_data: *mut c_void,
|
||||
) -> usize {
|
||||
if input.is_null() || user_data.is_null() {
|
||||
return usize::MAX;
|
||||
}
|
||||
let state = unsafe { &mut *user_data.cast::<OutputState>() };
|
||||
let Some(end) = state.position.checked_add(length) else {
|
||||
state.limit_exceeded = true;
|
||||
return usize::MAX;
|
||||
};
|
||||
if end > state.limit {
|
||||
state.limit_exceeded = true;
|
||||
return usize::MAX;
|
||||
}
|
||||
if end > state.bytes.len() {
|
||||
if state.bytes.try_reserve(end - state.bytes.len()).is_err() {
|
||||
return usize::MAX;
|
||||
}
|
||||
state.bytes.resize(end, 0);
|
||||
}
|
||||
let source = unsafe { std::slice::from_raw_parts(input.cast::<u8>(), length) };
|
||||
state.bytes[state.position..end].copy_from_slice(source);
|
||||
state.position = end;
|
||||
length
|
||||
}
|
||||
|
||||
unsafe extern "C" fn output_skip(offset: i64, user_data: *mut c_void) -> i64 {
|
||||
if user_data.is_null() {
|
||||
return -1;
|
||||
}
|
||||
let state = unsafe { &mut *user_data.cast::<OutputState>() };
|
||||
let Ok(offset_isize) = isize::try_from(offset) else {
|
||||
return -1;
|
||||
};
|
||||
let Some(position) = state.position.checked_add_signed(offset_isize) else {
|
||||
return -1;
|
||||
};
|
||||
if position > state.limit {
|
||||
state.limit_exceeded = true;
|
||||
return -1;
|
||||
}
|
||||
state.position = position;
|
||||
offset
|
||||
}
|
||||
|
||||
unsafe extern "C" fn output_seek(position: i64, user_data: *mut c_void) -> i32 {
|
||||
if user_data.is_null() || position < 0 {
|
||||
return 0;
|
||||
}
|
||||
let state = unsafe { &mut *user_data.cast::<OutputState>() };
|
||||
let Ok(position) = usize::try_from(position) else {
|
||||
return 0;
|
||||
};
|
||||
if position > state.limit {
|
||||
state.limit_exceeded = true;
|
||||
return 0;
|
||||
}
|
||||
state.position = position;
|
||||
1
|
||||
}
|
||||
|
||||
fn validate_header(raw: *mut ffi::opj_image_t, max_pixels: usize) -> Result<(), Error> {
|
||||
let raw = unsafe { raw.as_ref() }.ok_or(Error::InvalidInput)?;
|
||||
checked_pixels(
|
||||
raw.x1.checked_sub(raw.x0).ok_or(Error::InvalidInput)?,
|
||||
raw.y1.checked_sub(raw.y0).ok_or(Error::InvalidInput)?,
|
||||
max_pixels,
|
||||
)?;
|
||||
let count = usize::try_from(raw.numcomps).map_err(|_| Error::InvalidInput)?;
|
||||
if !(1..=5).contains(&count) || raw.comps.is_null() {
|
||||
return Err(Error::InvalidInput);
|
||||
}
|
||||
let components = unsafe { std::slice::from_raw_parts(raw.comps, count) };
|
||||
for component in components {
|
||||
checked_pixels(component.w, component.h, max_pixels)?;
|
||||
if !(1..=31).contains(&component.prec) {
|
||||
return Err(Error::InvalidInput);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn checked_pixels(width: u32, height: u32, maximum: usize) -> Result<usize, Error> {
|
||||
if width == 0 || height == 0 {
|
||||
return Err(Error::InvalidInput);
|
||||
}
|
||||
usize::try_from(width)
|
||||
.ok()
|
||||
.and_then(|width| {
|
||||
usize::try_from(height)
|
||||
.ok()
|
||||
.and_then(|height| width.checked_mul(height))
|
||||
})
|
||||
.filter(|pixels| *pixels <= maximum)
|
||||
.ok_or(Error::LimitExceeded)
|
||||
}
|
||||
|
||||
fn validate_encoded_header(bytes: &[u8], format: Format, max_pixels: usize) -> Result<(), Error> {
|
||||
match format {
|
||||
Format::J2k => validate_siz_marker(bytes, max_pixels),
|
||||
Format::Jp2 => {
|
||||
let mut position = 0_usize;
|
||||
while bytes.len().saturating_sub(position) >= 8 {
|
||||
let box_length = read_u32(bytes, position)?;
|
||||
let box_type = bytes
|
||||
.get(position + 4..position + 8)
|
||||
.ok_or(Error::InvalidInput)?;
|
||||
let (header_length, length) = if box_length == 1 {
|
||||
let extended = usize::try_from(read_u64(bytes, position + 8)?)
|
||||
.map_err(|_| Error::LimitExceeded)?;
|
||||
(16_usize, extended)
|
||||
} else if box_length == 0 {
|
||||
(8_usize, bytes.len() - position)
|
||||
} else {
|
||||
(
|
||||
8_usize,
|
||||
usize::try_from(box_length).map_err(|_| Error::LimitExceeded)?,
|
||||
)
|
||||
};
|
||||
if length < header_length {
|
||||
return Err(Error::InvalidInput);
|
||||
}
|
||||
let body = position
|
||||
.checked_add(header_length)
|
||||
.ok_or(Error::LimitExceeded)?;
|
||||
let end = position.checked_add(length).ok_or(Error::LimitExceeded)?;
|
||||
if end > bytes.len() {
|
||||
return Err(Error::InvalidInput);
|
||||
}
|
||||
if box_type == b"jp2c" {
|
||||
return validate_siz_marker(&bytes[body..end], max_pixels);
|
||||
}
|
||||
position = end;
|
||||
}
|
||||
Err(Error::InvalidInput)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_siz_marker(codestream: &[u8], max_pixels: usize) -> Result<(), Error> {
|
||||
if codestream.get(..4) != Some(&[0xff, 0x4f, 0xff, 0x51]) {
|
||||
return Err(Error::InvalidInput);
|
||||
}
|
||||
let length = usize::from(read_u16(codestream, 4)?);
|
||||
let end = 4_usize.checked_add(length).ok_or(Error::LimitExceeded)?;
|
||||
if length < 38 || end > codestream.len() {
|
||||
return Err(Error::InvalidInput);
|
||||
}
|
||||
let x1 = read_u32(codestream, 8)?;
|
||||
let y1 = read_u32(codestream, 12)?;
|
||||
let x0 = read_u32(codestream, 16)?;
|
||||
let y0 = read_u32(codestream, 20)?;
|
||||
if read_u32(codestream, 24)? == 0 || read_u32(codestream, 28)? == 0 {
|
||||
return Err(Error::InvalidInput);
|
||||
}
|
||||
let width = x1.checked_sub(x0).ok_or(Error::InvalidInput)?;
|
||||
let height = y1.checked_sub(y0).ok_or(Error::InvalidInput)?;
|
||||
checked_pixels(width, height, max_pixels)?;
|
||||
|
||||
let component_count = usize::from(read_u16(codestream, 40)?);
|
||||
if !(1..=5).contains(&component_count)
|
||||
|| length != 38 + component_count.checked_mul(3).ok_or(Error::LimitExceeded)?
|
||||
{
|
||||
return Err(Error::InvalidInput);
|
||||
}
|
||||
for component in 0..component_count {
|
||||
let offset = 42 + component * 3;
|
||||
let precision = codestream.get(offset).copied().ok_or(Error::InvalidInput)? & 0x7f;
|
||||
let dx = u32::from(
|
||||
codestream
|
||||
.get(offset + 1)
|
||||
.copied()
|
||||
.ok_or(Error::InvalidInput)?,
|
||||
);
|
||||
let dy = u32::from(
|
||||
codestream
|
||||
.get(offset + 2)
|
||||
.copied()
|
||||
.ok_or(Error::InvalidInput)?,
|
||||
);
|
||||
if precision >= 31 || dx == 0 || dy == 0 {
|
||||
return Err(Error::InvalidInput);
|
||||
}
|
||||
let component_width = ceiling_div(x1, dx)?
|
||||
.checked_sub(ceiling_div(x0, dx)?)
|
||||
.ok_or(Error::InvalidInput)?;
|
||||
let component_height = ceiling_div(y1, dy)?
|
||||
.checked_sub(ceiling_div(y0, dy)?)
|
||||
.ok_or(Error::InvalidInput)?;
|
||||
checked_pixels(component_width, component_height, max_pixels)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ceiling_div(value: u32, divisor: u32) -> Result<u32, Error> {
|
||||
value
|
||||
.checked_add(divisor - 1)
|
||||
.map(|value| value / divisor)
|
||||
.ok_or(Error::LimitExceeded)
|
||||
}
|
||||
|
||||
fn read_u16(bytes: &[u8], position: usize) -> Result<u16, Error> {
|
||||
let bytes: [u8; 2] = bytes
|
||||
.get(position..position.checked_add(2).ok_or(Error::LimitExceeded)?)
|
||||
.ok_or(Error::InvalidInput)?
|
||||
.try_into()
|
||||
.map_err(|_| Error::InvalidInput)?;
|
||||
Ok(u16::from_be_bytes(bytes))
|
||||
}
|
||||
|
||||
fn read_u32(bytes: &[u8], position: usize) -> Result<u32, Error> {
|
||||
let bytes: [u8; 4] = bytes
|
||||
.get(position..position.checked_add(4).ok_or(Error::LimitExceeded)?)
|
||||
.ok_or(Error::InvalidInput)?
|
||||
.try_into()
|
||||
.map_err(|_| Error::InvalidInput)?;
|
||||
Ok(u32::from_be_bytes(bytes))
|
||||
}
|
||||
|
||||
fn read_u64(bytes: &[u8], position: usize) -> Result<u64, Error> {
|
||||
let bytes: [u8; 8] = bytes
|
||||
.get(position..position.checked_add(8).ok_or(Error::LimitExceeded)?)
|
||||
.ok_or(Error::InvalidInput)?
|
||||
.try_into()
|
||||
.map_err(|_| Error::InvalidInput)?;
|
||||
Ok(u64::from_be_bytes(bytes))
|
||||
}
|
||||
|
||||
fn maximum_resolution_levels(width: u32, height: u32) -> i32 {
|
||||
let minimum = width.min(height);
|
||||
let levels = u32::BITS - minimum.leading_zeros();
|
||||
i32::try_from(levels.min(6)).unwrap_or(1).max(1)
|
||||
}
|
||||
|
||||
const fn codec_format(format: Format) -> ffi::OPJ_CODEC_FORMAT {
|
||||
match format {
|
||||
Format::J2k => ffi::OPJ_CODEC_FORMAT::OPJ_CODEC_J2K,
|
||||
Format::Jp2 => ffi::OPJ_CODEC_FORMAT::OPJ_CODEC_JP2,
|
||||
}
|
||||
}
|
||||
|
||||
fn ensure_supported_version() -> Result<(), Error> {
|
||||
let raw = unsafe { ffi::opj_version() };
|
||||
if raw.is_null() {
|
||||
return Err(Error::Codec);
|
||||
}
|
||||
let version = unsafe { CStr::from_ptr(raw) }
|
||||
.to_str()
|
||||
.map_err(|_| Error::Codec)?;
|
||||
let mut components = version.split('.').map(|component| {
|
||||
component
|
||||
.bytes()
|
||||
.take_while(u8::is_ascii_digit)
|
||||
.try_fold(0_u32, |value, digit| {
|
||||
value.checked_mul(10)?.checked_add(u32::from(digit - b'0'))
|
||||
})
|
||||
});
|
||||
let parsed = (
|
||||
components.next().flatten().ok_or(Error::Codec)?,
|
||||
components.next().flatten().ok_or(Error::Codec)?,
|
||||
components.next().flatten().ok_or(Error::Codec)?,
|
||||
);
|
||||
if parsed < (2, 5, 4) {
|
||||
return Err(Error::Codec);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user