use super::warning_ownership::merged_warning_capacity_bytes;
use super::{
additional_decode_scratch_bytes, output_format_from_parts, scaled_dimensions,
scaled_rect_covering, DecodeOptions, DecodeOutcome, DecodedTile, Decoder, DecoderContext,
Downscale, JpegError, JpegView, PixelFormat, PreparedJpeg, PreparedJpegTileJob, Rect,
ScratchPool, TileBatchError, TileBatchOptions, TileDecodeJob, TileDecodeOutput,
TileRegionScaledDecodeJob, TileScaledDecodeJob, Vec, CPU_ROI_CHECKPOINT_CADENCE_MCUS,
DEFAULT_CONTEXT, DEFAULT_MAX_DECODE_BYTES,
};
use crate::allocation::checked_add_allocation_bytes;
use crate::internal::checkpoint::DeviceCheckpoint;
pub fn decode_tile_into(
bytes: &[u8],
pool: &mut ScratchPool,
out: &mut [u8],
stride: usize,
fmt: PixelFormat,
) -> Result<DecodeOutcome, JpegError> {
DEFAULT_CONTEXT.with(|ctx| {
decode_tile_into_in_context(bytes, &mut ctx.borrow_mut(), pool, out, stride, fmt)
})
}
#[doc(hidden)]
pub fn decode_tile_into_in_context(
bytes: &[u8],
ctx: &mut DecoderContext,
pool: &mut ScratchPool,
out: &mut [u8],
stride: usize,
fmt: PixelFormat,
) -> Result<DecodeOutcome, JpegError> {
decode_tile_into_in_context_with_options(
bytes,
ctx,
pool,
out,
stride,
fmt,
DecodeOptions::default(),
)
}
#[doc(hidden)]
pub fn decode_tile_into_in_context_with_options(
bytes: &[u8],
ctx: &mut DecoderContext,
pool: &mut ScratchPool,
out: &mut [u8],
stride: usize,
fmt: PixelFormat,
options: DecodeOptions,
) -> Result<DecodeOutcome, JpegError> {
let dec = Decoder::from_view_in_context(JpegView::parse_with_options(bytes, options)?, ctx)?;
dec.decode_into_with_scratch(pool, out, stride, fmt)
}
pub(crate) fn decode_prepared_jpeg_tile_rgb8_in_context(
input: &PreparedJpeg<'_>,
ctx: &mut DecoderContext,
pool: &mut ScratchPool,
out: &mut [u8],
stride: usize,
options: DecodeOptions,
) -> Result<DecodedTile, JpegError> {
let view = JpegView::parse_with_options(input.as_bytes(), options)?;
let dimensions = view.info().dimensions;
let dec = Decoder::from_view_in_context(view, ctx)?;
let outcome = dec.decode_into_with_scratch(pool, out, stride, PixelFormat::Rgb8)?;
Ok(DecodedTile {
dimensions,
decoded: outcome.decoded,
warnings: outcome.warnings,
})
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) struct PlannedJpegTileDecode {
pub(crate) worker_live_bytes: usize,
pub(crate) retained_result_bytes: usize,
}
pub(crate) fn planned_jpeg_tile_decode_live_bytes(
input: &[u8],
ctx: &mut DecoderContext,
fmt: PixelFormat,
roi: Option<Rect>,
scale: Downscale,
options: DecodeOptions,
) -> Result<PlannedJpegTileDecode, JpegError> {
let decoder =
Decoder::from_view_in_context(JpegView::parse_with_options(input, options)?, ctx)?;
let source_rect = roi.unwrap_or_else(|| Rect::full(decoder.info.dimensions));
if !source_rect.is_within(decoder.info.dimensions) {
return Err(JpegError::RectOutOfBounds {
rect: source_rect,
width: decoder.info.dimensions.0,
height: decoder.info.dimensions.1,
});
}
let output_format = output_format_from_parts(decoder.info.sof_kind, fmt, scale)?;
let downscale = output_format.downscale();
let output_rect = if source_rect == Rect::full(decoder.info.dimensions) {
Rect::full(scaled_dimensions(decoder.info.dimensions, downscale))
} else {
scaled_rect_covering(source_rect, downscale)?
};
let additional_scratch = additional_decode_scratch_bytes(
decoder.info.sof_kind,
decoder.info.dimensions,
output_format,
source_rect,
output_rect,
downscale,
)?;
let workspace_cap = decoder.decode_workspace_cap()?;
let base_scratch = decoder.decode_scratch_bytes(workspace_cap)?;
let scratch = checked_add_allocation_bytes(base_scratch, additional_scratch)?;
let checkpoints = planned_roi_checkpoint_bytes(&decoder, source_rect)?;
let transient = checked_add_allocation_bytes(scratch, checkpoints)?;
let retained = DEFAULT_MAX_DECODE_BYTES.checked_sub(workspace_cap).ok_or(
JpegError::MemoryCapExceeded {
requested: usize::MAX,
cap: DEFAULT_MAX_DECODE_BYTES,
},
)?;
let worker_live_bytes = checked_add_allocation_bytes(retained, transient)?;
let retained_result_bytes = merged_warning_capacity_bytes(decoder.warnings.capacity())?;
Ok(PlannedJpegTileDecode {
worker_live_bytes,
retained_result_bytes,
})
}
fn planned_roi_checkpoint_bytes(decoder: &Decoder<'_>, roi: Rect) -> Result<usize, JpegError> {
if roi == Rect::full(decoder.info.dimensions) || decoder.plan.restart_interval.is_some() {
return Ok(0);
}
let mcu_width = u32::from(decoder.plan.sampling.max_h) * 8;
let mcu_height = u32::from(decoder.plan.sampling.max_v) * 8;
let total_mcus = decoder
.info
.dimensions
.0
.div_ceil(mcu_width)
.checked_mul(decoder.info.dimensions.1.div_ceil(mcu_height))
.ok_or(JpegError::MemoryCapExceeded {
requested: usize::MAX,
cap: DEFAULT_MAX_DECODE_BYTES,
})?;
let checkpoint_count = usize::try_from(total_mcus.div_ceil(CPU_ROI_CHECKPOINT_CADENCE_MCUS))
.ok()
.and_then(|count| count.checked_add(1))
.ok_or(JpegError::MemoryCapExceeded {
requested: usize::MAX,
cap: DEFAULT_MAX_DECODE_BYTES,
})?;
let checkpoint_bytes = checkpoint_count
.checked_mul(core::mem::size_of::<DeviceCheckpoint>())
.ok_or(JpegError::MemoryCapExceeded {
requested: usize::MAX,
cap: DEFAULT_MAX_DECODE_BYTES,
})?;
checked_add_allocation_bytes(0, checkpoint_bytes)
}
pub fn decode_prepared_jpeg_tiles_rgb8(
jobs: &mut [PreparedJpegTileJob<'_, '_>],
) -> Result<Vec<Result<DecodedTile, JpegError>>, crate::PreparedTileBatchError> {
crate::JpegBatchSession::new_one_shot(TileBatchOptions::default())
.decode_prepared_jpeg_tiles_rgb8(jobs)
}
pub fn decode_tiles_into(
jobs: &mut [TileDecodeJob<'_, '_>],
fmt: PixelFormat,
options: TileBatchOptions,
) -> Result<Vec<DecodeOutcome>, TileBatchError> {
decode_tiles_into_with_options(jobs, fmt, DecodeOptions::default(), options)
}
#[doc(hidden)]
pub fn decode_tiles_into_with_options(
jobs: &mut [TileDecodeJob<'_, '_>],
fmt: PixelFormat,
decode_options: DecodeOptions,
options: TileBatchOptions,
) -> Result<Vec<DecodeOutcome>, TileBatchError> {
crate::JpegBatchSession::new_one_shot(options).decode_tiles_into_with_options(
jobs,
fmt,
decode_options,
)
}
pub fn decode_tiles_scaled_into(
jobs: &mut [TileScaledDecodeJob<'_, '_>],
fmt: PixelFormat,
options: TileBatchOptions,
) -> Result<Vec<DecodeOutcome>, TileBatchError> {
decode_tiles_scaled_into_with_options(jobs, fmt, DecodeOptions::default(), options)
}
#[doc(hidden)]
pub fn decode_tiles_scaled_into_with_options(
jobs: &mut [TileScaledDecodeJob<'_, '_>],
fmt: PixelFormat,
decode_options: DecodeOptions,
options: TileBatchOptions,
) -> Result<Vec<DecodeOutcome>, TileBatchError> {
crate::JpegBatchSession::new_one_shot(options).decode_tiles_scaled_into_with_options(
jobs,
fmt,
decode_options,
)
}
pub fn decode_tiles_region_scaled_into(
jobs: &mut [TileRegionScaledDecodeJob<'_, '_>],
fmt: PixelFormat,
options: TileBatchOptions,
) -> Result<Vec<DecodeOutcome>, TileBatchError> {
decode_tiles_region_scaled_into_with_options(jobs, fmt, DecodeOptions::default(), options)
}
#[doc(hidden)]
pub fn decode_tiles_region_scaled_into_with_options(
jobs: &mut [TileRegionScaledDecodeJob<'_, '_>],
fmt: PixelFormat,
decode_options: DecodeOptions,
options: TileBatchOptions,
) -> Result<Vec<DecodeOutcome>, TileBatchError> {
crate::JpegBatchSession::new_one_shot(options).decode_tiles_region_scaled_into_with_options(
jobs,
fmt,
decode_options,
)
}
#[doc(hidden)]
pub fn decode_tile_region_into_in_context(
bytes: &[u8],
ctx: &mut DecoderContext,
pool: &mut ScratchPool,
output: TileDecodeOutput<'_>,
roi: Rect,
) -> Result<DecodeOutcome, JpegError> {
decode_tile_region_into_in_context_with_options(
bytes,
ctx,
pool,
output,
roi,
DecodeOptions::default(),
)
}
#[doc(hidden)]
pub fn decode_tile_region_into_in_context_with_options(
bytes: &[u8],
ctx: &mut DecoderContext,
pool: &mut ScratchPool,
output: TileDecodeOutput<'_>,
roi: Rect,
options: DecodeOptions,
) -> Result<DecodeOutcome, JpegError> {
let TileDecodeOutput { out, stride, fmt } = output;
let dec = Decoder::from_view_in_context(JpegView::parse_with_options(bytes, options)?, ctx)?;
dec.decode_region_into_with_scratch(pool, out, stride, fmt, roi)
}
#[doc(hidden)]
pub fn decode_tile_scaled_into_in_context(
bytes: &[u8],
ctx: &mut DecoderContext,
pool: &mut ScratchPool,
output: TileDecodeOutput<'_>,
scale: Downscale,
) -> Result<DecodeOutcome, JpegError> {
decode_tile_scaled_into_in_context_with_options(
bytes,
ctx,
pool,
output,
scale,
DecodeOptions::default(),
)
}
#[doc(hidden)]
pub fn decode_tile_scaled_into_in_context_with_options(
bytes: &[u8],
ctx: &mut DecoderContext,
pool: &mut ScratchPool,
output: TileDecodeOutput<'_>,
scale: Downscale,
options: DecodeOptions,
) -> Result<DecodeOutcome, JpegError> {
let TileDecodeOutput { out, stride, fmt } = output;
let dec = Decoder::from_view_in_context(JpegView::parse_with_options(bytes, options)?, ctx)?;
dec.decode_scaled_into_with_scratch(pool, out, stride, fmt, scale)
}
#[doc(hidden)]
pub fn decode_tile_region_scaled_into_in_context(
bytes: &[u8],
ctx: &mut DecoderContext,
pool: &mut ScratchPool,
output: TileDecodeOutput<'_>,
roi: Rect,
scale: Downscale,
) -> Result<DecodeOutcome, JpegError> {
decode_tile_region_scaled_into_in_context_with_options(
bytes,
ctx,
pool,
output,
roi,
scale,
DecodeOptions::default(),
)
}
#[doc(hidden)]
pub fn decode_tile_region_scaled_into_in_context_with_options(
bytes: &[u8],
ctx: &mut DecoderContext,
pool: &mut ScratchPool,
output: TileDecodeOutput<'_>,
roi: Rect,
scale: Downscale,
options: DecodeOptions,
) -> Result<DecodeOutcome, JpegError> {
let TileDecodeOutput { out, stride, fmt } = output;
let dec = Decoder::from_view_in_context(JpegView::parse_with_options(bytes, options)?, ctx)?;
dec.decode_region_scaled_into_with_scratch(pool, out, stride, fmt, roi, scale)
}
#[cfg(test)]
mod tests;