ludic/packages/ludic.lab/png_write.ludic
Orkuncakilkaya 6c690c5db6 render3d + lab: bakes as PNGs, and impostors, the sky's light and the carpet read from their bakes
ludic.lab: lab_png_write / lab_png_write_from (raw 8-bit, 1-4 channels, stored deflate) in png_write.ludic,
importable alone with its own LabPngState; png_convert.ludic's previews of float textures (R32F min..max,
RG16F x255, HDR x/(1+x) + sRGB); lab_ppm_to_png on the same encoder.
render3d: bake_load.ludic - impostor_from_baked / impostor_source / impostor_refill (a fog re-open reads
the bake), sky_baked_in and sky_precompute trying the bake at the start yaw (sky_compute is the
convolution, and sky_ibl_bytes always uses it), carpet_from_baked / carpet_bytes / carpet_finish,
bake_part_count / _len / _off. Compile-only: nothing run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 19:10:06 +03:00

133 lines
4.6 KiB
Text

# png_write.ludic - raw 8-bit pixels (1 to 4 channels) as a PNG anything opens: filter 0 on every row,
# deflate's stored blocks (no zlib here), the CRCs and the Adler-32. Needs nothing else of the lab, so a
# program with a lab of its own imports this file alone: import "ludic.lab/png_write.ludic"
export state LabPngState {
table: []long = lp_crc_table()
}
# the CRC-32 table, made once with a state
function lp_crc_table() -> []long {
let t = new []long
let poly: long = 3988292384 # 0xEDB88320: eight hex digits would be a 32-bit pattern
for n in 0 .. 256 {
var c = long(n)
for k in 0 .. 8 {
if (c & long(1)) != long(0) { c = (c >> long(1)) ^ poly } else { c = c >> long(1) }
}
push(t, c)
}
return t
}
# CRC-32 of out[from .. from + n)
function lp_crc_of(table: []long, out: []byte, from: int, n: int) -> long {
let mask: long = 4294967295
var c = mask
for i in from .. from + n {
let k = int((c ^ long(out[i])) & long(255))
c = table[k] ^ (c >> long(8))
}
return c ^ mask
}
function lp_put32(out: []byte, at: int, v: long) -> void {
out[at] = int((v >> long(24)) & long(255))
out[at + 1] = int((v >> long(16)) & long(255))
out[at + 2] = int((v >> long(8)) & long(255))
out[at + 3] = int(v & long(255))
}
function lp_tag(out: []byte, at: int, tag: string) -> void {
for i in 0 .. 4 { out[at + i] = tag[i] }
}
# PNG's colour type for 1..4 channels: grey, grey + alpha, RGB, RGBA
function lp_colour_type(channels: int) -> int {
if channels == 1 { return 0 }
if channels == 2 { return 4 }
if channels == 3 { return 2 }
return 6
}
# the header, then the pixels, then the end, into one buffer; the length written, or -1
@alloc_ok("a bake's preview or a lab shot: one image written, once")
function lp_encode(table: []long, w: int, h: int, channels: int, px: []byte, at: int, out: []byte) -> int {
let sig = [137, 80, 78, 71, 13, 10, 26, 10]
for i in 0 .. 8 { out[i] = sig[i] }
lp_put32(out, 8, long(13))
lp_tag(out, 12, "IHDR")
lp_put32(out, 16, long(w))
lp_put32(out, 20, long(h))
out[24] = 8
out[25] = lp_colour_type(channels)
out[26] = 0
out[27] = 0
out[28] = 0
lp_put32(out, 29, lp_crc_of(table, out, 12, 17))
let o = lp_idat(table, w, h, channels, px, at, out, 33)
lp_put32(out, o, long(0))
lp_tag(out, o + 4, "IEND")
lp_put32(out, o + 8, lp_crc_of(table, out, o + 4, 4))
return o + 12
}
# the IDAT chunk at `at0`: every row behind a filter byte of 0, in stored blocks; where it ends
function lp_idat(table: []long, w: int, h: int, channels: int, px: []byte, at: int, out: []byte, at0: int) -> int {
let stride = w * channels
let row = stride + 1
let raw = row * h
let blocks = (raw + 65534) / 65535
lp_put32(out, at0, long(2 + blocks * 5 + raw + 4))
lp_tag(out, at0 + 4, "IDAT")
var o = at0 + 8
out[o] = 120
out[o + 1] = 1
o += 2
var a = 1
var b = 0
var left = raw
var k = 0 # the byte of the raw stream being written
while left > 0 {
let n = Math.min(left, 65535)
left -= n
var fin = 0
if left == 0 { fin = 1 }
out[o] = fin
out[o + 1] = n & 255
out[o + 2] = (n >> 8) & 255
out[o + 3] = (~n) & 255
out[o + 4] = ((~n) >> 8) & 255
o += 5
for j in 0 .. n {
let x = k % row
var v = 0
if x > 0 { v = px[at + (k / row) * stride + x - 1] & 255 }
out[o] = v
a = (a + v) % 65521
b = (b + a) % 65521
o += 1
k += 1
}
}
lp_put32(out, o, long(b) * long(65536) + long(a))
o += 4
lp_put32(out, o, lp_crc_of(table, out, at0 + 4, o - at0 - 4))
return o + 4
}
# the bytes a PNG of this size takes, stored
function lp_png_size(w: int, h: int, channels: int) -> int {
let raw = (w * channels + 1) * h
return 8 + 25 + 12 + 2 + ((raw + 65534) / 65535) * 5 + raw + 4 + 12
}
# w x h pixels of `channels` 8-bit samples each (1 grey, 2 grey + alpha, 3 RGB, 4 RGBA), rows top down,
# starting at px[at], written as a PNG at `path`
@alloc_ok("a bake's preview or a lab shot: one image written, once")
export function lab_png_write_from(lab_png_st: LabPngState, path: string, w: int, h: int, channels: int, px: []byte, at: int) -> bool {
if channels < 1 or channels > 4 or w <= 0 or h <= 0 or at + w * h * channels > len(px) { return false }
let out = buffer(lp_png_size(w, h, channels))
let n = lp_encode(lab_png_st.table, w, h, channels, px, at, out)
let ok = Fs.write_bytes(path, out, n)
free(out)
return ok
}
export function lab_png_write(lab_png_st: LabPngState, path: string, w: int, h: int, channels: int, px: []byte) -> bool {
return lab_png_write_from(lab_png_st, path, w, h, channels, px, 0)
}