use error::DecodeError;
use image::{DynamicImage, GenericImage};
use openjpeg2_sys as ffi;
use slog::{self, Logger};
use std::ffi::CString;
use std::os::raw::c_void;
use std::ptr::null_mut;
mod color_convert;
use self::color_convert::ColorSpaceValue;
pub use self::color_convert::ColorSpace;
mod support;
pub struct DecodeConfig {
pub default_colorspace: Option<ColorSpace>,
pub discard_level: u32,
}
impl Default for DecodeConfig {
fn default() -> Self {
DecodeConfig {
default_colorspace: None,
discard_level: 0,
}
}
}
#[derive(Clone, Eq, PartialEq, Debug)]
pub enum Codec {
J2K,
JP2,
JPP,
JPT,
JPX,
}
impl Codec {
fn to_i32(&self) -> i32 {
match *self {
Codec::J2K => ffi::CODEC_FORMAT_OPJ_CODEC_J2K,
Codec::JP2 => ffi::CODEC_FORMAT_OPJ_CODEC_JP2,
Codec::JPP => ffi::CODEC_FORMAT_OPJ_CODEC_JPP,
Codec::JPT => ffi::CODEC_FORMAT_OPJ_CODEC_JPT,
Codec::JPX => ffi::CODEC_FORMAT_OPJ_CODEC_JPX,
}
}
}
impl ColorSpaceValue {
fn from_i32(val: i32) -> Self {
match val {
ffi::COLOR_SPACE_OPJ_CLRSPC_CMYK => ColorSpaceValue::CMYK,
ffi::COLOR_SPACE_OPJ_CLRSPC_EYCC => ColorSpaceValue::EYCC,
ffi::COLOR_SPACE_OPJ_CLRSPC_GRAY => ColorSpaceValue::GRAY,
ffi::COLOR_SPACE_OPJ_CLRSPC_SRGB => ColorSpaceValue::SRGB,
ffi::COLOR_SPACE_OPJ_CLRSPC_SYCC => ColorSpaceValue::SYCC,
ffi::COLOR_SPACE_OPJ_CLRSPC_UNSPECIFIED => ColorSpaceValue::Unspecified,
ffi::COLOR_SPACE_OPJ_CLRSPC_UNKNOWN | _ => ColorSpaceValue::Unknown(val),
}
}
}
unsafe fn get_default_decoder_parameters() -> ffi::opj_dparameters {
let mut jp2_dparams = ffi::opj_dparameters {
cp_reduce: 0,
cp_layer: 0,
infile: [0; 4096],
outfile: [0; 4096],
decod_format: 0,
cod_format: 0,
DA_x0: 0,
DA_x1: 0,
DA_y0: 0,
DA_y1: 0,
m_verbose: 0,
tile_index: 0,
nb_tile_to_decode: 0,
jpwl_correct: 0,
jpwl_exp_comps: 0,
jpwl_max_tiles: 0,
flags: 0,
};
ffi::opj_set_default_decoder_parameters(&mut jp2_dparams);
jp2_dparams
}
#[inline]
fn ceil_div_pow2(a: u32, b: u32) -> u32 {
(a + (1 << b) - 1) >> b
}
unsafe fn load_from_stream(
jp2_stream: *mut *mut c_void,
codec: Codec,
config: DecodeConfig,
logger: Logger,
) -> Result<DynamicImage, DecodeError> {
let jp2_codec = ffi::opj_create_decompress(codec.to_i32());
if jp2_codec.is_null() {
ffi::opj_stream_destroy(jp2_stream);
return Err(DecodeError::FfiError("Codec instantiation failed."));
}
let logger = logger.new(o!("function"=>"decode jpeg2000 stream"));
let mut data = support::LogHandlerData::new(logger.clone());
let data_ptr: *mut support::LogHandlerData = &mut data;
let data_ptr = data_ptr as *mut c_void;
ffi::opj_set_info_handler(jp2_codec, Some(support::info_handler), data_ptr);
ffi::opj_set_warning_handler(jp2_codec, Some(support::warning_handler), data_ptr);
ffi::opj_set_error_handler(jp2_codec, Some(support::error_handler), data_ptr);
let mut jp2_dparams = get_default_decoder_parameters();
jp2_dparams.cp_reduce = config.discard_level;
if ffi::opj_setup_decoder(jp2_codec, &mut jp2_dparams) != 1 {
ffi::opj_stream_destroy(jp2_stream);
ffi::opj_destroy_codec(jp2_codec);
return Err(DecodeError::FfiError("Setting up the decoder failed."));
}
let mut jp2_image: *mut ffi::opj_image = null_mut();
if ffi::opj_read_header(jp2_stream, jp2_codec, &mut jp2_image) != 1 {
ffi::opj_stream_destroy(jp2_stream);
ffi::opj_destroy_codec(jp2_codec);
return Err(DecodeError::ReadHeader);
}
ffi::opj_decode(jp2_codec, jp2_stream, jp2_image);
ffi::opj_stream_destroy(jp2_stream);
let color_space_raw = ColorSpaceValue::from_i32((*jp2_image).color_space);
let color_space = color_space_raw.determined();
let color_space: ColorSpace = if color_space.is_none() {
if color_space_raw == ColorSpaceValue::Unspecified {
match config.default_colorspace {
Some(cspace) => cspace,
None => return Err(DecodeError::UnspecifiedColorSpace),
}
} else {
ffi::opj_destroy_codec(jp2_codec);
ffi::opj_image_destroy(jp2_image);
return Err(DecodeError::UnknownColorSpace);
}
} else {
color_space.unwrap()
};
info!(logger, "color space: {:?}", color_space);
info!(logger, "icc_profile_len: {}", (*jp2_image).icc_profile_len);
let width = (*jp2_image).x1 - (*jp2_image).x0;
let height = (*jp2_image).y1 - (*jp2_image).y0;
info!(logger, "width: {}, height: {}", width, height);
let mut comps: Vec<*mut ffi::opj_image_comp> = Vec::new();
let comps_len = (*jp2_image).numcomps;
for i in 0..comps_len {
comps.push((*jp2_image).comps.offset(i as isize));
}
if comps.len() > color_convert::MAX_COMPONENTS {
ffi::opj_destroy_codec(jp2_codec);
ffi::opj_image_destroy(jp2_image);
return Err(DecodeError::TooManyComponents(comps.len()));
}
let mut image = DynamicImage::new_rgba8(width, height);
info!(logger, "number of components: {}", comps.len());
let comp_width = (*comps[0]).w;
let factor = (*comps[0]).factor;
let width = ceil_div_pow2(width, factor);
let height = ceil_div_pow2(height, factor);
for y in (0..height).rev() {
for x in 0..width {
let index = (y * comp_width + x) as isize;
let mut values = [0u8, 0, 0, 255];
for i in 0..comps.len() {
let data = (*comps[i]).data;
let ivalue: u8 = *data.offset(index) as u8;
values[i] = ivalue;
}
image.unsafe_put_pixel(x, y, color_space.convert_to_rgba(values))
}
}
ffi::opj_destroy_codec(jp2_codec);
ffi::opj_image_destroy(jp2_image);
Ok(image)
}
pub fn from_memory(
buf: &[u8],
codec: Codec,
config: DecodeConfig,
logger: Option<Logger>,
) -> Result<DynamicImage, DecodeError> {
let mut userdata = support::NdUserdata::new_input(buf);
let logger = logger.unwrap_or_else(|| Logger::root(slog::Discard, o!()));
unsafe {
let stream = ffi::opj_stream_default_create(1);
ffi::opj_stream_set_read_function(stream, Some(support::nd_opj_stream_read_fn));
ffi::opj_stream_set_write_function(stream, Some(support::nd_opj_stream_write_fn));
ffi::opj_stream_set_skip_function(stream, Some(support::nd_opj_stream_skip_fn));
ffi::opj_stream_set_seek_function(stream, Some(support::nd_opj_stream_seek_fn));
let userdata_ptr: *mut support::NdUserdata = &mut userdata;
ffi::opj_stream_set_user_data_length(stream, buf.len() as u64);
ffi::opj_stream_set_user_data(stream, userdata_ptr as *mut c_void, None);
load_from_stream(stream, codec, config, logger)
}
}
pub fn from_file<S: Into<String>>(
file_name: S,
codec: Codec,
config: DecodeConfig,
logger: Option<Logger>,
) -> Result<DynamicImage, DecodeError> {
let logger = logger.unwrap_or_else(|| Logger::root(slog::Discard, o!()));
unsafe {
let f = CString::new(file_name.into())?;
let jp2_stream = ffi::opj_stream_create_default_file_stream(f.as_ptr(), 1);
load_from_stream(jp2_stream, codec, config, logger)
}
}