cheetah_qrcode 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/ext/cheetah_qrcode/cheetah_qrcode.c +176 -0
- data/ext/cheetah_qrcode/extconf.rb +13 -0
- data/ext/cheetah_qrcode/qrcodegen.c +1027 -0
- data/ext/cheetah_qrcode/qrcodegen.h +385 -0
- data/ext/cheetah_qrcode/spng.c +6980 -0
- data/ext/cheetah_qrcode/spng.h +537 -0
- data/lib/cheetah_qrcode/version.rb +5 -0
- data/lib/cheetah_qrcode.rb +11 -0
- metadata +80 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 912449705c5a0b488b9a43a4767d7dd7005d9c318c5704fb747101dbea8ab154
|
4
|
+
data.tar.gz: 935e6b1110782dad3c2722017142eab60122a8984c52b8549d5caabbb423ba5d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 70856665fe50d1af0380136d3e07bb1743532edc7a9b2f8d8ec229c11639239138d685dd67add631deeed1f9b06de6ef6e438bbff884f79c515a8bf7d3c4cee9
|
7
|
+
data.tar.gz: e51e45b68857f4263d2ce4404db9fbaf7773838cd45254408e2c9c172eb549bd1d05233c53ccfd4822b3002601affdd63256c9239119479148af6a182d76ebca
|
@@ -0,0 +1,176 @@
|
|
1
|
+
#include <ruby.h>
|
2
|
+
#include <stdint.h>
|
3
|
+
#include <stdlib.h>
|
4
|
+
#include <stdbool.h>
|
5
|
+
#include "qrcodegen.h"
|
6
|
+
#include "spng.h"
|
7
|
+
|
8
|
+
static VALUE encode_text(int argc, VALUE* argv, VALUE self);
|
9
|
+
|
10
|
+
static VALUE encode_text(int argc, VALUE* argv, VALUE self) {
|
11
|
+
VALUE arg_text, arg_ec_level, arg_border, arg_size, png_string = Qnil;
|
12
|
+
size_t qrcode_ec_level, qrcode_modules, qrcode_border, qrcode_size;
|
13
|
+
size_t image_size, image_scanline_width, image_length, png_size;
|
14
|
+
float image_scale;
|
15
|
+
uint8_t qrcode[qrcodegen_BUFFER_LEN_MAX];
|
16
|
+
uint8_t qrcode_buffer[qrcodegen_BUFFER_LEN_MAX];
|
17
|
+
uint8_t *image = NULL;
|
18
|
+
spng_ctx *ctx = NULL;
|
19
|
+
void *png_buffer = NULL;
|
20
|
+
|
21
|
+
rb_scan_args(argc, argv, "40", &arg_text, &arg_ec_level, &arg_border, &arg_size);
|
22
|
+
|
23
|
+
if (rb_type(arg_text) != T_STRING) {
|
24
|
+
rb_raise(rb_eTypeError, "Invalid text");
|
25
|
+
}
|
26
|
+
|
27
|
+
if (rb_type(arg_ec_level) == T_SYMBOL) {
|
28
|
+
arg_ec_level = RB_SYM2ID(arg_ec_level);
|
29
|
+
} else {
|
30
|
+
rb_raise(rb_eTypeError, "Invalid error correction level, use :L, :M, :Q, :H");
|
31
|
+
}
|
32
|
+
|
33
|
+
if (arg_ec_level == rb_intern("L") || arg_ec_level == rb_intern("l")) {
|
34
|
+
qrcode_ec_level = qrcodegen_Ecc_LOW;
|
35
|
+
} else if (arg_ec_level == rb_intern("M") || arg_ec_level == rb_intern("m")) {
|
36
|
+
qrcode_ec_level = qrcodegen_Ecc_MEDIUM;
|
37
|
+
} else if (arg_ec_level == rb_intern("Q") || arg_ec_level == rb_intern("q")) {
|
38
|
+
qrcode_ec_level = qrcodegen_Ecc_QUARTILE;
|
39
|
+
} else if (arg_ec_level == rb_intern("H") || arg_ec_level == rb_intern("h")) {
|
40
|
+
qrcode_ec_level = qrcodegen_Ecc_HIGH;
|
41
|
+
} else {
|
42
|
+
rb_raise(rb_eTypeError, "Invalid error correction level, use :L, :M, :Q, :H");
|
43
|
+
}
|
44
|
+
|
45
|
+
if (rb_type(arg_border) == T_FIXNUM) {
|
46
|
+
qrcode_border = RB_NUM2UINT(arg_border);
|
47
|
+
} else {
|
48
|
+
rb_raise(rb_eTypeError, "Invalid border");
|
49
|
+
}
|
50
|
+
|
51
|
+
if (rb_type(arg_size) == T_FIXNUM) {
|
52
|
+
image_size = RB_NUM2UINT(arg_size);
|
53
|
+
} else {
|
54
|
+
rb_raise(rb_eTypeError, "Invalid size");
|
55
|
+
}
|
56
|
+
|
57
|
+
bool ok = qrcodegen_encodeText(
|
58
|
+
RSTRING_PTR(arg_text),
|
59
|
+
qrcode_buffer,
|
60
|
+
qrcode,
|
61
|
+
qrcode_ec_level,
|
62
|
+
qrcodegen_VERSION_MIN,
|
63
|
+
qrcodegen_VERSION_MAX,
|
64
|
+
qrcodegen_Mask_AUTO,
|
65
|
+
true
|
66
|
+
);
|
67
|
+
if (!ok) {
|
68
|
+
rb_raise(rb_eRuntimeError, "Unable to create QR Code, maybe it's too large");
|
69
|
+
}
|
70
|
+
|
71
|
+
// Dimension of original qrcode
|
72
|
+
qrcode_modules = qrcodegen_getSize(qrcode);
|
73
|
+
qrcode_size = qrcode_modules + (qrcode_border * 2);
|
74
|
+
|
75
|
+
// Do not resize if no size is supplied
|
76
|
+
if (image_size == 0) {
|
77
|
+
image_size = qrcode_size;
|
78
|
+
}
|
79
|
+
|
80
|
+
// Prevent downscale
|
81
|
+
if (image_size < qrcode_size) {
|
82
|
+
rb_raise(rb_eArgError, "Downscale QR Code will result in data loss");
|
83
|
+
}
|
84
|
+
|
85
|
+
// Dimension of output image
|
86
|
+
// Image is consist of scanline_width * lines(height)
|
87
|
+
// For 100x100 image, lines(height) is always 100
|
88
|
+
// @8bit: scanline_width = 8 bit * 100 = 800 bits = 100 bytes
|
89
|
+
// buffer size needed = 100 bytes * 100 lines = 10000 bytes
|
90
|
+
// @1bit: scanline_width = 1 bit * 100 = 100 bits = 13 bytes(round up)
|
91
|
+
// buffer size needed = 13 bytes * 100 lines = 1300 bytes
|
92
|
+
image_scanline_width = (image_size + 7) / 8;
|
93
|
+
image_length = image_scanline_width * image_size;
|
94
|
+
image_scale = (float)image_size / qrcode_size;
|
95
|
+
|
96
|
+
// Create image initialized to 0 (Entirely black image)
|
97
|
+
image = calloc(image_length, sizeof(uint8_t));
|
98
|
+
if (!image) {
|
99
|
+
rb_raise(rb_eRuntimeError, "Unable to create image buffer");
|
100
|
+
}
|
101
|
+
|
102
|
+
// Loop through qrcode to find white modules to write into image
|
103
|
+
for (size_t qy = 0; qy < qrcode_size; qy++) {
|
104
|
+
for (size_t qx = 0; qx < qrcode_size; qx++) {
|
105
|
+
// Skip black qrcode module
|
106
|
+
if (qrcodegen_getModule(qrcode, qx - qrcode_border, qy - qrcode_border)) {
|
107
|
+
continue;
|
108
|
+
}
|
109
|
+
|
110
|
+
// Map current qrcode module to image coordinates
|
111
|
+
size_t ix_begin = (size_t)(qx * image_scale) + 1;
|
112
|
+
size_t ix_end = (size_t)((qx + 1) * image_scale) + 1;
|
113
|
+
size_t iy_begin = (size_t)(qy * image_scale) + 1;
|
114
|
+
size_t iy_end = (size_t)((qy + 1) * image_scale) + 1;
|
115
|
+
|
116
|
+
// For boundary safety
|
117
|
+
if (ix_end > image_size) {
|
118
|
+
ix_end = image_size;
|
119
|
+
}
|
120
|
+
if (iy_end > image_size) {
|
121
|
+
iy_end = image_size;
|
122
|
+
}
|
123
|
+
|
124
|
+
// Fill white pixels into image
|
125
|
+
for (size_t iy = iy_begin; iy < iy_end; iy++) {
|
126
|
+
for (size_t ix = ix_begin; ix < ix_end; ix++) {
|
127
|
+
size_t i = (iy * image_scanline_width) + (ix / 8);
|
128
|
+
|
129
|
+
image[i] |= 0b10000000 >> (ix % 8);
|
130
|
+
}
|
131
|
+
}
|
132
|
+
}
|
133
|
+
}
|
134
|
+
|
135
|
+
// Proceed to create PNG
|
136
|
+
ctx = spng_ctx_new(SPNG_CTX_ENCODER);
|
137
|
+
|
138
|
+
// Use internal buffer provided by spng
|
139
|
+
spng_set_option(ctx, SPNG_ENCODE_TO_BUFFER, 1);
|
140
|
+
|
141
|
+
// Set PNG IHDR
|
142
|
+
struct spng_ihdr ihdr = {0};
|
143
|
+
ihdr.width = image_size;
|
144
|
+
ihdr.height = image_size;
|
145
|
+
ihdr.color_type = SPNG_COLOR_TYPE_GRAYSCALE;
|
146
|
+
ihdr.bit_depth = 1;
|
147
|
+
spng_set_ihdr(ctx, &ihdr);
|
148
|
+
|
149
|
+
// SPNG_ENCODE_FINALIZE will finalize the PNG with the end-of-file marker
|
150
|
+
int error_code = spng_encode_image(ctx, image, image_length, SPNG_FMT_PNG, SPNG_ENCODE_FINALIZE);
|
151
|
+
if (!error_code) {
|
152
|
+
// Retrieve png from spng internal buffer
|
153
|
+
png_buffer = spng_get_png_buffer(ctx, &png_size, &error_code);
|
154
|
+
if (png_buffer) {
|
155
|
+
png_string = rb_str_new(png_buffer, png_size);
|
156
|
+
}
|
157
|
+
}
|
158
|
+
|
159
|
+
// After calling spng_get_png_buffer(), png_buffer is then owned by us
|
160
|
+
// We have to free it manually
|
161
|
+
spng_ctx_free(ctx);
|
162
|
+
free(image);
|
163
|
+
free(png_buffer);
|
164
|
+
|
165
|
+
if (error_code) {
|
166
|
+
rb_raise(rb_eRuntimeError, "Unable to encode image: %s", spng_strerror(error_code));
|
167
|
+
}
|
168
|
+
|
169
|
+
return png_string;
|
170
|
+
}
|
171
|
+
|
172
|
+
void Init_cheetah_qrcode(void) {
|
173
|
+
VALUE cCheetahQRCode = rb_const_get(rb_cObject, rb_intern("CheetahQRCode"));
|
174
|
+
|
175
|
+
rb_define_singleton_method(cCheetahQRCode, "encode_text", encode_text, -1);
|
176
|
+
}
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'mkmf'
|
4
|
+
|
5
|
+
REQUIRED_HEADER = %w[
|
6
|
+
ruby.h
|
7
|
+
].freeze
|
8
|
+
|
9
|
+
REQUIRED_HEADER.each do |header|
|
10
|
+
abort "missing header: #{header}" unless have_header(header)
|
11
|
+
end
|
12
|
+
|
13
|
+
create_makefile 'cheetah_qrcode/cheetah_qrcode'
|