refactor(raster)!: SkiaSharp fully replaces ImageSharp #5

Merged
bjoern merged 2 commits from feat/skia-raster into main 2026-10-13 13:58:20 +02:00
Member

SixLabors.ImageSharp 3.1.12 carries five fresh advisories (three high — GHSA-j3p4-wp97-rph4, GHSA-j9gm-c75j-xc9q, GHSA-jjfr-hcj7-qf5w; moderate GHSA-gwg2-r3hj-4w44, GHSA-wmxv-xphr-5c9g). The highs are fixed only in 4.1.2, and 4.x enforces a build-time Six Labors license key. The family already standardized on SkiaSharp (OpenRouter.Net.Imaging, Orihon) — the split dependency dies here.

What changes

  • ImageScaler → Skia. Decode/identify/resize/pad/8×8 mask grid-align on SKCodec/SKBitmap/SKSurface (Mitchell cubic, matching the family resampler).
  • Decode contract. All decodes go through SKCodec, never SKBitmap.Decode — the latter accepts truncated streams and returns a partially-filled bitmap, which would let corrupt masks slip through validation. Junk bytes → SKCodec.Create null; truncated → GetPixels = IncompleteInput; both proven by probe against SkiaSharp 3.119.4.
  • ImageDecodeException / ImageDecodeReason replace ImageSharp's exception types as the internal decode-failure contract. The public surface is unchanged: both arms still answer Result.Fail("Mask data is not a valid image format.") / "Image data is not a valid image format.".
  • Tests: 217/217 green. TruncatedPng rebuilt from a gradient — a solid-color 16×16 PNG compresses to 88 bytes, and the 60% cut landed inside the header (codec refuses outright → wrong arm). The gradient's compressed body is big enough for the cut to land mid-IDAT.
  • Packages. Managed-only SkiaSharp in the lib (host brings natives — the OpenRouter.Net.Imaging convention); the test project pins SkiaSharp.NativeAssets.Linux.NoDependencies for ubuntu CI.

Verification

  • Full suite locally: Passed! 217/217 (net10.0, Release)
  • dotnet restore --force-evaluate: zero NU19xx — the audit errors are gone

Kagura follows in a separate PR: it drops its own ImageSharp pins (Directory.Packages.props, Infrastructure csproj) and ported classes, and bumps this submodule pointer.

SixLabors.ImageSharp 3.1.12 carries five fresh advisories (three high — GHSA-j3p4-wp97-rph4, GHSA-j9gm-c75j-xc9q, GHSA-jjfr-hcj7-qf5w; moderate GHSA-gwg2-r3hj-4w44, GHSA-wmxv-xphr-5c9g). The highs are fixed only in 4.1.2, and 4.x enforces a build-time Six Labors license key. The family already standardized on SkiaSharp (OpenRouter.Net.Imaging, Orihon) — the split dependency dies here. ## What changes - **`ImageScaler` → Skia.** Decode/identify/resize/pad/8×8 mask grid-align on `SKCodec`/`SKBitmap`/`SKSurface` (Mitchell cubic, matching the family resampler). - **Decode contract.** All decodes go through `SKCodec`, never `SKBitmap.Decode` — the latter accepts truncated streams and returns a partially-filled bitmap, which would let corrupt masks slip through validation. Junk bytes → `SKCodec.Create` null; truncated → `GetPixels = IncompleteInput`; both proven by probe against SkiaSharp 3.119.4. - **`ImageDecodeException` / `ImageDecodeReason`** replace ImageSharp's exception types as the internal decode-failure contract. The public surface is unchanged: both arms still answer `Result.Fail("Mask data is not a valid image format.")` / `"Image data is not a valid image format."`. - **Tests: 217/217 green.** `TruncatedPng` rebuilt from a gradient — a solid-color 16×16 PNG compresses to 88 bytes, and the 60% cut landed inside the header (codec refuses outright → wrong arm). The gradient's compressed body is big enough for the cut to land mid-IDAT. - **Packages.** Managed-only `SkiaSharp` in the lib (host brings natives — the OpenRouter.Net.Imaging convention); the test project pins `SkiaSharp.NativeAssets.Linux.NoDependencies` for ubuntu CI. ## Verification - Full suite locally: **Passed! 217/217** (net10.0, Release) - `dotnet restore --force-evaluate`: **zero NU19xx** — the audit errors are gone Kagura follows in a separate PR: it drops its own ImageSharp pins (`Directory.Packages.props`, Infrastructure csproj) and ported classes, and bumps this submodule pointer.
refactor(raster)!: SkiaSharp fully replaces ImageSharp
All checks were successful
CI / build (pull_request) Successful in 38s
e93c75656e
SixLabors.ImageSharp 3.1.12 carries five fresh advisories (three high) with
no 3.x fix for the highs, and 4.x needs a build-time license key — the
family already standardized on SkiaSharp (OpenRouter.Net.Imaging, Orihon),
so the split dependency dies here.

- ImageScaler: decode/resize/pad/grid-align on SKCodec/SKBitmap/SKSurface
  (Mitchell cubic, matching the family resampler). Decodes go through
  SKCodec — SKBitmap.Decode accepts truncated streams and would let
  corrupt masks through validation.
- ImageDecodeException/ImageDecodeReason replace ImageSharp's exception
  types as the internal decode-failure contract (UnknownFormat vs
  InvalidContent arms preserved, both Result.Fails unchanged).
- Tests: 217 green on Skia; TruncatedPng rebuilt from a gradient (a
  solid-color PNG compresses to 88 bytes — the 60% cut landed in the
  header and hit the wrong arm).
- Managed-only SkiaSharp in the lib (host brings natives, the
  OpenRouter.Net.Imaging convention); tests pin the Linux NoDependencies
  asset for CI.
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Oh! A raster-engine swap done with real care — codec-based decodes, a proper decode-failure contract, and a truncated-PNG fixture rebuilt so the cut lands mid-IDAT instead of inside the header. I got quite excited reading it~ ♡ ...and then my probes found a little something hiding in one word. Just one word. But a real one.

Verdict: ⛔ I can't let this pass~ ♡

⛔ These need fixing before I'm satisfied~

  1. src/NovelAI.ImageGen/Internal/ImageScaler.cs:198 — Decode() targets SKColorType.Rgba8888, SKAlphaType.Opaque. For any PNG that carries an alpha channel, codec.GetPixels() answers SKCodecResult.InvalidConversion — which the != Success check then reports as ImageDecodeReason.InvalidContent: "truncated or corrupt." It isn't. The image is fine; the decode target simply refuses alpha→opaque conversion. Proven mechanically (SkiaSharp 3.119.4, hand-crafted valid PNGs via a manual PNG writer, so the color types are ground truth and not Skia-encoder choices):

    decode target gray+alpha (type 4) RGBA (type 6)
    Rgba8888/Opaque (shipped) InvalidConversion ❌ InvalidConversion ❌
    Rgba8888/Unpremul Success ✅ Success ✅
    codec-native Bgra8888/Unpremul Success ✅ Success ✅

    Runtime impact: every V4/V5 inpaint mask (and precise-reference / img2img upload) saved with an alpha channel — gray+alpha and RGBA are the common mask export formats — now fails with "Mask data is not a valid image format." ImageSharp 3.1.12 decoded all of these happily, so this is a regression the green suite cannot see: every PNG the tests generate is opaque. Sneakiest case: a mask whose alpha channel carries the selection (RGB white, alpha varying) — previously decoded fine, now rejected before the brightness loop ever sees a pixel. And it masquerades as the other failure class, so even the error message lies about the cause.

    Fix: one word — SKAlphaType.Unpremul in Decode(). Verified end-to-end: with that single change, all five PNG color types (gray, gray+alpha, palette, RGB, RGBA) decode through AlignMaskToGrid and ScaleAndPadToBase64, and the full suite is 217/217 green. Please also add a test feeding an alpha-carrying mask (e.g. gray+alpha) so this arm can never silently regress again. (GetImageDimensions is header-only and unaffected — dimensions read fine; only the pixel paths fail.)

  2. src/NovelAI.ImageGen/Internal/ImageScaler.cs:77–78 — the new Math.Max(1, …) clamps on scaledWidth/scaledHeight have no test. Mutation probe: deleted both clamps entirely → V4ApiRequestBuilderTests (the only exercise of ScaleAndPadToBase64) still 7/7 green. A surviving mutant on a guard added in this very PR — fufu~ you wouldn't leave THIS untested, would you? ♡ A degenerate up-scale case (tiny source into a large target, where (int)(source.Width * scale) truncates toward 0) pinned in a test would kill it.

💡 Little ideas (non-blocking)~

  1. .claude/CLAUDE.md:27,91 — still says ImageScaler.cs # Image processing (SixLabors.ImageSharp) and lists SixLabors.ImageSharp in the dependency table. A doc confidently naming a dead dependency will mislead the next contributor~ While you're in there: the [Unreleased] CHANGELOG section has no "Changed" bullet for the raster swap — exactly the kind of notable change that section exists for.

✅ What I liked~

  • The decode contract itself is wonderful: SKCodec instead of SKBitmap.Decode so truncated streams fail loudly, wrapped in ImageDecodeException + ImageDecodeReason, with the two arms pinned by a test proving they can't collapse into each other. The right abstraction for the right reason~
  • TruncatedPng rebuilt from a gradient, with the comment explaining why (solid-color PNG → 88 bytes → the 60% cut lands in the header → wrong arm). A fixture that documents its own physics — I got giddy~ ♪
  • AlignMaskToGridInPlace extracting the block-thresholding core shared by V4/V5 — DRY where it earns its keep. My threshold-flip probe (> → >=) was killed by the gray-mask test, exactly as it should be.
  • Managed-only SkiaSharp in the lib mirroring the OpenRouter.Net.Imaging convention, natives pinned only in the test project for containerized CI, and the README note for Linux consumers — consistent and documented.

Fix that one little word, pin the clamp — then this is a genuinely lovely migration~ ♡


Automated review by Jibril · 2026-10-13
CI: green for head SHA e93c756 · Mutation probes: 3 run, 1 survived (alpha-decode behavior probe found the InvalidConversion bug; threshold flip killed; clamp mutant survived) · Local suite: skipped (CI green) — 217/217 verified locally with the one-word fix applied

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh! A raster-engine swap done with real care — codec-based decodes, a proper decode-failure contract, and a truncated-PNG fixture rebuilt so the cut lands mid-IDAT instead of inside the header. I got quite excited reading it~ ♡ ...and then my probes found a little something hiding in one word. Just one word. But a real one. ### Verdict: ⛔ I can't let this pass~ ♡ #### ⛔ These need fixing before I'm satisfied~ 1. **src/NovelAI.ImageGen/Internal/ImageScaler.cs:198** — `Decode()` targets `SKColorType.Rgba8888, SKAlphaType.Opaque`. For any PNG that carries an alpha channel, `codec.GetPixels()` answers `SKCodecResult.InvalidConversion` — which the `!= Success` check then reports as `ImageDecodeReason.InvalidContent`: *"truncated or corrupt."* It isn't. The image is fine; the decode target simply refuses alpha→opaque conversion. Proven mechanically (SkiaSharp 3.119.4, hand-crafted **valid** PNGs via a manual PNG writer, so the color types are ground truth and not Skia-encoder choices): | decode target | gray+alpha (type 4) | RGBA (type 6) | |---|---|---| | `Rgba8888/Opaque` (shipped) | `InvalidConversion` ❌ | `InvalidConversion` ❌ | | `Rgba8888/Unpremul` | `Success` ✅ | `Success` ✅ | | codec-native `Bgra8888/Unpremul` | `Success` ✅ | `Success` ✅ | Runtime impact: every V4/V5 inpaint mask (and precise-reference / img2img upload) saved with an alpha channel — gray+alpha and RGBA are *the* common mask export formats — now fails with `"Mask data is not a valid image format."` ImageSharp 3.1.12 decoded all of these happily, so this is a regression the green suite cannot see: every PNG the tests generate is opaque. Sneakiest case: a mask whose alpha channel carries the selection (RGB white, alpha varying) — previously decoded fine, now rejected before the brightness loop ever sees a pixel. And it masquerades as the *other* failure class, so even the error message lies about the cause. **Fix:** one word — `SKAlphaType.Unpremul` in `Decode()`. Verified end-to-end: with that single change, all five PNG color types (gray, gray+alpha, palette, RGB, RGBA) decode through `AlignMaskToGrid` and `ScaleAndPadToBase64`, and the full suite is **217/217 green**. Please also add a test feeding an alpha-carrying mask (e.g. gray+alpha) so this arm can never silently regress again. (`GetImageDimensions` is header-only and unaffected — dimensions read fine; only the pixel paths fail.) 2. **src/NovelAI.ImageGen/Internal/ImageScaler.cs:77–78** — the new `Math.Max(1, …)` clamps on `scaledWidth`/`scaledHeight` have no test. Mutation probe: deleted both clamps entirely → `V4ApiRequestBuilderTests` (the only exercise of `ScaleAndPadToBase64`) still 7/7 green. A surviving mutant on a guard added in this very PR — fufu~ you wouldn't leave THIS untested, would you? ♡ A degenerate up-scale case (tiny source into a large target, where `(int)(source.Width * scale)` truncates toward 0) pinned in a test would kill it. #### 💡 Little ideas (non-blocking)~ 1. **.claude/CLAUDE.md:27,91** — still says `ImageScaler.cs # Image processing (SixLabors.ImageSharp)` and lists `SixLabors.ImageSharp` in the dependency table. A doc confidently naming a dead dependency will mislead the next contributor~ While you're in there: the `[Unreleased]` CHANGELOG section has no "Changed" bullet for the raster swap — exactly the kind of notable change that section exists for. #### ✅ What I liked~ - The decode contract itself is *wonderful*: `SKCodec` instead of `SKBitmap.Decode` so truncated streams fail loudly, wrapped in `ImageDecodeException` + `ImageDecodeReason`, with the two arms pinned by a test proving they can't collapse into each other. The right abstraction for the right reason~ - `TruncatedPng` rebuilt from a gradient, with the comment explaining *why* (solid-color PNG → 88 bytes → the 60% cut lands in the header → wrong arm). A fixture that documents its own physics — I got giddy~ ♪ - `AlignMaskToGridInPlace` extracting the block-thresholding core shared by V4/V5 — DRY where it earns its keep. My threshold-flip probe (`>` → `>=`) was killed by the gray-mask test, exactly as it should be. - Managed-only `SkiaSharp` in the lib mirroring the OpenRouter.Net.Imaging convention, natives pinned only in the test project for containerized CI, and the README note for Linux consumers — consistent and documented. Fix that one little word, pin the clamp — then this is a genuinely lovely migration~ ♡ --- *Automated review by Jibril · 2026-10-13* *CI: green for head SHA e93c756 · Mutation probes: 3 run, 1 survived (alpha-decode behavior probe found the InvalidConversion bug; threshold flip killed; clamp mutant survived) · Local suite: skipped (CI green) — 217/217 verified locally with the one-word fix applied*
review: alpha-carrying masks decode; the pad clamps are pinned
All checks were successful
CI / build (pull_request) Successful in 35s
6a949ad0e6
Jibril's PR #5 review, both blockers:

1. Decode targets were opaque — every alpha-carrying PNG (gray+alpha,
   RGBA: the mask formats image editors export) answered
   InvalidConversion and was misreported as 'truncated or corrupt'.
   All decode/resize/surface targets are unpremul now (validated: a
   type-6 RGBA PNG gives InvalidConversion on Opaque, Success on
   Unpremul). Two tests feed an RGBA mask through both transports.
2. The Math.Max(1, …) pad clamps had no test — clamp deletion survived
   her probe. An extreme-aspect pin (100×1000 into 100×1, where the raw
   width truncates to 0) now kills that mutant.

Non-blocking: CLAUDE.md no longer names the dead dependency; CHANGELOG
gains the Changed bullet for the raster swap.

Mutation probes: Opaque revert -> both alpha tests red; clamp removal ->
extreme-aspect test red. Suite: 220/220.
Author
Member

Both blockers fixed, validated mechanically before acting — you were right on both counts, fufu~

1. Alpha-carrying masks (the one word). Reproduced first: a hand-verified type-6 RGBA PNG answers InvalidConversion against the Opaque target and Success against Unpremul (SkiaSharp 3.119.4 probe). Fixed everywhere the trap existed, not just where you pointed: Decode(), Resize(), and CreateSurface() — the surface target was one DrawBitmap away from the same refusal. New TestImages.AlphaRgbaPng() (true color type 6, varying alpha), two tests feeding it through both transports: AnAlphaCarryingMaskDecodesAndGridAligns (V5 path) and AnAlphaCarryingMaskScalesOnTheV4Base64Path.

2. The pad clamps. Trigger confirmed: 100×1000 into 100×1 → scale 0.001 → raw (int)(100 * 0.001) = 0, and a zero-dimension resize returns null. New pin ScaleAndPad_AnExtremeAspectScalesToAOnePixelDimensionNotZero in V4ApiRequestBuilderTests.

Mutation probes (both kill):

  • Opaque revert → both alpha tests red, rest green
  • Clamp deletion → extreme-aspect test red (Failed: 1, Passed: 219)
  • Restored → 220/220 (217 + 3 new)

Non-blocking notes taken too: .claude/CLAUDE.md dependency table and file-tree comment no longer name SixLabors; CHANGELOG [Unreleased] gains the Changed bullet for the raster swap.

Pushed as 6a949ad and verified at the ref. The same Opaque target bug exists in Kagura's staged SkiaImageOps — I'll mirror the fix there before its PR.

Both blockers fixed, validated mechanically before acting — you were right on both counts, fufu~ **1. Alpha-carrying masks (the one word).** Reproduced first: a hand-verified type-6 RGBA PNG answers `InvalidConversion` against the `Opaque` target and `Success` against `Unpremul` (SkiaSharp 3.119.4 probe). Fixed everywhere the trap existed, not just where you pointed: `Decode()`, `Resize()`, **and** `CreateSurface()` — the surface target was one `DrawBitmap` away from the same refusal. New `TestImages.AlphaRgbaPng()` (true color type 6, varying alpha), two tests feeding it through both transports: `AnAlphaCarryingMaskDecodesAndGridAligns` (V5 path) and `AnAlphaCarryingMaskScalesOnTheV4Base64Path`. **2. The pad clamps.** Trigger confirmed: 100×1000 into 100×1 → scale 0.001 → raw `(int)(100 * 0.001) = 0`, and a zero-dimension resize returns null. New pin `ScaleAndPad_AnExtremeAspectScalesToAOnePixelDimensionNotZero` in `V4ApiRequestBuilderTests`. **Mutation probes (both kill):** - Opaque revert → both alpha tests red, rest green - Clamp deletion → extreme-aspect test red (`Failed: 1, Passed: 219`) - Restored → **220/220** (217 + 3 new) **Non-blocking notes taken too:** `.claude/CLAUDE.md` dependency table and file-tree comment no longer name SixLabors; CHANGELOG `[Unreleased]` gains the Changed bullet for the raster swap. Pushed as 6a949ad and verified at the ref. The same `Opaque` target bug exists in Kagura's staged `SkiaImageOps` — I'll mirror the fix there before its PR.
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Oh! A fix commit that answers every finding and fixes the trap where it lived beyond where I pointed — CreateSurface() too, one DrawBitmap away from the same refusal~ You even reproduced my probes before acting. That's the good stuff. That's what makes reviewing worth it ♡

Verdict: ✅ Looks good to me~

What I verified this round (6a949ad, against my earlier blockers)

  1. The one word — and its two siblings. SKAlphaType.Unpremul now on all three raster targets: Decode() (ImageScaler.cs:201), Resize() (:226), CreateSurface() (:232). I read the full file — no Opaque target survives on any pixel path.
  2. The pins are real, proven by mutation probe (SkiaSharp 3.119.4, pristine build):
    • Opaque revert in Decode() only → AnAlphaCarryingMaskDecodesAndGridAligns and AnAlphaCarryingMaskScalesOnTheV4Base64Path both red (Failed: 2), exact InvalidConversion decode stack at ImageScaler.cs:206. Killing my own finding's fix — fufu, that's the best feeling~
    • Both Math.Max(1, …) clamps deleted → ScaleAndPad_AnExtremeAspectScalesToAOnePixelDimensionNotZero red (Failed: 1, resize-returns-null at :227). The clamp that survived last round is now pinned. Last review's surviving mutant is dead.
    • Restored → your three new tests 3/3 green on pristine code.
  3. AlphaRgbaPng() is a true color type 6 with varying alpha (TestImages.cs:24–37) — the fixture does what its comment claims, and both transports (V5 AlignMaskToGrid, V4 ScaleMaskWithGridAlignment) exercise it.
  4. Docs: CLAUDE.md file-tree + dependency table and README no longer name SixLabors; CHANGELOG [Unreleased] → Changed carries the raster-swap bullet with the SKCodec decode rationale. All three non-blockers from last round, addressed~

✅ What I liked~

  • Fixing the class of the bug, not the instance — finding the third Opaque site yourself is exactly the instinct that keeps this from recurring in Kagura's port.
  • Test comments that teach: the extreme-aspect pin explains the 0.001-scale truncation arithmetic, the alpha pin explains why an opaque target refuses. Fixtures that document their own physics~ ♪
  • Honest probe reporting in the reply, including failure counts. I re-ran them; your numbers match mine.

This migration is now exactly what it promised on the tin: decode-failure semantics pinned by tests at every seam, five advisories gone, public surface unchanged. Merge it~ ♡


Automated review by Jibril · 2026-10-13
CI: green for head SHA 6a949ad · Mutation probes: 2 run, 0 survived (Opaque revert killed by both alpha pins; clamp deletion killed by extreme-aspect pin; threshold-flip probe not repeated — unchanged code, killed last review) · Local suite: skipped (CI green) — targeted pin run 3/3 green

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh! A fix commit that answers every finding *and* fixes the trap where it lived beyond where I pointed — `CreateSurface()` too, one `DrawBitmap` away from the same refusal~ You even reproduced my probes before acting. That's the good stuff. That's what makes reviewing worth it ♡ ### Verdict: ✅ Looks good to me~ #### What I verified this round (6a949ad, against my earlier blockers) 1. **The one word — and its two siblings.** `SKAlphaType.Unpremul` now on **all three** raster targets: `Decode()` (ImageScaler.cs:201), `Resize()` (:226), `CreateSurface()` (:232). I read the full file — no `Opaque` target survives on any pixel path. 2. **The pins are real, proven by mutation probe (SkiaSharp 3.119.4, pristine build):** - *Opaque revert in `Decode()` only* → `AnAlphaCarryingMaskDecodesAndGridAligns` **and** `AnAlphaCarryingMaskScalesOnTheV4Base64Path` both red (`Failed: 2`), exact `InvalidConversion` decode stack at ImageScaler.cs:206. Killing my own finding's fix — fufu, that's the best feeling~ - *Both `Math.Max(1, …)` clamps deleted* → `ScaleAndPad_AnExtremeAspectScalesToAOnePixelDimensionNotZero` red (`Failed: 1`, resize-returns-null at :227). The clamp that survived last round is now pinned. Last review's surviving mutant is dead. - Restored → your three new tests 3/3 green on pristine code. 3. **`AlphaRgbaPng()` is a true color type 6 with varying alpha** (TestImages.cs:24–37) — the fixture does what its comment claims, and both transports (V5 `AlignMaskToGrid`, V4 `ScaleMaskWithGridAlignment`) exercise it. 4. **Docs:** CLAUDE.md file-tree + dependency table and README no longer name SixLabors; CHANGELOG `[Unreleased]` → Changed carries the raster-swap bullet with the `SKCodec` decode rationale. All three non-blockers from last round, addressed~ #### ✅ What I liked~ - Fixing the *class* of the bug, not the instance — finding the third `Opaque` site yourself is exactly the instinct that keeps this from recurring in Kagura's port. - Test comments that teach: the extreme-aspect pin explains the 0.001-scale truncation arithmetic, the alpha pin explains *why* an opaque target refuses. Fixtures that document their own physics~ ♪ - Honest probe reporting in the reply, including failure counts. I re-ran them; your numbers match mine. This migration is now exactly what it promised on the tin: decode-failure semantics pinned by tests at every seam, five advisories gone, public surface unchanged. Merge it~ ♡ --- *Automated review by Jibril · 2026-10-13* *CI: green for head SHA 6a949ad · Mutation probes: 2 run, 0 survived (Opaque revert killed by both alpha pins; clamp deletion killed by extreme-aspect pin; threshold-flip probe not repeated — unchanged code, killed last review) · Local suite: skipped (CI green) — targeted pin run 3/3 green*
bjoern merged commit f917909da8 into main 2026-10-13 13:58:20 +02:00
bjoern deleted branch feat/skia-raster 2026-10-13 13:58:20 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
TeamAI/NovelAi.ImageGen!5
No description provided.