Texture Rendering #9
@@ -161,6 +161,11 @@ register_texture_from_binary :: proc(
|
|||||||
return register_texture_from_raw(qrcode_buf, dark, light, temp_allocator)
|
return register_texture_from_raw(qrcode_buf, dark, light, 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.
|
// Default fit=.Fit preserves the QR's square aspect; override as needed.
|
||||||
clay_image :: #force_inline proc(
|
clay_image :: #force_inline proc(
|
||||||
texture: draw.Texture_Id,
|
texture: draw.Texture_Id,
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package examples
|
|||||||
|
|
||||||
import "../../draw"
|
import "../../draw"
|
||||||
import "../../draw/draw_qr"
|
import "../../draw/draw_qr"
|
||||||
import "core:math"
|
|
||||||
import "core:os"
|
import "core:os"
|
||||||
import sdl "vendor:sdl3"
|
import sdl "vendor:sdl3"
|
||||||
|
|
||||||
@@ -17,9 +16,8 @@ textures :: proc() {
|
|||||||
FONT_SIZE :: u16(14)
|
FONT_SIZE :: u16(14)
|
||||||
LABEL_OFFSET :: f32(8) // gap between item and its label
|
LABEL_OFFSET :: f32(8) // gap between item and its label
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
//----- Texture registration ----------------------------------
|
||||||
// Procedural checkerboard texture (8x8, RGBA8)
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
checker_size :: 8
|
checker_size :: 8
|
||||||
checker_pixels: [checker_size * checker_size * 4]u8
|
checker_pixels: [checker_size * checker_size * 4]u8
|
||||||
for y in 0 ..< checker_size {
|
for y in 0 ..< checker_size {
|
||||||
@@ -47,9 +45,6 @@ textures :: proc() {
|
|||||||
)
|
)
|
||||||
defer draw.unregister_texture(checker_texture)
|
defer draw.unregister_texture(checker_texture)
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
// Non-square gradient stripe texture (16x8, RGBA8) for fit mode demos
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
stripe_w :: 16
|
stripe_w :: 16
|
||||||
stripe_h :: 8
|
stripe_h :: 8
|
||||||
stripe_pixels: [stripe_w * stripe_h * 4]u8
|
stripe_pixels: [stripe_w * stripe_h * 4]u8
|
||||||
@@ -76,14 +71,13 @@ textures :: proc() {
|
|||||||
)
|
)
|
||||||
defer draw.unregister_texture(stripe_texture)
|
defer draw.unregister_texture(stripe_texture)
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
qr_texture, _ := draw_qr.register_texture_from("https://x.com/miiilato/status/1880241066471051443")
|
||||||
// QR code texture (R8_UNORM — see rendering note below)
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
qr_texture, _ := draw_qr.register_texture_from_text("https://x.com/miiilato/status/1880241066471051443")
|
|
||||||
defer draw.unregister_texture(qr_texture)
|
defer draw.unregister_texture(qr_texture)
|
||||||
|
|
||||||
spin_angle: f32 = 0
|
spin_angle: f32 = 0
|
||||||
|
|
||||||
|
//----- Draw loop ----------------------------------
|
||||||
|
|
||||||
for {
|
for {
|
||||||
defer free_all(context.temp_allocator)
|
defer free_all(context.temp_allocator)
|
||||||
ev: sdl.Event
|
ev: sdl.Event
|
||||||
@@ -97,9 +91,8 @@ textures :: proc() {
|
|||||||
// Background
|
// Background
|
||||||
draw.rectangle(base_layer, {0, 0, 800, 600}, {30, 30, 30, 255})
|
draw.rectangle(base_layer, {0, 0, 800, 600}, {30, 30, 30, 255})
|
||||||
|
|
||||||
// =====================================================================
|
//----- Row 1: Sampler presets (y=30) ----------------------------------
|
||||||
// Row 1: Sampler presets (y=30)
|
|
||||||
// =====================================================================
|
|
||||||
ROW1_Y :: f32(30)
|
ROW1_Y :: f32(30)
|
||||||
ITEM_SIZE :: f32(120)
|
ITEM_SIZE :: f32(120)
|
||||||
COL1 :: f32(30)
|
COL1 :: f32(30)
|
||||||
@@ -156,9 +149,8 @@ textures :: proc() {
|
|||||||
color = draw.WHITE,
|
color = draw.WHITE,
|
||||||
)
|
)
|
||||||
|
|
||||||
// =====================================================================
|
//----- Row 2: Sampler presets (y=190) ----------------------------------
|
||||||
// Row 2: QR code, Rounded, Rotating (y=190)
|
|
||||||
// =====================================================================
|
|
||||||
ROW2_Y :: f32(190)
|
ROW2_Y :: f32(190)
|
||||||
|
|
||||||
// QR code (RGBA texture with baked colors, nearest sampling)
|
// QR code (RGBA texture with baked colors, nearest sampling)
|
||||||
@@ -214,9 +206,8 @@ textures :: proc() {
|
|||||||
color = draw.WHITE,
|
color = draw.WHITE,
|
||||||
)
|
)
|
||||||
|
|
||||||
// =====================================================================
|
//----- Row 3: Fit modes + Per-corner radii (y=360) ----------------------------------
|
||||||
// Row 3: Fit modes + Per-corner radii (y=360)
|
|
||||||
// =====================================================================
|
|
||||||
ROW3_Y :: f32(360)
|
ROW3_Y :: f32(360)
|
||||||
FIT_SIZE :: f32(120) // square target rect
|
FIT_SIZE :: f32(120) // square target rect
|
||||||
|
|
||||||
|
|||||||
@@ -4,10 +4,6 @@ import "core:log"
|
|||||||
import "core:mem"
|
import "core:mem"
|
||||||
import sdl "vendor:sdl3"
|
import sdl "vendor:sdl3"
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Texture types
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
Texture_Id :: distinct u32
|
Texture_Id :: distinct u32
|
||||||
INVALID_TEXTURE :: Texture_Id(0) // Slot 0 is reserved/unused
|
INVALID_TEXTURE :: Texture_Id(0) // Slot 0 is reserved/unused
|
||||||
|
|
||||||
@@ -61,10 +57,6 @@ Texture_Slot :: struct {
|
|||||||
// GLOB.pending_texture_releases : [dynamic]Texture_Id
|
// GLOB.pending_texture_releases : [dynamic]Texture_Id
|
||||||
// GLOB.samplers : [SAMPLER_PRESET_COUNT]^sdl.GPUSampler
|
// GLOB.samplers : [SAMPLER_PRESET_COUNT]^sdl.GPUSampler
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Clay integration type
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
Clay_Image_Data :: struct {
|
Clay_Image_Data :: struct {
|
||||||
texture_id: Texture_Id,
|
texture_id: Texture_Id,
|
||||||
fit: Fit_Mode,
|
fit: Fit_Mode,
|
||||||
@@ -75,9 +67,9 @@ clay_image_data :: proc(id: Texture_Id, fit: Fit_Mode = .Stretch, tint: Color =
|
|||||||
return {texture_id = id, fit = fit, tint = tint}
|
return {texture_id = id, fit = fit, tint = tint}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------------------------------------------------
|
||||||
// Registration
|
// ----- Registration -------------
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
// Register a texture. Draw owns the GPU resource and releases it on unregister.
|
// Register a texture. Draw owns the GPU resource and releases it on unregister.
|
||||||
// `data` is tightly-packed row-major bytes matching desc.format.
|
// `data` is tightly-packed row-major bytes matching desc.format.
|
||||||
@@ -236,130 +228,9 @@ update_texture_region :: proc(id: Texture_Id, region: Rectangle, data: []u8) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------------------------------------------------
|
||||||
// Accessors
|
// ----- Helpers -------------
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
texture_size :: proc(id: Texture_Id) -> [2]u32 {
|
|
||||||
if id == INVALID_TEXTURE do return {0, 0}
|
|
||||||
slot := &GLOB.texture_slots[u32(id)]
|
|
||||||
return {slot.desc.width, slot.desc.height}
|
|
||||||
}
|
|
||||||
|
|
||||||
texture_format :: proc(id: Texture_Id) -> sdl.GPUTextureFormat {
|
|
||||||
if id == INVALID_TEXTURE do return .INVALID
|
|
||||||
return GLOB.texture_slots[u32(id)].desc.format
|
|
||||||
}
|
|
||||||
|
|
||||||
texture_kind :: proc(id: Texture_Id) -> Texture_Kind {
|
|
||||||
if id == INVALID_TEXTURE do return .Static
|
|
||||||
return GLOB.texture_slots[u32(id)].desc.kind
|
|
||||||
}
|
|
||||||
|
|
||||||
// Internal: get the raw GPU texture pointer for binding during draw.
|
|
||||||
@(private)
|
|
||||||
texture_gpu_handle :: proc(id: Texture_Id) -> ^sdl.GPUTexture {
|
|
||||||
if id == INVALID_TEXTURE do return nil
|
|
||||||
idx := u32(id)
|
|
||||||
if idx >= u32(len(GLOB.texture_slots)) do return nil
|
|
||||||
return GLOB.texture_slots[idx].gpu_texture
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Deferred release (called from draw.end / clear_global)
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
@(private)
|
|
||||||
process_pending_texture_releases :: proc() {
|
|
||||||
device := GLOB.device
|
|
||||||
for id in GLOB.pending_texture_releases {
|
|
||||||
idx := u32(id)
|
|
||||||
if idx >= u32(len(GLOB.texture_slots)) do continue
|
|
||||||
slot := &GLOB.texture_slots[idx]
|
|
||||||
if slot.gpu_texture != nil {
|
|
||||||
sdl.ReleaseGPUTexture(device, slot.gpu_texture)
|
|
||||||
slot.gpu_texture = nil
|
|
||||||
}
|
|
||||||
slot.generation += 1
|
|
||||||
append(&GLOB.texture_free_list, idx)
|
|
||||||
}
|
|
||||||
clear(&GLOB.pending_texture_releases)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Sampler pool
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
@(private)
|
|
||||||
get_sampler :: proc(preset: Sampler_Preset) -> ^sdl.GPUSampler {
|
|
||||||
idx := int(preset)
|
|
||||||
if GLOB.samplers[idx] != nil do return GLOB.samplers[idx]
|
|
||||||
|
|
||||||
// Lazily create
|
|
||||||
min_filter, mag_filter: sdl.GPUFilter
|
|
||||||
address_mode: sdl.GPUSamplerAddressMode
|
|
||||||
|
|
||||||
switch preset {
|
|
||||||
case .Nearest_Clamp:
|
|
||||||
min_filter = .NEAREST; mag_filter = .NEAREST; address_mode = .CLAMP_TO_EDGE
|
|
||||||
case .Linear_Clamp:
|
|
||||||
min_filter = .LINEAR; mag_filter = .LINEAR; address_mode = .CLAMP_TO_EDGE
|
|
||||||
case .Nearest_Repeat:
|
|
||||||
min_filter = .NEAREST; mag_filter = .NEAREST; address_mode = .REPEAT
|
|
||||||
case .Linear_Repeat:
|
|
||||||
min_filter = .LINEAR; mag_filter = .LINEAR; address_mode = .REPEAT
|
|
||||||
}
|
|
||||||
|
|
||||||
sampler := sdl.CreateGPUSampler(
|
|
||||||
GLOB.device,
|
|
||||||
sdl.GPUSamplerCreateInfo {
|
|
||||||
min_filter = min_filter,
|
|
||||||
mag_filter = mag_filter,
|
|
||||||
mipmap_mode = .LINEAR,
|
|
||||||
address_mode_u = address_mode,
|
|
||||||
address_mode_v = address_mode,
|
|
||||||
address_mode_w = address_mode,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
if sampler == nil {
|
|
||||||
log.errorf("Failed to create sampler preset %v: %s", preset, sdl.GetError())
|
|
||||||
return GLOB.pipeline_2d_base.sampler // fallback to existing default sampler
|
|
||||||
}
|
|
||||||
|
|
||||||
GLOB.samplers[idx] = sampler
|
|
||||||
return sampler
|
|
||||||
}
|
|
||||||
|
|
||||||
// Internal: destroy all sampler pool entries. Called from draw.destroy().
|
|
||||||
@(private)
|
|
||||||
destroy_sampler_pool :: proc() {
|
|
||||||
device := GLOB.device
|
|
||||||
for &s in GLOB.samplers {
|
|
||||||
if s != nil {
|
|
||||||
sdl.ReleaseGPUSampler(device, s)
|
|
||||||
s = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Internal: destroy all registered textures. Called from draw.destroy().
|
|
||||||
@(private)
|
|
||||||
destroy_all_textures :: proc() {
|
|
||||||
device := GLOB.device
|
|
||||||
for &slot in GLOB.texture_slots {
|
|
||||||
if slot.gpu_texture != nil {
|
|
||||||
sdl.ReleaseGPUTexture(device, slot.gpu_texture)
|
|
||||||
slot.gpu_texture = nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
delete(GLOB.texture_slots)
|
|
||||||
delete(GLOB.texture_free_list)
|
|
||||||
delete(GLOB.pending_texture_releases)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Fit mode helper
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// Compute UV rect, recommended sampler, and inner rect for a given fit mode.
|
// Compute UV rect, recommended sampler, and inner rect for a given fit mode.
|
||||||
// `rect` is the target drawing area; `texture_id` identifies the texture whose
|
// `rect` is the target drawing area; `texture_id` identifies the texture whose
|
||||||
@@ -431,3 +302,113 @@ fit_params :: proc(
|
|||||||
|
|
||||||
return {0, 0, 1, 1}, .Linear_Clamp, inner_rect
|
return {0, 0, 1, 1}, .Linear_Clamp, inner_rect
|
||||||
}
|
}
|
||||||
|
|
||||||
|
texture_size :: proc(id: Texture_Id) -> [2]u32 {
|
||||||
|
if id == INVALID_TEXTURE do return {0, 0}
|
||||||
|
slot := &GLOB.texture_slots[u32(id)]
|
||||||
|
return {slot.desc.width, slot.desc.height}
|
||||||
|
}
|
||||||
|
|
||||||
|
texture_format :: proc(id: Texture_Id) -> sdl.GPUTextureFormat {
|
||||||
|
if id == INVALID_TEXTURE do return .INVALID
|
||||||
|
return GLOB.texture_slots[u32(id)].desc.format
|
||||||
|
}
|
||||||
|
|
||||||
|
texture_kind :: proc(id: Texture_Id) -> Texture_Kind {
|
||||||
|
if id == INVALID_TEXTURE do return .Static
|
||||||
|
return GLOB.texture_slots[u32(id)].desc.kind
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal: get the raw GPU texture pointer for binding during draw.
|
||||||
|
@(private)
|
||||||
|
texture_gpu_handle :: proc(id: Texture_Id) -> ^sdl.GPUTexture {
|
||||||
|
if id == INVALID_TEXTURE do return nil
|
||||||
|
idx := u32(id)
|
||||||
|
if idx >= u32(len(GLOB.texture_slots)) do return nil
|
||||||
|
return GLOB.texture_slots[idx].gpu_texture
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deferred release (called from draw.end / clear_global)
|
||||||
|
@(private)
|
||||||
|
process_pending_texture_releases :: proc() {
|
||||||
|
device := GLOB.device
|
||||||
|
for id in GLOB.pending_texture_releases {
|
||||||
|
idx := u32(id)
|
||||||
|
if idx >= u32(len(GLOB.texture_slots)) do continue
|
||||||
|
slot := &GLOB.texture_slots[idx]
|
||||||
|
if slot.gpu_texture != nil {
|
||||||
|
sdl.ReleaseGPUTexture(device, slot.gpu_texture)
|
||||||
|
slot.gpu_texture = nil
|
||||||
|
}
|
||||||
|
slot.generation += 1
|
||||||
|
append(&GLOB.texture_free_list, idx)
|
||||||
|
}
|
||||||
|
clear(&GLOB.pending_texture_releases)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
get_sampler :: proc(preset: Sampler_Preset) -> ^sdl.GPUSampler {
|
||||||
|
idx := int(preset)
|
||||||
|
if GLOB.samplers[idx] != nil do return GLOB.samplers[idx]
|
||||||
|
|
||||||
|
// Lazily create
|
||||||
|
min_filter, mag_filter: sdl.GPUFilter
|
||||||
|
address_mode: sdl.GPUSamplerAddressMode
|
||||||
|
|
||||||
|
switch preset {
|
||||||
|
case .Nearest_Clamp:
|
||||||
|
min_filter = .NEAREST; mag_filter = .NEAREST; address_mode = .CLAMP_TO_EDGE
|
||||||
|
case .Linear_Clamp:
|
||||||
|
min_filter = .LINEAR; mag_filter = .LINEAR; address_mode = .CLAMP_TO_EDGE
|
||||||
|
case .Nearest_Repeat:
|
||||||
|
min_filter = .NEAREST; mag_filter = .NEAREST; address_mode = .REPEAT
|
||||||
|
case .Linear_Repeat:
|
||||||
|
min_filter = .LINEAR; mag_filter = .LINEAR; address_mode = .REPEAT
|
||||||
|
}
|
||||||
|
|
||||||
|
sampler := sdl.CreateGPUSampler(
|
||||||
|
GLOB.device,
|
||||||
|
sdl.GPUSamplerCreateInfo {
|
||||||
|
min_filter = min_filter,
|
||||||
|
mag_filter = mag_filter,
|
||||||
|
mipmap_mode = .LINEAR,
|
||||||
|
address_mode_u = address_mode,
|
||||||
|
address_mode_v = address_mode,
|
||||||
|
address_mode_w = address_mode,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if sampler == nil {
|
||||||
|
log.errorf("Failed to create sampler preset %v: %s", preset, sdl.GetError())
|
||||||
|
return GLOB.pipeline_2d_base.sampler // fallback to existing default sampler
|
||||||
|
}
|
||||||
|
|
||||||
|
GLOB.samplers[idx] = sampler
|
||||||
|
return sampler
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal: destroy all sampler pool entries. Called from draw.destroy().
|
||||||
|
@(private)
|
||||||
|
destroy_sampler_pool :: proc() {
|
||||||
|
device := GLOB.device
|
||||||
|
for &s in GLOB.samplers {
|
||||||
|
if s != nil {
|
||||||
|
sdl.ReleaseGPUSampler(device, s)
|
||||||
|
s = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal: destroy all registered textures. Called from draw.destroy().
|
||||||
|
@(private)
|
||||||
|
destroy_all_textures :: proc() {
|
||||||
|
device := GLOB.device
|
||||||
|
for &slot in GLOB.texture_slots {
|
||||||
|
if slot.gpu_texture != nil {
|
||||||
|
sdl.ReleaseGPUTexture(device, slot.gpu_texture)
|
||||||
|
slot.gpu_texture = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete(GLOB.texture_slots)
|
||||||
|
delete(GLOB.texture_free_list)
|
||||||
|
delete(GLOB.pending_texture_releases)
|
||||||
|
}
|
||||||
|
|||||||
@@ -73,57 +73,32 @@ main :: proc() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------------------------------
|
|
||||||
// Utilities
|
|
||||||
// -------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// Prints the given QR Code to the console.
|
|
||||||
print_qr :: proc(qrcode: []u8) {
|
|
||||||
size := qr.get_size(qrcode)
|
|
||||||
border :: 4
|
|
||||||
for y in -border ..< size + border {
|
|
||||||
for x in -border ..< size + border {
|
|
||||||
fmt.print("##" if qr.get_module(qrcode, x, y) else " ")
|
|
||||||
}
|
|
||||||
fmt.println()
|
|
||||||
}
|
|
||||||
fmt.println()
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------------------------------------------------------------------------------------
|
|
||||||
// Demo: Basic
|
|
||||||
// -------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// Creates a single QR Code, then prints it to the console.
|
// Creates a single QR Code, then prints it to the console.
|
||||||
basic :: proc() {
|
basic :: proc() {
|
||||||
text :: "Hello, world!"
|
text :: "Hello, world!"
|
||||||
ecl :: qr.Ecc.Low
|
ecl :: qr.Ecc.Low
|
||||||
|
|
||||||
qrcode: [qr.BUFFER_LEN_MAX]u8
|
qrcode: [qr.BUFFER_LEN_MAX]u8
|
||||||
ok := qr.encode(text, qrcode[:], ecl)
|
ok := qr.encode_auto(text, qrcode[:], ecl)
|
||||||
if ok do print_qr(qrcode[:])
|
if ok do print_qr(qrcode[:])
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------------------------------
|
|
||||||
// Demo: Variety
|
|
||||||
// -------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// Creates a variety of QR Codes that exercise different features of the library.
|
// Creates a variety of QR Codes that exercise different features of the library.
|
||||||
variety :: proc() {
|
variety :: proc() {
|
||||||
qrcode: [qr.BUFFER_LEN_MAX]u8
|
qrcode: [qr.BUFFER_LEN_MAX]u8
|
||||||
|
|
||||||
{ // Numeric mode encoding (3.33 bits per digit)
|
{ // Numeric mode encoding (3.33 bits per digit)
|
||||||
ok := qr.encode("314159265358979323846264338327950288419716939937510", qrcode[:], qr.Ecc.Medium)
|
ok := qr.encode_auto("314159265358979323846264338327950288419716939937510", qrcode[:], qr.Ecc.Medium)
|
||||||
if ok do print_qr(qrcode[:])
|
if ok do print_qr(qrcode[:])
|
||||||
}
|
}
|
||||||
|
|
||||||
{ // Alphanumeric mode encoding (5.5 bits per character)
|
{ // Alphanumeric mode encoding (5.5 bits per character)
|
||||||
ok := qr.encode("DOLLAR-AMOUNT:$39.87 PERCENTAGE:100.00% OPERATIONS:+-*/", qrcode[:], qr.Ecc.High)
|
ok := qr.encode_auto("DOLLAR-AMOUNT:$39.87 PERCENTAGE:100.00% OPERATIONS:+-*/", qrcode[:], qr.Ecc.High)
|
||||||
if ok do print_qr(qrcode[:])
|
if ok do print_qr(qrcode[:])
|
||||||
}
|
}
|
||||||
|
|
||||||
{ // Unicode text as UTF-8
|
{ // Unicode text as UTF-8
|
||||||
ok := qr.encode(
|
ok := qr.encode_auto(
|
||||||
"\xE3\x81\x93\xE3\x82\x93\xE3\x81\xAB\xE3\x81\xA1wa\xE3\x80\x81" +
|
"\xE3\x81\x93\xE3\x82\x93\xE3\x81\xAB\xE3\x81\xA1wa\xE3\x80\x81" +
|
||||||
"\xE4\xB8\x96\xE7\x95\x8C\xEF\xBC\x81\x20\xCE\xB1\xCE\xB2\xCE\xB3\xCE\xB4",
|
"\xE4\xB8\x96\xE7\x95\x8C\xEF\xBC\x81\x20\xCE\xB1\xCE\xB2\xCE\xB3\xCE\xB4",
|
||||||
qrcode[:],
|
qrcode[:],
|
||||||
@@ -133,7 +108,7 @@ variety :: proc() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
{ // Moderately large QR Code using longer text (from Lewis Carroll's Alice in Wonderland)
|
{ // Moderately large QR Code using longer text (from Lewis Carroll's Alice in Wonderland)
|
||||||
ok := qr.encode(
|
ok := qr.encode_auto(
|
||||||
"Alice was beginning to get very tired of sitting by her sister on the bank, " +
|
"Alice was beginning to get very tired of sitting by her sister on the bank, " +
|
||||||
"and of having nothing to do: once or twice she had peeped into the book her sister was reading, " +
|
"and of having nothing to do: once or twice she had peeped into the book her sister was reading, " +
|
||||||
"but it had no pictures or conversations in it, 'and what is the use of a book,' thought Alice " +
|
"but it had no pictures or conversations in it, 'and what is the use of a book,' thought Alice " +
|
||||||
@@ -148,10 +123,6 @@ variety :: proc() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------------------------------
|
|
||||||
// Demo: Segment
|
|
||||||
// -------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// Creates QR Codes with manually specified segments for better compactness.
|
// Creates QR Codes with manually specified segments for better compactness.
|
||||||
segment :: proc() {
|
segment :: proc() {
|
||||||
qrcode: [qr.BUFFER_LEN_MAX]u8
|
qrcode: [qr.BUFFER_LEN_MAX]u8
|
||||||
@@ -163,7 +134,7 @@ segment :: proc() {
|
|||||||
// Encode as single text (auto mode selection)
|
// Encode as single text (auto mode selection)
|
||||||
{
|
{
|
||||||
concat :: silver0 + silver1
|
concat :: silver0 + silver1
|
||||||
ok := qr.encode(concat, qrcode[:], qr.Ecc.Low)
|
ok := qr.encode_auto(concat, qrcode[:], qr.Ecc.Low)
|
||||||
if ok do print_qr(qrcode[:])
|
if ok do print_qr(qrcode[:])
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,7 +143,7 @@ segment :: proc() {
|
|||||||
seg_buf0: [qr.BUFFER_LEN_MAX]u8
|
seg_buf0: [qr.BUFFER_LEN_MAX]u8
|
||||||
seg_buf1: [qr.BUFFER_LEN_MAX]u8
|
seg_buf1: [qr.BUFFER_LEN_MAX]u8
|
||||||
segs := [2]qr.Segment{qr.make_alphanumeric(silver0, seg_buf0[:]), qr.make_numeric(silver1, seg_buf1[:])}
|
segs := [2]qr.Segment{qr.make_alphanumeric(silver0, seg_buf0[:]), qr.make_numeric(silver1, seg_buf1[:])}
|
||||||
ok := qr.encode(segs[:], qr.Ecc.Low, qrcode[:])
|
ok := qr.encode_auto(segs[:], qr.Ecc.Low, qrcode[:])
|
||||||
if ok do print_qr(qrcode[:])
|
if ok do print_qr(qrcode[:])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -185,7 +156,7 @@ segment :: proc() {
|
|||||||
// Encode as single text (auto mode selection)
|
// Encode as single text (auto mode selection)
|
||||||
{
|
{
|
||||||
concat :: golden0 + golden1 + golden2
|
concat :: golden0 + golden1 + golden2
|
||||||
ok := qr.encode(concat, qrcode[:], qr.Ecc.Low)
|
ok := qr.encode_auto(concat, qrcode[:], qr.Ecc.Low)
|
||||||
if ok do print_qr(qrcode[:])
|
if ok do print_qr(qrcode[:])
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,7 +172,7 @@ segment :: proc() {
|
|||||||
qr.make_numeric(golden1, seg_buf1[:]),
|
qr.make_numeric(golden1, seg_buf1[:]),
|
||||||
qr.make_alphanumeric(golden2, seg_buf2[:]),
|
qr.make_alphanumeric(golden2, seg_buf2[:]),
|
||||||
}
|
}
|
||||||
ok := qr.encode(segs[:], qr.Ecc.Low, qrcode[:])
|
ok := qr.encode_auto(segs[:], qr.Ecc.Low, qrcode[:])
|
||||||
if ok do print_qr(qrcode[:])
|
if ok do print_qr(qrcode[:])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -219,7 +190,7 @@ segment :: proc() {
|
|||||||
"\xEF\xBD\x84\xEF\xBD\x85\xEF\xBD\x93\xEF" +
|
"\xEF\xBD\x84\xEF\xBD\x85\xEF\xBD\x93\xEF" +
|
||||||
"\xBD\x95\xE3\x80\x80\xCE\xBA\xCE\xB1\xEF" +
|
"\xBD\x95\xE3\x80\x80\xCE\xBA\xCE\xB1\xEF" +
|
||||||
"\xBC\x9F"
|
"\xBC\x9F"
|
||||||
ok := qr.encode(madoka, qrcode[:], qr.Ecc.Low)
|
ok := qr.encode_auto(madoka, qrcode[:], qr.Ecc.Low)
|
||||||
if ok do print_qr(qrcode[:])
|
if ok do print_qr(qrcode[:])
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -254,16 +225,12 @@ segment :: proc() {
|
|||||||
seg.data = seg_buf[:(seg.bit_length + 7) / 8]
|
seg.data = seg_buf[:(seg.bit_length + 7) / 8]
|
||||||
|
|
||||||
segs := [1]qr.Segment{seg}
|
segs := [1]qr.Segment{seg}
|
||||||
ok := qr.encode(segs[:], qr.Ecc.Low, qrcode[:])
|
ok := qr.encode_auto(segs[:], qr.Ecc.Low, qrcode[:])
|
||||||
if ok do print_qr(qrcode[:])
|
if ok do print_qr(qrcode[:])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------------------------------
|
|
||||||
// Demo: Mask
|
|
||||||
// -------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// Creates QR Codes with the same size and contents but different mask patterns.
|
// Creates QR Codes with the same size and contents but different mask patterns.
|
||||||
mask :: proc() {
|
mask :: proc() {
|
||||||
qrcode: [qr.BUFFER_LEN_MAX]u8
|
qrcode: [qr.BUFFER_LEN_MAX]u8
|
||||||
@@ -271,10 +238,10 @@ mask :: proc() {
|
|||||||
{ // Project Nayuki URL
|
{ // Project Nayuki URL
|
||||||
ok: bool
|
ok: bool
|
||||||
|
|
||||||
ok = qr.encode("https://www.nayuki.io/", qrcode[:], qr.Ecc.High)
|
ok = qr.encode_auto("https://www.nayuki.io/", qrcode[:], qr.Ecc.High)
|
||||||
if ok do print_qr(qrcode[:])
|
if ok do print_qr(qrcode[:])
|
||||||
|
|
||||||
ok = qr.encode("https://www.nayuki.io/", qrcode[:], qr.Ecc.High, mask = qr.Mask.M3)
|
ok = qr.encode_auto("https://www.nayuki.io/", qrcode[:], qr.Ecc.High, mask = qr.Mask.M3)
|
||||||
if ok do print_qr(qrcode[:])
|
if ok do print_qr(qrcode[:])
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -290,16 +257,29 @@ mask :: proc() {
|
|||||||
|
|
||||||
ok: bool
|
ok: bool
|
||||||
|
|
||||||
ok = qr.encode(text, qrcode[:], qr.Ecc.Medium, mask = qr.Mask.M0)
|
ok = qr.encode_auto(text, qrcode[:], qr.Ecc.Medium, mask = qr.Mask.M0)
|
||||||
if ok do print_qr(qrcode[:])
|
if ok do print_qr(qrcode[:])
|
||||||
|
|
||||||
ok = qr.encode(text, qrcode[:], qr.Ecc.Medium, mask = qr.Mask.M1)
|
ok = qr.encode_auto(text, qrcode[:], qr.Ecc.Medium, mask = qr.Mask.M1)
|
||||||
if ok do print_qr(qrcode[:])
|
if ok do print_qr(qrcode[:])
|
||||||
|
|
||||||
ok = qr.encode(text, qrcode[:], qr.Ecc.Medium, mask = qr.Mask.M5)
|
ok = qr.encode_auto(text, qrcode[:], qr.Ecc.Medium, mask = qr.Mask.M5)
|
||||||
if ok do print_qr(qrcode[:])
|
if ok do print_qr(qrcode[:])
|
||||||
|
|
||||||
ok = qr.encode(text, qrcode[:], qr.Ecc.Medium, mask = qr.Mask.M7)
|
ok = qr.encode_auto(text, qrcode[:], qr.Ecc.Medium, mask = qr.Mask.M7)
|
||||||
if ok do print_qr(qrcode[:])
|
if ok do print_qr(qrcode[:])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prints the given QR Code to the console.
|
||||||
|
print_qr :: proc(qrcode: []u8) {
|
||||||
|
size := qr.get_size(qrcode)
|
||||||
|
border :: 4
|
||||||
|
for y in -border ..< size + border {
|
||||||
|
for x in -border ..< size + border {
|
||||||
|
fmt.print("##" if qr.get_module(qrcode, x, y) else " ")
|
||||||
|
}
|
||||||
|
fmt.println()
|
||||||
|
}
|
||||||
|
fmt.println()
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,10 +2,30 @@ package qrcode
|
|||||||
|
|
||||||
import "core:slice"
|
import "core:slice"
|
||||||
|
|
||||||
|
VERSION_MIN :: 1
|
||||||
|
VERSION_MAX :: 40
|
||||||
|
|
||||||
// -------------------------------------------------------------------------------------------------
|
// The worst-case number of bytes needed to store one QR Code, up to and including version 40.
|
||||||
// Types
|
BUFFER_LEN_MAX :: 3918 // buffer_len_for_version(VERSION_MAX)
|
||||||
// -------------------------------------------------------------------------------------------------
|
|
||||||
|
// Returns the number of bytes needed to store any QR Code up to and including the given version.
|
||||||
|
buffer_len_for_version :: #force_inline proc(n: int) -> int {
|
||||||
|
size := n * 4 + 17
|
||||||
|
return (size * size + 7) / 8 + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
@(private)
|
||||||
|
LENGTH_OVERFLOW :: -1
|
||||||
|
@(private)
|
||||||
|
REED_SOLOMON_DEGREE_MAX :: 30
|
||||||
|
@(private)
|
||||||
|
PENALTY_N1 :: 3
|
||||||
|
@(private)
|
||||||
|
PENALTY_N2 :: 3
|
||||||
|
@(private)
|
||||||
|
PENALTY_N3 :: 40
|
||||||
|
@(private)
|
||||||
|
PENALTY_N4 :: 10
|
||||||
|
|
||||||
// The error correction level in a QR Code symbol.
|
// The error correction level in a QR Code symbol.
|
||||||
Ecc :: enum {
|
Ecc :: enum {
|
||||||
@@ -44,39 +64,6 @@ Segment :: struct {
|
|||||||
bit_length: int,
|
bit_length: int,
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------------------------------
|
|
||||||
// Constants
|
|
||||||
// -------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
VERSION_MIN :: 1
|
|
||||||
VERSION_MAX :: 40
|
|
||||||
|
|
||||||
// The worst-case number of bytes needed to store one QR Code, up to and including version 40.
|
|
||||||
BUFFER_LEN_MAX :: 3918 // buffer_len_for_version(VERSION_MAX)
|
|
||||||
|
|
||||||
// Returns the number of bytes needed to store any QR Code up to and including the given version.
|
|
||||||
buffer_len_for_version :: #force_inline proc(n: int) -> int {
|
|
||||||
size := n * 4 + 17
|
|
||||||
return (size * size + 7) / 8 + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------------------------------------------------------------------------------------
|
|
||||||
// Private constants
|
|
||||||
// -------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
@(private)
|
|
||||||
LENGTH_OVERFLOW :: -1
|
|
||||||
@(private)
|
|
||||||
REED_SOLOMON_DEGREE_MAX :: 30
|
|
||||||
@(private)
|
|
||||||
PENALTY_N1 :: 3
|
|
||||||
@(private)
|
|
||||||
PENALTY_N2 :: 3
|
|
||||||
@(private)
|
|
||||||
PENALTY_N3 :: 40
|
|
||||||
@(private)
|
|
||||||
PENALTY_N4 :: 10
|
|
||||||
|
|
||||||
//odinfmt: disable
|
//odinfmt: disable
|
||||||
// For generating error correction codes. Index 0 is padding (set to illegal value).
|
// For generating error correction codes. Index 0 is padding (set to illegal value).
|
||||||
@(private)
|
@(private)
|
||||||
@@ -96,10 +83,9 @@ NUM_ERROR_CORRECTION_BLOCKS := [4][41]i8{
|
|||||||
}
|
}
|
||||||
//odinfmt: enable
|
//odinfmt: enable
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------------------------------------------------
|
||||||
// -------------------------------------------------------------------------------------------------
|
// ----- Encode Procedures ------------------------
|
||||||
// Encode procedures
|
// ---------------------------------------------------------------------------------------------------------------------
|
||||||
// -------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// Encodes the given text string to a QR Code, automatically selecting
|
// Encodes the given text string to a QR Code, automatically selecting
|
||||||
// numeric, alphanumeric, or byte mode based on content.
|
// numeric, alphanumeric, or byte mode based on content.
|
||||||
@@ -548,9 +534,10 @@ encode_auto :: proc {
|
|||||||
encode_segments_advanced_auto,
|
encode_segments_advanced_auto,
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------------------------------
|
|
||||||
// Error correction code generation
|
// ---------------------------------------------------------------------------------------------------------------------
|
||||||
// -------------------------------------------------------------------------------------------------
|
// ----- Error Correction Code Generation ------------------------
|
||||||
|
// ---------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
// Appends error correction bytes to each block of data, then interleaves bytes from all blocks.
|
// Appends error correction bytes to each block of data, then interleaves bytes from all blocks.
|
||||||
@(private)
|
@(private)
|
||||||
@@ -618,10 +605,6 @@ get_num_raw_data_modules :: proc(ver: int) -> int {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------------------------------
|
|
||||||
// Reed-Solomon ECC generator
|
|
||||||
// -------------------------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
@(private)
|
@(private)
|
||||||
reed_solomon_compute_divisor :: proc(degree: int, result: []u8) {
|
reed_solomon_compute_divisor :: proc(degree: int, result: []u8) {
|
||||||
assert(1 <= degree && degree <= REED_SOLOMON_DEGREE_MAX, "reed-solomon degree out of range")
|
assert(1 <= degree && degree <= REED_SOLOMON_DEGREE_MAX, "reed-solomon degree out of range")
|
||||||
@@ -668,9 +651,9 @@ reed_solomon_multiply :: proc(x, y: u8) -> u8 {
|
|||||||
return z
|
return z
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------------------------------------------------
|
||||||
// Drawing function modules
|
// ----- Drawing Function Modules ------------------------
|
||||||
// -------------------------------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
// Clears the QR Code grid and marks every function module as dark.
|
// Clears the QR Code grid and marks every function module as dark.
|
||||||
@(private)
|
@(private)
|
||||||
@@ -816,9 +799,9 @@ fill_rectangle :: proc(left, top, width, height: int, qrcode: []u8) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------------------------------------------------
|
||||||
// Drawing data modules and masking
|
// ----- Drawing data modules and masking ------------------------
|
||||||
// -------------------------------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
@(private)
|
@(private)
|
||||||
draw_codewords :: proc(data: []u8, data_len: int, qrcode: []u8) {
|
draw_codewords :: proc(data: []u8, data_len: int, qrcode: []u8) {
|
||||||
@@ -996,9 +979,9 @@ finder_penalty_add_history :: proc(current_run_length: int, run_history: ^[7]int
|
|||||||
run_history[0] = current_run_length
|
run_history[0] = current_run_length
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------------------------------------------------
|
||||||
// Basic QR Code information
|
// ----- Basic QR code information ------------------------
|
||||||
// -------------------------------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
// Returns the minimum buffer size (in bytes) needed for both temp_buffer and qrcode
|
// Returns the minimum buffer size (in bytes) needed for both temp_buffer and qrcode
|
||||||
// to encode the given content at the given ECC level within the given version range.
|
// to encode the given content at the given ECC level within the given version range.
|
||||||
@@ -1158,9 +1141,9 @@ get_bit :: #force_inline proc(x: int, i: uint) -> bool {
|
|||||||
return ((x >> i) & 1) != 0
|
return ((x >> i) & 1) != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------------------------------------------------
|
||||||
// Segment handling
|
// ----- Segment Handling ------------------------
|
||||||
// -------------------------------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
// Tests whether the given string can be encoded in numeric mode.
|
// Tests whether the given string can be encoded in numeric mode.
|
||||||
is_numeric :: proc(text: string) -> bool {
|
is_numeric :: proc(text: string) -> bool {
|
||||||
@@ -1349,11 +1332,11 @@ make_eci :: proc(assign_val: int, buf: []u8) -> Segment {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------------------------------------------------
|
||||||
// Private helpers
|
// ----- Helpers ------------------------
|
||||||
// -------------------------------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
@(private)
|
// Internal
|
||||||
append_bits_to_buffer :: proc(val: uint, num_bits: int, buffer: []u8, bit_len: ^int) {
|
append_bits_to_buffer :: proc(val: uint, num_bits: int, buffer: []u8, bit_len: ^int) {
|
||||||
assert(0 <= num_bits && num_bits <= 16 && val >> uint(num_bits) == 0, "invalid bit count or value overflow")
|
assert(0 <= num_bits && num_bits <= 16 && val >> uint(num_bits) == 0, "invalid bit count or value overflow")
|
||||||
for i := num_bits - 1; i >= 0; i -= 1 {
|
for i := num_bits - 1; i >= 0; i -= 1 {
|
||||||
@@ -1362,7 +1345,7 @@ append_bits_to_buffer :: proc(val: uint, num_bits: int, buffer: []u8, bit_len: ^
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@(private)
|
// Internal
|
||||||
get_total_bits :: proc(segs: []Segment, version: int) -> int {
|
get_total_bits :: proc(segs: []Segment, version: int) -> int {
|
||||||
result := 0
|
result := 0
|
||||||
for &seg in segs {
|
for &seg in segs {
|
||||||
@@ -1384,7 +1367,7 @@ get_total_bits :: proc(segs: []Segment, version: int) -> int {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
@(private)
|
// Internal
|
||||||
num_char_count_bits :: proc(mode: Mode, version: int) -> int {
|
num_char_count_bits :: proc(mode: Mode, version: int) -> int {
|
||||||
assert(VERSION_MIN <= version && version <= VERSION_MAX, "version out of bounds")
|
assert(VERSION_MIN <= version && version <= VERSION_MAX, "version out of bounds")
|
||||||
i := (version + 7) / 17
|
i := (version + 7) / 17
|
||||||
@@ -1406,8 +1389,8 @@ num_char_count_bits :: proc(mode: Mode, version: int) -> int {
|
|||||||
unreachable()
|
unreachable()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Internal
|
||||||
// Returns the index of c in the alphanumeric charset (0-44), or -1 if not found.
|
// Returns the index of c in the alphanumeric charset (0-44), or -1 if not found.
|
||||||
@(private)
|
|
||||||
alphanumeric_index :: proc(c: u8) -> int {
|
alphanumeric_index :: proc(c: u8) -> int {
|
||||||
switch c {
|
switch c {
|
||||||
case '0' ..= '9': return int(c - '0')
|
case '0' ..= '9': return int(c - '0')
|
||||||
|
|||||||
Reference in New Issue
Block a user