Account for remaining site changes
This commit is contained in:
parent
0717383be9
commit
8ce7b655bc
8 changed files with 123 additions and 36 deletions
|
|
@ -11,9 +11,9 @@ pub fn ocean_palette(root_index: u8, iterations: u32, max_iterations: u32) -> (u
|
||||||
|
|
||||||
// Ocean theme colors for each root basin
|
// Ocean theme colors for each root basin
|
||||||
let (r, g, b) = match root_index {
|
let (r, g, b) = match root_index {
|
||||||
0 => (0.05, 0.25, 0.55), // Deep ocean blue
|
0 => (0.05, 0.25, 0.55), // Deep ocean blue
|
||||||
1 => (0.0, 0.45, 0.50), // Teal
|
1 => (0.0, 0.45, 0.50), // Teal
|
||||||
2 => (0.15, 0.55, 0.65), // Cyan/turquoise
|
2 => (0.15, 0.55, 0.65), // Cyan/turquoise
|
||||||
_ => (0.05, 0.25, 0.55),
|
_ => (0.05, 0.25, 0.55),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,9 @@ pub fn compute(z_re: f64, z_im: f64, time: f64) -> (u8, u32) {
|
||||||
|
|
||||||
// The three roots of z^3 - 1
|
// The three roots of z^3 - 1
|
||||||
const ROOTS: [(f64, f64); 3] = [
|
const ROOTS: [(f64, f64); 3] = [
|
||||||
(1.0, 0.0), // 1
|
(1.0, 0.0), // 1
|
||||||
(-0.5, 0.866025403784439), // e^(2πi/3)
|
(-0.5, 0.866025403784439), // e^(2πi/3)
|
||||||
(-0.5, -0.866025403784439), // e^(4πi/3)
|
(-0.5, -0.866025403784439), // e^(4πi/3)
|
||||||
];
|
];
|
||||||
|
|
||||||
for iter in 0..MAX_ITERATIONS {
|
for iter in 0..MAX_ITERATIONS {
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,9 @@ use crate::newton::compute;
|
||||||
const MAX_ITERATIONS: u32 = 32;
|
const MAX_ITERATIONS: u32 = 32;
|
||||||
|
|
||||||
/// Get the canvas element and 2D context
|
/// Get the canvas element and 2D context
|
||||||
fn get_canvas_and_context(canvas_id: &str) -> Result<(HtmlCanvasElement, CanvasRenderingContext2d), JsValue> {
|
fn get_canvas_and_context(
|
||||||
|
canvas_id: &str,
|
||||||
|
) -> Result<(HtmlCanvasElement, CanvasRenderingContext2d), JsValue> {
|
||||||
let window = web_sys::window().ok_or("no window")?;
|
let window = web_sys::window().ok_or("no window")?;
|
||||||
let document = window.document().ok_or("no document")?;
|
let document = window.document().ok_or("no document")?;
|
||||||
let canvas = document
|
let canvas = document
|
||||||
|
|
@ -66,7 +68,8 @@ pub fn render_fractal_animated(canvas_id: &str, time: f64, scale: u32) -> Result
|
||||||
|
|
||||||
// Create ImageData at reduced size
|
// Create ImageData at reduced size
|
||||||
let clamped = wasm_bindgen::Clamped(&pixels[..]);
|
let clamped = wasm_bindgen::Clamped(&pixels[..]);
|
||||||
let image_data = ImageData::new_with_u8_clamped_array_and_sh(clamped, width as u32, height as u32)?;
|
let image_data =
|
||||||
|
ImageData::new_with_u8_clamped_array_and_sh(clamped, width as u32, height as u32)?;
|
||||||
|
|
||||||
// Scale up when drawing to canvas
|
// Scale up when drawing to canvas
|
||||||
if scale > 1 {
|
if scale > 1 {
|
||||||
|
|
@ -89,8 +92,10 @@ pub fn render_fractal_animated(canvas_id: &str, time: f64, scale: u32) -> Result
|
||||||
)?;
|
)?;
|
||||||
context.draw_image_with_html_canvas_element_and_dw_and_dh(
|
context.draw_image_with_html_canvas_element_and_dw_and_dh(
|
||||||
&temp_canvas,
|
&temp_canvas,
|
||||||
0.0, 0.0,
|
0.0,
|
||||||
canvas_width as f64, canvas_height as f64
|
0.0,
|
||||||
|
canvas_width as f64,
|
||||||
|
canvas_height as f64,
|
||||||
)?;
|
)?;
|
||||||
} else {
|
} else {
|
||||||
context.put_image_data(&image_data, 0.0, 0.0)?;
|
context.put_image_data(&image_data, 0.0, 0.0)?;
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,18 @@
|
||||||
|
|
||||||
WEB_UI_PORT=3001
|
WEB_UI_PORT=3001
|
||||||
SSH_PORT=2223
|
SSH_PORT=2223
|
||||||
|
DATA_DIR=/home/deploy/forgejo/data
|
||||||
|
|
||||||
docker run -d \
|
docker run -d \
|
||||||
--name forgejo \
|
--name forgejo \
|
||||||
-p ${WEB_UI_PORT}:3000 \
|
-p 127.0.0.1:${WEB_UI_PORT}:3000 \
|
||||||
-p ${SSH_PORT}:22 \
|
-p ${SSH_PORT}:22 \
|
||||||
-v ~/forgejo/data:/data \
|
-v ${DATA_DIR}:/data \
|
||||||
-v /etc/timezone:/etc/timezone:ro \
|
-v /etc/timezone:/etc/timezone:ro \
|
||||||
-v /etc/localtime:/etc/localtime:ro \
|
-v /etc/localtime:/etc/localtime:ro \
|
||||||
--restart always \
|
--restart always \
|
||||||
codeberg.org/forgejo/forgejo:9
|
codeberg.org/forgejo/forgejo:9
|
||||||
|
|
||||||
echo "Forgejo started on:"
|
echo "Forgejo started on:"
|
||||||
echo " Web UI: http://zachery.lol:${WEB_UI_PORT}"
|
echo " Web UI: http://127.0.0.1:${WEB_UI_PORT} (proxied at https://zachery.lol/code/)"
|
||||||
echo " ssh port: ${SSH_PORT}"
|
echo " ssh port: ${SSH_PORT}"
|
||||||
|
|
|
||||||
|
|
@ -33,8 +33,8 @@ server {
|
||||||
|
|
||||||
listen 443 ssl; # managed by Certbot
|
listen 443 ssl; # managed by Certbot
|
||||||
listen [::]:443 ssl ipv6only=on; # managed by Certbot
|
listen [::]:443 ssl ipv6only=on; # managed by Certbot
|
||||||
ssl_certificate /etc/letsencrypt/live/zachery.lol/fullchain.pem; # managed by Certbot
|
ssl_certificate /etc/letsencrypt/live/zachery.lol-0001/fullchain.pem; # managed by Certbot
|
||||||
ssl_certificate_key /etc/letsencrypt/live/zachery.lol/privkey.pem; # managed by Certbot
|
ssl_certificate_key /etc/letsencrypt/live/zachery.lol-0001/privkey.pem; # managed by Certbot
|
||||||
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
|
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
|
||||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
|
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -117,6 +117,8 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
<link rel="preload" href="/routes/root/dist-strips.bin?v=1" as="fetch" crossorigin />
|
||||||
|
<script defer src="/routes/root/fractal-gl.js?v=12"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<canvas id="fractal-canvas"></canvas>
|
<canvas id="fractal-canvas"></canvas>
|
||||||
|
|
@ -135,15 +137,7 @@
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<!-- fractal background loaded via deferred <script> in <head> -->
|
||||||
requestAnimationFrame(function () {
|
|
||||||
requestAnimationFrame(function () {
|
|
||||||
var s = document.createElement('script');
|
|
||||||
s.src = '/routes/root/fractal-gl.js?v=9';
|
|
||||||
s.async = true;
|
|
||||||
document.body.appendChild(s);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -279,6 +279,8 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
<link rel="preload" href="/routes/root/dist-strips.bin?v=1" as="fetch" crossorigin />
|
||||||
|
<script defer src="/routes/root/fractal-gl.js?v=12"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<canvas id="fractal-canvas"></canvas>
|
<canvas id="fractal-canvas"></canvas>
|
||||||
|
|
@ -323,15 +325,7 @@
|
||||||
<a href="/contact">back to contact</a>
|
<a href="/contact">back to contact</a>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<script>
|
<!-- fractal background loaded via deferred <script> in <head> -->
|
||||||
requestAnimationFrame(function () {
|
|
||||||
requestAnimationFrame(function () {
|
|
||||||
var s = document.createElement('script');
|
|
||||||
s.src = '/routes/root/fractal-gl.js?v=9';
|
|
||||||
s.async = true;
|
|
||||||
document.body.appendChild(s);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
93
tools/gen-strips.py
Normal file
93
tools/gen-strips.py
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
# /// script
|
||||||
|
# requires-python = ">=3.9"
|
||||||
|
# dependencies = ["pillow"]
|
||||||
|
# ///
|
||||||
|
"""
|
||||||
|
Generate routes/root/dist-strips.bin from routes/root/dist.png.
|
||||||
|
|
||||||
|
Splits the 512x512 grayscale Julia distance field into N horizontal bands,
|
||||||
|
re-encodes each band as its own WebP, and concatenates them in CENTER-OUT order
|
||||||
|
(the band the pan/zoom animation shows first) so a streaming client can upload
|
||||||
|
the visible region before the outer bands have finished downloading. WebP q90 is
|
||||||
|
~half the size of PNG with no visible loss after the color-band mapping.
|
||||||
|
|
||||||
|
File layout (little-endian):
|
||||||
|
u32 headerLen
|
||||||
|
bytes headerLen -- UTF-8 JSON: {fullW, fullH, bands:[{y,h,off,len}, ...]}
|
||||||
|
bytes payload -- concatenated band WebPs in the order listed in `bands`
|
||||||
|
(off/len are byte ranges within payload, stream order)
|
||||||
|
|
||||||
|
Re-run only when dist.png changes; the output is committed.
|
||||||
|
Usage: uv run tools/gen-strips.py
|
||||||
|
"""
|
||||||
|
import io
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import struct
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
ROOT = os.path.dirname(HERE)
|
||||||
|
SRC = os.path.join(ROOT, "routes", "root", "dist.png")
|
||||||
|
OUT = os.path.join(ROOT, "routes", "root", "dist-strips.bin")
|
||||||
|
NBANDS = 8
|
||||||
|
WEBP_QUALITY = 90 # lossy; ~half of PNG, visually identical. Bump to 95 / use lossless if needed.
|
||||||
|
|
||||||
|
|
||||||
|
def center_out(n):
|
||||||
|
"""Band indices ordered from the middle outward: e.g. n=8 -> [4,3,5,2,6,1,7,0]."""
|
||||||
|
order = []
|
||||||
|
lo, hi = n // 2 - 1, n // 2
|
||||||
|
while lo >= 0 or hi < n:
|
||||||
|
if hi < n:
|
||||||
|
order.append(hi)
|
||||||
|
hi += 1
|
||||||
|
if lo >= 0:
|
||||||
|
order.append(lo)
|
||||||
|
lo -= 1
|
||||||
|
return order
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
img = Image.open(SRC).convert("L")
|
||||||
|
w, h = img.size
|
||||||
|
|
||||||
|
# Row boundaries (robust if h is not divisible by NBANDS).
|
||||||
|
bounds = [round(i * h / NBANDS) for i in range(NBANDS + 1)]
|
||||||
|
bands_by_index = []
|
||||||
|
for i in range(NBANDS):
|
||||||
|
y0, y1 = bounds[i], bounds[i + 1]
|
||||||
|
crop = img.crop((0, y0, w, y1))
|
||||||
|
buf = io.BytesIO()
|
||||||
|
crop.save(buf, format="WEBP", quality=WEBP_QUALITY, method=6)
|
||||||
|
bands_by_index.append((y0, y1 - y0, buf.getvalue()))
|
||||||
|
|
||||||
|
payload = bytearray()
|
||||||
|
meta = []
|
||||||
|
order = center_out(NBANDS)
|
||||||
|
for idx in order:
|
||||||
|
y, bh, png = bands_by_index[idx]
|
||||||
|
meta.append({"y": y, "h": bh, "off": len(payload), "len": len(png)})
|
||||||
|
payload += png
|
||||||
|
|
||||||
|
header = json.dumps(
|
||||||
|
{"fullW": w, "fullH": h, "bands": meta}, separators=(",", ":")
|
||||||
|
).encode("utf-8")
|
||||||
|
|
||||||
|
with open(OUT, "wb") as f:
|
||||||
|
f.write(struct.pack("<I", len(header)))
|
||||||
|
f.write(header)
|
||||||
|
f.write(payload)
|
||||||
|
|
||||||
|
total = 4 + len(header) + len(payload)
|
||||||
|
src_size = os.path.getsize(SRC)
|
||||||
|
print(f"{w}x{h} -> {NBANDS} bands, center-out order {order}")
|
||||||
|
print(
|
||||||
|
f"header {len(header)}B, payload {len(payload)}B, total {total}B "
|
||||||
|
f"({total / 1024:.1f} KB) vs dist.png {src_size}B ({src_size / 1024:.1f} KB)"
|
||||||
|
)
|
||||||
|
print(f"wrote {OUT}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Loading…
Reference in a new issue