A chain of geometry effects makes at most 2 million anchors of a path (of a
type, symbol or image art's paths together) and about 8 million lines for the
renderer to draw at 1 px/pt, or what the path costs when that is more.
Roughen spaces its points further apart, Zig Zag makes fewer ridges a
segment, Free Distort, Twist and the warps split segments into longer pieces
(map_nonlinear_within) and Transform makes fewer copies where they would pass
it; a result drawn with too many lines is built again with fewer points, and
an effect that still doesn't fit is left out.
Three Roughens of a rectangle in a 25 KB file needed gigabytes of geometry,
and 2 million anchors of them flattened into 141 million lines (7 GB, past
vello's 2^27 lines a path); the file now opens in 0.4 s at about 450 MB.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Users can pick an image in the Texturizer dialog; it is stored as relief heights and survives save/open like Glass textures.
Co-authored-by: Cursor <cursoragent@cursor.com>
With Use Preview Bounds on, the bounding box, the Transform panel,
Align, Distribute, Distribute Spacing and object.setBounds measured
each object without its geometry effects. A square with Offset Path
10 measured as the square, and type with Outline Object measured by
its layout box instead of its glyphs.
The new vectorcraft_effects::preview module measures objects as they
are drawn. A path is measured by its effected shape, with the
geometry effects of each fill and stroke that paints. Type, images,
live objects and symbol instances are measured by their reshaped art.
A group or layer with geometry or Pathfinder effects, or with a fill
or stroke of its own that has geometry effects or paints members with
geometry effects, is measured by its evaluated art. A blend, envelope
or repeat whose objects have geometry effects is measured by its
evaluated art, with those objects measured after their effects. Raster
effects are not measured. In one object, the art of at most 4,096 symbol
instances is measured with its geometry effects, and the other
instances are measured without them. With Use Preview Bounds off, the
measures are unchanged.
object.setBounds sizes objects with geometry effects by measuring
them after each scale it tries, so type with Outline Object set to
100 pt wide gets a glyph box 100 pt wide.
Selection::anchors_box is split out of Selection::anchor_bounds. The
doc comment of VectorcraftApp::selection_bounds said that it includes
effects; it now says that it leaves them out.
Squashed from #2533 onto #2525 (Smudge Stick, Sponge, Watercolor): both sets in PIXEL_EFFECTS (45), the catalogs and the docs, whose counts now read 45/57.
Add opaque RGB light color and up to ten independently adjustable lights to
Revolve, Extrude & Bevel, Inflate and Rotate. Keep light selection, Add/Delete,
selected-light gizmos and preview transactions on the existing command path.
Preserve legacy white-light Inflate polygons, paint composition and seam joins,
including the 0.05 seam minimum. Use shared smooth normals for colored Inflate,
and batch solid-fill RGB channel paths behind one surface clip. Keep source
paths editable and SVG/PDF output vector artwork.
Rebase onto main f7de3d6a, retaining both sides of progress docs and all 22
catalogs. Shade freeform points once through the shared paint mapper. Document
release measurements and a reproducible CLI fixture, including the remaining
source-gradient cost and the one-light timing tradeoff.
Validation: full cargo xtask ci passed all 10 steps; 7,978 tests passed, zero
failed and 11 ignored. Four legacy white-light PNGs match unchanged main byte
for byte. Three paired release bench/perf repetitions completed without busy
warnings; all six standard perf runs passed every budget. Two-light solid
Inflate improved from 136.2 to 96.8 ms/frame at 1200x1200, default eight threads.
Three original Artistic filters on the pixel pipeline, each with a
catalogue row, the generic live dialog, translations and tests.
- Cutout: colours softened by Edge Simplicity (less with Edge Fidelity),
split into Number of Levels steps per channel and painted as flat
pieces in their average colour; slivers along softened edges take the
nearest large piece's colour.
- Poster Edges: channels posterized to Posterization + 2 levels, black
lines on the edges of any channel and of the outline, widened by Edge
Thickness and darkened by Edge Intensity.
- Neon Glow: dimmed greys screened with a glow colour from the light
areas (positive Glow Size) or the dark ones (negative), as strong as
Glow Brightness.
All keep coverage and work in document units at any Document Raster
Effects resolution. Photoshop filters 33/57; cargo xtask ci passes all
9 steps.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Smooth Zig Zag ran the ridge points through a Catmull-Rom spline. Its end tangents slant across the path, and where the ridges are taller than about twice their spacing the sharp-turn floor of turn_tangent lengthens the handles, so the result read as straight diagonals with rounded tips. Each peak and valley is now a smooth point whose handles run along the path, a third of the way to the neighbouring ridge, so every span follows a half cosine through the path.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Effect menu had no Rasterize. Object › Rasterize replaces the
object with an image, so its paths can no longer be edited.
Effect › Rasterize… (`rasterize`) is a live effect with Object ›
Rasterize's options: color model, resolution (the document's raster
effects resolution by default), white or transparent background,
anti-aliasing, Create Clipping Mask and Add Around Object. The canvas
draws the object as square pixels over its bounds, and its paths, type
and other art stay as they are. The effects above it in the stack,
shadows included, are part of its image, and shadows below it are cast
by the image. When Rasterize is an object's only raster effect, Expand
Appearance and PDF and SVG export give the image Object › Rasterize
gives with the same options.
Object › Rasterize now measures the art as drawn, with its geometry
effects, brush art and raster effects, so a Transform effect that moves
the art no longer leaves the image empty. Its Create Clipping Mask
clips the image to the art as drawn. The canvas no longer panics
drawing a pixel effect such as Gaussian Blur on an object about 65,500
screen pixels wide: the raster of such an object is at most 32,768
pixels a side, and the object is drawn into it at a lower resolution.
Docs: docs/mcp.md, docs/effects-parity.md, docs/target-app-parity.md,
ROADMAP.
Effect › Path had Offset Path and Outline Stroke but not Outline Object.
The new effect (path.outlineObject) has no options: the Effect menu
applies it at once, and effect.apply refuses any parameter. It is a
geometry effect that returns its input. Type already reaches geometry
effects as its glyph outlines, so type that carries it is drawn,
exported (PDF and SVG as paths, with no font) and expanded as those
outlines, stays editable type, and the effects after it reshape the
outlines. Paths, compound paths and images draw as before, and no
bounds change: with Use Preview Bounds on, type is still measured by
its layout box.
Every complete catalog gets a row for the label.
Docs: docs/effects-parity.md, docs/target-app-parity.md (an evidence
line), docs/mcp.md (the effect, and what Expand Appearance does to
type).
A corner whose anchor carries a handle (where a curve meets a line, or two
curves meet) was left sharp. It now gets Live Corners' round cut
(geom::corners, #1144), reaching `radius` along each side and trimming a
curved side. Corners between handle-less anchors are rounded exactly as
before, and a closed path still starts where its first corner's rounding
starts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Effect › Distort › Glass… offered only its four built-in textures. The
reference app's Texture menu also has Load Texture, which takes an
image file as the glass surface.
The Glass dialog now has a Load Texture… button. It reads a PNG, JPEG,
GIF, WebP, TIFF, BMP, PSD or PSB file within the pixel allowance the
renderer draws images in, and the Texture menu gains Loaded Texture.
The effect stores the image's gray levels in `textureImage` as a
grayscale PNG in base64, so the document saves the texture with the
effect, and copies of the effect carry it. effect.apply and
effect.setParams take `textureImage` as {path}, {name, dataBase64} or
the stored string; any other value is an error.
The texture lies one image pixel per point at Scaling 100%, centered on
the object and repeated. Each pixel's luma is the surface's height,
from 0 for black to 1 pt for white, and a transparent pixel counts as
middle gray. A Glass without `textureImage` draws and saves as before.
Docs: docs/mcp.md (Glass textures), docs/effects-parity.md,
docs/gaps.md, docs/target-app-parity.md.
The reference app's Scribble Options have a Variation for each of Path
Overlap, Curviness and Spacing. Scribble had one `variation`, which
moved each line's ends both along the line and across it, and every
turn of a scribble took the same curviness. An angle such as 270 was
stored as given.
Scribble now takes `overlapVariation` (pt, default 0.5), which moves
the ends along the line, `spacingVariation` (pt, 0.5), which moves
them across it, and `curvinessVariation` (%, 0), which varies the
curviness from one turn to the next. The dialog shows each Variation
after the option it varies, and the Appearance panel lists all three.
A Scribble saved or scripted with the earlier `variation` reads it as
the Path Overlap and Spacing variations. With the defaults, and with
the earlier `variation`, Scribble draws the same outline as before.
`effect.apply` and `effect.setParams` store an angle above -180 and up
to 180, so 270 is stored as -90, and the dialog shows the stored value.
The Appearance panel's inline editor lists an effect's values in
their current form, as the dialog does. It listed an earlier key next
to the defaults of the keys that replace it.
Docs: docs/effects-parity.md (Stylize row), docs/target-app-parity.md.
Effect › Blur › Smart Blur had only its Normal mode: the dialog and
blur.smart took Radius, Threshold and Quality and drew the blur.
Smart Blur now takes a `blurMode` param ("normal", "edgeOnly" or
"overlayEdge"; "normal" by default), which its dialog shows as a Mode
menu. Edge Only draws the edges in white on black, and Overlay Edge
draws them in white over the Normal blur. A pixel is on an edge when
one of its four nearest pixels differs from it by more than the
threshold in a channel and its channels, alpha included, add up to at
least that pixel's. The white and the black keep each pixel's
coverage. Normal draws the same pixels as before, and an effect saved
without `blurMode` draws as Normal.
Docs: docs/effects-parity.md, docs/gaps.md, docs/target-app-parity.md.
Free Distort's dialog showed the four corners as read-only text, and
so did the Appearance panel's inline editor. Only effect.apply and
effect.setParams could set them.
The dialog now shows a box in the proportions of the first selected
object's bounds with a handle on each corner. Dragging a handle moves
that corner, and the preview follows. A handle focused by a click or
Tab moves 1% of the box with each arrow key, and 10% with Shift. Reset
puts the four corners back on the box. Each handle has a
screen-reader name. On the corners row of the Appearance panel's
inline editor, Edit… opens the dialog.
Docs: docs/effects-parity.md, docs/target-app-parity.md.
Use automatic vector smooth shading without a separate control.
Enable Cap by default and keep continuous rim curves and bounded seam joins.
Integrate the shared dialog with the other three 3D effects.
The public 3D and Materials panel options give the Light Settings as
Intensity (0–100 %), Rotation (-180° to 180°), Height (0° to 90°),
Softness (0–100 %) and Ambient Light (0–200 %), with the light moved to
the back or the front of the object. The 3D effects (Revolve included)
had Direction and Elevation (an azimuth and elevation), Intensity and
Ambient (0–100 %).
- Rotation turns the light round the object on the page (our reading:
0° on its right, counterclockwise); Height raises it from beside the
object (0°) to straight in front (90°); Light behind the object puts
it at the back. The light gizmo's ring and line are Rotation and
Height.
- Softness wraps the light round the object: at 0 % it falls off with
the angle as before, at 100 % it reaches nothing only straight away.
- Ambient Light runs to 200 %; faces stay no brighter than the art.
- lightRotation, lightHeight, lightBehind and lightSoftness replace
lightAzimuth and lightElevation. An earlier light is read as the same
direction, so it shades exactly as before (old and new code give the
same colours for the four effects and five lights); effect.setParams
turns a stored one into the new form before applying new values, and
the parameter description names lightAzimuth and lightElevation as the
earlier form, so effect.apply and effect.setParams still take them. The
default light is the earlier default's: Rotation 125.26°, Height 30°.
- The dialog's Lighting tab shows the settings in the documented order;
Softness, Ambient Light and Light behind the object are in every
catalog, and the unused Direction, Elevation and Ambient are gone.
Light colour, presets, several lights and shadows are not built.
The public 3D and Materials panel options give Perspective as 0° to 160°
("how close or far the object appears"); it was a 0–100 % strength, for
Revolve, Extrude & Bevel, Inflate and Rotate alike.
- The eye stands where a sphere about the object's centre, √3 times its
half size (the box around it fits inside), fills that angle of the
view: 0° is none, wider brings the eye nearer, and at 160° it is just
outside the sphere, never inside the object. The documentation doesn't
say how the angle places the eye; docs/three-d.md says this is our
reading.
- `perspectiveAngle` (degrees) replaces `perspective` (%). A strength from
an earlier file or script is read as the angle that shows exactly the
same view: sin(angle / 2) = √3 × strength / 4, 100 % about 51.3°. An
angle given too wins. effect.setParams turns a stored strength into the
angle before applying new values. The parameter description names
`perspective` as the earlier form, so effect.apply and effect.setParams
still take it under the key check of #1592. Old and new code draw the four effects identically at 0, 25,
60 and 100 % (bounds equal to 1e-9).
- The dialogs' Perspective fields run 0–160°.
# Conflicts:
# docs/target-app-parity.md
The public 3D and Materials panel options list Depth, Twist, Taper and Cap
for every 3D type; Inflate had only Depth.
- Twist (0° to 360°) and Taper (1–100 %) turn and scale the back about the
shape's centre, as Extrude & Bevel's do: a puffed back is turned and
lowered in proportion, and the sides follow in slices.
- Cap: Solid closes the back; Hollow leaves it off, so the inflated front is
a shell open behind. The documentation doesn't say what Cap means for
Inflate; this reading is stated in docs/three-d.md.
- The dialog's Object tab shows them in the documented order (Depth, Twist,
Taper, Cap), then Volume and Both sides.
At 0 % volume a twisted, tapered Inflate has the extrusion's outline from
every side (tested).
A twisted, tapered, hollow Inflate draws on the canvas exactly as it
exports, and expands to faces with their own ids (tested). ROADMAP and the
parity pages list the new options; the 3D score stays at ~25 %.
Bevel Shape, Space, Bevel Inside, Bevel both sides and Presets, as the public 3D and Materials panel
options page names them. The 3D effects' error messages get rows in the twelve complete catalogs.
The three 3D types besides Revolve, as live effects in the three-d crate,
drawn as shaded vector faces by the canvas, SVG/PDF export and Expand
Appearance alike, from the public 3D and Materials documentation:
- Extrude & Bevel: depth (0-2000 pt), twist, taper, solid or hollow caps,
and bevels in six original cross-sections (classic, rounded, cove, step,
ogee) with width, height, repeat, spacing, inside and both sides.
- Inflate: the shape as a membrane under pressure (a Poisson solve held at
zero along the outline and holes), so it rises vertically from the edge
and rounds without creases; volume, depth and both sides. Steep cells
near the outline are refined without cracks.
- Rotate: every fill and stroke turned in 3D and shaded as one plane.
All three share Revolve's view and lighting parameters, the rotation and
light gizmos (turning about the art's centre) and a dialog with Object,
Rotation and Lighting tabs, with Position presets (Front ... Isometric
Bottom, our own values). An object takes one 3D effect. Inflate cuts the
grid cells that straddle a hole's edge with pathops, hence the new
pathops -> three-d layer order.
Docs: docs/three-d.md, mcp.md, and the 3D rows of the parity docs.
Add deterministic document-space grain, highlight controls and a live
dialog with preview, cancel, undo and appearance editing. Preserve
coverage, support existing native and raster export paths, translate
new labels and update parity documentation.
Validate the tonal curve, stationarity, subpixel strength, zoom filtering
and tile seams. Full cargo xtask ci passes all seven gates.
The report module moves to cmd/rasterfx/report.rs (the crate's foo.rs +
foo/ layout instead of #[path]). An object is the document's when its id
is below the document's next id, so building the report no longer walks
the whole document (and its symbols and patterns) on every PDF, SVG,
print and clipboard export. The parity docs say what changed in the
status line and one evidence bullet instead of a separate paragraph.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The 64 million pixel cap can silently lower raster-effect resolution. Report each original object's id and name with requested and achieved ppi through the existing export warnings, retaining the lowest resolution across its images.
Keep the cap and rendering unchanged, cover capped and uncapped exports, and record the evidence without changing parity scores.
Effect › Distort & Transform › Zig Zag… lays out its options the way
Illustrator's dialog does: Size and Ridges per segment each have a
slider beside their field, Relative / Absolute are radio buttons under
Size, and Points offers Smooth and Corner as radio buttons instead of a
dropdown. The label reads "Ridges per segment".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Eight raster effects on the pixel-filter pipeline
(vectorcraft_effects::pixel::brushstrokes), under a new Effect › Brush
Strokes submenu between Blur and Distort in the Raster Effects section:
- Accented Edges (edgeWidth pt 1..14, default 2; edgeBrightness 0..50,
38; smoothness 1..15, 5): the edges of the softened object painted
towards white chalk at high brightness or black ink at low brightness;
Smoothness also leaves the faint edges out. The shape stays as it was.
- Angled Strokes (directionBalance 0..100, 50; strokeLength pt 3..50, 15;
sharpness 0..10, 3): light areas in right diagonal strokes, dark areas
in left diagonal ones; 0 makes every stroke a left diagonal, 100 every
stroke a right diagonal. Sharpness brings detail and bristles back.
- Crosshatch (strokeLength pt 3..50, 9; sharpness 0..20, 6; strength
1..3, 1): the detail kept under pencil hatching along both diagonals,
Strength passes of it, the edges roughened.
- Dark Strokes (balance 0..10, 5; blackIntensity 0..10, 6;
whiteIntensity 0..10, 2): short strokes towards black in the dark
areas, long strokes towards white in the light areas.
- Ink Outlines (strokeLength pt 1..50, 4; darkIntensity 0..50, 20;
lightIntensity 0..50, 10): black outlines over the edges, ink hatching
in the shadows, shadows darkened and highlights lightened.
- Spatter (sprayRadius pt 0..25, 10; smoothness 1..15, 5): each pixel
shifted by noise up to 0.4 x the radius; Smoothness makes the grains
larger and smoother.
- Sprayed Strokes (strokeLength pt 0..20, 12; sprayRadius pt 0..25, 7;
strokeDirection Right Diagonal, Horizontal, Left Diagonal or Vertical):
strokes along the direction, scattered by up to 0.3 x the radius.
- Sumi-e (strokeWidth pt 3..15, 10; strokePressure 0..15, 2; contrast
0..40, 16): wet strokes, ink bleeding from the darks along paper
fibres, more ink with pressure and an S-curve contrast for rich blacks.
It works in value only: hues and saturation stay, and so does the
object's shape (no fringe in the transparency).
A stroke averages the colour along a segment through the pixel (16
samples at most, the art softened first when they lie more than a pixel
apart); strokes lie in rows around the object's centre, each row
starting at random and each stroke shifted along itself, with streaked
noise for the bristles. Everything is in document space, so the effects
look the same at any zoom or resolution, move with the object and draw
the same every time; outsets and reaches are exact. On a 2000 x 2000
raster in release the slowest settings take 0.3-2.0 s (Crosshatch at
50 pt with 3 passes the most).
Shared helpers instead of copies: edges::sobel (Glowing Edges now uses
it, unchanged), distort::displace and texture::Tones are pub(super).
Catalogue rows with parameter docs, menu entries, Sprayed Strokes'
direction dropdown, translations (de, es, fr, it, ja, ru, uk, zh-hans,
zh-hant; menu labels in cs and pt-br), tests (parameters and clamping,
outsets and reaches, each filter and option showing, chalk and ink on
the edges only, shadows and rich blacks, Sumi-e keeping the shape and
hues, hatching keeping the tone, strokes smearing along their direction,
Spatter's radius, resolution and move invariance, canvas, multithreaded,
PDF and SVG, the command sweep with junk parameters, the menu) and
ROADMAP.
Behaviour from public documentation only:
https://helpx.adobe.com/photoshop-elements/using/brush-stroke-filters.htmlhttps://www.computerhope.com/jargon/p/photoshop-sprayed-strokes.htm
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Progress docs per craftrules standards/progress-docs.md: target-app parity, gaps, roadmap with alpha gate, readiness by audience (full / mainstream / essentials) with Opus 5.5 hours, and feature, format, hardware, UI and localization parity docs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>