use crate::backend::Backend;
use crate::context::DecoderContext;
use crate::entropy::block::{decode_block_with_activity, BlockActivity, CoefficientBlock};
use crate::entropy::huffman::{HuffmanTable, PreparedHuffmanTableId, PreparedHuffmanTables};
use crate::entropy::progressive::{
decode_progressive, decode_progressive_dct_blocks, PreparedProgressiveComponentPlan,
PreparedProgressivePlan, PreparedProgressiveScan, PreparedProgressiveScanComponent,
COMPONENT_IMAGE_METADATA_BYTES,
};
use crate::entropy::sequential::{
decode_scan_baseline, decode_scan_baseline_rgb, decode_scan_fast_rgb_444,
decode_scan_fast_tile_rgb, decode_scan_fast_tile_rgb_region,
decode_scan_fast_tile_rgb_region_scaled, fast_tile_region_first_decode_mcu, finish_scan,
stripe_region_layout, FastTileRegionScaledRequest, PreparedComponentPlan, PreparedDecodePlan,
ResolvedPreparedComponentPlan,
};
use crate::entropy::ZIGZAG;
use crate::error::{JpegError, MarkerKind, Warning};
use crate::info::{
ColorSpace, DecodeOptions, DownscaleFactor, Info, OutputFormat, Rect, RestartIndex,
RestartSegment, SofKind,
};
use crate::internal::bit_reader::BitReader;
use crate::internal::checkpoint::{checkpoint_before_mcu, CpuCheckpointCache, DeviceCheckpoint};
use crate::internal::scratch::ScratchPool;
use crate::lossless::{lossless_predict, LosslessSample};
use crate::output::{
validate_buffer, Gray8Writer, InterleavedRgbWriter, OutputWriter, Rgb8Writer, Rgba8Writer,
};
use crate::parse::header::{parse_info, ParsedHeader};
use crate::profile::{emit_jpeg_profile_fields, jpeg_profile_stages_enabled, ProfileField};
use crate::segment::PreparedJpeg;
use crate::JpegCodec;
use alloc::vec::Vec;
use core::cell::RefCell;
pub use j2k_core::TileBatchOptions;
use j2k_core::{
CompressedTransferSyntax, DecodeOutcome as CoreDecodeOutcome, DecodeRowsError,
DecoderContext as CoreDecoderContext, Downscale, ImageCodec, ImageDecode, ImageDecodeRows,
PixelFormat, RowSink, TileBatchDecode,
};
use std::sync::Mutex;
use std::time::{Duration, Instant};
pub(crate) const DEFAULT_MAX_DECODE_BYTES: usize = 512 * 1024 * 1024;
pub(super) const MAX_DECODE_SCAN_WARNINGS: usize = 1;
const CPU_ROI_CHECKPOINT_CADENCE_MCUS: u32 = 1024;
const CPU_ROI_CHECKPOINT_MIN_TARGET_MCUS: u32 = 4096;
std::thread_local! {
static DEFAULT_SCRATCH: RefCell<ScratchPool> = RefCell::new(ScratchPool::new());
static DEFAULT_CONTEXT: RefCell<DecoderContext> = RefCell::new(DecoderContext::new());
}
mod view;
pub use self::view::JpegView;
mod allocation;
mod output_format;
use self::output_format::{
allocate_output_buffer_with_live_budget, checked_output_geometry, downscale_profile_name,
jpeg_downscale, output_format_from_parts, output_format_profile_name, scaled_dimensions,
scaled_rect_covering,
};
mod extended12;
use self::extended12::{lossless_color_sampling, upsample_h2v1_sample_at, upsample_h2v2_rows_at};
mod lossless_helpers;
pub(crate) use self::lossless_helpers::restart_index_allocation_bytes;
use self::lossless_helpers::{
decode_lossless_color_sample, decode_lossless_sampled_color_mcu, emit_decode_scan_profile,
lossless_predictor_gray_rows, lossless_predictor_value, lossless_predictor_value_u16,
restart_index_for_stream, validate_lossless_color_plan, write_lossless_color16_sampled_output,
write_lossless_color8_sampled_output, LosslessColorIntoSample, LosslessColorPlanes,
LosslessColorRowSample, LosslessRestartTracker, LosslessSampledColorPlanesMut,
LosslessSampledMcu,
};
mod color_convert;
use self::color_convert::{
convert_ycbcr16_to_rgb16_in_place, convert_ycbcr8_to_rgb8_in_place, copy_gray16_scaled_rect,
copy_gray8_scaled_rect, copy_rgb16_to_rgba16, copy_ycbcr16_row_to_rgb16,
copy_ycbcr8_row_to_rgb8,
};
mod warning_ownership;
use self::warning_ownership::{merged_warnings, try_clone_warnings};
mod core_traits;
use self::core_traits::{CroppedWriter, ProgressiveDownscaleWriter};
mod lossless_region;
use self::lossless_region::{LosslessRegionRequest, LosslessRgbRegionFallback, LosslessRgbaAlpha};
mod scratch;
use self::scratch::{
additional_decode_scratch_bytes, checked_scratch_len, checked_usize_product,
compute_decode_scratch_bytes, compute_extended12_planes_scratch_bytes,
compute_lossless_scratch_bytes, lossless_sampled_plane_layout, LosslessSampledPlaneLayout,
};
mod sink_writer;
pub(crate) use self::sink_writer::SinkWriter;
mod plan;
use self::plan::find_component_index;
mod routing;
mod rows;
mod sequential;
mod tile;
pub(crate) use self::tile::{
decode_prepared_jpeg_tile_rgb8_in_context, planned_jpeg_tile_decode_live_bytes,
PlannedJpegTileDecode,
};
pub use self::tile::{
decode_prepared_jpeg_tiles_rgb8, decode_tile_into, decode_tile_into_in_context,
decode_tile_into_in_context_with_options, decode_tile_region_into_in_context,
decode_tile_region_into_in_context_with_options, decode_tile_region_scaled_into_in_context,
decode_tile_region_scaled_into_in_context_with_options, decode_tile_scaled_into_in_context,
decode_tile_scaled_into_in_context_with_options, decode_tiles_into,
decode_tiles_into_with_options, decode_tiles_region_scaled_into,
decode_tiles_region_scaled_into_with_options, decode_tiles_scaled_into,
decode_tiles_scaled_into_with_options,
};
mod lossless_render;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DecodeOutcome {
pub decoded: Rect,
pub warnings: Vec<Warning>,
}
impl From<DecodeOutcome> for CoreDecodeOutcome<Warning> {
fn from(outcome: DecodeOutcome) -> Self {
Self {
decoded: outcome.decoded.into(),
warnings: outcome.warnings,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct DecodeRequest {
pub fmt: PixelFormat,
pub region: Option<Rect>,
pub scale: Downscale,
}
impl DecodeRequest {
#[must_use]
pub const fn full(fmt: PixelFormat) -> Self {
Self {
fmt,
region: None,
scale: Downscale::None,
}
}
#[must_use]
pub const fn scaled(fmt: PixelFormat, scale: Downscale) -> Self {
Self {
fmt,
region: None,
scale,
}
}
#[must_use]
pub const fn region(fmt: PixelFormat, region: Rect) -> Self {
Self {
fmt,
region: Some(region),
scale: Downscale::None,
}
}
#[must_use]
pub const fn region_scaled(fmt: PixelFormat, region: Rect, scale: Downscale) -> Self {
Self {
fmt,
region: Some(region),
scale,
}
}
}
pub type TileDecodeJob<'i, 'o> = j2k_core::TileDecodeJob<'i, 'o>;
pub struct TileDecodeOutput<'o> {
pub out: &'o mut [u8],
pub stride: usize,
pub fmt: PixelFormat,
}
pub struct PreparedJpegTileJob<'i, 'o> {
pub input: PreparedJpeg<'i>,
pub out: &'o mut [u8],
pub stride: usize,
pub options: DecodeOptions,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DecodedTile {
pub dimensions: (u32, u32),
pub decoded: Rect,
pub warnings: Vec<Warning>,
}
pub type TileScaledDecodeJob<'i, 'o> = j2k_core::TileScaledDecodeJob<'i, 'o>;
pub type TileRegionScaledDecodeJob<'i, 'o> = j2k_core::TileRegionScaledDecodeJob<'i, 'o>;
pub type TileBatchError = j2k_core::BatchDecodeError<JpegError>;
pub type PreparedTileBatchError = j2k_core::BatchInfrastructureError;
pub trait ComponentRowWriter {
fn write_gray_row(&mut self, y: u32, gray_row: &[u8]) -> Result<(), JpegError>;
fn write_ycbcr_row(
&mut self,
y: u32,
y_row: &[u8],
cb_row: &[u8],
cr_row: &[u8],
) -> Result<(), JpegError>;
fn write_rgb_row(
&mut self,
y: u32,
r_row: &[u8],
g_row: &[u8],
b_row: &[u8],
) -> Result<(), JpegError>;
}
#[derive(Debug)]
pub struct Decoder<'a> {
pub(crate) bytes: &'a [u8],
pub(crate) info: Info,
pub(crate) warnings: Vec<Warning>,
pub(crate) backend: Backend,
pub(crate) plan: PreparedDecodePlan,
pub(crate) progressive_plan: Option<PreparedProgressivePlan>,
lossless_plan: Option<PreparedLosslessPlan>,
pub(crate) cpu_entropy_checkpoints: Mutex<CpuCheckpointCache>,
}
struct PreparedDecoderMetadata {
info: Info,
warnings: Vec<Warning>,
plan: PreparedDecodePlan,
progressive_plan: Option<PreparedProgressivePlan>,
lossless_plan: Option<PreparedLosslessPlan>,
}
#[derive(Debug, Clone)]
struct PreparedLosslessPlan {
predictor: u8,
bit_depth: u8,
dc_table: PreparedHuffmanTableId,
dimensions: (u32, u32),
scan_offset: usize,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum LosslessColorSampling {
S444,
S422,
S420,
}
impl<'a> Decoder<'a> {
pub fn inspect(input: &'a [u8]) -> Result<Info, JpegError> {
let info = parse_info(input)?;
Ok(info)
}
fn from_bytes_with_options(input: &'a [u8], options: DecodeOptions) -> Result<Self, JpegError> {
let view = JpegView::parse_with_options(input, options)?;
DEFAULT_CONTEXT.with(|ctx| Self::from_view_in_context(view, &mut ctx.borrow_mut()))
}
pub fn new(input: &'a [u8]) -> Result<Self, JpegError> {
Self::from_bytes_with_options(input, DecodeOptions::default())
}
pub fn from_view(view: JpegView<'a>) -> Result<Self, JpegError> {
DEFAULT_CONTEXT.with(|ctx| Self::from_view_in_context(view, &mut ctx.borrow_mut()))
}
pub(crate) fn from_view_with_external_live(
view: JpegView<'a>,
external_live_bytes: usize,
) -> Result<Self, JpegError> {
DEFAULT_CONTEXT.with(|ctx| {
Self::from_view_in_context_with_external_live(
view,
&mut ctx.borrow_mut(),
external_live_bytes,
)
})
}
pub fn from_view_in_context(
view: JpegView<'a>,
ctx: &mut DecoderContext,
) -> Result<Self, JpegError> {
Self::from_view_in_context_with_external_live(view, ctx, 0)
}
fn from_view_in_context_with_external_live(
view: JpegView<'a>,
ctx: &mut DecoderContext,
external_live_bytes: usize,
) -> Result<Self, JpegError> {
let JpegView {
bytes,
header,
info,
options,
} = view;
let backend = Backend::detect();
let PreparedDecoderMetadata {
info,
warnings,
plan,
progressive_plan,
lossless_plan,
} = Self::prepare_header_with_external_live(
header,
info,
options,
bytes,
ctx,
external_live_bytes,
)?;
Ok(Self {
bytes,
info,
warnings,
backend,
plan,
progressive_plan,
lossless_plan,
cpu_entropy_checkpoints: Mutex::new(CpuCheckpointCache::default()),
})
}
pub fn info(&self) -> &Info {
&self.info
}
pub(crate) fn restart_index(&self) -> Result<Option<RestartIndex>, JpegError> {
restart_index_for_stream(
self.bytes,
Some(self.plan.scan_offset),
&self.info,
self.plan.restart_interval,
)
}
}
#[cfg(test)]
mod tests;