The image cache, layer variants, content boxes and tone cache key by
SourceKey instead of the bytes' address, so they no longer hold or
reserve the encoded bytes: decoded levels outlive them.
- A request turned away as recently decoded schedules its wake only for the
first decode of that image, level and epoch: a view larger than the cache
(or more failed images than are remembered) no longer decodes every 2 s.
- A worker that takes a job while a wake is scheduled hands the timed wait on.
- A full decode drawn at level 0 from the cache leaves the zoom-in list.
- Pixels decoded under colour settings changed since are not cached.
- Shadows and glows drawn while an image in them decodes are not kept.
- A panic anywhere in a background job, sizing and the wait at the gate
included, marks the image bad. A guard takes the job off the busy
list however it ends, and a thread that ends is counted out so
another starts.
- Background decodes reserve their bytes where mip chooses the decode:
the reduced JPEG estimate before a reduced decode (a JPEG without one
goes to the full decode), the full peak before a full decode, nothing
when a cached level is halved.
- A request turned away as recently decoded schedules one generation
advance for when its window has passed, so its stand-in doesn't stay
until the view changes. Emptying the cache (new colour settings,
clear) starts a new epoch, and decodes into the old cache no longer
count as recent.
- At most two full decodes are kept for zooming in; a newer one evicts
the oldest.
- warm's planning runs under catch_unwind; a panic skips warming.
- Renderer::images_pending says whether the last render drew an image
still decoding.
Regression tests use a test-only fault hook that panics inside the job
and inside warm's planning.
The image cache kept the full-resolution level 0 of every TIFF, PNG,
PSD and CMYK JPEG, and of any image shown at High Quality: about 48 MB
for a 12 MP photo, so after a dozen photos every edit evicted and
decoded them again on the render thread.
- mip keeps only the level drawn. It remembers each asset's full size
(from its header, then from a decode), builds a coarser level from a
finer cached one, and tries the reduced JPEG decode before a full one.
When an image is zoomed into (a finer level asked for while a coarser
one is cached) it keeps the full decode too, up to 96 MB, so the
following steps halve it instead of decoding again.
- With RenderOptions::defer_images (the canvas) the image draw uses
mip_now: a level that isn't cached is decoded on one of three
background threads, newest request first, and a coarser cached level
or a light box is drawn meanwhile. images::generation advances and
the waker runs when a decode finishes. A decoder panic marks the
image bad. Background decodes reserve their peak bytes at the same
gate as warm, so they wait while decodes in flight hold half the
budget.
- An image asked for again within 2 s of its decode (evicted at once:
the view holds more than the cache) isn't decoded again until then,
in place or in the background, so the canvas never decodes on its
own thread.
- warm counts an asset as cached when the level each drawing takes, or
a finer one, is cached, and sizes a job by the levels it keeps. It
leaves a decode larger than the whole budget to the render, and the
canvas skips it.
- A render that deferred an image isn't kept as the CMYK view snapshot.
Based on #2051 by texjer.
The image cache keys decodes by the bytes' address, so a variant rebuilt
as a new Arc misses the decodes it already has. With decodes in the
background, more variants on screen than the variant cache kept would
decode and re-render without end. Variants are now looked up weakly,
with the 64 most recent held strongly.
Based on #2051 by texjer.
A JPEG drawn small is decoded at a reduced level and has no full-size entry, so every later render queued it again and started decode threads that found it cached.
The draw path scales an item's stroke extent and effect outset by its transform before culling; the pass that picks images to decode ahead used the unscaled values, so a scaled image whose stroke or shadow reaches the view decoded on the render thread.
With the 3/2 headroom on each decode, a 6,500 pixel square PSD reserves more than half the budget, so the small image can no longer decode beside it and the test failed before calling warm. 5,000 pixel square images still reserve more than half the budget as a pair and leave room for the small image beside one.
The gate that limits parallel decodes counted each image at its cached
size (4 bytes a pixel and a third more for the smaller levels). A decode
holds more than that while it converts the pixels: an 8-bit image keeps
its RGBA copy beside the decoded or the premultiplied pixels, 8 bytes a
pixel. Two 5,500 pixel square images passed the gate together and could
hold more than half the cache budget at once.
- decode_sizes returns the cached size and the peak size from one size
read. The peak counts the samples as stored beside their converted
copy (designcraft_images::decode_bytes_per_pixel): 8 bytes a pixel for
8-bit color, 10 for 16-bit RGB, 12 for 16-bit CMYK TIFFs and 16-bit
RGBA, 10 for 8-bit CMYKA TIFFs, 15 for 16-bit CMYKA TIFFs, 9 for
PSDs. An EPS counts as its TIFF preview. PDF pages, SVGs and EPS
files without a preview count 8.
- warm budgets the cache with the cached size and gates decodes with
the peak size.
- Every warm call in the process shares one gate, so the canvas
worker, thumbnails and exports together stay within half the budget
while they decode. warm returns the most its own call had decoding.
- An EPS without a preview is sized at the larger of its drawn
PostScript and its placeholder. The placeholder is clamped to 4000
pixels on each side, so a 10,000 by 4,000 point EPS that is not drawn
takes 4000 by 4000 pixels, not the 4000 by 1600 counted before.
Culling grew an object's box by its unscaled stroke and effect outset,
so a view or repaint patch that showed only the outer part of a scaled
object's stroke or hardened glow skipped the object. Culling now scales
both by the object's largest scale factor, as drawing does. The CMYK
ink-pass area grows each object, and each nested child, by its stroke
and effect outset under its full transform.
A render decoded each placed image that was not cached yet while drawing it,
one after another, so page thumbnails, image exports and the first canvas
frame of a page with several large photos took the sum of their decodes.
Before drawing, the renderer now lists the images in view (with the same
culling as drawing: hidden items and layers, nonprinting items, items out of
view, PDF layer choices) and decodes the uncached ones on up to eight threads,
with the mip levels they are drawn at. It leaves the work to the render when
fewer than two images need decoding, on the web, and in Fast display. The
images it decodes stay within what the cache keeps, sized by their pixels
(PDF pages and SVGs at the size they are rasterized at), and decodes in
flight together stay within half the cache budget.
Decoding a placed raster built a second full-size pixel vector from the
RGBA image and premultiplied every pixel into it. The pixels are now
premultiplied in place, opaque pixels are skipped, and the buffer becomes
the pixmap's pixels without a copy (bytemuck's cast_vec, with a copy when
the capacity is not a whole number of pixels). The rendered SVG path
reuses its buffer the same way. For a 6000 x 4000 opaque image the step
takes 7 ms instead of 120 ms, with identical pixels.
A placed JPEG drawn at a fraction of its pixel size was decoded at full
size first and then halved down to the level the screen needs. For a
24 MP photo that is a 96 MB decode per image, and with several such
images on a page the decoded pixels outgrow the image cache.
A YCbCr or grayscale JPEG whose wanted level is a half, a quarter or an
eighth of its size (or smaller) is now decoded at that size straight
from its compressed blocks with jpeg-decoder's scaled IDCT, cut to the
size the halving path gives the same level, and halved from there for
smaller levels. The embedded ICC profile is applied as in a full
decode. CMYK and YCCK JPEGs, other formats and the full size still
decode in full.
The spread of a drop shadow or outer glow is stroked through the item's
transform, so on a scaled item it reaches that much farther. Damage now
grows the effect outset by the item's largest scale factor, and the
multithreaded renderer crops the effect's offscreen image to the scaled
reach, so the outer part of the glow is no longer cut off.
The coverage was eroded by sampling two rings of points, which could miss a hole smaller than the gaps between them. Erosion now takes the smallest alpha within an octagon of the band's radius, in four passes along the axes and the diagonals.
Both shifted copies are the same at distance 0, so the coverage mask is empty. The effect is now skipped before the image is drawn offscreen and scanned.
Satin on a frame holding a graphic draws the object's coverage once
offscreen and compares the two shifted copies pixel by pixel, taking the
difference of their coverages. A partly transparent area that both copies
cover equally gets no plain satin, so a uniformly half-transparent image
no longer shows satin across its middle. Inverted satin keeps the
object's coverage where the copies agree.
The offscreen coverage for satin and for a center inner glow on a graphic
now covers its whole area at a lower resolution when it would be wider
than the largest raster side or hold more than 4096 x 4096 pixels.