Skip to content

feat: waifu2x upscaling, as a resample method in Image Size - #72

Merged
IAmJSD merged 2 commits into
mainfrom
t3code/add-waifu2x-upscaling
Aug 29, 2026
Merged

feat: waifu2x upscaling, as a resample method in Image Size#72
IAmJSD merged 2 commits into
mainfrom
t3code/add-waifu2x-upscaling

Conversation

@IAmJSD

@IAmJSD IAmJSD commented Aug 29, 2026

Copy link
Copy Markdown
Member

Super Zoom was only ever half an upscale — a filter cannot resize its own buffer, so the network could put the high frequencies back but could not make the image bigger. This adds the other half in the place that can: Image Size grows a Resample dropdown with waifu2x ×2 (Photo) and waifu2x ×2 (Art) beside bicubic, bilinear and nearest.

The models

nagadomi's upconv_7, MIT, shipped in the binary at 2.2 MB each. tools/train/waifu2x.py converts rather than trains: it fetches a pinned revision of the Torch JSON weight dumps, checks them against a known hash, rebuilds the seven-layer graph and exports ONNX, which tract runs unmodified.

The one deliberate change to the graph is padding. upconv_7 is all valid convolutions and returns 2N−28 pixels for N in, so seven pixels of replicate padding go in front of the first layer and a tile comes back exactly doubled — the contract run_scaled tiles against. Replicate for the same reason the tile driver mirrors at image edges: a network fed a black border draws one.

Against held-out kodim23: bilinear 23.49 dB → waifu2x 28.31 dB (+4.82).

Inference

Input::Tiles gains a scale, and run_scaled sits beside run_tiled — the same overlapping-tile walk with mirrored image edges, every write-back coordinate multiplied by the scale, starting from a nearest-neighbour enlargement so a tile that fails degrades to soft pixels rather than a black square. Layers resample premultiplied, as everything here does, with alpha doubled bilinearly alongside: a coverage ramp has no detail for a network to invent.

Why the resize is split in two

It costs ~9 s per input megapixel on 16 cores, so a 12 MP photo would have frozen the window for two minutes. Deciding needs the document and is cheap; running carries the pixels instead and goes to the background executor. That wanted a "working" modal, and the one Photoshop plug-ins already use was right except for its wording — so FilterRunning is now Busy, with the title and text passed in.

Non-power-of-two targets double until they reach the target and let bicubic cover the remainder; downscales never involve the network; and if a model will not load, bicubic stands in and the status bar says it did, rather than quietly substituting the thing the user did not pick.

Checks

807 workspace tests pass. New coverage: the upscaler beats interpolation on a held-out photograph, odd and tiny sizes survive, tile coverage holds at scale, and the document-level resize doubles, reaches a non-power-of-two target, downscales classically and undoes as one edit.

Driven in the real app headlessly — dropdown, busy modal mid-run on a 1100×800 → 2200×1600, and one undoable "Image Size" entry.

🤖 Generated with Claude Code

IAmJSD and others added 2 commits August 29, 2026 20:17
Super Zoom was only ever half an upscale -- a filter cannot resize its
own buffer, so the network could put high frequencies back but could
not make the image bigger. This adds the other half in the place that
can: Image Size grows a Resample dropdown with waifu2x x2 (Photo) and
waifu2x x2 (Art) beside bicubic, bilinear and nearest.

The models are nagadomi's upconv_7, MIT and shipped in the binary at
2.2 MB each. tools/train/waifu2x.py converts them rather than training
anything: it fetches a pinned revision of the Torch JSON weight dumps,
checks them against a known hash, rebuilds the seven-layer graph and
exports ONNX, which tract then runs unmodified. The one deliberate
change to the graph is padding. upconv_7 is all valid convolutions and
returns 2N-28 pixels for N in, so seven pixels of replicate padding go
in front of the first layer and a tile comes back exactly doubled --
which is the contract run_scaled tiles against. Replicate for the same
reason the tile driver mirrors at image edges: a network fed a black
border draws one. Against held-out kodim23 it is +4.8 dB on bilinear.

Input::Tiles gains a scale, and run_scaled sits beside run_tiled: the
same overlapping-tile walk with mirrored image edges, every write-back
coordinate multiplied by the scale, starting from a nearest-neighbour
enlargement so a tile that fails degrades to soft pixels rather than a
black square. Layers are resampled premultiplied, as everything here
is, with alpha doubled bilinearly alongside -- a coverage ramp has no
detail for a network to invent.

It costs about nine seconds per input megapixel on sixteen cores, so
the resize is split: deciding needs the document and is cheap, running
carries the pixels instead and goes to the background executor. That
wanted a "working" modal, and the one Photoshop plug-ins already use
was right except for its wording, so FilterRunning is now Busy with
the title and text passed in. Non-power-of-two targets double until
they reach the target and let bicubic cover the remainder; downscales
never involve the network; and if a model will not load, bicubic
stands in and the status bar says that it did, rather than quietly
substituting the thing the user did not pick.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The new resize method went in immediately above it, which put it
between that function's doc comment and the function itself -- so the
#[allow(clippy::too_many_arguments)] guarding its eight arguments
silently moved onto the new five-argument one, and clippy failed on
the function it had been guarding all along. Moved the new method
above the whole doc block.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@IAmJSD
IAmJSD merged commit 23fc3ec into main Aug 29, 2026
3 checks passed
@IAmJSD
IAmJSD deleted the t3code/add-waifu2x-upscaling branch August 29, 2026 20:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant