mirror of
https://github.com/storytold/craft-libs.git
synced 2026-11-14 22:42:46 +00:00
Preserve shared format boundary behavior
This commit is contained in:
6 files changed
+83
-12
No files matched your search
@@ -679,7 +679,11 @@ impl<'s, 'a, 'b> Reader<'s, 'a, 'b> {
|
||||
}
|
||||
let class = s.class(id).unwrap_or(Tag::of(b"ShpN"));
|
||||
let (path, even_odd) = crate::geometry::outline(self, id, class, world).unwrap_or_else(|| {
|
||||
self.warn("an artboard whose outline could not be read (clipped to its rectangle)");
|
||||
if class == Tag::of(b"PCrv") {
|
||||
self.warn("a converted artboard outline could not be read (clipped to its rectangle)");
|
||||
} else {
|
||||
self.warn("an artboard whose outline could not be read (clipped to its rectangle)");
|
||||
}
|
||||
(Path { subpaths: vec![crate::shapes::rectangle(b.x0, b.y0, b.x1, b.y1)] }, false)
|
||||
});
|
||||
let (background, strokes) = paint::node_paint(self, id, world);
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
//! Predecode bounds for raster containers whose frames may exceed their declared canvas.
|
||||
|
||||
use crate::codecs::webp;
|
||||
use crate::error::CodecError;
|
||||
use crate::format::Format;
|
||||
use crate::image::{ChannelLayout, SampleType};
|
||||
use crate::options::Limits;
|
||||
|
||||
/// The largest width and height any decoded frame can declare, without allocating pixel data.
|
||||
/// Animated GIF and WebP frames are inspected individually, including frames larger than the
|
||||
@@ -12,12 +9,10 @@ use crate::options::Limits;
|
||||
pub fn probe_allocation_size(bytes: &[u8]) -> Result<(u32, u32), CodecError> {
|
||||
let format = crate::detect(bytes).ok_or(CodecError::UnknownFormat)?;
|
||||
let (width, height) = match format {
|
||||
Format::Png => png_bounds(bytes)?,
|
||||
Format::Jpeg => jpeg_bounds(bytes)?,
|
||||
Format::Gif => gif_bounds(bytes)?,
|
||||
Format::WebP => {
|
||||
let (width, height, _) = webp::probe(bytes, &Limits::default())?;
|
||||
let (frame_width, frame_height) = webp_bounds(bytes)?;
|
||||
(width.max(frame_width), height.max(frame_height))
|
||||
}
|
||||
Format::WebP => webp_bounds(bytes)?,
|
||||
_ => {
|
||||
if let Some(info) = crate::inspect_raster(bytes)? {
|
||||
(info.width, info.height)
|
||||
@@ -26,7 +21,9 @@ pub fn probe_allocation_size(bytes: &[u8]) -> Result<(u32, u32), CodecError> {
|
||||
}
|
||||
}
|
||||
};
|
||||
Limits::default().check(width, height, ChannelLayout::Rgba, SampleType::U8)?;
|
||||
// This is an allocation-size probe, not a decode policy. Callers need the coded extents even
|
||||
// when they exceed this crate's default decode limits so they can report/refuse against their
|
||||
// own budgets before passing the bytes to a decoder.
|
||||
Ok((width, height))
|
||||
}
|
||||
|
||||
@@ -34,6 +31,57 @@ fn malformed(format: Format, message: &'static str) -> CodecError {
|
||||
CodecError::malformed(format, message)
|
||||
}
|
||||
|
||||
fn png_bounds(bytes: &[u8]) -> Result<(u32, u32), CodecError> {
|
||||
let bad = || malformed(Format::Png, "truncated or malformed PNG header");
|
||||
if bytes.get(..8) != Some(b"\x89PNG\r\n\x1a\n") || bytes.get(8..16) != Some(b"\0\0\0\rIHDR") {
|
||||
return Err(bad());
|
||||
}
|
||||
let width = u32::from_be_bytes(bytes.get(16..20).ok_or_else(bad)?.try_into().map_err(|_| bad())?);
|
||||
let height = u32::from_be_bytes(bytes.get(20..24).ok_or_else(bad)?.try_into().map_err(|_| bad())?);
|
||||
if width == 0 || height == 0 {
|
||||
return Err(bad());
|
||||
}
|
||||
Ok((width, height))
|
||||
}
|
||||
|
||||
fn jpeg_bounds(bytes: &[u8]) -> Result<(u32, u32), CodecError> {
|
||||
let bad = || malformed(Format::Jpeg, "truncated or malformed JPEG frame header");
|
||||
if bytes.get(..2) != Some(&[0xff, 0xd8]) {
|
||||
return Err(bad());
|
||||
}
|
||||
let mut at = 2usize;
|
||||
while at < bytes.len() {
|
||||
while bytes.get(at) == Some(&0xff) {
|
||||
at += 1;
|
||||
}
|
||||
let marker = *bytes.get(at).ok_or_else(bad)?;
|
||||
at += 1;
|
||||
if matches!(marker, 0xd8 | 0xd9 | 0x01 | 0xd0..=0xd7) {
|
||||
continue;
|
||||
}
|
||||
let length =
|
||||
usize::from(u16::from_be_bytes(bytes.get(at..at.checked_add(2).ok_or_else(bad)?).ok_or_else(bad)?.try_into().map_err(|_| bad())?));
|
||||
if length < 2 {
|
||||
return Err(bad());
|
||||
}
|
||||
let end = at.checked_add(length).ok_or_else(bad)?;
|
||||
let segment = bytes.get(at..end).ok_or_else(bad)?;
|
||||
if matches!(marker, 0xc0..=0xc3 | 0xc5..=0xc7 | 0xc9..=0xcb | 0xcd..=0xcf) {
|
||||
if segment.len() < 7 {
|
||||
return Err(bad());
|
||||
}
|
||||
let height = u32::from(u16::from_be_bytes([segment[3], segment[4]]));
|
||||
let width = u32::from(u16::from_be_bytes([segment[5], segment[6]]));
|
||||
if width == 0 || height == 0 {
|
||||
return Err(bad());
|
||||
}
|
||||
return Ok((width, height));
|
||||
}
|
||||
at = end;
|
||||
}
|
||||
Err(bad())
|
||||
}
|
||||
|
||||
fn u16_le(bytes: &[u8], at: usize) -> Option<u16> {
|
||||
Some(u16::from_le_bytes(bytes.get(at..at.checked_add(2)?)?.try_into().ok()?))
|
||||
}
|
||||
|
||||
@@ -184,6 +184,13 @@ fn black_at_active(levels: [f32; 4], pattern: u8, active: Rect) -> BlackLevel {
|
||||
BlackLevel { repeat_rows: 2, repeat_cols: 2, values, delta_h: vec![], delta_v: vec![] }
|
||||
}
|
||||
|
||||
/// Orientation metadata usable for embedded-preview fallback even when the raw track is unsupported.
|
||||
pub(crate) fn orientation(bytes: &[u8]) -> Option<u16> {
|
||||
let container = parse_cr3(bytes)?;
|
||||
let cmt1 = container.cmt.first().and_then(|b| *b).and_then(|b| Tiff::parse(b).ok())?;
|
||||
Some(cmt1.ifds.first()?.u16(craft_tiff::tags::ORIENTATION).unwrap_or(1))
|
||||
}
|
||||
|
||||
pub(crate) fn decode(bytes: &[u8], mode: Mode, limits: &Limits) -> Result<RawImage> {
|
||||
let container = parse_cr3(bytes).ok_or(RawError::NotRaw)?;
|
||||
let track = main_track(&container.tracks).ok_or_else(|| RawError::Unsupported("CR3 without a Bayer raw track".into()))?;
|
||||
|
||||
@@ -238,6 +238,10 @@ fn open(src: &[u8], cw: usize, ch: usize, vw: usize, vh: usize, cpp: usize, bits
|
||||
}
|
||||
let max = match header.metadata.bit_depth {
|
||||
BitDepth::IntegerSample { bits_per_sample } if !float && (1..=16).contains(&bits_per_sample) => ((1u32 << bits_per_sample) - 1) as f32,
|
||||
// Some DNG writers label LinearRaw samples as floating point while storing a lossless
|
||||
// integer JPEG XL codestream. JXL exposes decoded pixels as normalized f32 in either
|
||||
// case, so retain them as floats instead of rejecting the otherwise valid IFD/tile pair.
|
||||
BitDepth::IntegerSample { bits_per_sample: 1..=16 } if float => 0.0,
|
||||
BitDepth::FloatSample { .. } if float => 0.0,
|
||||
d => return Err(RawError::Corrupt(format!("JPEG XL tile sample type {d:?} does not match the IFD"))),
|
||||
};
|
||||
|
||||
@@ -638,7 +638,15 @@ pub fn probe_info(bytes: &[u8]) -> Result<RawInfo> {
|
||||
|
||||
/// Orientation of a supported raw, read through the same format parser as [`probe_info`].
|
||||
pub fn orientation(bytes: &[u8]) -> Option<u16> {
|
||||
probe_info(bytes).ok().map(|info| info.orientation.to_exif())
|
||||
if let Ok(info) = probe_info(bytes) {
|
||||
return Some(info.orientation.to_exif());
|
||||
}
|
||||
// A damaged/unsupported pixel IFD must not hide usable orientation metadata from preview
|
||||
// fallback. CR3 keeps IFD0 in its CMT1 box; TIFF-based raws keep it at the file start.
|
||||
if probe(bytes) == Some(RawFormat::Cr3) {
|
||||
return cr3::orientation(bytes);
|
||||
}
|
||||
craft_tiff::Tiff::parse(bytes).ok()?.ifds.first()?.u16(craft_tiff::tags::ORIENTATION).or(Some(1))
|
||||
}
|
||||
|
||||
/// [`probe_info`] within `limits`: a file that declares more than they allow fails as [`decode_with`] would.
|
||||
|
||||
@@ -44,7 +44,7 @@ pub enum Error {
|
||||
#[error("the XML is larger than {0} bytes")]
|
||||
TooLarge(usize),
|
||||
/// Elements nest deeper than [`Limits::max_depth`].
|
||||
#[error("XML elements nest deeper than {0} levels")]
|
||||
#[error("XML elements are nested deeper than {0} levels")]
|
||||
TooDeep(usize),
|
||||
/// The tree would hold more than [`Limits::max_nodes`] nodes.
|
||||
#[error("the XML holds more than {0} nodes")]
|
||||
|
||||
Reference in new issue
Block a user