Files
levlib/draw/draw_qr/draw_qr.odin
T
zack 6a0a984310 Added full clay border support to draw (#28)
Co-authored-by: Zachary Levy <zachary@sunforge.is>
Reviewed-on: #28
2026-05-12 04:47:23 +00:00

192 lines
5.8 KiB
Odin

package draw_qr
import "core:mem"
import "core:slice"
import draw ".."
import "../../qrcode"
DFT_QR_DARK :: draw.BLACK // Default QR code dark module color.
DFT_QR_LIGHT :: draw.WHITE // Default QR code light module color.
DFT_QR_BOOST_ECL :: true // Default QR error correction level boost.
DFT_QR_QUIET_ZONE :: 4 // Default light-pixel border on each side; 4 is the QR spec value.
// Returns the number of bytes to_texture will write. Equals dim*dim*4 where
// dim = qrcode.get_size(qrcode_buf) + 2*quiet_zone.
texture_size :: #force_inline proc(qrcode_buf: []u8, quiet_zone: int = DFT_QR_QUIET_ZONE) -> int {
size := qrcode.get_size(qrcode_buf)
if size == 0 || quiet_zone < 0 do return 0
padded_size := size + 2 * quiet_zone
return padded_size * padded_size * 4
}
// Decodes an encoded QR buffer into tightly-packed RGBA pixel data written to
// texture_buf. No allocations, no GPU calls. Returns the Texture_Desc the
// caller should pass to draw.register_texture alongside texture_buf.
//
// quiet_zone adds that many `light` pixels on each side; the spec value is 4.
// Final dimension is qrcode.get_size + 2*quiet_zone on each axis.
//
// Returns ok=false when:
// - qrcode_buf is invalid (qrcode.get_size returns 0).
// - quiet_zone is negative.
// - texture_buf is smaller than texture_size(qrcode_buf, quiet_zone).
@(require_results)
to_texture :: proc(
qrcode_buf: []u8,
texture_buf: []u8,
dark: draw.Color = DFT_QR_DARK,
light: draw.Color = DFT_QR_LIGHT,
quiet_zone: int = DFT_QR_QUIET_ZONE,
) -> (
desc: draw.Texture_Desc,
ok: bool,
) {
size := qrcode.get_size(qrcode_buf)
if size == 0 || quiet_zone < 0 do return
padded_size := size + 2 * quiet_zone
if len(texture_buf) < padded_size * padded_size * 4 do return
// Type-pun to []Color so each store is a single 32-bit write.
pixels := mem.slice_data_cast([]draw.Color, texture_buf[:padded_size * padded_size * 4])
// Bulk-fill with light: handles the border and every light QR module at once.
slice.fill(pixels, light)
// Overwrite only the dark modules, offset by the quiet-zone border.
for y in 0 ..< size {
row := (y + quiet_zone) * padded_size + quiet_zone
for x in 0 ..< size {
if qrcode.get_module(qrcode_buf, x, y) {
pixels[row + x] = dark
}
}
}
return draw.Texture_Desc {
width = u32(padded_size),
height = u32(padded_size),
depth_or_layers = 1,
type = .D2,
format = .R8G8B8A8_UNORM,
usage = {.SAMPLER},
mip_levels = 1,
kind = .Static,
},
true
}
// Allocates pixel buffer via temp_allocator, decodes qrcode_buf into it, and
// registers with the GPU. The pixel allocation is freed before return.
//
// Returns ok=false when:
// - qrcode_buf is invalid (qrcode.get_size returns 0).
// - temp_allocator fails to allocate the pixel buffer.
// - GPU texture registration fails.
@(require_results)
register_texture_from_raw :: proc(
qrcode_buf: []u8,
dark: draw.Color = DFT_QR_DARK,
light: draw.Color = DFT_QR_LIGHT,
quiet_zone: int = DFT_QR_QUIET_ZONE,
temp_allocator := context.temp_allocator,
) -> (
texture: draw.Texture_Id,
ok: bool,
) {
tex_size := texture_size(qrcode_buf, quiet_zone)
if tex_size == 0 do return draw.INVALID_TEXTURE, false
pixels, alloc_err := make([]u8, tex_size, temp_allocator)
if alloc_err != nil do return draw.INVALID_TEXTURE, false
defer delete(pixels, temp_allocator)
desc := to_texture(qrcode_buf, pixels, dark, light, quiet_zone) or_return
return draw.register_texture(desc, pixels)
}
// Encodes text as a QR Code and registers the result as an RGBA texture.
//
// Returns ok=false when:
// - temp_allocator fails to allocate.
// - The text cannot fit in any version within [min_version, max_version] at the given ECL.
// - GPU texture registration fails.
@(require_results)
register_texture_from_text :: proc(
text: string,
ecl: qrcode.Ecc = .Low,
min_version: int = qrcode.VERSION_MIN,
max_version: int = qrcode.VERSION_MAX,
mask: Maybe(qrcode.Mask) = nil,
boost_ecl: bool = DFT_QR_BOOST_ECL,
dark: draw.Color = DFT_QR_DARK,
light: draw.Color = DFT_QR_LIGHT,
quiet_zone: int = DFT_QR_QUIET_ZONE,
temp_allocator := context.temp_allocator,
) -> (
texture: draw.Texture_Id,
ok: bool,
) {
qrcode_buf, alloc_err := make([]u8, qrcode.buffer_len_for_version(max_version), temp_allocator)
if alloc_err != nil do return draw.INVALID_TEXTURE, false
defer delete(qrcode_buf, temp_allocator)
qrcode.encode_auto(
text,
qrcode_buf,
ecl,
min_version,
max_version,
mask,
boost_ecl,
temp_allocator,
) or_return
return register_texture_from_raw(qrcode_buf, dark, light, quiet_zone, temp_allocator)
}
// Encodes arbitrary binary data as a QR Code and registers the result as an RGBA texture.
//
// Returns ok=false when:
// - temp_allocator fails to allocate.
// - The payload cannot fit in any version within [min_version, max_version] at the given ECL.
// - GPU texture registration fails.
@(require_results)
register_texture_from_binary :: proc(
bin_data: []u8,
ecl: qrcode.Ecc = .Low,
min_version: int = qrcode.VERSION_MIN,
max_version: int = qrcode.VERSION_MAX,
mask: Maybe(qrcode.Mask) = nil,
boost_ecl: bool = DFT_QR_BOOST_ECL,
dark: draw.Color = DFT_QR_DARK,
light: draw.Color = DFT_QR_LIGHT,
quiet_zone: int = DFT_QR_QUIET_ZONE,
temp_allocator := context.temp_allocator,
) -> (
texture: draw.Texture_Id,
ok: bool,
) {
qrcode_buf, alloc_err := make([]u8, qrcode.buffer_len_for_version(max_version), temp_allocator)
if alloc_err != nil do return draw.INVALID_TEXTURE, false
defer delete(qrcode_buf, temp_allocator)
qrcode.encode_auto(
bin_data,
qrcode_buf,
ecl,
min_version,
max_version,
mask,
boost_ecl,
temp_allocator,
) or_return
return register_texture_from_raw(qrcode_buf, dark, light, quiet_zone, temp_allocator)
}
register_texture_from :: proc {
register_texture_from_text,
register_texture_from_binary,
}