From 1af8b5cdfb27efa3ceaf932ef195a7d888b92282 Mon Sep 17 00:00:00 2001 From: Zachary Levy Date: Mon, 11 May 2026 21:36:58 -0700 Subject: [PATCH] Added quiet zone support to QR rendering --- draw/draw_qr/draw_qr.odin | 66 +++++++++++++++++++++++---------------- 1 file changed, 39 insertions(+), 27 deletions(-) diff --git a/draw/draw_qr/draw_qr.odin b/draw/draw_qr/draw_qr.odin index f9e9e9d..9d8c0cf 100644 --- a/draw/draw_qr/draw_qr.odin +++ b/draw/draw_qr/draw_qr.odin @@ -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) -}