Added full clay border support to draw (#28)

Co-authored-by: Zachary Levy <zachary@sunforge.is>
Reviewed-on: #28
This commit was merged in pull request #28.
This commit is contained in:
2026-05-12 04:47:23 +00:00
parent 0ecd93a334
commit 6a0a984310
8 changed files with 1236 additions and 449 deletions
+39 -27
View File
@@ -1,54 +1,71 @@
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 for the given encoded
// QR buffer. Equivalent to size*size*4 where size = qrcode.get_size(qrcode_buf).
texture_size :: #force_inline proc(qrcode_buf: []u8) -> int {
// 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)
return size * size * 4
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).
// - texture_buf is smaller than texture_size(qrcode_buf).
// - 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 do return {}, false
if len(texture_buf) < size * size * 4 do return {}, false
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 {
i := (y * size + x) * 4
c := dark if qrcode.get_module(qrcode_buf, x, y) else light
texture_buf[i + 0] = c[0]
texture_buf[i + 1] = c[1]
texture_buf[i + 2] = c[2]
texture_buf[i + 3] = c[3]
if qrcode.get_module(qrcode_buf, x, y) {
pixels[row + x] = dark
}
}
}
return draw.Texture_Desc {
width = u32(size),
height = u32(size),
width = u32(padded_size),
height = u32(padded_size),
depth_or_layers = 1,
type = .D2,
format = .R8G8B8A8_UNORM,
@@ -71,19 +88,20 @@ 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)
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) or_return
desc := to_texture(qrcode_buf, pixels, dark, light, quiet_zone) or_return
return draw.register_texture(desc, pixels)
}
@@ -103,6 +121,7 @@ register_texture_from_text :: proc(
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,
@@ -123,7 +142,7 @@ register_texture_from_text :: proc(
temp_allocator,
) or_return
return register_texture_from_raw(qrcode_buf, dark, light, temp_allocator)
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.
@@ -142,6 +161,7 @@ register_texture_from_binary :: proc(
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,
@@ -162,18 +182,10 @@ register_texture_from_binary :: proc(
temp_allocator,
) or_return
return register_texture_from_raw(qrcode_buf, dark, light, temp_allocator)
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,
}
// Default fit=.Fit preserves the QR's square aspect; override as needed.
clay_image :: #force_inline proc(
texture: draw.Texture_Id,
tint: draw.Color = draw.DFT_TINT,
) -> draw.Clay_Image_Data {
return draw.clay_image_data(texture, fit = .Fit, tint = tint)
}