Skip to content

Navigation Menu

Sign in
Sign up

Repository files navigation

Schist

A layered image editor written in Rust on GPUI, with first-class PSD support and a plugin-first architecture — every tool, filter, format and menu command is a plugin, including the built-in ones.

Status: v1 feature-complete, plus two Photoshop-parity passes — 55 tools, 57 filters, 16 adjustments, all nine layer effects, and live vector shapes. 540 tests, clippy-clean, verified end-to-end under a real window. What is still missing is a short list now.

Backers

Thanks to the people and brands supporting Schist.

Build and run

# Linux needs GPUI's system dependencies:
sudo apt-get install build-essential pkg-config libfontconfig-dev \
 libwayland-dev libxkbcommon-x11-dev libxcb1-dev libxcb-render0-dev \
 libxcb-shape0-dev libxcb-xfixes0-dev libvulkan-dev clang
cargo run --release -p schist-app -- [file.psd|file.png|...]

Those are the packages to build against. Running also needs a Vulkan driver installed — GPUI renders through it, and the loader above is only the part that finds one. On Debian and Fedora that is mesa-vulkan-drivers; on Arch it is vulkan-driver (any of vulkan-radeon, vulkan-intel, nvidia-utils, ...), or vulkan-swrast on a virtual machine with no GPU driver of its own. With no driver installed Schist stops at startup and says which package is missing.

Schist also runs in the browser: make web assembles a static deployment (WebGPU, chunked wasm, a loading page) into dist/web/ — see docs/web.md for what's included and what isn't.

cargo test --workspace runs everything. make helpers cross-compiles the Photoshop plug-in helpers, which are separate binaries for the architectures a .8bf plug-in might be built for — see docs/8bf-host.md. Packaging scripts for macOS, Windows and Linux live in packaging/; tagging vX.Y.Z builds them all in CI, signing and notarizing the macOS bundle when the signing secrets are set. All three register .psd, .psb, .afphoto, .afdesign, .afpub and .af as openable, never as the default handler: an installed Schist joins the "Open with" menu rather than taking files off Photoshop or Affinity.

On macOS the bundle also carries two Quick Look app extensions, so Finder draws real thumbnails for those files and the space bar opens a real preview of them — layers, masks, blend modes and effects composited by Schist itself, or the writing app's own embedded preview when that is already big enough for the size asked for. Nothing needs enabling: the extensions register when the app is first opened, and schist-quicklook --render file.afphoto out.png shows what Quick Look would show. See docs/quicklook.md.

The logo is generated, not drawn: tools/logo.py holds the geometry and python3 tools/logo.py re-emits the SVGs and every .icns, .ico and .png the packaging needs. Edit the constants at the top of that file, never its output.

What it does

Documents. PSD and PSB read and write — layers, nested groups, masks, all 27 blend modes, adjustment layers, layer effects, vector shapes, 8/16/32-bit, RGB, greyscale, CMYK, Lab and Indexed, RLE and zip-compressed channels. Every block Schist doesn't understand is preserved byte-for-byte, so a round trip never loses work. Smart objects keep their source pixels, so transforming one repeatedly costs no more quality than transforming it once. Also PNG, JPEG, WebP and TIFF, plus HEIC/HEIF import (iPhone photos) and camera raw import: NEF, ARW, CR2, DNG, RAF, ORF, RW2, PEF, SRW and the rest. A capture opens in Camera Raw with sensor-domain white balance and exposure, live fast-demosaic previews and a best-quality render on Apply; the original capture and all 15 development settings stay attached to the layer and survive PSD/PSB save and reopen, so adjustments never compound on the previously developed pixels. The rendered document remains 16-bit sRGB. Raws decode through Schist's own clean-room schist-codec-raw crate (pure Rust, written from the public specifications and verified sample for sample against LibRaw across some 250 camera files), covering every vendor's codecs including Canon's CR3 and sRAW, Fuji's compressed RAF and SuperCCD, Sigma's Foveon and GoPro's VC-5; nothing links at build time and there is no fallback library to install, on the desktop or in the browser. The one thing it refuses is Nikon's licensed High Efficiency NEF, which no decoder reads without the vendor's SDK. HEIC decodes through libheif the same way: the system's copy if installed, otherwise Schist offers to download a hash-pinned, decode-only build (with its LGPL license texts) from libheif-prebuilt — nothing links at build time and the build stays pure Rust. Affinity files (.af/.afphoto/.afdesign/.afpub — Affinity 1, 2 and the unified Canva-era format) open through a natively reverse-engineered reader (docs/affinity-format.md): pixel layers, placed images, groups, masks, live shapes, free paths, editable text (set in the document's real fonts, GPOS kerning and all), layer effects — on groups too — and sixteen adjustment types come in as real layers, each verified against renders and exports from Affinity itself; whatever Affinity would re-render live and we can't rebuild yet is covered by the file's embedded flattened preview, imported as a hidden reference layer or, when nothing else survives, as the document itself. Export writes layered .af documents back (unified version-12 container): rasters as native tiles, groups, masks, clipping, blend modes, drop shadows / glows / outlines / colour overlays, and adjustment layers round-tripped from their preserved native parameters — the writer's object graphs re-serialize every fixture and corpus document byte-for-byte, so the on-disk shapes are exactly what Affinity itself writes. Text and vector layers export as pixels for now.

Selecting. Rectangular and elliptical marquee; free, polygonal and magnetic lassos; magic wand with tolerance and contiguity; quick selection that grows to match what you paint over; object selection that runs a segmentation network — U^2-Net, from Filter ▸ Neural Filters ▸ Manage Models — over the box you drew and cuts round what it finds, settling the boundary against the picture's own colours, and falls back to reading the box's border as background when there is no model or nothing in the box. Then Modify (expand, contract, border, smooth, feather), Grow, Similar, Colour Range, and save/load. Marching ants trace the selection's real boundary — holes and all.

Painting and retouching. Brush, pencil, eraser, background eraser, magic eraser, clone stamp, history brush, gradient, paint bucket, dodge/burn/sponge, blur, sharpen, smudge; spot healing and healing brushes, patch, content-aware move and red eye. Healing takes texture from the source and colour from around the edge, so patching a blemish gives you skin rather than a blurred blemish. Content-Aware Fill is a network and a patch search working together — the network, trained here and shipped in the binary, says what should be behind the hole, and the search finds that in the photograph and copies it in, so what lands there is real texture arranged the right way rather than either one's idea of an average.

Vector. Pen, freeform pen and curvature pen draw paths that are stored, so Path Selection and Direct Selection can edit them and Layer ▸ Path can fill, stroke or convert them to a selection. Rectangle, ellipse, line (with its own weight, 45° constrain and arrowheads), polygon and six custom shapes — as live shape layers by default, which keep their path, regenerate their pixels from it, and survive a PSD round trip as vectors. Editable text layers, including text on paths with a baseline offset and alignment, plus OpenType controls for kerning, ligatures, discretionary ligatures and small caps. Text settings survive PSD/PSB save and reopen. See docs/text.md.

Non-destructive. Sixteen adjustments — levels, curves, hue/saturation, brightness/contrast, black & white, colour balance, vibrance, exposure, photo filter, gradient map, selective colour, channel mixer, invert, posterize, threshold, solid colour — as layers, or applied straight to the pixels from Image ▸ Adjustments. Layer masks, clipping masks, group isolation, per-layer blend mode and opacity.

Layer effects. All nine: bevel & emboss, stroke, inner shadow, inner glow, satin, colour overlay, gradient overlay, outer glow and drop shadow, with Photoshop's Fill-vs-Opacity semantics so "Fill 0% plus a drop shadow" does what you expect.

Filters. A hundred and thirty-four, which is Photoshop's Filter menu with nothing left out: 3D, Artistic, Blur, Blur Gallery, Brush Strokes, Distort, Noise, Pixelate, Render, Sharpen, Sketch, Stylize, Texture, Video, Other, Camera Raw and Neural Filters, plus Lens Correction and Adaptive Wide Angle. All preview live on the canvas inside the selection, with Cancel restoring exactly. The count includes the forty-six effects Photoshop keeps inside the Filter Gallery — Watercolor, Sumi-e, Chrome, Stained Glass, Craquelure and the rest — which are ordinary filters here too, so each can be run from the menu on its own or stacked in the Filter Gallery, which previews the result of the lot.

Their dialogs match as well, which is most of what makes a filter feel like the one you know: Mezzotint's ten screens, Lens Blur's iris shapes, Smart Sharpen's choice of which blur it is undoing, Wave's several generators, Diffuse's anisotropic mode, Extrude's pyramids, Wind's blast and stagger.

And they read the same things Photoshop's read. The Sketch group draws in the foreground and background colours, as do Clouds, Fibers, Tiles, Neon Glow, Colored Pencil's paper and Diffuse Glow's glow — set the swatches to sepia and Stamp comes out as a sepia print. Displace warps through a map you pick from a file, red for horizontal and green for vertical, stretched or tiled. Flame burns along the active path when the document has one. Lens Blur can take its depth from the layer's transparency or from what is underneath it, so part of the picture stays sharp. Harmonization, Colour Transfer and Landscape Mixer match against the layer below. Everything a filter needs beyond its own pixels is gathered by the host and handed over, which is what FilterPlugin::wants_map, wants_path and wants_backdrop are for.

What is left is ergonomic rather than functional: Photoshop puts blur pins, light gizmos and flame paths on the canvas, and here they are position sliders and the path you already drew.

Warping. Liquify with all seven brushes, Puppet Warp (Moving Least Squares, so pins hold and nothing shears), Content-Aware Scale (seam carving, with the selection as the protect mask), and Vanishing Point, which clones along a perspective plane so the copy foreshortens with the surface.

Document furniture. Artboards and slices, each exportable to its own file; frames that clip their contents; notes; the Count tool; and layer comps that capture every layer's visibility and appearance under a name.

Colour. ICC profiles honoured on open, assign vs. convert as separate operations, a document→display transform, soft proofing, and ordered dithering when exporting to 8-bit.

GPU. Compositing runs on the GPU when an adapter exists: the layer tree — blend modes, masks, clipping, group isolation, all sixteen adjustment kinds — compiles to a compute-shader program (wgpu), and zooming, rotating and panning resample on the GPU too, which is what keeps large documents responsive. The big filter sweeps go the same way: the box passes behind every Gaussian, the lens blur's disc, and the displacement resample Liquify and Puppet Warp re-run on every pointer move — the operations that cross the whole selection per keystroke of a dialog, where a lens blur at radius 60 is eleven thousand taps a pixel. Content-Aware Scale runs there too, and runs entirely there: find the lowest-energy seam, cut it, start again is hundreds of full-image passes for one command, so the whole loop stays on the device and only the finished image comes back. Large warps and seam carves can exceed a storage-buffer binding: warp sources live in texture arrays and their output is banded; large carves keep their image planes in texture arrays and cumulative costs in two rows. Device texture limits and available memory still apply, and small carves stay on the CPU when dispatch overhead would cost more than the GPU saves. The CPU is the semantic reference throughout: parity tests hold the GPU to it, anything it can't express (layers mid-drag) or can't fit falls back for that call, and machines with no usable adapter just run the CPU path. Toggle it in Preferences, or override with SCHIST_GPU=0 / SCHIST_GPU=1.

Image. Mode (RGB, greyscale, CMYK, Lab, Indexed), Auto Tone / Contrast / Colour, image and canvas size, the five rotations and flips, crop and trim.

Editor. Rotate View, rulers with drag-out guides, grid and snapping, screen modes, light/dark themes, navigator, history with click-to-jump, unlimited undo, crash recovery, and a fully remappable keymap. Right-click the layers, history, colour or navigator panels — or the canvas — for Photoshop-style context menus (layer properties, duplicate, clipping mask, reorder, merge...).

Keyboard

Photoshop's defaults (⌘ on macOS, Ctrl elsewhere):

Tools V move · M marquee · L lasso · W wand · C crop · B brush · E eraser · S clone · J spot healing · Y history brush · G gradient · O dodge · P pen · A path selection · T type · U shapes · I eyedropper · H/space hand · Z zoom
Tool groups Shift+the tool's key cycles nested tools (Shift+M marquee ⇄ ellipse); hold or right-click a toolbar slot for its flyout
Edit ⌘Z / ⌘⇧Z undo・redo · ⌘X/C/V · ⌘⇧C copy merged · ⌘T free transform
Select ⌘A all · ⌘D deselect · ⌘⇧D reselect · ⌘⇧I inverse · shift/alt-drag to add/subtract
Layers ⌘⇧N new · ⌘J duplicate · ⌘⇧J via cut · ⌘G group · ⌘E/⌘⇧E merge · ⌘[ ⌘] reorder · ⌘⌥G clipping mask
Adjust ⌘L levels · ⌘M curves · ⌘U hue/sat · ⌘I invert
Fill ⇧F5 Fill... · ⌥⌫ / ⌃⌫ fill with fore/background
View ⌘0 fit · ⌘1 100% · ⌘R rulers · ⌘' grid · ⌘; guides · ⌘H extras · Tab/F screen modes · ⌘K preferences
Painting [/] brush size · digits set opacity · D/X default・swap colours

Mouse and touchpad

Two-finger scroll pans; Ctrl/⌘/Alt + scroll zooms toward the pointer. Prefer it the other way round? Preferences ▸ Zoom with scroll wheel swaps them, so plain scrolling zooms and the modifier pans.

Pinch-to-zoom works on macOS, Linux — Wayland, and X11 on xorg-server 21.1+ (XI 2.4) — and Windows touchscreens, zooming about the centre of the gesture, and stylus pressure drives brush size on all four backends. Upstream GPUI surfaces neither, so both come from a fork — IAmJSD/gpui, which adds PinchEvent, on_pinch and a pressure field on the mouse events on top of gpui 0.2.2 — pinned by revision in the workspace Cargo.toml.

The one pinch gap left is Windows precision touchpads, and it is a gap by choice. Windows delivers their pinches as Ctrl+scroll rather than as a gesture — which is already a zoom here anyway — and the only way to see the real gesture is Direct Manipulation, which cannot be claimed for pinches alone: the same claim covers two-finger pans and takes over scrolling with them. The fork implements it, behind GPUI_ENABLE_DIRECT_MANIPULATION=1, but it is untested on hardware and off by default rather than put in the path of ordinary scrolling. On a pre-21.1 X11 server there is no pinch at all. Ctrl+scroll, the zoom-with-scroll preference, ⌘+/⌘-, and the navigator's zoom slider work everywhere.

Remap anything in ~/.config/schist/keymap.json:

{ "ctrl-shift-x": "command:edit.fill_foreground", "f1": "tool:brush" }

Not there yet

  • CMYK and Lab edit in RGB. Files open, edit and save in their own mode, converting at the boundaries; the editing in between is RGB, so individual ink channels are not separately editable. This remains unresolved because tiles, editing operations and plugin APIs carry RGBA, and import has already converted the native channels to RGB. Correct native editing needs a coordinated storage, undo, compositor and plugin-contract migration; RGB-derived controls cannot recover independent CMYK separations. See the implementation constraints and remaining work.

Diagnostics

Schist sends one thing without being asked: a daily ping, so we know roughly how many people run it and on what. It carries a random ID (for counting the same machine once — it is tied to nothing else), the Schist version, OS and architecture, the CPU model and core count, the GPU adapter and driver the compositor opened, and the amount of RAM. No hostname, no username, no paths, nothing about what you are working on.

The server (telemetry.schist.app) keeps one row per ID: what the last ping said, the country Cloudflare places the request in — the address itself is not stored — and when that ID was first and last heard from.

Turn it off with the tick in Preferences ▸ Diagnostics, or — without launching the app — by creating an empty file named no_telemetry in the config folder (~/.config/schist/, or $XDG_CONFIG_HOME/schist/). SCHIST_NO_TELEMETRY=1 in the environment turns it off for one run.

Everything else it can send over the network is off until you turn it on: Check for Updates, the on-demand font and model downloads, and crash reporting.

Crash reporting is two separate ticks in Preferences ▸ Diagnostics. The first writes a report next to the crash-recovery snapshot in ~/.local/state/schist/crashes/ and sends nothing anywhere. The second also uploads it to the project's Sentry, and only appears on the official releases — a build from source is given no DSN, so the code that would report has nowhere to report to and never starts. Uploads carry no personal data, no hostname and no breadcrumbs, and the panic message has your home directory rewritten to ~ before it leaves, because a panic tends to quote the path it choked on and that path is your work.

Set SCHIST_CRASH_REPORTS=1 or SCHIST_CRASH_UPLOAD=1 to turn either on for a single run without changing preferences.

Plugins

Third-party plugins are sandboxed WebAssembly — no filesystem, network or clock, and a fuel budget so a runaway plugin can't hang the editor. A filter is one function:

schist_filter! {
 id: "com.example.sepia",
 name: "Sepia",
 category: "Plugins",
 params: [param("amount", "Amount", 0.0, 100.0, 100.0, "%")],
 apply: |pixels: &mut [f32], _w: usize, _h: usize, params: &Params| { /* ... */ }
}

Drop the .wasm in ~/.config/schist/plugins/ — or use File ▸ Plugins..., which also shows why anything failed to load. Full instructions and a format example: docs/plugin-guide.md.

MCP

schist-mcp is a Model Context Protocol server that drives Schist headless — sessions instead of windows. Every canvas tool, menu command, filter and adjustment in the registry the app uses is published as its own MCP tool, with its own parameters described, plus inline PNG rendering. It ships with every release; cargo build --release -p schist-mcp builds it from source. See docs/mcp.md.

The same tool surface also powers the in-app AI panel (View ▸ AI Panel): a sidebar that drives your installed claude or codex CLI against the document you have open, edits streaming onto the canvas as undoable history entries. No API keys — it uses the agent CLI you are already logged into. See docs/ai-panel.md.

Documentation

About

In alpha. The open source image editor that feels good to use.

Resources

Stars

19 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

AltStyle によって変換されたページ (->オリジナル) /