706 lines
28 KiB
Rust
706 lines
28 KiB
Rust
//! Data the program reads: from itself (`currentfile`) through decoding filters, and images
|
|
//! sampled from it, from strings or from procedures.
|
|
|
|
use std::cell::RefCell;
|
|
use std::collections::HashMap;
|
|
use std::rc::Rc;
|
|
|
|
use vectorcraft_color::{Color, Paint};
|
|
use vectorcraft_doc::{ImageBlob, ImageObject, Node, NodeId, NodeKind};
|
|
use vectorcraft_geom::Affine;
|
|
|
|
use super::graphics::{Space, process};
|
|
use super::interp::{Interp, matrix_of};
|
|
use super::lex::{find, hex_decode};
|
|
use super::obj::{DictRef, Key, Obj, Op, PsError, Res, Shared, ps_err};
|
|
use crate::ps;
|
|
|
|
/// Most bytes one filter or image reads.
|
|
const MAX_DATA: usize = 1 << 28;
|
|
/// Most pixels an image may have.
|
|
const MAX_PIXELS: usize = 1 << 26;
|
|
/// Most distinct colours an image converts through a tint transform.
|
|
const MAX_TINTS: usize = 1 << 16;
|
|
|
|
/// A file object: the program itself, or data a filter decoded.
|
|
#[derive(Debug)]
|
|
pub(crate) enum Stream {
|
|
Current,
|
|
/// Filters over the program's own data, decoded when first read.
|
|
Pending(Vec<Filter>),
|
|
Buf {
|
|
data: Vec<u8>,
|
|
pos: usize,
|
|
/// The samples are 8-bit RGB (or grey) whatever the image's colour space: a decoded JPEG.
|
|
rgb: Option<u8>,
|
|
},
|
|
}
|
|
|
|
fn io<T>(at: &str) -> Res<T> {
|
|
ps_err("ioerror", at)
|
|
}
|
|
|
|
/// The bytes `RunLengthDecode` makes of `raw`, and how many it read.
|
|
fn run_length(raw: &[u8]) -> Res<(Vec<u8>, usize)> {
|
|
let mut out = vec![];
|
|
let mut i = 0;
|
|
while let Some(&n) = raw.get(i) {
|
|
i += 1;
|
|
match n {
|
|
128 => break,
|
|
0..=127 => {
|
|
let len = usize::from(n) + 1;
|
|
out.extend_from_slice(raw.get(i..i + len).ok_or(PsError::Ps("ioerror", "RunLengthDecode".into()))?);
|
|
i += len;
|
|
}
|
|
_ => {
|
|
let b = *raw.get(i).ok_or(PsError::Ps("ioerror", "RunLengthDecode".into()))?;
|
|
i += 1;
|
|
out.extend(std::iter::repeat_n(b, 257 - usize::from(n)));
|
|
}
|
|
}
|
|
if out.len() > MAX_DATA {
|
|
return Err(PsError::Limit("decoded data is too large"));
|
|
}
|
|
}
|
|
Ok((out, i))
|
|
}
|
|
|
|
/// zlib data inflated, and how many bytes of `raw` it took.
|
|
fn flate(raw: &[u8]) -> Res<(Vec<u8>, usize)> {
|
|
let mut z = flate2::Decompress::new(true);
|
|
let mut out = Vec::with_capacity(raw.len().saturating_mul(3).min(MAX_DATA));
|
|
loop {
|
|
let before = (z.total_in(), z.total_out());
|
|
if out.capacity() - out.len() < 64 * 1024 {
|
|
out.reserve(256 * 1024);
|
|
}
|
|
let input = raw.get(z.total_in() as usize..).unwrap_or_default();
|
|
let status = z.decompress_vec(input, &mut out, flate2::FlushDecompress::None).map_err(|_| PsError::Ps("ioerror", "FlateDecode".into()))?;
|
|
if out.len() > MAX_DATA {
|
|
return Err(PsError::Limit("decoded data is too large"));
|
|
}
|
|
match status {
|
|
flate2::Status::StreamEnd => break,
|
|
_ if (z.total_in(), z.total_out()) == before => break,
|
|
_ => {}
|
|
}
|
|
}
|
|
Ok((out, z.total_in() as usize))
|
|
}
|
|
|
|
/// LZW data (`EarlyChange` 1) decoded, and how many bytes of `raw` it took.
|
|
fn lzw(raw: &[u8]) -> Res<(Vec<u8>, usize)> {
|
|
let mut table: Vec<Vec<u8>> = (0..=255u8).map(|b| vec![b]).collect();
|
|
table.push(vec![]);
|
|
table.push(vec![]);
|
|
let (mut out, mut prev): (Vec<u8>, Option<Vec<u8>>) = (vec![], None);
|
|
let (mut acc, mut bits, mut width, mut i) = (0u32, 0u32, 9u32, 0usize);
|
|
loop {
|
|
while bits < width {
|
|
let Some(&b) = raw.get(i) else { return Ok((out, i)) };
|
|
i += 1;
|
|
acc = (acc << 8) | u32::from(b);
|
|
bits += 8;
|
|
}
|
|
let code = ((acc >> (bits - width)) & ((1 << width) - 1)) as usize;
|
|
bits -= width;
|
|
match code {
|
|
256 => {
|
|
table.truncate(258);
|
|
width = 9;
|
|
prev = None;
|
|
}
|
|
257 => return Ok((out, i)),
|
|
_ => {
|
|
let entry = match (table.get(code), &prev) {
|
|
(Some(e), _) if !e.is_empty() => e.clone(),
|
|
(_, Some(p)) if code == table.len() => {
|
|
let mut e = p.clone();
|
|
e.extend(p.first());
|
|
e
|
|
}
|
|
_ => return io("LZWDecode"),
|
|
};
|
|
out.extend_from_slice(&entry);
|
|
if let Some(mut p) = prev.take()
|
|
&& table.len() < 4096
|
|
{
|
|
p.extend(entry.first());
|
|
table.push(p);
|
|
}
|
|
width = match table.len() + 1 {
|
|
n if n >= 2048 => 12,
|
|
n if n >= 1024 => 11,
|
|
n if n >= 512 => 10,
|
|
_ => 9,
|
|
};
|
|
prev = Some(entry);
|
|
if out.len() > MAX_DATA {
|
|
return Err(PsError::Limit("decoded data is too large"));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A JPEG's pixels (grey or RGB, 8 bits) with its channel count, and how many bytes of `raw` it
|
|
/// took.
|
|
fn dct(raw: &[u8]) -> Res<(Vec<u8>, usize, u8)> {
|
|
let end = find(raw, &[0xFF, 0xD9]).map_or(raw.len(), |e| e + 2);
|
|
let img = image::load_from_memory_with_format(raw.get(..end).unwrap_or_default(), image::ImageFormat::Jpeg)
|
|
.map_err(|_| PsError::Ps("ioerror", "DCTDecode".into()))?;
|
|
if img.color().channel_count() == 1 { Ok((img.to_luma8().into_raw(), end, 1)) } else { Ok((img.to_rgb8().into_raw(), end, 3)) }
|
|
}
|
|
|
|
/// A decoding filter and its parameters.
|
|
#[derive(Clone, Debug)]
|
|
pub(crate) struct Filter {
|
|
name: Rc<str>,
|
|
/// `SubFileDecode`'s count and end-of-data string.
|
|
sub: (usize, Vec<u8>),
|
|
}
|
|
|
|
/// The bytes `f` decodes from `raw`, how many it read, and the channels of a decoded JPEG.
|
|
fn decode(f: &Filter, raw: &[u8]) -> Res<(Vec<u8>, usize, Option<u8>)> {
|
|
let (data, used) = match &*f.name {
|
|
"ASCIIHexDecode" => {
|
|
let end = raw.iter().position(|b| *b == b'>');
|
|
let body = raw.get(..end.unwrap_or(raw.len())).unwrap_or_default();
|
|
(hex_decode(body).ok_or(PsError::Ps("ioerror", "ASCIIHexDecode".into()))?, end.map_or(raw.len(), |e| e + 1))
|
|
}
|
|
"ASCII85Decode" => {
|
|
let end = find(raw, b"~>");
|
|
let mut text = String::from_utf8_lossy(raw.get(..end.unwrap_or(raw.len())).unwrap_or_default()).into_owned();
|
|
text.push_str("~>");
|
|
(ps::ascii85_decode(&text).ok_or(PsError::Ps("ioerror", "ASCII85Decode".into()))?, end.map_or(raw.len(), |e| e + 2))
|
|
}
|
|
"RunLengthDecode" => run_length(raw)?,
|
|
"FlateDecode" => flate(raw)?,
|
|
"LZWDecode" => lzw(raw)?,
|
|
"DCTDecode" => {
|
|
let (d, used, channels) = dct(raw)?;
|
|
return Ok((d, used, Some(channels)));
|
|
}
|
|
"SubFileDecode" => {
|
|
let (count, eod) = &f.sub;
|
|
if eod.is_empty() {
|
|
let n = (*count).min(raw.len());
|
|
(raw.get(..n).unwrap_or_default().to_vec(), n)
|
|
} else {
|
|
let (mut at, mut seen) = (0, 0);
|
|
loop {
|
|
match find(raw.get(at..).unwrap_or_default(), eod) {
|
|
Some(i) if seen < *count => {
|
|
seen += 1;
|
|
at += i + eod.len();
|
|
}
|
|
Some(i) => break (raw.get(..at + i).unwrap_or_default().to_vec(), at + i + eod.len()),
|
|
None => break (raw.to_vec(), raw.len()),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
"NullEncode" => (raw.to_vec(), raw.len()),
|
|
other => return ps_err("undefined", other),
|
|
};
|
|
Ok((data, used, None))
|
|
}
|
|
|
|
/// What a source of image data gives.
|
|
enum Source {
|
|
File(Rc<RefCell<Stream>>),
|
|
Str(Vec<u8>),
|
|
Proc(Obj),
|
|
}
|
|
|
|
impl Interp<'_> {
|
|
pub fn data_op(&mut self, op: Op) -> Res {
|
|
use Op::*;
|
|
match op {
|
|
CurrentFile => self.push(Obj::File(Rc::new(RefCell::new(Stream::Current))))?,
|
|
Filter => self.filter()?,
|
|
ReadHexString | ReadString => {
|
|
let s = self.pop_str()?;
|
|
let Obj::File(f) = self.pop()? else { return ps_err("typecheck", "") };
|
|
let n = s.len();
|
|
let got = if op == ReadString { self.read(&f, n)? } else { self.read_hex(&f, n)? };
|
|
self.fill(s, &got)?;
|
|
self.push(Obj::Bool(got.len() == n))?;
|
|
}
|
|
ReadLine => {
|
|
let s = self.pop_str()?;
|
|
let Obj::File(f) = self.pop()? else { return ps_err("typecheck", "") };
|
|
let n = s.len();
|
|
let mut line = vec![];
|
|
let mut ended = false;
|
|
while line.len() < n {
|
|
match self.read(&f, 1)?.first() {
|
|
Some(b'\n') | None => {
|
|
ended = true;
|
|
break;
|
|
}
|
|
Some(b'\r') => {
|
|
ended = true;
|
|
break;
|
|
}
|
|
Some(b) => line.push(*b),
|
|
}
|
|
}
|
|
self.fill(s, &line)?;
|
|
self.push(Obj::Bool(ended))?;
|
|
}
|
|
FlushFile | CloseFile => {
|
|
self.pop()?;
|
|
}
|
|
File => {
|
|
self.pop()?;
|
|
self.pop()?;
|
|
return ps_err("invalidfileaccess", "file");
|
|
}
|
|
Eexec => self.eexec()?,
|
|
Image | ImageMask | ColorImage => self.image(op)?,
|
|
_ => self.text_op(op)?,
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Up to `n` bytes of file `f`.
|
|
fn read(&mut self, f: &Rc<RefCell<Stream>>, n: usize) -> Res<Vec<u8>> {
|
|
self.materialize(f)?;
|
|
Ok(match &mut *f.borrow_mut() {
|
|
Stream::Current => {
|
|
let rest = self.lex.data();
|
|
let k = n.min(rest.len());
|
|
let v = rest.get(..k).unwrap_or_default().to_vec();
|
|
self.lex.advance(k);
|
|
v
|
|
}
|
|
Stream::Buf { data, pos, .. } => {
|
|
let start = (*pos).min(data.len());
|
|
let end = start.saturating_add(n).min(data.len());
|
|
*pos = end;
|
|
data.get(start..end).unwrap_or_default().to_vec()
|
|
}
|
|
Stream::Pending(_) => vec![],
|
|
})
|
|
}
|
|
|
|
/// Up to `n` bytes read as hexadecimal digits from `f` (other characters skipped).
|
|
fn read_hex(&mut self, f: &Rc<RefCell<Stream>>, n: usize) -> Res<Vec<u8>> {
|
|
let mut out = Vec::with_capacity(n);
|
|
let mut high: Option<u8> = None;
|
|
while out.len() < n {
|
|
let Some(&b) = self.read(f, 1)?.first() else { break };
|
|
let Some(v) = char::from(b).to_digit(16) else { continue };
|
|
match high.take() {
|
|
Some(h) => out.push(h << 4 | v as u8),
|
|
None => high = Some(v as u8),
|
|
}
|
|
}
|
|
Ok(out)
|
|
}
|
|
|
|
/// Read bytes `got` into string `s` and push the part of `s` they fill.
|
|
fn fill(&mut self, s: Shared<u8>, got: &[u8]) -> Res {
|
|
if !s.write(0, got) {
|
|
return ps_err("rangecheck", "");
|
|
}
|
|
self.push_interval(Obj::Str(s), 0, got.len())
|
|
}
|
|
|
|
/// `filter`: a file that decodes its source. Over the program's own data it decodes when
|
|
/// first read (the data follows the operator that reads it); over other data, at once.
|
|
fn filter(&mut self) -> Res {
|
|
let name = self.pop()?.text().ok_or(PsError::Ps("typecheck", "filter".into()))?;
|
|
let sub = if &*name == "SubFileDecode" {
|
|
let eod = self.pop_str()?.to_vec();
|
|
let count = self.pop_count()?;
|
|
(count, eod)
|
|
} else {
|
|
(0, vec![])
|
|
};
|
|
if let Some(Obj::Dict(d)) = self.stack.last().cloned() {
|
|
self.pop()?;
|
|
let predictor = d.borrow().get(&Key::name("Predictor")).and_then(Obj::as_num).unwrap_or(1.0);
|
|
if predictor > 1.0 {
|
|
return ps_err("undefined", "a filter predictor");
|
|
}
|
|
}
|
|
let spec = Filter { name, sub };
|
|
let stream = match self.pop()? {
|
|
Obj::File(f) => {
|
|
if let Stream::Buf { data, pos, .. } = &*f.borrow() {
|
|
self.alloc(data.len().saturating_sub(*pos))?;
|
|
}
|
|
let chain = match &*f.borrow() {
|
|
Stream::Current => Some(vec![]),
|
|
Stream::Pending(c) => Some(c.clone()),
|
|
Stream::Buf { .. } => None,
|
|
};
|
|
match chain {
|
|
Some(mut c) => {
|
|
c.push(spec);
|
|
Stream::Pending(c)
|
|
}
|
|
None => {
|
|
let mut b = f.borrow_mut();
|
|
let Stream::Buf { data, pos, .. } = &mut *b else { return io("filter") };
|
|
let (out, used, rgb) = decode(&spec, data.get(*pos..).unwrap_or_default())?;
|
|
*pos = pos.saturating_add(used);
|
|
Stream::Buf { data: out, pos: 0, rgb }
|
|
}
|
|
}
|
|
}
|
|
Obj::Str(s) => {
|
|
self.alloc(s.borrow().len())?;
|
|
let (data, _, rgb) = decode(&spec, &s.borrow())?;
|
|
Stream::Buf { data, pos: 0, rgb }
|
|
}
|
|
p @ Obj::Array { .. } => {
|
|
let raw = self.gather(&p, MAX_DATA)?;
|
|
let (data, _, rgb) = decode(&spec, &raw)?;
|
|
Stream::Buf { data, pos: 0, rgb }
|
|
}
|
|
_ => return ps_err("typecheck", "filter"),
|
|
};
|
|
self.push(Obj::File(Rc::new(RefCell::new(stream))))
|
|
}
|
|
|
|
/// Decode the filters a file waits to run over the program's data, from where it is now.
|
|
fn materialize(&mut self, f: &Rc<RefCell<Stream>>) -> Res {
|
|
let chain = match &*f.borrow() {
|
|
Stream::Pending(c) => c.clone(),
|
|
_ => return Ok(()),
|
|
};
|
|
let mut buf: Option<(Vec<u8>, Option<u8>)> = None;
|
|
for spec in &chain {
|
|
buf = Some(match buf.take() {
|
|
None => {
|
|
let raw = self.lex.data();
|
|
let (data, used, rgb) = decode(spec, raw)?;
|
|
self.alloc(data.len())?;
|
|
self.lex.advance(used);
|
|
(data, rgb)
|
|
}
|
|
Some((prev, was)) => {
|
|
let (data, _, rgb) = decode(spec, &prev)?;
|
|
self.alloc(data.len())?;
|
|
(data, rgb.or(was))
|
|
}
|
|
});
|
|
}
|
|
let (data, rgb) = buf.unwrap_or_default();
|
|
*f.borrow_mut() = Stream::Buf { data, pos: 0, rgb };
|
|
Ok(())
|
|
}
|
|
|
|
/// The bytes a data procedure gives, called until it gives an empty string or `n` bytes.
|
|
fn gather(&mut self, p: &Obj, n: usize) -> Res<Vec<u8>> {
|
|
let mut out = vec![];
|
|
while out.len() < n {
|
|
self.call(p.clone())?;
|
|
let s = self.pop_str()?;
|
|
let s = s.borrow();
|
|
if s.is_empty() {
|
|
break;
|
|
}
|
|
self.alloc(s.len())?;
|
|
out.extend_from_slice(&s);
|
|
}
|
|
out.truncate(n);
|
|
Ok(out)
|
|
}
|
|
|
|
/// `n` bytes of image data from `src` (zeros past its end).
|
|
fn source_bytes(&mut self, src: &Source, n: usize) -> Res<Vec<u8>> {
|
|
let mut v = match src {
|
|
Source::File(f) => self.read(f, n)?,
|
|
Source::Str(s) if !s.is_empty() => s.iter().copied().cycle().take(n).collect(),
|
|
Source::Str(_) => vec![],
|
|
Source::Proc(p) => self.gather(p, n)?,
|
|
};
|
|
v.resize(n, 0);
|
|
Ok(v)
|
|
}
|
|
|
|
/// `eexec`: an encrypted font program follows. It is skipped (type is drawn in the app's
|
|
/// fonts, by name): the font dictionary it would complete is defined as it is.
|
|
fn eexec(&mut self) -> Res {
|
|
self.pop()?;
|
|
if let Some(Obj::Dict(d)) = self.stack.last().cloned() {
|
|
let name = d.borrow().get(&Key::name("FontName")).cloned();
|
|
if let Some(k) = name.and_then(|n| n.key()) {
|
|
self.pop()?;
|
|
self.fonts.borrow_mut().insert(k, Obj::Dict(d));
|
|
}
|
|
}
|
|
let rest = self.lex.data();
|
|
let at = find(rest, b"cleartomark").ok_or(PsError::Ps("ioerror", "eexec".into()))?;
|
|
self.lex.advance(at);
|
|
self.push(Obj::Mark)
|
|
}
|
|
|
|
// ---------- images ----------
|
|
|
|
fn image(&mut self, op: Op) -> Res {
|
|
let mask = op == Op::ImageMask;
|
|
let spec = match self.stack.last() {
|
|
Some(Obj::Dict(_)) if op != Op::ColorImage => {
|
|
let d = self.pop_dict()?;
|
|
self.image_dict(&d, mask)?
|
|
}
|
|
_ => self.image_operands(op)?,
|
|
};
|
|
let Some(img) = self.decode_image(spec)? else { return Ok(()) };
|
|
self.place_image(img)
|
|
}
|
|
|
|
/// The dictionary form of `image` and `imagemask`.
|
|
fn image_dict(&mut self, d: &DictRef, mask: bool) -> Res<Spec> {
|
|
let get = |k: &str| d.borrow().get(&Key::name(k)).cloned();
|
|
let num = |k: &str| get(k).and_then(|o| o.as_num());
|
|
let kind = num("ImageType").unwrap_or(1.0);
|
|
if kind != 1.0 && kind != 4.0 {
|
|
return ps_err("rangecheck", "ImageType");
|
|
}
|
|
let size = |k: &str| num(k).filter(|v| *v >= 1.0 && *v <= 65_535.0).map(|v| v as usize).ok_or(PsError::Ps("rangecheck", k.to_string()));
|
|
let (w, h) = (size("Width")?, size("Height")?);
|
|
let bpc = if mask { 1 } else { num("BitsPerComponent").unwrap_or(8.0) as u8 };
|
|
let matrix =
|
|
get("ImageMatrix").and_then(|o| o.items().and_then(|i| matrix_of(&i.borrow()))).ok_or(PsError::Ps("typecheck", "ImageMatrix".into()))?;
|
|
let decode: Vec<f64> = get("Decode").and_then(|o| o.items().map(|i| i.borrow().iter().filter_map(Obj::as_num).collect())).unwrap_or_default();
|
|
let space = if mask { Space::Gray } else { (*self.g.space).clone() };
|
|
let multi = get("MultipleDataSources").is_some_and(|o| matches!(o, Obj::Bool(true)));
|
|
let src = get("DataSource").ok_or(PsError::Ps("undefined", "DataSource".into()))?;
|
|
let sources = match (&src, multi) {
|
|
(Obj::Array { items, exec: false }, true) => items.borrow().iter().map(source).collect::<Res<Vec<_>>>()?,
|
|
_ => vec![source(&src)?],
|
|
};
|
|
let mask_color: Option<Vec<u32>> = (kind == 4.0)
|
|
.then(|| {
|
|
get("MaskColor").and_then(|o| o.items().map(|i| i.borrow().iter().filter_map(|v| v.as_num().map(|v| v.max(0.0) as u32)).collect()))
|
|
})
|
|
.flatten();
|
|
// An image mask paints its 0 bits with Decode [0 1], its 1 bits with [1 0].
|
|
let stencil = mask.then(|| decode.first().is_some_and(|v| *v > 0.5));
|
|
Ok(Spec { w, h, bpc, space, decode, matrix, sources, mask_color, stencil })
|
|
}
|
|
|
|
/// The operand forms: `w h bpc matrix src image`, `w h polarity matrix src imagemask`, and
|
|
/// `w h bpc matrix src… multi ncomp colorimage`.
|
|
fn image_operands(&mut self, op: Op) -> Res<Spec> {
|
|
let (n, multi) = if op == Op::ColorImage {
|
|
let n = self.pop_count()?;
|
|
(n, self.pop_bool()?)
|
|
} else {
|
|
(1, false)
|
|
};
|
|
let space = match n {
|
|
1 => Space::Gray,
|
|
3 => Space::Rgb,
|
|
4 => Space::Cmyk,
|
|
_ => return ps_err("rangecheck", "colorimage"),
|
|
};
|
|
let mut sources = vec![];
|
|
for _ in 0..if multi { n } else { 1 } {
|
|
let o = self.pop()?;
|
|
sources.push(source(&o)?);
|
|
}
|
|
sources.reverse();
|
|
let matrix = self.pop_matrix()?;
|
|
let (bpc, stencil) = if op == Op::ImageMask { (1, Some(self.pop_bool()?)) } else { (self.pop_int()?.clamp(1, 16) as u8, None) };
|
|
let size = |v: i64| usize::try_from(v).ok().filter(|v| (1..=65_535).contains(v)).ok_or(PsError::Ps("rangecheck", "image".into()));
|
|
let h = size(self.pop_int()?)?;
|
|
let w = size(self.pop_int()?)?;
|
|
Ok(Spec { w, h, bpc, space, decode: vec![], matrix, sources, mask_color: None, stencil })
|
|
}
|
|
|
|
/// The image's pixels, straight RGBA, rows from the first one read.
|
|
fn decode_image(&mut self, s: Spec) -> Res<Option<Pixels>> {
|
|
if !matches!(s.bpc, 1 | 2 | 4 | 8 | 12 | 16) {
|
|
return ps_err("rangecheck", "BitsPerComponent");
|
|
}
|
|
if s.w.saturating_mul(s.h) > MAX_PIXELS {
|
|
self.out.warn("images too large to read were left out");
|
|
return Ok(None);
|
|
}
|
|
self.alloc(s.w * s.h * 4)?;
|
|
let n = if s.stencil.is_some() { 1 } else { s.space.n() };
|
|
let bpc = usize::from(s.bpc);
|
|
for src in &s.sources {
|
|
if let Source::File(f) = src {
|
|
self.materialize(f)?;
|
|
}
|
|
}
|
|
// A decoded JPEG gives 8-bit grey or RGB samples whatever the colour space says.
|
|
let jpeg = s.sources.iter().find_map(|src| match src {
|
|
Source::File(f) => match &*f.borrow() {
|
|
Stream::Buf { rgb, .. } => *rgb,
|
|
_ => None,
|
|
},
|
|
_ => None,
|
|
});
|
|
let (n, bpc, space) = match jpeg {
|
|
Some(3) => (3, 8, Space::Rgb),
|
|
Some(_) => (1, 8, Space::Gray),
|
|
None => (n, bpc, s.space.clone()),
|
|
};
|
|
let multi = s.sources.len() > 1;
|
|
let row_bits = if multi { s.w * bpc } else { s.w * n * bpc };
|
|
let row = row_bits.div_ceil(8);
|
|
let planes: Vec<Vec<u8>> = if multi {
|
|
let mut v = vec![];
|
|
for src in &s.sources {
|
|
v.push(self.source_bytes(src, row * s.h)?);
|
|
}
|
|
v
|
|
} else {
|
|
let src = s.sources.first().ok_or(PsError::Ps("undefined", "DataSource".into()))?;
|
|
vec![self.source_bytes(src, row * s.h)?]
|
|
};
|
|
let max = ((1u32 << bpc.min(16)) - 1) as f64;
|
|
let sample = |plane: &[u8], y: usize, i: usize| -> u32 {
|
|
let bit = y * row * 8 + i * bpc;
|
|
let byte = bit / 8;
|
|
match bpc {
|
|
8 => u32::from(plane.get(byte).copied().unwrap_or(0)),
|
|
16 => u32::from(plane.get(byte).copied().unwrap_or(0)) << 8 | u32::from(plane.get(byte + 1).copied().unwrap_or(0)),
|
|
12 => {
|
|
let v = u32::from(plane.get(byte).copied().unwrap_or(0)) << 8 | u32::from(plane.get(byte + 1).copied().unwrap_or(0));
|
|
if bit.is_multiple_of(8) { v >> 4 } else { v & 0xfff }
|
|
}
|
|
_ => (u32::from(plane.get(byte).copied().unwrap_or(0)) >> (8 - bpc - bit % 8)) & ((1 << bpc) - 1),
|
|
}
|
|
};
|
|
let default_decode: Vec<f64> = match &space {
|
|
Space::Indexed { .. } => vec![0.0, max],
|
|
_ => (0..n).flat_map(|_| [0.0, 1.0]).collect(),
|
|
};
|
|
let decode = if s.decode.len() >= 2 * n && jpeg.is_none() { s.decode.clone() } else { default_decode };
|
|
let paint = self.g.paint.color().map(|c| c.to_rgba8(1.0));
|
|
let mut rgba = Vec::with_capacity(s.w * s.h * 4);
|
|
let mut cache: HashMap<Vec<u32>, [u8; 3]> = HashMap::new();
|
|
let mut raw = vec![0u32; n];
|
|
let mut comps = vec![0f64; n];
|
|
for y in 0..s.h {
|
|
for x in 0..s.w {
|
|
for (c, slot) in raw.iter_mut().enumerate() {
|
|
*slot = match planes.as_slice() {
|
|
[one] => sample(one, y, x * n + c),
|
|
many => many.get(c).map_or(0, |p| sample(p, y, x)),
|
|
};
|
|
}
|
|
if let Some(paint_ones) = s.stencil {
|
|
let on = (raw.first().copied().unwrap_or(0) == 1) == paint_ones;
|
|
let [r, g, b, _] = paint.unwrap_or([0, 0, 0, 255]);
|
|
rgba.extend([r, g, b, if on { 255 } else { 0 }]);
|
|
continue;
|
|
}
|
|
let masked = s.mask_color.as_ref().is_some_and(|m| masked(m, &raw));
|
|
for (c, slot) in comps.iter_mut().enumerate() {
|
|
let (d0, d1) = (decode.get(2 * c).copied().unwrap_or(0.0), decode.get(2 * c + 1).copied().unwrap_or(1.0));
|
|
*slot = d0 + f64::from(*raw.get(c).unwrap_or(&0)) * (d1 - d0) / max;
|
|
}
|
|
let rgb = match (&space, raw.as_slice()) {
|
|
(Space::Gray, [v]) if bpc == 8 && decode == [0.0, 1.0] => [*v as u8; 3],
|
|
(Space::Rgb, [r, g, b]) if bpc == 8 && decode == [0.0, 1.0, 0.0, 1.0, 0.0, 1.0] => [*r as u8, *g as u8, *b as u8],
|
|
_ => match cache.get(&raw) {
|
|
Some(c) => *c,
|
|
None => {
|
|
let c = self.sample_rgb(&space, &comps)?;
|
|
if cache.len() < MAX_TINTS {
|
|
cache.insert(raw.clone(), c);
|
|
}
|
|
c
|
|
}
|
|
},
|
|
};
|
|
rgba.extend([rgb[0], rgb[1], rgb[2], if masked { 0 } else { 255 }]);
|
|
}
|
|
}
|
|
Ok(Some(Pixels { w: s.w as u32, h: s.h as u32, rgba, matrix: s.matrix }))
|
|
}
|
|
|
|
/// One pixel's colour in `space` as display RGB.
|
|
fn sample_rgb(&mut self, space: &Space, comps: &[f64]) -> Res<[u8; 3]> {
|
|
let color = match space {
|
|
Space::Gray | Space::Rgb | Space::Cmyk => process(space, comps),
|
|
_ => match self.paint_of(space, comps)? {
|
|
Paint::Solid { color, .. } => Some(color),
|
|
_ => None,
|
|
},
|
|
};
|
|
let [r, g, b, _] = color.unwrap_or(Color::BLACK).to_rgba8(1.0);
|
|
Ok([r, g, b])
|
|
}
|
|
|
|
/// Place decoded pixels in user space (its unit square through the image matrix).
|
|
fn place_image(&mut self, img: Pixels) -> Res {
|
|
let m = img.matrix;
|
|
if m.determinant().abs() < 1e-12 {
|
|
return Ok(());
|
|
}
|
|
let xf = self.xf() * m.inverse();
|
|
if !xf.as_coeffs().iter().all(|v| v.is_finite()) || xf.determinant().abs() < 1e-12 {
|
|
return Ok(());
|
|
}
|
|
let Some(rgba) = image::RgbaImage::from_raw(img.w, img.h, img.rgba) else { return Ok(()) };
|
|
let mut png = vec![];
|
|
if rgba.write_to(&mut std::io::Cursor::new(&mut png), image::ImageFormat::Png).is_err() {
|
|
self.out.warn("an image that couldn't be read was left out");
|
|
return Ok(());
|
|
}
|
|
let blob = ImageBlob::new("image/png", png);
|
|
let key = blob.content_key();
|
|
self.out.doc.images.entry(key.clone()).or_insert(blob);
|
|
let im = ImageObject { key, width: img.w, height: img.h, xf, link: None, placement: Default::default() };
|
|
let clips = self.g.clips.clone();
|
|
self.out.push(Node::new(NodeId(0), NodeKind::Image(im)), &clips);
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// An image to decode.
|
|
struct Spec {
|
|
w: usize,
|
|
h: usize,
|
|
bpc: u8,
|
|
space: Space,
|
|
decode: Vec<f64>,
|
|
/// User space → image space.
|
|
matrix: Affine,
|
|
/// One source, or one per component.
|
|
sources: Vec<Source>,
|
|
/// `MaskColor` (ImageType 4): samples that are transparent.
|
|
mask_color: Option<Vec<u32>>,
|
|
/// An image mask: whether its 1 bits paint.
|
|
stencil: Option<bool>,
|
|
}
|
|
|
|
/// Decoded pixels.
|
|
struct Pixels {
|
|
w: u32,
|
|
h: u32,
|
|
rgba: Vec<u8>,
|
|
matrix: Affine,
|
|
}
|
|
|
|
fn source(o: &Obj) -> Res<Source> {
|
|
Ok(match o {
|
|
Obj::File(f) => Source::File(f.clone()),
|
|
Obj::Str(s) => Source::Str(s.to_vec()),
|
|
Obj::Array { items, .. } => Source::Proc(Obj::Array { items: items.clone(), exec: true }),
|
|
_ => return ps_err("typecheck", "DataSource"),
|
|
})
|
|
}
|
|
|
|
/// Do samples `raw` match `MaskColor` `m` (one value per component, or a range)?
|
|
fn masked(m: &[u32], raw: &[u32]) -> bool {
|
|
if m.len() == raw.len() {
|
|
return m.iter().zip(raw).all(|(a, b)| a == b);
|
|
}
|
|
m.len() == raw.len() * 2
|
|
&& raw.iter().enumerate().all(|(i, v)| (m.get(2 * i).copied().unwrap_or(0)..=m.get(2 * i + 1).copied().unwrap_or(0)).contains(v))
|
|
}
|