mostly qoi image support

refactor image loader for multiple filetypes
port some broken math stuff
make logger more readable
This commit is contained in:
koniifer 2024-11-07 01:55:08 +00:00
parent ab8522fbe1
commit 6a8d92d2c2
13 changed files with 268 additions and 161 deletions

View file

@ -36,7 +36,19 @@ impl log::Log for Logger {
Level::Debug => "25",
Level::Trace => "103",
};
let module = record.module_path().unwrap_or_default();
let module = record
.module_path()
.unwrap_or_default()
.rsplit_once(':')
.unwrap_or_default()
.1;
if module == "" {
crate::arch::log(format_args!(
"\x1b[38;5;{lvl_color}m{lvl}\x1b[0m: {}\r\n",
record.args(),
))
.expect("write to serial console");
} else {
let line = record.line().unwrap_or_default();
crate::arch::log(format_args!(
"\x1b[38;5;{lvl_color}m{lvl}\x1b[0m [{module}:{line}]: {}\r\n",
@ -44,6 +56,7 @@ impl log::Log for Logger {
))
.expect("write to serial console");
}
}
fn flush(&self) {}
}

View file

@ -74,7 +74,6 @@ impl Package {
&path,
Options {
fmt: true,
// optimize: true,
..Default::default()
},
&mut bytes,
@ -83,7 +82,6 @@ impl Package {
hblang::run_compiler(
&path,
Options {
// optimize: true,
..Default::default()
},
&mut bytes,
@ -100,7 +98,6 @@ impl Package {
&path,
Options {
dump_asm: true,
// optimize: true,
..Default::default()
},
&mut bytes,

View file

@ -1,102 +0,0 @@
.{Color, Surface, new_surface} := @use("./lib.hb");
.{log, memory} := @use("../../stn/src/lib.hb")
BitmapFileHeader := packed struct {
img_type: u16,
size: u32,
reserved_1: u16,
reserved_2: u16,
offset: u32,
}
BitmapInfoHeader := packed struct {
size: u32,
width: i32,
height: i32,
planes: u16,
bits: u16,
compression: u32,
image_size: u32,
x_resolution: i32,
y_resolution: i32,
n_colours: u32,
important_colours: u32,
}
BitmapColorHeader := packed struct {
red_mask: u32,
green_mask: u32,
blue_mask: u32,
alpha_mask: u32,
color_space_type: u32,
unused: u32,
}
surface_from_bmp := fn(bmp: ^u8): ?Surface {
file_header := @as(^BitmapFileHeader, @bitcast(bmp))
if file_header.img_type != 0x4D42 {
log.error("failed to load bmp image: not a bmp image, idiot\0")
return null
}
info_header := @as(^BitmapInfoHeader, @bitcast(bmp + @sizeof(BitmapFileHeader)))
bmp += file_header.offset
px := info_header.width * info_header.height
ptr := @as(^Color, @bitcast(bmp))
tmp := @as(Color, idk)
row := @as(i32, 0)
loop if row == info_header.height / 2 break else {
col := @as(i32, 0)
loop if col == info_header.width break else {
top_index := row * info_header.width + col
bottom_index := (info_header.height - 1 - row) * info_header.width + col
tmp = *(ptr + top_index);
*(ptr + top_index) = *(ptr + bottom_index);
*(ptr + bottom_index) = tmp
col += 1
}
row += 1
}
return .(@bitcast(bmp), info_header.width, info_header.height)
}
new_surface_from_bmp := fn(bmp: ^u8): ?Surface {
file_header := @as(^BitmapFileHeader, @bitcast(bmp))
if file_header.img_type != 0x4D42 {
log.error("failed to load bmp image: not a bmp image, idiot\0")
return null
}
info_header := @as(^BitmapInfoHeader, @bitcast(bmp + @sizeof(BitmapFileHeader)))
bmp += file_header.offset
width := @as(uint, @intcast(info_header.width))
height := @as(uint, @intcast(info_header.height))
surface := new_surface(width, height)
top_start_idx := surface.buf
bottom_start_idx := surface.buf + width * (height - 1)
rows_to_copy := height
top_cursor := @as(^Color, @bitcast(bmp))
bottom_cursor := top_cursor + width * (height - 1)
loop if rows_to_copy <= 1 break else {
@inline(memory.copy, Color, top_cursor, bottom_start_idx, @bitcast(width))
@inline(memory.copy, Color, bottom_cursor, top_start_idx, @bitcast(width))
top_start_idx += surface.width
bottom_start_idx -= surface.width
top_cursor += width
bottom_cursor -= width
rows_to_copy -= 2
}
if rows_to_copy == 1 {
@inline(memory.copy, Color, top_cursor, top_start_idx, @bitcast(width))
}
return surface
}

View file

@ -0,0 +1,52 @@
.{Color, Surface, new_surface, put_surface} := @use("../lib.hb");
.{log, memory} := @use("../../../stn/src/lib.hb")
BitmapFileHeader := packed struct {
magic: u16,
size: u32,
reserved_1: u16,
reserved_2: u16,
offset: u32,
}
BitmapInfoHeader := packed struct {
size: u32,
width: i32,
height: i32,
planes: u16,
bits: u16,
compression: u32,
image_size: u32,
x_resolution: i32,
y_resolution: i32,
n_colours: u32,
important_colours: u32,
}
BitmapColorHeader := packed struct {
red_mask: u32,
green_mask: u32,
blue_mask: u32,
alpha_mask: u32,
color_space_type: u32,
unused: u32,
}
from := fn(bmp: ^u8): ?Surface {
file_header := @as(^BitmapFileHeader, @bitcast(bmp))
info_header := @as(^BitmapInfoHeader, @bitcast(bmp + @sizeof(BitmapFileHeader)))
bmp += file_header.offset
if file_header.magic != 0x4D42 | info_header.width == 0 | info_header.height == 0 {
log.error("Invalid BMP image.\0")
return null
}
width := @as(uint, info_header.width)
height := @as(uint, info_header.height)
lhs := Surface.(@bitcast(bmp), width, height)
rhs := new_surface(width, height)
put_surface(rhs, lhs, .(0, 0), true)
return rhs
}

View file

@ -0,0 +1,34 @@
.{log} := @use("../../../stn/src/lib.hb");
.{Surface} := @use("../lib.hb")
bmp := @use("bmp.hb")
qoi := @use("qoi.hb")
BMP := 0x4D42
QOI := 0x66696F71
// stand-in for null until bugfix
DOES_NOT_EXIST := 1 << 32
get_format := fn(file: ^u8): uint {
if *@as(^u16, @bitcast(file)) == BMP {
return BMP
} else if *@as(^u32, @bitcast(file)) == QOI {
return QOI
} else {
return DOES_NOT_EXIST
}
}
from := fn(file: ^u8): ?Surface {
format := get_format(file)
if format == DOES_NOT_EXIST {
log.error("Could not detect image format.\0")
return null
} else if format == BMP {
return bmp.from(file)
} else if format == QOI {
return qoi.from(file)
}
return null
}

View file

@ -0,0 +1,101 @@
.{Color, Surface, new_surface} := @use("../lib.hb");
.{log, memory} := @use("../../../stn/src/lib.hb")
/* source:
https://github.com/phoboslab/qoi/blob/master/qoi.h */
QOI_SRGB := 0
QOI_LINEAR := 1
QOI_OP_INDEX := 0x0
QOI_OP_DIFF := 0x40
QOI_OP_LUMA := 0x80
QOI_OP_RUN := 0xC0
QOI_OP_RGB := 0xFE
QOI_OP_RGBA := 0xFF
QOI_MASK_2 := 0xC0
QOI_COLOR_HASH := fn(c: Color): u8 {
return (c.r * 3 + c.g * 5 + c.b * 7 + c.a * 11) % 64
}
QOI_MAGIC := 0x716F6966
QOI_PIXELS_MAX := 400000000
QuiteOkayHeader := packed struct {
magic: u32,
width: u32,
height: u32,
channels: u8,
colorspace: u8,
}
be_to_le := fn(big: u32): u32 {
return (big & 0xFF000000) >> 24 | (big & 0xFF0000) >> 8 | (big & 0xFF00) << 8 | (big & 0xFF) << 24
}
from := fn(qoi: ^u8): ?Surface {
header := @as(^QuiteOkayHeader, @bitcast(qoi))
qoi += @sizeof(QuiteOkayHeader)
width := be_to_le(header.width)
height := be_to_le(header.height)
if be_to_le(header.magic) != QOI_MAGIC | width == 0 | height == 0 | header.channels < 3 | header.channels > 4 {
log.error("Invalid QOI image.\0")
return null
}
surface := new_surface(width, height)
index := @as([Color; 64], idk)
run := 0
px := Color.(0, 0, 0, 255)
px_pos := 0
total_pixels := width * height
loop if px_pos >= total_pixels break else {
if run > 0 {
run -= 1
} else {
b1 := *qoi
qoi += 1
if b1 == QOI_OP_RGB {
px.r = *qoi
px.g = *(qoi + 1)
px.b = *(qoi + 2)
qoi += 3
} else if b1 == QOI_OP_RGBA {
px.r = *qoi
px.g = *(qoi + 1)
px.b = *(qoi + 2)
px.a = *(qoi + 3)
qoi += 4
} else if (b1 & QOI_MASK_2) == QOI_OP_INDEX {
px = index[b1 & 0x3F]
} else if (b1 & QOI_MASK_2) == QOI_OP_DIFF {
px.r = px.r + (b1 >> 4 & 0x3) - 2 & 0xFF
px.g = px.g + (b1 >> 2 & 0x3) - 2 & 0xFF
px.b = px.b + (b1 & 0x3) - 2 & 0xFF
} else if (b1 & QOI_MASK_2) == QOI_OP_LUMA {
b2 := *qoi
vg := (b1 & 0x3F) - 32
px.r = px.r + vg - 8 + (b2 >> 4 & 0xF) & 0xFF
px.g = px.g + vg & 0xFF
px.b = px.b + vg - 8 + (b2 & 0xF) & 0xFF
qoi += 1
} else if (b1 & QOI_MASK_2) == QOI_OP_RUN {
run = b1 & 0x3F
}
index[@inline(QOI_COLOR_HASH, px)] = px
};
*(surface.buf + px_pos) = px
px_pos += 1
}
return surface
}

View file

@ -1,5 +1,5 @@
software := @use("software.hb")
image := @use("image.hb")
image := @use("image/lib.hb")
text := @use("text.hb")
// default mode

File diff suppressed because one or more lines are too long

View file

@ -68,3 +68,17 @@ reverse := fn(s: ^u8): void {
}
return
}
equals := fn(lhs: ^u8, rhs: ^u8): bool {
if lhs == rhs {
return true
}
i := 0
loop if *(lhs + i) != *(rhs + i) {
return false
} else if *lhs == 0 {
return true
} else {
i += 1
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

After

Width:  |  Height:  |  Size: 100 KiB

View file

@ -1,38 +1,29 @@
.{Vec2} := @use("../../../../libraries/stn/src/lib.hb").math
.{log} := @use("../../../../libraries/stn/src/lib.hb")
render := @use("../../../../libraries/render/src/lib.hb")
/* expected result:
a cute image bounces around the screen */
a cute qoi image and a cute bmp image */
bmp_1 := @embed("./assets/able.bmp")
bmp_2 := @embed("./assets/mini.bmp")
qoi := @embed("./assets/mini.qoi")
bmp := @embed("./assets/mini.bmp")
example := fn(): void {
// strictly we should be null checking here but i am lazy
images := [render.Surface].(
@unwrap(render.image.surface_from_bmp(@bitcast(&bmp_1))),
@unwrap(render.image.surface_from_bmp(@bitcast(&bmp_2))),
)
screen := render.init(true)
vel := Vec2(int).(1, 1)
pos := Vec2(uint).(100, 100)
n := 0
loop {
image := images[n]
render.put_surface(screen, image, pos, false)
render.sync(screen)
image_qoi := render.image.from(@bitcast(&qoi))
image_bmp := render.image.from(@bitcast(&bmp))
if image_qoi == null {
log.error("failed to load qoi image for whatever reason\0")
return
}
if image_bmp == null {
log.error("failed to load bmp image for whatever reason\0")
return
}
render.clear(screen, render.black)
if pos.x == 0 | pos.x == screen.width - image.width {
vel.x = -vel.x
n = 1 - n
}
if pos.y == 0 | pos.y == screen.height - image.height {
vel.y = -vel.y
n = 1 - n
}
pos += @bitcast(vel)
}
render.put_surface(screen, image_bmp, .((screen.width - image_bmp.width * 3) / 2, (screen.height - image_bmp.height) / 2), false)
render.put_surface(screen, image_qoi, .((screen.width + image_qoi.width) / 2, (screen.height - image_qoi.height) / 2), false)
render.sync(screen)
return
}

View file

@ -1 +1 @@
.{example: main} := @use("./examples/text.hb")
.{example: main} := @use("./examples/image.hb")