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 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'