QR code improvements
This commit is contained in:
@@ -3,76 +3,188 @@ package draw_qr
|
||||
import draw ".."
|
||||
import "../../qrcode"
|
||||
|
||||
// A registered QR code texture, ready for display via draw.rectangle_texture.
|
||||
QR :: struct {
|
||||
texture_id: draw.Texture_Id,
|
||||
size: int, // modules per side (e.g. 21..177)
|
||||
// -----------------------------------------------------------------------------
|
||||
// Layer 1 — pure: encoded QR buffer → RGBA pixels + descriptor
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// 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 {
|
||||
size := qrcode.get_size(qrcode_buf)
|
||||
return size * size * 4
|
||||
}
|
||||
|
||||
// Encode text as a QR code and register the result as an R8 texture.
|
||||
// The texture uses Nearest_Clamp sampling by default (sharp module edges).
|
||||
// Returns ok=false if encoding or registration fails.
|
||||
// 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.
|
||||
//
|
||||
// Returns ok=false when:
|
||||
// - qrcode_buf is invalid (qrcode.get_size returns 0).
|
||||
// - texture_buf is smaller than to_texture_size(qrcode_buf).
|
||||
@(require_results)
|
||||
create_from_text :: proc(
|
||||
to_texture :: proc(
|
||||
qrcode_buf: []u8,
|
||||
texture_buf: []u8,
|
||||
dark: draw.Color = draw.BLACK,
|
||||
light: draw.Color = draw.WHITE,
|
||||
) -> (
|
||||
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
|
||||
|
||||
for y in 0 ..< size {
|
||||
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]
|
||||
}
|
||||
}
|
||||
|
||||
return draw.Texture_Desc {
|
||||
width = u32(size),
|
||||
height = u32(size),
|
||||
depth_or_layers = 1,
|
||||
type = .D2,
|
||||
format = .R8G8B8A8_UNORM,
|
||||
usage = {.SAMPLER},
|
||||
mip_levels = 1,
|
||||
kind = .Static,
|
||||
},
|
||||
true
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Layer 2 — raw: pre-encoded QR buffer → registered GPU texture
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// 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 = draw.BLACK,
|
||||
light: draw.Color = draw.WHITE,
|
||||
temp_allocator := context.temp_allocator,
|
||||
) -> (
|
||||
texture: draw.Texture_Id,
|
||||
ok: bool,
|
||||
) {
|
||||
tex_size := texture_size(qrcode_buf)
|
||||
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
|
||||
return draw.register_texture(desc, pixels)
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Layer 3 — text → registered GPU texture
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// 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 = true,
|
||||
dark: draw.Color = draw.BLACK,
|
||||
light: draw.Color = draw.WHITE,
|
||||
temp_allocator := context.temp_allocator,
|
||||
) -> (
|
||||
qr: QR,
|
||||
texture: draw.Texture_Id,
|
||||
ok: bool,
|
||||
) {
|
||||
qrcode_buf: [qrcode.BUFFER_LEN_MAX]u8
|
||||
encode_ok := qrcode.encode(text, qrcode_buf[:], ecl, min_version, max_version, mask, boost_ecl)
|
||||
if !encode_ok do return {}, false
|
||||
return create(qrcode_buf[:])
|
||||
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, temp_allocator)
|
||||
}
|
||||
|
||||
// Register an already-encoded QR code buffer as an R8 texture.
|
||||
// qrcode_buf must be the output of qrcode.encode (byte 0 = side length, remaining = bit-packed modules).
|
||||
// -----------------------------------------------------------------------------
|
||||
// Layer 4 — binary → registered GPU texture
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// 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)
|
||||
create :: proc(qrcode_buf: []u8) -> (qr: QR, ok: bool) {
|
||||
size := qrcode.get_size(qrcode_buf)
|
||||
if size == 0 do return {}, false
|
||||
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 = true,
|
||||
dark: draw.Color = draw.BLACK,
|
||||
light: draw.Color = draw.WHITE,
|
||||
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)
|
||||
|
||||
// Build R8 pixel buffer: 0 = light, 255 = dark
|
||||
pixels := make([]u8, size * size, context.temp_allocator)
|
||||
for y in 0 ..< size {
|
||||
for x in 0 ..< size {
|
||||
pixels[y * size + x] = 255 if qrcode.get_module(qrcode_buf, x, y) else 0
|
||||
}
|
||||
}
|
||||
qrcode.encode_auto(
|
||||
bin_data,
|
||||
qrcode_buf,
|
||||
ecl,
|
||||
min_version,
|
||||
max_version,
|
||||
mask,
|
||||
boost_ecl,
|
||||
temp_allocator,
|
||||
) or_return
|
||||
|
||||
id, reg_ok := draw.register_texture(
|
||||
draw.Texture_Desc {
|
||||
width = u32(size),
|
||||
height = u32(size),
|
||||
depth_or_layers = 1,
|
||||
type = .D2,
|
||||
format = .R8_UNORM,
|
||||
usage = {.SAMPLER},
|
||||
mip_levels = 1,
|
||||
kind = .Static,
|
||||
},
|
||||
pixels,
|
||||
)
|
||||
if !reg_ok do return {}, false
|
||||
|
||||
return QR{texture_id = id, size = size}, true
|
||||
return register_texture_from_raw(qrcode_buf, dark, light, temp_allocator)
|
||||
}
|
||||
|
||||
// Release the GPU texture.
|
||||
destroy :: proc(qr: ^QR) {
|
||||
draw.unregister_texture(qr.texture_id)
|
||||
qr.texture_id = draw.INVALID_TEXTURE
|
||||
qr.size = 0
|
||||
}
|
||||
// -----------------------------------------------------------------------------
|
||||
// Clay integration helper
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
// Convenience: build a Clay_Image_Data for embedding a QR in Clay layouts.
|
||||
// Uses Nearest_Clamp sampling (set via Sampler_Preset at draw time, not here) and Fit mode
|
||||
// to preserve the QR's square aspect ratio.
|
||||
clay_image :: proc(qr: QR, tint: draw.Color = draw.WHITE) -> draw.Clay_Image_Data {
|
||||
return draw.clay_image_data(qr.texture_id, fit = .Fit, tint = tint)
|
||||
// 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.WHITE,
|
||||
) -> draw.Clay_Image_Data {
|
||||
return draw.clay_image_data(texture, fit = .Fit, tint = tint)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user