File formats: JPEG 2000 prefers the display resolution box over the capture box in either order

This commit is contained in:
dotCooCoo
2026-11-02 12:04:14 +07:00
committed by bflatastic
parent 3dad9dbbfc
commit d8d0602c64
4 changed files with 19 additions and 11 deletions
+12 -7
View File
@@ -40,29 +40,34 @@ fn jp2_axis(n: u16, d: u16, e: i8) -> Option<f64> {
(ppi.is_finite() && (1.0..=100_000.0).contains(&ppi)).then_some(ppi)
}
/// JP2 resolution is independent on each axis. Later capture or display boxes replace earlier
/// valid values for that axis; an invalid later axis leaves the earlier value intact.
/// JP2 resolution is independent on each axis. A valid display (`resd`) value, the resolution the
/// image is meant to be reproduced at, wins over a capture (`resc`) value whichever box comes first;
/// the capture value is used for an axis with no valid display value. Within one box type, later
/// boxes replace earlier valid values for that axis; an invalid later axis leaves the earlier value intact.
fn jp2(b: &[u8]) -> Option<(f64, f64)> {
if !b.starts_with(b"\0\0\0\x0cjP \r\n\x87\n") {
return Some((72.0, 72.0));
}
let (mut x, mut y) = (None, None);
// [capture, display], each (x, y).
let mut found = [(None, None); 2];
for (_, header) in super::jp2read::boxes(b).filter(|(ty, _)| *ty == b"jp2h") {
for (_, res) in super::jp2read::boxes(header).filter(|(ty, _)| *ty == b"res ") {
for (_, data) in super::jp2read::boxes(res).filter(|(ty, _)| *ty == b"resc" || *ty == b"resd") {
for (ty, data) in super::jp2read::boxes(res).filter(|(ty, _)| *ty == b"resc" || *ty == b"resd") {
let Some([vn0, vn1, vd0, vd1, hn0, hn1, hd0, hd1, ve, he]): Option<[u8; 10]> = data.get(..10).and_then(|v| v.try_into().ok()) else {
continue;
};
let (x, y) = &mut found[usize::from(ty == b"resd")];
if let Some(value) = jp2_axis(u16::from_be_bytes([hn0, hn1]), u16::from_be_bytes([hd0, hd1]), he as i8) {
x = Some(value);
*x = Some(value);
}
if let Some(value) = jp2_axis(u16::from_be_bytes([vn0, vn1]), u16::from_be_bytes([vd0, vd1]), ve as i8) {
y = Some(value);
*y = Some(value);
}
}
}
}
Some((x.unwrap_or(72.0), y.unwrap_or(72.0)))
let [(cx, cy), (dx, dy)] = found;
Some((dx.or(cx).unwrap_or(72.0), dy.or(cy).unwrap_or(72.0)))
}
/// `n` bytes at `at` (`None` past the end; offsets come from the file, so no overflow either).
@@ -76,7 +76,7 @@ fn signatures_and_every_extension() {
}
#[test]
fn resolution_boxes_use_last_valid_axis_and_72_fallback() {
fn resolution_boxes_prefer_display_then_last_valid_axis_and_72_fallback() {
let check = |children: &[Vec<u8>], want: (f64, f64)| {
let file = with_resolution(children);
let got = ppi::resolution(&file).unwrap();
@@ -90,7 +90,10 @@ fn resolution_boxes_use_last_valid_axis_and_72_fallback() {
check(&[res(b"resc", 30000, 30000)], (300.0, 300.0));
check(&[res(b"resd", 15000, 15000)], (150.0, 150.0));
check(&[res(b"resc", 30000, 30000), res(b"resd", 15000, 15000)], (150.0, 150.0));
check(&[res(b"resd", 15000, 15000), res(b"resc", 30000, 30000)], (300.0, 300.0));
check(&[res(b"resd", 15000, 15000), res(b"resc", 30000, 30000)], (150.0, 150.0));
check(&[res(b"resd", 30000, 15000), res(b"resc", 7500, 7500)], (150.0, 300.0));
check(&[res(b"resd", 0, 15000), res(b"resc", 30000, 30000)], (150.0, 300.0));
check(&[res(b"resc", 30000, 30000), res(b"resd", 0, 15000)], (150.0, 300.0));
check(&[res(b"resc", 30000, 15000)], (150.0, 300.0));
assert_eq!(ppi::resolution(RAW), Some((72.0, 72.0)));
assert!((ppi::resolution(PPI300).unwrap().0 - 300.0).abs() < 0.01);
+1 -1
View File
@@ -128,7 +128,7 @@ the app's own editing copy of a file (an EPS after its `%%EOF`, a `.ai`'s `AIPri
| WebP | export (Export for Screens) | yes | yes (lossless only; lossy written lossless with a warning) | |
| HEIC / HEIF | open, place | no | no | |
| PCX | open, place | yes (1 bit in the header palette's first two colors, black and white without them; 1 bit in two to four planes and 4 bits with up to 16 colors; 2 bits in the CGA colors the header's CGA palette settings pick; 8 bits with the 256-color palette, gray without one; 24-bit color in three planes) | no | the header's HDpi and VDpi size the image; a file of another depth or plane count, cut short or past the pixel allowance is refused with a message; tests in `crates/engine/src/cmd/fileio/tests_pcxread.rs` and `crates/engine/tests/import_fuzz.rs` |
| JPEG 2000 | open, place | yes (JP2 and raw codestream images; multiple codestreams and JPX composition or layers are refused) | no | `.jpf`, `.jpx`, `.jp2`, `.j2k`, `.j2c`, `.jpc`; resolution uses the last valid `resc` or `resd` value on each axis, or 72 ppi; bounded decoding; `tests_jp2read.rs` |
| JPEG 2000 | open, place | yes (JP2 and raw codestream images; multiple codestreams and JPX composition or layers are refused) | no | `.jpf`, `.jpx`, `.jp2`, `.j2k`, `.j2c`, `.jpc`; resolution uses the valid `resd` (display) value on each axis, or else the valid `resc` (capture) value, or 72 ppi; bounded decoding; `tests_jp2read.rs` |
| PICT, Pixar PXR | open, place (legacy) | no | no | rarely used |
| FreeHand `.fh*` | open (legacy) | no | no | |
| CorelDRAW `.cdr` | open | no | no | |
+1 -1
View File
@@ -1093,7 +1093,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
EPS export writes a linear or radial gradient of tints of one spot color (and paper white) in that ink's Separation space, at Level 2 and 3, and at Level 3 one that mixes a spot color with process or gray colors, or two spot colors, in a DeviceN space of the spot inks and the process inks its stops use, so each spot ink prints on its own plate; an off-center midpoint is written as a stop with the inks halfway between its two stops. At Level 2, such a mix is written in process colors with a warning. In an RGB export, a gradient that mixes a spot color with RGB or Lab colors is written without the spot ink, with a warning; a CMYK export converts those stops to CMYK first, and the gradient follows the rules above. A Perceptual gradient of spot colors, and a mix whose DeviceN space would need more than 32 inks or a spot color named Cyan, Magenta, Yellow or Black, are also written in process colors with a warning (`crates/eps/src/tests.rs`). A spot color named All or None (in a Separation space, the names of every plate and of no plate: [ISO 32000-1:2008](https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/PDF32000_2008.pdf), 8.6.6.4), is written as the ink All Spot or None Spot (All Spot 2 and on when a spot swatch already has that name) in its fills, strokes, gradients and DSC comments; Registration still prints on every plate (`tests::spot_inks_named_all_or_none_are_written_under_names_of_their_own`).
- Targa files (`.tga`, `.vda`, `.icb`, `.vst`) open, place linked or embedded, relink and update at their pixel size: color-mapped, true-color and black-and-white, raw or run-length encoded, in any of the four row orders. The image descriptor's attribute bits and the extension area's Attributes Type decide the transparency: a file with no attribute bits, or with attribute data the Attributes Type calls undefined, opens opaque; with one bit, a 16-bit true-color pixel takes that bit as its alpha, and a color-mapped pixel takes the bit of the 16-bit color map entry it indexes; premultiplied colors are divided by their alpha. Open and Place recognize a Targa file by its extension, and `clipboard.importImage` and Glass's Load Texture… by its header; a Targa file copied in a file manager pastes as a bitmap (`crates/engine/src/cmd/fileio/tests_tiffbmp.rs`, `crates/engine/src/tests/links.rs`).
- PCX files (`.pcx`) open and place, from the ZSoft PCX Technical Reference Manual: 1 bit in the header palette's first two colors (black and white when the version has no palette or both colors are the same), 1 bit in two to four planes and 4 bits with up to 16 colors from the header's palette (the EGA colors for versions 0 and 3), 2 bits in the CGA colors that the header's CGA palette settings pick (background, color burst, palette and intensity), 8 bits with the 256-color palette after the image data (gray levels without one) and 24-bit color in three planes, run-length encoded. The header's HDpi and VDpi size the image. A file of another depth or plane count, one cut short, and one whose pixels as RGBA pass the pixel allowance are refused with a message before its pixels are decoded. Open and Place recognize a PCX file by its extension, and `clipboard.importImage` and Glass's Load Texture… by its header; a PCX file copied in a file manager pastes as a bitmap (`crates/engine/src/cmd/fileio/tests_pcxread.rs`).
- JPEG 2000 JP2 and raw codestream images open and place from `.jpf`, `.jpx`, `.jp2`, `.j2k`, `.j2c` and `.jpc`. The last valid `resc` or `resd` resolution value wins on each axis; an axis with none uses 72 ppi. Multiple codestreams, JPX composition or layers, and unsupported color spaces are refused with a message. The decoder checks image size before allocating (`crates/engine/src/cmd/fileio/tests_jp2read.rs`).
- JPEG 2000 JP2 and raw codestream images open and place from `.jpf`, `.jpx`, `.jp2`, `.j2k`, `.j2c` and `.jpc`. Each axis uses the valid display resolution (`resd`), else the valid capture resolution (`resc`), whichever box comes first ([ITU-T T.800](https://www.itu.int/rec/T-REC-T.800/en), I.5.3.7.2); an axis with none uses 72 ppi. Multiple codestreams, JPX composition or layers, and unsupported color spaces are refused with a message. The decoder checks image size before allocating (`crates/engine/src/cmd/fileio/tests_jp2read.rs`).
- EPS and DXF in and out, EMF/WMF in and out; PNG (with PNG-8), JPEG, WebP, GIF, TIFF, BMP, Targa and layered PSD export; grayscale TIFFs with an alpha channel or other extra channels place, relink and open, the alpha as the image's transparency (#1466 in part); PSD and PSB files open and place, linked or embedded, as their merged image in every colour mode and depth (#918). EPS import runs a PostScript Level 3 interpreter written from the PostScript Language Reference: resource categories and `resourceforall`, local and global VM (`currentglobal`/`setglobal`/`gcheck`), `clipsave`/`cliprestore`, subarrays and substrings that share storage, the standard dictionaries in `systemdict`, `dictstack`, `internaldict`, executable filters and strings, `load` by any key, `//name` immediate names, `token`, `execform`, user paths, `cshow`, `rootfont`, `cachestatus`, colour rendering and halftone queries, file `status` and data read with `flushfile`, and `pdfmark` marks dropped (Discord feedback, #466, #469, #474). Masked images (`ImageType 3` in its three interleavings) and reusable streams are read, and an image's several data sources are read in turn, as the reference defines (procedures that read one file get their own rows: such images came in banded, #498). `restore` puts dictionaries back as they were at the `save`, so programs that count their saves in a dictionary read to their end (part of #505). Tiling patterns come in as pattern swatches, uncoloured ones too; Type 3 fonts' glyphs as their outlines, spaced by `ashow`/`widthshow`/`xshow`/`kshow`; mesh and function-based shadings (types 1, 4–7) as gradient meshes; sampled functions (type 0) and Flate/LZW predictors are read. It is tested with programs written from the specification in the ways common generators use these features; Inkscape/cairo EPS files open as vectors. When a file's PostScript still can't be read, it opens as its TIFF preview, its Windows metafile preview (as vectors) or its EPSI preview, and the warning (in the status bar, `document.open`/`app.open`/MCP `open_file` replies and `vectorcraft-cli info`) names the error, the operator that raised it and the procedures it ran in. Legacy PostScript `.ai` files (Illustrator 3–8, written with the prolog that defines their operators) keep their groups and nested groups (`u` … `U`), between and inside clipping groups; EPS and EMF/WMF import nest clips (and groups) at most 128 deep, so a hostile file can't exhaust the stack. DXF TEXT justified Fit or Aligned spans its two points (stretched, or scaled as a whole). PDF export writes type as real, searchable text in embedded subset fonts by default, as Illustrator's PDF presets do (Convert Text to Outlines is an option; printer's marks stay shapes), so text-heavy PDFs stay small (#733). DXF export writes each fill as its outlines followed by its hatch, so laser and cutter software, which skips hatches, gets filled art and type too (#546).
- TIFFs that store each channel as its own plane (PlanarConfiguration 2) place, relink and open as the same image stored pixel by pixel does, 8 and 16 bits: grayscale with an alpha channel or other extra channels, and CMYK with or without them. A planar CMYK TIFF keeps its ink amounts and is stored as a CMYK TIFF with each pixel's inks together, at the file's resolution. A TIFF whose header claims more strips or tiles than can be counted is refused as too large to draw (#1466 in part; `crates/engine/src/cmd/fileio/tests.rs`).
- 1-bit TIFFs compressed with CCITT Group 3 (ITU-T T.4) place, relink and open: Compression 2 (modified Huffman) and Compression 3 with one-dimensional rows or, with T4Options bit 0, two-dimensional ones, with or without fill bits, in strips or tiles, with either FillOrder and either polarity. Rows in T.4's uncompressed mode are not read. A file whose pixels would take more than the decoding allowance as RGBA is refused as too large to draw (#1466 in part; `crates/engine/src/cmd/fileio/tests.rs`).