Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,9 @@ rustflags = [

[env]
CARGO_WORKSPACE_DIR = { value = "", relative = true }

[alias]
# Refill the generated regions of the repository's markdown from the registries.
# `cargo sync-docs -- --check` reports drift without writing; `tests/docs_md.rs`
# asserts the same thing, so CI catches a stale region either way.
sync-docs = "run --quiet -p darkly --bin sync-docs"
289 changes: 0 additions & 289 deletions AGENTS.md

This file was deleted.

1 change: 1 addition & 0 deletions AGENTS.md
2 changes: 1 addition & 1 deletion CLAUDE.md
331 changes: 325 additions & 6 deletions CONTRIBUTING.md

Large diffs are not rendered by default.

35 changes: 26 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<a href="https://github.com/darkly-art/darkly"><img src="https://github.com/user-attachments/assets/62115b89-ab63-453c-93ce-a513e500fad7" alt="darkly" width="675"></a>

[![Discord](https://img.shields.io/discord/1495886270780539021?label=Discord&logo=discord&logoColor=white&style=for-the-badge&color=9500ff)](https://discord.gg/kFz2FGhbpu)
[![Patreon](https://img.shields.io/badge/Patreon-Forbidden_Relics-orange?logo=patreon&style=for-the-badge&color=6914ff)](https://www.patreon.com/c/DarklyArt)
[![Patreon](https://img.shields.io/badge/Patreon-Hidden_Relics-orange?logo=patreon&style=for-the-badge&color=6914ff)](https://www.patreon.com/c/DarklyArt)
[![Blog](https://img.shields.io/badge/Blog-Deranged_Texts-orange?logo=substack&logoColor=white&style=for-the-badge&color=4400ff)](https://darkly.art/blog)

![Rust](https://img.shields.io/badge/Rust-000000?style=for-the-badge&logo=rust&logoColor=9500ff)
Expand All @@ -14,7 +14,7 @@
> [!IMPORTANT]
> **Darkly is in beta**! Features are being [added daily](#feature-roadmap). Please [report bugs](https://github.com/darkly-art/darkly/issues/new) so we can squash them.

Do you suffer from the _oppressive sanity_ of rulers, guides, and nondestructive workflows? Break free with [Darkly](https://darkly.art), the home of happy accidents and beautiful catastrophies. Madness isn't a bug, it's a feature.
Do you suffer from the _oppressive sanity_ of rulers, guides, and nondestructive workflows? Break free with [Darkly](https://darkly.art), the home of happy accidents and beautiful catastrophies. Embrace the chaos, and release your hidden masterpiece. Madness isn't a bug, it's a feature.

Darkly is a Photoshop alternative where painters are first-class citizens. It has a powerful brush engine, and **[dark arts](#dark-arts)** to help you commune with your imagination.

Expand Down Expand Up @@ -43,18 +43,35 @@ Darkly's unique brushes live inside a node-based system. This enables infinite c

### Veils

https://github.com/user-attachments/assets/ee281ac2-37a8-4e52-91b3-78d564420e9d
Veils are where Darkly gets its name; *"For now we see through a glass, darkly"*. They're a special layer that sits above the viewport, visible only to the artist. By shrouding your canvas behind a mysterious pane, they invite you to see something that maybe wasn't there before.

Veils are where Darkly gets its name; *"For now we see through a glass, darkly"*. They're a special type of layer that sits overtop the viewport, visible only to the artist. By shrouding your art behind a mysterious pane, they invite you to see something that maybe wasn't there before.
![veil-demo](https://github.com/user-attachments/assets/df05c881-4572-46a1-9a31-366236fabbd3)

Veils have practical uses too:
Veils are nondestructive. You can paint as usual, behind the veil, and when you disable it, you'll see the full-res result.

<!-- darkly:catalog-table catalog=veils -->
| | Name | What it does |
| :-: | --- | --- |
| <img src="docs/images/previews/veils/black_and_white.jpg" width="200" alt="Black and White"> | **Black and White** | Desaturate to black and white — six grayscale formulas or custom channel weights, with an optional color tint. |
| <img src="docs/images/previews/veils/chromatic_aberration.jpg" width="200" alt="Chromatic Aberration"> | **Chromatic Aberration** | Split the color channels apart along their hue axes, like a misaligned lens. |
| <img src="docs/images/previews/veils/frozen.jpg" width="200" alt="Frozen"> | **Frozen** | Frost the view behind a pane of refracting ice. |
| <img src="docs/images/previews/veils/grain.jpg" width="200" alt="Grain"> | **Grain** | Film grain noise over the view, optionally animated. |
| <img src="docs/images/previews/veils/lens_blur.jpg" width="200" alt="Lens Blur"> | **Lens Blur** | Defocus the view with a soft camera-lens blur. |
| <img src="docs/images/previews/veils/painting.jpg" width="200" alt="Painting"> | **Painting** | Smooth the view into painterly, brush-like daubs. |
| <img src="docs/images/previews/veils/pixelate.jpg" width="200" alt="Pixelate"> | **Pixelate** | Downsample the view into a blocky pixel mosaic. |
| <img src="docs/images/previews/veils/rainy_glass.jpg" width="200" alt="Rainy Glass"> | **Rainy Glass** | Raindrops run down a pane of glass over the view. |
| <img src="docs/images/previews/veils/vhs.jpg" width="200" alt="VHS"> | **VHS** | Analog VHS tape artifacts — scanlines, noise, and color bleed. |
| <img src="docs/images/previews/veils/watercolor.jpg" width="200" alt="Watercolor"> | **Watercolor** | Bleed the view outward into soft watercolor washes. |
<!-- /darkly:catalog-table -->

Veils are a fun toy, but they have practical uses too:

- By hiding fine details, they can prevent **premature fixation on detail**, freeing you to focus on composition.
- During the sketching / ideation phase, they can help with **blank page syndrome** and **destructive self-criticism** by giving you permission to be messy, and explore freely.
- They can also help remedy **art fatigue** (losing eyes for a piece by staring at it for too long) by helping you view it through a fresh lens.
- They can also help remedy **art fatigue** (losing eyes for a piece by staring at it for too long) by helping you see it through a fresh lens.

> [!NOTE]
> Veils live in their own group, but within it you can stack and order them however you like. Remember that adding too many can drain your battery, due to the heavy load on your GPU.
> You can add unlimited veils, and stack them in any order; but adding too many can drain your battery because of the heavy load on your GPU.

### Voids

Expand Down Expand Up @@ -231,15 +248,15 @@ See the [crate README](crates/darkly/README.md) for a runnable example, and the

## Contribution

We love hackers as much as we love artists. Contributions are welcome! Please see [AGENTS.md](./AGENTS.md) for details on how to contribute and rules of thumb for the repo.
We love hackers as much as we love artists. Contributions are welcome! Please see [CONTRIBUTING.md](./CONTRIBUTING.md) for details on how to contribute and rules of thumb for the repo.

### Use of AI

It's acceptable to use AI for this codebase, but careless vibe coding is **strictly forbidden**.

I (TheTechromancer) learned to code before AI, and have spent much of my career maintaining [large codebases](https://github.com/blacklanternsecurity/bbot). The [danger](https://www.reddit.com/r/vibecoding/comments/1su03dk/vibe_coded_for_6_months_my_codebase_is_a_disaster/) of feature creep and architectural bloat is real, which is why whenever a feature is implemented in Darkly, a human must first understand the changes and their long-term implications for the codebase.

Great care is being taken to keep Darkly lean and clean. This means enforcing modularity, guarding vigilantly against duplicate/dead code, and writing a *shit ton* of unit tests, including at least one regression test for every bug. See [AGENTS.md](AGENTS.md) for how we avoid AI slop.
Great care is being taken to keep Darkly lean and clean. This means enforcing modularity, guarding vigilantly against duplicate/dead code, and writing a *shit ton* of unit tests, including at least one regression test for every bug. See [CONTRIBUTING.md](CONTRIBUTING.md) for how we avoid AI slop.

Note that while we allow AI for coding, we are **unlikely to accept any PR implementing generative AI in Darkly itself**. AI features are not off the table; however they must run fully offline and without any reliance on third party APIs. Additionally, any feature that speeds up generation while sacrificing creative input or control from the artist, will likely be rejected.

Expand Down
10 changes: 7 additions & 3 deletions crates/darkly/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ categories = ["graphics", "rendering", "wasm"]
[features]
profile = []
# Enables blocking GPU readbacks, `gpu::test_utils`, and `*::test_readback_*`
# accessors. WebGPU/WASM deadlocks on these (see CLAUDE.md "No Blocking GPU
# accessors. WebGPU/WASM deadlocks on these (see CONTRIBUTING.md "No Blocking GPU
# Readbacks"); the feature exists so `cargo test` and the bench bins can opt
# in while production / WASM builds cannot reach the API at all.
testing = []
Expand Down Expand Up @@ -61,12 +61,16 @@ pollster = "1.0"
naga = { version = "29.0", features = ["wgsl-in"] }
syn = { version = "3", features = ["visit", "full"] }

# The hyphenated name does not match the file stem, so cargo cannot infer the
# path from it. No `required-features`: the exporter needs no GPU.
# The hyphenated names do not match the file stems, so cargo cannot infer the
# paths from them. No `required-features`: neither needs a GPU.
[[bin]]
name = "export-docs"
path = "src/bin/export_docs.rs"

[[bin]]
name = "sync-docs"
path = "src/bin/sync_docs.rs"

[[bin]]
name = "render_docs"
required-features = ["testing"]
Expand Down
1 change: 0 additions & 1 deletion crates/darkly/brushes/airbrush.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
name: Airbrush
category: Basic
description: A fully soft disc that builds color up gradually; hold it in one place and the tone deepens.
nodes:
pen_input:
Expand Down
1 change: 0 additions & 1 deletion crates/darkly/brushes/blur.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
name: Blur
category: Effects
description: Softens whatever is already on the layer instead of laying down color.
nodes:
pen_input:
Expand Down
1 change: 0 additions & 1 deletion crates/darkly/brushes/calligraphy.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
name: Calligraphy
category: Basic
description: A broad elliptical nib held at a fixed angle, so strokes thicken and thin with direction.
nodes:
pen_input:
Expand Down
1 change: 0 additions & 1 deletion crates/darkly/brushes/charcoal.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
name: Charcoal
category: Dry Media
description: A grainy stick that catches on the paper's tooth, laying color down heavily where you press and skipping where you don't.
nodes:
brush_settings:
Expand Down
1 change: 0 additions & 1 deletion crates/darkly/brushes/clone.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
name: Clone
category: Misc
description: Paints with pixels sampled from elsewhere on the canvas rather than with the current color.
nodes:
pen_input:
Expand Down
3 changes: 1 addition & 2 deletions crates/darkly/brushes/hair.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
name: Hair
category: Dry Media
description: A lock of individual strands with optional twirling
nodes:
add:
Expand All @@ -9,7 +8,7 @@ nodes:
inputs:
size: 0.2
spacing: 0.01
stabilize: 1.0
stabilize: 0.5
circle:
type: circle
curve:
Expand Down
1 change: 0 additions & 1 deletion crates/darkly/brushes/ink_pen.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
name: Ink Pen
category: Basic
description: A crisp-edged nib with a slow pressure ramp, for confident line work that holds its weight.
nodes:
brush_settings:
Expand Down
2 changes: 0 additions & 2 deletions crates/darkly/brushes/liquify.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
name: Liquify
category: Effects
description: Pushes the pixels under the cursor along the stroke, warping the image without repainting it.
nodes:
pen_input:
Expand All @@ -14,7 +13,6 @@ nodes:
type: liquify
connections:
- 'pen_input.distance -> liquify.distance'
- 'pen_input.drawing_angle -> liquify.direction'
- 'pen_input.motion -> liquify.motion'
- 'pen_input.position -> liquify.position'
exposed_ports:
Expand Down
1 change: 0 additions & 1 deletion crates/darkly/brushes/rough_ink.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
name: Rough Ink
category: Basic
description: An ink nib whose edge is reshaped at random every dab — line work that looks bitten rather than printed.
nodes:
brush_settings:
Expand Down
1 change: 0 additions & 1 deletion crates/darkly/brushes/rough_watercolor.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
name: Rough Watercolor
category: Wet Media
description: The same bleeding pigment over a rougher paper — granulated, with a broken edge.
nodes:
pen_input:
Expand Down
1 change: 0 additions & 1 deletion crates/darkly/brushes/smooth_watercolor.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
name: Smooth Watercolor
category: Wet Media
description: Wet pigment that pools and blends into what is already on the canvas, with a soft, even edge.
nodes:
pen_input:
Expand Down
1 change: 0 additions & 1 deletion crates/darkly/brushes/smudge.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
name: Smudge
category: Effects
description: Drags existing pigment along the stroke, the way a finger pulls through wet paint.
nodes:
pen_input:
Expand Down
37 changes: 27 additions & 10 deletions crates/darkly/brushes/sponge.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
name: Sponge
category: Dry Media
description: A textured brush useful for laying down big, smooth shapes
nodes:
add:
Expand All @@ -15,9 +14,9 @@ nodes:
inputs:
curve:
- - 0.0
- 0.70523924
- - 0.59307975
- 0.36470732
- 0.7
- - 0.6
- 0.36
- - 1.0
- 0.0
curve_2:
Expand All @@ -26,12 +25,23 @@ nodes:
curve:
- - 0.0
- 0.0
- - 0.6392242
- 0.36304563
- - 0.64
- 0.36
- - 1.0
- 1.0
curve_3:
type: curve
inputs:
curve:
- - 0.0
- 0.0
- - 1.0
- 0.5
input: 0.10
levels:
type: levels
multiply:
type: multiply
noise:
type: noise
inputs:
Expand All @@ -47,10 +57,10 @@ nodes:
type: polygon
inputs:
points: 4
rounding: 0.30
softness: 0.30
squeeze: 0.35
squeeze_angle: -0.7853981852531433
rounding: 0.5
softness: 0.3
squeeze: 0.5
squeeze_angle: -0.78
random:
type: random
random_2:
Expand All @@ -63,9 +73,12 @@ nodes:
a: 0.11924592405557632
connections:
- add.result -> levels.input
- brush_settings.size -> multiply.a
- curve.output -> levels.in_low
- curve_2.output -> paint.flow
- curve_3.output -> multiply.b
- levels.output -> stamp.tip
- multiply.result -> noise.scale
- noise.value -> subtract.b
- paint_color.color -> stamp.color
- pen_input.drawing_angle -> polygon.rotation_input
Expand All @@ -84,3 +97,7 @@ exposed_ports:
paint.opacity: {}
polygon.softness: {}
polygon.rounding: {}
curve_3.input:
label: Texture Size
description: Fineness of the brush texture
icon: mdi:blur
Loading