# ============================================================================ # image.ludic — PNG decoding, images and sprites, written in Ludic. # # Replaces the C runtime's image half. Reads a real .png off disk, inflates the # IDAT stream with the Ludic DEFLATE decoder next door, reverses the per-scanline # filters, and expands whatever colour type the file uses into 0xAARRGGBB. # # Supported: 8/4/2/1-bit greyscale, 8-bit truecolour, indexed with PLTE/tRNS, # greyscale+alpha and RGBA — the same set the C runtime handled. # ============================================================================ const IMG_MAX: int = 32 const SPR_MAX: int = 160 const SPR_SZ: int = 16 var img_px: pointers = null # IMG_MAX pointers to RGBA buffers var img_w: words = null var img_h: words = null var img_n: int = 0 var spr_px: words = null # SPR_MAX * 16 * 16 RGBA pixels, one block var spr_n: int = 0 function rt_image_init() -> void { img_px = bytes(IMG_MAX * 8) img_w = words(IMG_MAX) img_h = words(IMG_MAX) spr_px = words(SPR_MAX * SPR_SZ * SPR_SZ) fill(spr_px, 0, SPR_MAX * SPR_SZ * SPR_SZ * 4) } # ---- decoding ------------------------------------------------------------- function png_be32(b: pointer, at: int) -> int { return (b[at] << 24) | (b[at + 1] << 16) | (b[at + 2] << 8) | b[at + 3] } function png_tag(b: pointer, at: int, a: int, c: int, d: int, e: int) -> bool { if b[at] != a { return false } if b[at + 1] != c { return false } if b[at + 2] != d { return false } if b[at + 3] != e { return false } return true } function rt_read_file(path: string) -> pointer { let f = file_open(path, "rb") if (f == null) { return null } file_seek(f, 0, 2) let n = file_tell(f) file_seek(f, 0, 0) if n <= 0 { file_close(f); return null } let buf = bytes(n + 8) file_read(f, buf, n) file_close(f) rt_file_len = n return buf } var rt_file_len: int = 0 # decoded image is left in these; -1 width means failure var png_w: int = 0 var png_h: int = 0 var png_px: words = null function rt_decode_png(path: string) -> bool { png_w = 0 png_h = 0 png_px = null let d = rt_read_file(path) if (d == null) { return false } let size = rt_file_len if size < 8 { free(d); return false } if d[0] != 137 { free(d); return false } if d[1] != 80 { free(d); return false } var w = 0 var h = 0 var bd = 0 var ct = 0 let plte = bytes(768) let trns = bytes(256) var ntrns = 0 let idat = bytes(size) var idlen = 0 var i = 8 var done = 0 while done == 0 { if i + 8 > size { done = 1 } if done == 0 { let ln = png_be32(d, i) let typ = i + 4 let body = i + 8 if ln < 0 { done = 1 } if body + ln > size { done = 1 } if done == 0 { if png_tag(d, typ, 73, 72, 68, 82) { # IHDR w = png_be32(d, body) h = png_be32(d, body + 4) bd = d[body + 8] ct = d[body + 9] } if png_tag(d, typ, 80, 76, 84, 69) { # PLTE let m = min(ln, 768) for k in 0 .. m { plte[k] = d[body + k] } } if png_tag(d, typ, 116, 82, 78, 83) { # tRNS ntrns = min(ln, 256) for k in 0 .. ntrns { trns[k] = d[body + k] } } if png_tag(d, typ, 73, 68, 65, 84) { # IDAT for k in 0 .. ln { idat[idlen + k] = d[body + k] } idlen = idlen + ln } if png_tag(d, typ, 73, 69, 78, 68) { done = 1 } # IEND i = i + 12 + ln } } } if w <= 0 { free(d); return false } if h <= 0 { free(d); return false } var channels = 1 if ct == 2 { channels = 3 } if ct == 6 { channels = 4 } if ct == 4 { channels = 2 } let bppbits = bd * channels var fbpp = (bppbits + 7) / 8 if fbpp < 1 { fbpp = 1 } let stride = (w * bppbits + 7) / 8 let rawlen = h * (stride + 1) let raw = bytes(rawlen + 8) if z_uncompress(idat, idlen, raw, rawlen) < 0 { free(d); free(raw); free(idat) return false } # reverse the per-scanline filters, in place for y in 0 .. h { let line = y * (stride + 1) let ft = raw[line] let cur = line + 1 let prev = cur - (stride + 1) for x in 0 .. stride { var a = 0 var b = 0 var c = 0 if x >= fbpp { a = raw[cur + x - fbpp] } if y > 0 { b = raw[prev + x] } if x >= fbpp { if y > 0 { c = raw[prev + x - fbpp] } } var v = raw[cur + x] if ft == 1 { v = v + a } if ft == 2 { v = v + b } if ft == 3 { v = v + (a + b) / 2 } if ft == 4 { let p = a + b - c let pa = abs(p - a) let pb = abs(p - b) let pc = abs(p - c) var pick = c if pb <= pc { pick = b } if pa <= pb { if pa <= pc { pick = a } } v = v + pick } raw[cur + x] = (v & 255) } } # expand to 0xAARRGGBB let out: words = words(w * h) let maxv = (1 << bd) - 1 for y in 0 .. h { let row = y * (stride + 1) + 1 for x in 0 .. w { var R = 0 var G = 0 var B = 0 var A = 255 if ct == 6 { R = raw[row + x * 4] G = raw[row + x * 4 + 1] B = raw[row + x * 4 + 2] A = raw[row + x * 4 + 3] } if ct == 2 { R = raw[row + x * 3] G = raw[row + x * 3 + 1] B = raw[row + x * 3 + 2] } if ct == 4 { R = raw[row + x * 2] G = R B = R A = raw[row + x * 2 + 1] } if ct == 3 { let bp = x * bd let idx = ((raw[row + bp / 8] >> (8 - bd - bp % 8)) & maxv) R = plte[idx * 3] G = plte[idx * 3 + 1] B = plte[idx * 3 + 2] if idx < ntrns { A = trns[idx] } } if ct == 0 { let bp = x * bd let s = ((raw[row + bp / 8] >> (8 - bd - bp % 8)) & maxv) var g = s if bd != 8 { g = s * 255 / maxv } R = g G = g B = g } out[y * w + x] = (((A << 24) | (R << 16)) | ((G << 8) | B)) } } free(d) free(idat) free(raw) free(plte) free(trns) png_w = w png_h = h png_px = out return true } # ---- images --------------------------------------------------------------- function rt_image_load(path: string) -> int { if img_n >= IMG_MAX { return 0 - 1 } if rt_decode_png(path) == false { return 0 - 1 } let id = img_n img_n = img_n + 1 img_px[id] = png_px img_w[id] = png_w img_h[id] = png_h return id } function rt_blend_px(x: int, y: int, argb: int) -> void { let a = ((argb >> 24) & 255) if a == 0 { return } if x < 0 { return } if y < 0 { return } if x >= rt_fbw { return } if y >= rt_fbh { return } var r = ((argb >> 16) & 255) var g = ((argb >> 8) & 255) var b = (argb & 255) if a != 255 { let dst = rt_fb[y * rt_fbw + x] let dr = ((dst >> 16) & 255) let dg = ((dst >> 8) & 255) let db = (dst & 255) r = (r * a + dr * (255 - a)) / 255 g = (g * a + dg * (255 - a)) / 255 b = (b * a + db * (255 - a)) / 255 } rt_fb[y * rt_fbw + x] = (((r << 16) | (g << 8)) | b) } function rt_draw_image(id: int, dx: int, dy: int) -> void { if id < 0 { return } if id >= img_n { return } let w = img_w[id] let h = img_h[id] let s: words = img_px[id] for y in 0 .. h { for x in 0 .. w { rt_blend_px(dx + x, dy + y, s[y * w + x]) } } } function rt_draw_image_scaled(id: int, dx: int, dy: int, dw: int, dh: int) -> void { if id < 0 { return } if id >= img_n { return } if dw <= 0 { return } if dh <= 0 { return } let w = img_w[id] let h = img_h[id] let s: words = img_px[id] for y in 0 .. dh { for x in 0 .. dw { rt_blend_px(dx + x, dy + y, s[(y * h / dh) * w + x * w / dw]) } } } # map one destination axis onto the source for a 9-slice: the two insets are # copied 1:1 and only the middle stretches function rt_9map(d: int, dsz: int, ssz: int, inset: int) -> int { if inset * 2 >= dsz { return d * ssz / max(dsz, 1) } if inset * 2 >= ssz { return d * ssz / max(dsz, 1) } if d < inset { return d } if d >= dsz - inset { return ssz - (dsz - d) } return inset + (d - inset) * (ssz - 2 * inset) / (dsz - 2 * inset) } function rt_draw_9slice(id: int, dx: int, dy: int, dw: int, dh: int, inset: int) -> void { if id < 0 { return } if id >= img_n { return } if dw <= 0 { return } if dh <= 0 { return } let w = img_w[id] let h = img_h[id] let s: words = img_px[id] for y in 0 .. dh { let sy = rt_9map(y, dh, h, inset) for x in 0 .. dw { rt_blend_px(dx + x, dy + y, s[sy * w + rt_9map(x, dw, w, inset)]) } } } # ---- sprites (16x16 art) -------------------------------------------------- function rt_png_load(path: string) -> int { if spr_n >= SPR_MAX { return 0 - 1 } if rt_decode_png(path) == false { return 0 - 1 } let id = spr_n spr_n = spr_n + 1 let base = id * SPR_SZ * SPR_SZ for y in 0 .. SPR_SZ { for x in 0 .. SPR_SZ { var px = 0 if x < png_w { if y < png_h { let c = png_px[y * png_w + x] if ((c >> 24) & 255) >= 128 { px = ((255 << 24) | (c & 16777215)) } } } spr_px[base + y * SPR_SZ + x] = px } } free(png_px) png_px = null return id } function rt_draw_sprite(id: int, px: int, py: int) -> void { if id < 0 { return } if id >= spr_n { return } let base = id * SPR_SZ * SPR_SZ for y in 0 .. SPR_SZ { for x in 0 .. SPR_SZ { let p = spr_px[base + y * SPR_SZ + x] if ((p >> 24) & 255) != 0 { rt_put_px(px + x, py + y, (p & 16777215)) } } } } function rt_draw_sprite_scaled(id: int, px: int, py: int, sc: int) -> void { if id < 0 { return } if id >= spr_n { return } if sc < 1 { return } let base = id * SPR_SZ * SPR_SZ for y in 0 .. SPR_SZ { for x in 0 .. SPR_SZ { let p = spr_px[base + y * SPR_SZ + x] if ((p >> 24) & 255) != 0 { rt_fill_rect(px + x * sc, py + y * sc, sc, sc, (p & 16777215)) } } } } # ---- .lspr sprite sheets (palette + ASCII rows) --------------------------- function rt_hexval(c: int) -> int { if c >= 48 { if c <= 57 { return c - 48 } } if c >= 97 { if c <= 102 { return c - 87 } } if c >= 65 { if c <= 70 { return c - 55 } } return 0 - 1 } function rt_sprites_load(path: string) -> void { let d = rt_read_file(path) if (d == null) { spr_n = 0; return } let size = rt_file_len let pal: words = words(128) fill(pal, 0, 128 * 4) var cur = 0 - 1 var row = 0 var i = 0 while i < size { let start = i var len = 0 var j = start while j < size { let ch = d[j] if ch == 10 { j = size } else { len = len + 1; j = j + 1 } } let c0 = d[start] if c0 == 112 { # 'p' — "pal " let ch = d[start + 4] var v = 0 for k in 0 .. 6 { let hv = rt_hexval(d[start + 6 + k]) if hv >= 0 { v = v * 16 + hv } } if ch < 128 { pal[ch] = ((255 << 24) | v) } } if c0 == 115 { # 's' — "spr" starts a new sprite cur = 0 - 1 if spr_n < SPR_MAX { cur = spr_n spr_n = spr_n + 1 fill(offset(spr_px, cur * SPR_SZ * SPR_SZ * 4), 0, SPR_SZ * SPR_SZ * 4) } row = 0 } if c0 != 112 { if c0 != 115 { if c0 != 35 { # '#' comment if cur >= 0 { if row < SPR_SZ { for x in 0 .. SPR_SZ { if x < len { spr_px[cur * SPR_SZ * SPR_SZ + row * SPR_SZ + x] = pal[(d[start + x] & 127)] } } row = row + 1 } } } } } i = start + len + 1 } free(pal) free(d) }