ShellRS
ShellRS 是一款免費、開源的 SSH 客戶端,使用 Rust 編寫:主機管理、終端機、WinSCP 風格的 SFTP 與埠轉發,全部於一個 GPU 渲染視窗中,支援 macOS、Windows 與 Linux。
AI 翻譯
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證製作者的介紹僅有上面的一句話。 更多詳情請閱讀儲存庫 README。 儲存庫 README ↗
正在載入完整 README。
檢視完整 README ↗A compiler from images to short HTML/CSS. It is aimed at CSSBattle targets: flat 400×300 pictures that players redraw in as few characters as they can.
<body bgcolor=c31c1d><b style="position:fixed;inset:70px 80px 70px 80px;background:#660301;clip-path:polygon(25% 0,75% 0,100% 100%,75% 100%,75% 75%,140px 75%,140px 50%,100px 50%,100px 75%,25% 75%,25% 100%,0 100%)">
214 characters, a 99.9% pixel match. The human solution it was rendered from is 493 characters even after minifying.
The in-browser demo: pick a
target, compile, read the code, download it and open the file on its own.
The waits for Python to load and for the compile are cut from the recording;
the captions give their real length (demo/record.js made it).
See all 66 targets, outputs and generated code on the results page →
Try it on your own picture, in your browser →
On the 66 targets of the development set (the ones the compiler was developed on; held-out results are below), rendered in Chromium and compared pixel by pixel:
| Shorter than the human solution (minified) | 56 of 66 |
| Total code length | 23,329 characters (human: 35,532) |
| Targets at ≥ 99% pixel match | 66 of 66 (lowest 99.21%) |
| Mean pixel match | 99.82% |
The human solutions are readable code from two public repositories, minified
but not golfed. Top CSSBattle players write far shorter code, so these
lengths are a baseline, not a leaderboard. The full results, with every
target, output and generated program, are on the
results page (built from
bench/cssynth-results.html).
Each row recompiles all 66 targets with one technique switched off
(bench/ablation.py, run by the Ablation workflow). Scores come from
cssynth's own rasterizer rather than Chromium, and the parts overlap, so the
rows don't add up.
| Switched off | Total J | Characters | Shorter than human |
|---|---|---|---|
| nothing (full compiler) | 60,441 | 23,329 | 56 / 66 |
| Insertion at any depth (shapes go on top) | 64,637 (+6.9%) | 24,887 (+1,558) | 54 / 66 |
| Polygons traced from outlines | 64,449 (+6.6%) | 25,791 (+2,462) | 52 / 66 |
| Re-search of the best result | 64,444 (+6.6%) | 24,652 (+1,323) | 55 / 66 |
| Gradient backgrounds | 63,265 (+4.7%) | 24,077 (+748) | 54 / 66 |
| Length-aware shape choice (by pixels only) | 62,921 (+4.1%) | 24,017 (+688) | 54 / 66 |
| Length-aware clean-up | 61,982 (+2.5%) | 24,914 (+1,585) | 52 / 66 |
| Splitting regions that touch at corners | 61,705 (+2.1%) | 24,005 (+676) | 55 / 66 |
| Nested-ring motifs | 61,309 (+1.4%) | 23,495 (+166) | 56 / 66 |
| Box-shadows | 61,258 (+1.4%) | 24,150 (+821) | 56 / 66 |
| Soft gradient stops | 61,106 (+1.1%) | 23,680 (+351) | 55 / 66 |
Per-tag rules (<i>, <u>, <s>) | 60,807 (+0.6%) | 23,583 (+254) | 55 / 66 |
clip-path reuse for copies and mirrors | 60,708 (+0.4%) | 23,544 (+215) | 55 / 66 |
| Exact (CP-SAT) shadow grouping, greedy kept | 60,554 (+0.2%) | 23,442 (+113) | 56 / 66 |
Inserting shapes below existing ones, outline polygons and the re-search
matter most; gradient backgrounds and choosing shapes by length as well as
pixels come next. Exact shadow grouping and clip-path reuse are small on
average but decide a few targets.
The 66 targets are where the compiler was developed, so two more benchmarks test it elsewhere (details in docs/method.md).
Held-out structural families. 48 targets generated from six structures:
mirrored shapes, repeats, concentric rings, petals, stripes and unrelated
shapes. They are compiled once per version, never inspected, and scored in
Chromium (bench/evaluate.py). The second baseline climbs 40% more starts,
to spend at least the CPU time the motif search does:
| solved (≥ 99%) | characters, on the 40 all solve | CPU | |
|---|---|---|---|
| v0.5.0 | 40 / 48 | 11,944 | 2,634 s |
| v0.5.0, 14 starts per step | 41 / 48 | 11,240 | 3,177 s |
| with nested-ring motifs | 43 / 48 | 11,627 | 2,311 s |
The motifs solve the most with the least time. The baseline with more starts writes 3.4% less code on the targets all three solve, but that rests on three scatter and petals targets: it is shorter on 6 and longer on 7, and the 95% bootstrap interval runs from 2.1% shorter to 9.6% longer. Giving the default more starts makes its code longer, not shorter (+6.5% on the development split). (This is the third held-out set; the earlier two were spent on earlier versions, see docs/method.md.)
Known optima. On 40 tiny targets (up to four grid-aligned boxes and ellipses), an exact solver finds the shortest program that cssynth's own emitter can write. cssynth reproduces 35 exactly and is optimal on 33 of them (30 before this round); the other two are 40 characters over.
Petals (leaves turned about a center) remain the weak family: Chromium antialiases turned and curved edges differently from cssynth's model, so the right program looks wrong to the search.
pip install .
cssynth target.png # prints the code; length and details on stderr
cssynth -v target.png # also logs the search
cssynth --rho 2 target.png # golf harder (see "Objective")
cssynth --fast target.png # skip the re-search: about 2x faster, longer code
From Python:
from cssynth import compile_image
code, info, model = compile_image("target.png")
Everything is scored by one number:
J = characters + wrong pixels / rho
rho is how many wrong pixels the compiler accepts to save one character
(default 0.5). CSSBattle tells players to reach a 100% match before cutting
characters, so the default is cautious; bench/tradeoff.py measures what
larger values buy.
A longer write-up of the method, with the reasoning behind each part, is in docs/method.md.
flowchart LR
A[target PNG] --> B[palette]
B --> C[base layer<br>color or gradient]
C --> D[layered greedy search]
D --> E[refine]
E --> F[simplify for length]
F --> G[code generation]
G --> H{lowest J<br>of 16 variants}
H --> I[re-search parts<br>of the best]
search.extract_palette): flat colors with at least 40
pixels. A color lying between two others counts as antialiasing only when
its pixels form thin lines (nothing survives one erosion); a solid region
of such a color is a real fill, e.g. a semi-transparent shape.linear-gradient (search.fit_gradient). Every pixel center is
projected onto the gradient line for each angle (1° scan, then refined);
dynamic programming over 1-px bins then chooses band boundaries minimizing
wrong pixels + a penalty per stop. Chromium evaluates gradients at pixel
centers and does not antialias hard stops, so a target edge drawn by a
rotated box leaves a band of wrong pixels; each boundary is then moved to
a sub-pixel position and given a short linear ramp (a soft stop, #a 159.5px,#b 160.5px) where that pays for its characters. Chromium draws
these within 3/255 of the model.search.greedy): repeatedly add the shape that
fixes the most wrong pixels, or, in the length-aware mode, the one with the
best pixels fixed / rho minus its estimated characters (its own element, or
a box-shadow entry when it is a shifted copy of a shape already placed). Candidates come from connected regions of wrong
pixels (and, where a region only holds together at corners, from each of its
edge-connected parts): boxes, ellipses, boxes with various corner radii, a
45° diamond, a plain and a rounded bar along the region's principal axis, a
leaf (a square rounded on two opposite corners, turned onto that axis), and
clip-path polygons, both from
the region's convex hull (3–5 vertices) and traced from its outline, which
may be concave (4–12 vertices, simplified by Visvalingam's algorithm). The
plain bounding box of each color's largest regions is always climbed too:
from there a climb peels a compound region (a cross, an I-beam) down to its
biggest bar. The best starts (each at its best depth) are hill-climbed on
their edges, radii, rotation or vertices; a gain is a few lookups in
running sums along pixel rows, over the runs of pixels a shape covers
(cached by shape, and exact: see docs/method.md).
A shape may be inserted at any depth in the stack: for each
depth the gain is computed against the canvas below it and only the pixels
that the shapes above leave visible. This is what lets it build rings and
nested figures.search.refine): merge two touching same-color shapes into one
when that costs fewer than min_gain pixels; re-fit each shape against its
final surroundings; delete shapes that no longer fix enough pixels; snap
coordinates to multiples of 5/10 when it changes nothing.simplify.simplify): local moves scored by J,
with code length measured by running the real code generator: drop a
shape; drop a polygon vertex and re-fit; snap two near-equal shapes to
identical (or uniformly grown) geometry so they can become box-shadows of
one another or share CSS (also turned copies at different angles), then
re-position; make odd sizes even so can size the box.bench/ holds the 66 cases (cases.json), the generated code and its renders
(out/), and the scores (results.json, plus results_v1.json and out_v1/
from the first version for comparison).
Targets are Chromium renders of public human solutions, so each target is exactly reproducible. Match = share of pixels within 8/255 per channel of the target, measured on Chromium's render of the generated code.
bench/fetch.py downloads them at a pinned commit, checks them against
cases.json and renders the targets. See THIRD_PARTY.md.cd bench && npm ci && cd .. # puppeteer-core + @sparticuz/chromium
python bench/fetch.py # download and render the remaining targets
python bench/run.py # compile all, render in Chromium, score
python bench/reemit.py # re-run only code generation on saved shapes
python bench/tradeoff.py # characters vs match for several rho
python bench/build_page.py # results page -> bench/cssynth-results.html
python bench/evaluate.py --set heldout2 --method full # held-out families, scored in Chromium
python bench/evaluate.py --set heldout2 --compare base full
python bench/tiny.py # tiny targets against their known optima
python bench/families.py train 8 # a development split of the families
python bench/proposals.py data --out props.pkl # outcome-labeled motif proposals
The targets were rendered with @sparticuz/chromium 131 on Linux, which only
runs there. Elsewhere, point CHROME_PATH at Chrome for Testing 131
(npx @puppeteer/browsers install [email protected]). Its edges differ
slightly from the Linux build: the human solutions themselves match their
Linux targets by 99.8% on average on macOS (99.1% at worst), which lowers
match scores by the same amount. The generated code is the same on both. The
committed results and the trade-off table come from the Benchmark
workflow, which runs everything on Linux (about 35 minutes on GitHub's
runners).
pip install -e ".[dev]"
pytest # unit tests and small end-to-end compiles, ~20 s
ruff check .
cssynth/
compiler.py compile_image: run the variants, simplify, keep the lowest J
improve.py re-search: take the best result apart and refill it
search.py palette, base layer, greedy shape search, refine
shapes.py the Shape model and a rasterizer that predicts Chromium's
simplify.py length-aware clean-up moves
codegen.py golfed HTML/CSS, including the CP-SAT shadow grouping
bench/ benchmark data, runner and results page
demo/ the in-browser demo (Pyodide) and a local Gradio app
tests/
clip-path, but nothing yet uses ::before/::after to
repeat a shape, or mirrors a rounded box.repeating-*-gradient (only
one benchmark target repeats a band pattern, and its output is already half
the human length) and a radial-gradient background for a circle (8
targets start with one, but circle at … plus soft stops costs about as
much as the element; at best 34 characters in total).docs/method.md). The exact program for two turned leaves scores 599
wrong pixels in the model, which is why petals are the weakest held-out
family.MIT, see LICENSE. Third-party benchmark material is described in THIRD_PARTY.md. Not affiliated with CSSBattle.
paddingcodegen.emit):
make_groups_exact, CP-SAT):
each shape is an element or a shadow of one compatible element; each
gets a paint position, shadows share their anchor's, and for every
constrained pair (i below j) the positions strictly increase unless both
are in the same group, which excludes cycles between groups. The
objective is the characters of elements, shadow entries and
box-shadow: overhead. A greedy grouping also runs; the shorter final
code wins;<b> tags with position:fixed, except rotated or
clipped ones, which are closed because they'd transform/clip descendants;padding (quirks mode) or inset when shorter;7em);<body bgcolor=...> or a
gradient rule;b{...} rule, in a
<style> at the very end of the document, left unclosed along with its
last rule (the parser closes both at end of input);<i>, <u> or <s>, with their own rule,
which is how copies of a shape at different angles share their CSS;clip-path with scale:-1 1, 1 -1 or -1, so the two can share it
in a rule (the clean-up snaps near-mirrors to exact ones).compiler.VARIANTS): six searches run per image, each in both
modes, by pixels and length-aware; two of them also report where a higher
min_gain would have stopped, so there are 16 results. Each is simplified
and scored by J, and the lowest J wins. Outline polygons run as separate
variants because the greedy search can also take them where boxes and
shadows would end up shorter. The length-aware mode wins on most targets,
but not all: it can pick a cheap shape that leaves more for later.improve.improve): the greedy search commits to shapes in
the order they first help, so an early polygon can take a region that a
shorter primitive would have drawn. The best result is taken apart and
refilled: one polygon at a time (refilled without polygons of its color),
then every shape of one color (refilled as before, and without polygons).
Refills use the winning variant's mode. Each is refined and simplified,
and kept when J drops. --fast
skips this step.