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, }