fastqr 1.0.1

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.
Files changed (53) hide show
  1. checksums.yaml +7 -0
  2. data/BUILD.md +482 -0
  3. data/CHANGELOG.md +51 -0
  4. data/CMakeLists.txt +126 -0
  5. data/CONTRIBUTING.md +63 -0
  6. data/DISTRIBUTION.md +730 -0
  7. data/INSTALL.md +171 -0
  8. data/LICENSE +39 -0
  9. data/PREBUILT.md +171 -0
  10. data/README.md +312 -0
  11. data/VERSION +1 -0
  12. data/bindings/nodejs/binding.gyp +38 -0
  13. data/bindings/nodejs/fastqr_node.cpp +125 -0
  14. data/bindings/nodejs/index.d.ts +80 -0
  15. data/bindings/nodejs/index.js +187 -0
  16. data/bindings/nodejs/lib/platform.js +72 -0
  17. data/bindings/nodejs/package.json +45 -0
  18. data/bindings/nodejs/test/test.js +45 -0
  19. data/bindings/php/fastqr_php.cpp +85 -0
  20. data/bindings/php/src/FastQR.php +316 -0
  21. data/bindings/php/tests/FastQRTest.php +97 -0
  22. data/bindings/ruby/extconf.rb +29 -0
  23. data/bindings/ruby/fastqr_ruby.cpp +122 -0
  24. data/bindings/ruby/lib/fastqr/platform.rb +70 -0
  25. data/bindings/ruby/lib/fastqr/version.rb +6 -0
  26. data/bindings/ruby/lib/fastqr.rb +129 -0
  27. data/bindings/ruby/prebuilt/macos-arm64.tar.gz +1 -0
  28. data/bindings/ruby/prebuilt/macos-x86_64.tar.gz +1 -0
  29. data/build.sh +109 -0
  30. data/cmake/fastqrConfig.cmake.in +6 -0
  31. data/composer.json +36 -0
  32. data/docs/CLI_USAGE.md +478 -0
  33. data/docs/NODEJS_USAGE.md +694 -0
  34. data/docs/PHP_USAGE.md +815 -0
  35. data/docs/README.md +191 -0
  36. data/docs/RUBY_USAGE.md +537 -0
  37. data/examples/CMakeLists.txt +7 -0
  38. data/examples/basic.cpp +58 -0
  39. data/include/fastqr.h +97 -0
  40. data/include/stb_image.h +7988 -0
  41. data/include/stb_image_write.h +1724 -0
  42. data/phpunit.xml +18 -0
  43. data/prebuilt/README.md +131 -0
  44. data/scripts/README.md +248 -0
  45. data/scripts/build-binaries.sh +98 -0
  46. data/scripts/build-local.sh +18 -0
  47. data/scripts/install.sh +87 -0
  48. data/scripts/release.sh +78 -0
  49. data/scripts/update-version.sh +58 -0
  50. data/src/cli.cpp +316 -0
  51. data/src/fastqr.cpp +665 -0
  52. data/test.sh +86 -0
  53. metadata +155 -0
data/src/fastqr.cpp ADDED
@@ -0,0 +1,665 @@
1
+ /*
2
+ * FastQR - Fast QR Code Generator Library
3
+ * Copyright (C) 2025 FastQR Project
4
+ *
5
+ * This library is free software; you can redistribute it and/or
6
+ * modify it under the terms of the GNU Lesser General Public
7
+ * License as published by the Free Software Foundation; either
8
+ * version 2.1 of the License, or (at your option) any later version.
9
+ */
10
+
11
+ #include "fastqr.h"
12
+ #include <qrencode.h>
13
+ #include <png.h>
14
+ #include <zlib.h>
15
+ #define STB_IMAGE_IMPLEMENTATION
16
+ #include "stb_image.h"
17
+ #include <cstring>
18
+ #include <memory>
19
+ #include <iostream>
20
+ #include <vector>
21
+ #include <algorithm>
22
+ #include <cstdio>
23
+ #include <chrono>
24
+
25
+ // Enable benchmarking
26
+ // #define FASTQR_BENCHMARK
27
+
28
+ #define FASTQR_VERSION "1.0.1"
29
+
30
+ namespace fastqr {
31
+
32
+ // RAII wrapper for QRcode
33
+ struct QRCodeDeleter {
34
+ void operator()(QRcode* qr) const {
35
+ if (qr) QRcode_free(qr);
36
+ }
37
+ };
38
+ using QRCodePtr = std::unique_ptr<QRcode, QRCodeDeleter>;
39
+
40
+ // Detect text encoding and return appropriate encoding mode
41
+ static QRecLevel to_qr_level(ErrorCorrectionLevel level) {
42
+ switch (level) {
43
+ case ErrorCorrectionLevel::LOW: return QR_ECLEVEL_L;
44
+ case ErrorCorrectionLevel::MEDIUM: return QR_ECLEVEL_M;
45
+ case ErrorCorrectionLevel::QUARTILE: return QR_ECLEVEL_Q;
46
+ case ErrorCorrectionLevel::HIGH: return QR_ECLEVEL_H;
47
+ default: return QR_ECLEVEL_M;
48
+ }
49
+ }
50
+
51
+ // Detect if string contains only ASCII
52
+ static bool is_ascii(const std::string& str) {
53
+ for (unsigned char c : str) {
54
+ if (c > 127) return false;
55
+ }
56
+ return true;
57
+ }
58
+
59
+ // Generate QR code using libqrencode with proper UTF-8 support
60
+ static QRCodePtr generate_qr_code(const std::string& data, ErrorCorrectionLevel ec_level) {
61
+ QRecLevel level = to_qr_level(ec_level);
62
+
63
+ // Use appropriate encoding based on content
64
+ QRcode* qr = nullptr;
65
+ if (is_ascii(data)) {
66
+ // ASCII data - use standard encoding
67
+ qr = QRcode_encodeString(data.c_str(), 0, level, QR_MODE_8, 1);
68
+ } else {
69
+ // UTF-8 data - encode as 8-bit data
70
+ qr = QRcode_encodeData(data.length(),
71
+ reinterpret_cast<const unsigned char*>(data.c_str()),
72
+ 0, level);
73
+ }
74
+
75
+ if (!qr) {
76
+ std::cerr << "Failed to generate QR code" << std::endl;
77
+ return nullptr;
78
+ }
79
+
80
+ return QRCodePtr(qr);
81
+ }
82
+
83
+ // Write indexed PNG (1-bit, black and white) - fastest method
84
+ static bool write_indexed_png(const char* filename, const std::vector<unsigned char>& data,
85
+ int width, int height) {
86
+ FILE* fp = fopen(filename, "wb");
87
+ if (!fp) return false;
88
+
89
+ // Large buffer for faster I/O
90
+ setvbuf(fp, nullptr, _IOFBF, 65536);
91
+
92
+ png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
93
+ if (!png) {
94
+ fclose(fp);
95
+ return false;
96
+ }
97
+
98
+ png_infop info = png_create_info_struct(png);
99
+ if (!info) {
100
+ png_destroy_write_struct(&png, nullptr);
101
+ fclose(fp);
102
+ return false;
103
+ }
104
+
105
+ if (setjmp(png_jmpbuf(png))) {
106
+ png_destroy_write_struct(&png, &info);
107
+ fclose(fp);
108
+ return false;
109
+ }
110
+
111
+ png_init_io(png, fp);
112
+
113
+ // Set PNG parameters for 1-bit indexed (palette)
114
+ png_set_IHDR(png, info, width, height, 1, PNG_COLOR_TYPE_PALETTE,
115
+ PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
116
+
117
+ // Create palette: 0=white, 1=black
118
+ png_color palette[2];
119
+ palette[0].red = palette[0].green = palette[0].blue = 255; // white
120
+ palette[1].red = palette[1].green = palette[1].blue = 0; // black
121
+ png_set_PLTE(png, info, palette, 2);
122
+
123
+ // Level 1 compression - good balance between speed and size
124
+ png_set_compression_level(png, 1);
125
+ png_set_filter(png, 0, PNG_FILTER_NONE);
126
+
127
+ png_write_info(png, info);
128
+
129
+ // Data is already packed (8 pixels per byte), no packing needed
130
+
131
+ // Write image data row by row
132
+ std::vector<png_bytep> row_pointers(height);
133
+ for (int y = 0; y < height; y++) {
134
+ row_pointers[y] = const_cast<png_bytep>(&data[y * ((width + 7) / 8)]);
135
+ }
136
+
137
+ png_write_image(png, row_pointers.data());
138
+ png_write_end(png, nullptr);
139
+
140
+ png_destroy_write_struct(&png, &info);
141
+ fclose(fp);
142
+ return true;
143
+ }
144
+
145
+ // Write grayscale PNG (8-bit)
146
+ static bool write_grayscale_png(const char* filename, const std::vector<unsigned char>& data,
147
+ int width, int height) {
148
+ FILE* fp = fopen(filename, "wb");
149
+ if (!fp) return false;
150
+
151
+ png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
152
+ if (!png) {
153
+ fclose(fp);
154
+ return false;
155
+ }
156
+
157
+ png_infop info = png_create_info_struct(png);
158
+ if (!info) {
159
+ png_destroy_write_struct(&png, nullptr);
160
+ fclose(fp);
161
+ return false;
162
+ }
163
+
164
+ if (setjmp(png_jmpbuf(png))) {
165
+ png_destroy_write_struct(&png, &info);
166
+ fclose(fp);
167
+ return false;
168
+ }
169
+
170
+ png_init_io(png, fp);
171
+ png_set_IHDR(png, info, width, height, 8, PNG_COLOR_TYPE_GRAY,
172
+ PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
173
+
174
+ png_set_compression_level(png, 1);
175
+ png_set_filter(png, 0, PNG_FILTER_NONE);
176
+
177
+ png_write_info(png, info);
178
+
179
+ std::vector<png_bytep> row_pointers(height);
180
+ for (int y = 0; y < height; y++) {
181
+ row_pointers[y] = const_cast<png_bytep>(&data[y * width]);
182
+ }
183
+
184
+ png_write_image(png, row_pointers.data());
185
+ png_write_end(png, nullptr);
186
+
187
+ png_destroy_write_struct(&png, &info);
188
+ fclose(fp);
189
+ return true;
190
+ }
191
+
192
+ // Write RGB PNG
193
+ static bool write_rgb_png(const char* filename, const std::vector<unsigned char>& data,
194
+ int width, int height) {
195
+ FILE* fp = fopen(filename, "wb");
196
+ if (!fp) return false;
197
+
198
+ png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
199
+ if (!png) {
200
+ fclose(fp);
201
+ return false;
202
+ }
203
+
204
+ png_infop info = png_create_info_struct(png);
205
+ if (!info) {
206
+ png_destroy_write_struct(&png, nullptr);
207
+ fclose(fp);
208
+ return false;
209
+ }
210
+
211
+ if (setjmp(png_jmpbuf(png))) {
212
+ png_destroy_write_struct(&png, &info);
213
+ fclose(fp);
214
+ return false;
215
+ }
216
+
217
+ png_init_io(png, fp);
218
+ png_set_IHDR(png, info, width, height, 8, PNG_COLOR_TYPE_RGB,
219
+ PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
220
+
221
+ png_set_compression_level(png, 1);
222
+ png_set_filter(png, 0, PNG_FILTER_NONE);
223
+
224
+ png_write_info(png, info);
225
+
226
+ std::vector<png_bytep> row_pointers(height);
227
+ for (int y = 0; y < height; y++) {
228
+ row_pointers[y] = const_cast<png_bytep>(&data[y * width * 3]);
229
+ }
230
+
231
+ png_write_image(png, row_pointers.data());
232
+ png_write_end(png, nullptr);
233
+
234
+ png_destroy_write_struct(&png, &info);
235
+ fclose(fp);
236
+ return true;
237
+ }
238
+
239
+ // Simple nearest-neighbor resize for logo
240
+ static void resize_logo(const std::vector<unsigned char>& src, int src_w, int src_h, int channels,
241
+ std::vector<unsigned char>& dst, int dst_w, int dst_h) {
242
+ dst.resize(dst_w * dst_h * channels);
243
+ double x_ratio = static_cast<double>(src_w) / dst_w;
244
+ double y_ratio = static_cast<double>(src_h) / dst_h;
245
+
246
+ for (int y = 0; y < dst_h; y++) {
247
+ int src_y = static_cast<int>(y * y_ratio);
248
+ for (int x = 0; x < dst_w; x++) {
249
+ int src_x = static_cast<int>(x * x_ratio);
250
+ for (int c = 0; c < channels; c++) {
251
+ dst[(y * dst_w + x) * channels + c] = src[(src_y * src_w + src_x) * channels + c];
252
+ }
253
+ }
254
+ }
255
+ }
256
+
257
+ // Add logo to QR code (works with grayscale or RGB)
258
+ static void add_logo_to_image(std::vector<unsigned char>& qr_img, int qr_size, int qr_channels,
259
+ const std::string& logo_path, int logo_size_percent) {
260
+ // Load logo
261
+ int logo_w, logo_h, logo_channels;
262
+ unsigned char* logo_data = stbi_load(logo_path.c_str(), &logo_w, &logo_h, &logo_channels, 0);
263
+
264
+ if (!logo_data) {
265
+ std::cerr << "Warning: Failed to load logo: " << logo_path << std::endl;
266
+ return;
267
+ }
268
+
269
+ // Calculate logo size
270
+ int logo_target_size = (qr_size * logo_size_percent) / 100;
271
+ int logo_new_w, logo_new_h;
272
+
273
+ // Keep aspect ratio
274
+ if (logo_w > logo_h) {
275
+ logo_new_w = logo_target_size;
276
+ logo_new_h = (logo_h * logo_target_size) / logo_w;
277
+ } else {
278
+ logo_new_h = logo_target_size;
279
+ logo_new_w = (logo_w * logo_target_size) / logo_h;
280
+ }
281
+
282
+ // Resize logo
283
+ std::vector<unsigned char> logo_src(logo_data, logo_data + logo_w * logo_h * logo_channels);
284
+ stbi_image_free(logo_data);
285
+
286
+ std::vector<unsigned char> logo_resized;
287
+ resize_logo(logo_src, logo_w, logo_h, logo_channels, logo_resized, logo_new_w, logo_new_h);
288
+
289
+ // Calculate position (center)
290
+ int start_x = (qr_size - logo_new_w) / 2;
291
+ int start_y = (qr_size - logo_new_h) / 2;
292
+
293
+ // Composite logo onto QR
294
+ for (int y = 0; y < logo_new_h; y++) {
295
+ for (int x = 0; x < logo_new_w; x++) {
296
+ int qr_x = start_x + x;
297
+ int qr_y = start_y + y;
298
+
299
+ if (qr_x >= 0 && qr_x < qr_size && qr_y >= 0 && qr_y < qr_size) {
300
+ int logo_idx = (y * logo_new_w + x) * logo_channels;
301
+ int qr_idx = (qr_y * qr_size + qr_x) * qr_channels;
302
+
303
+ if (logo_channels == 4) {
304
+ // Has alpha channel
305
+ float alpha = logo_resized[logo_idx + 3] / 255.0f;
306
+
307
+ if (qr_channels == 1) {
308
+ // Grayscale QR
309
+ unsigned char logo_gray = (logo_resized[logo_idx] +
310
+ logo_resized[logo_idx + 1] +
311
+ logo_resized[logo_idx + 2]) / 3;
312
+ qr_img[qr_idx] = static_cast<unsigned char>(
313
+ logo_gray * alpha + qr_img[qr_idx] * (1 - alpha)
314
+ );
315
+ } else {
316
+ // RGB QR
317
+ for (int c = 0; c < 3; c++) {
318
+ qr_img[qr_idx + c] = static_cast<unsigned char>(
319
+ logo_resized[logo_idx + c] * alpha + qr_img[qr_idx + c] * (1 - alpha)
320
+ );
321
+ }
322
+ }
323
+ } else {
324
+ // No alpha, direct copy
325
+ if (qr_channels == 1 && logo_channels >= 3) {
326
+ // Convert RGB logo to grayscale
327
+ qr_img[qr_idx] = (logo_resized[logo_idx] +
328
+ logo_resized[logo_idx + 1] +
329
+ logo_resized[logo_idx + 2]) / 3;
330
+ } else if (qr_channels == 3 && logo_channels == 1) {
331
+ // Grayscale logo to RGB
332
+ qr_img[qr_idx] = qr_img[qr_idx + 1] = qr_img[qr_idx + 2] = logo_resized[logo_idx];
333
+ } else {
334
+ // Same channels
335
+ for (int c = 0; c < std::min(qr_channels, logo_channels); c++) {
336
+ qr_img[qr_idx + c] = logo_resized[logo_idx + c];
337
+ }
338
+ }
339
+ }
340
+ }
341
+ }
342
+ }
343
+ }
344
+
345
+ bool generate(const std::string& data, const std::string& output_path, const QROptions& options) {
346
+ #ifdef FASTQR_BENCHMARK
347
+ auto t_start = std::chrono::high_resolution_clock::now();
348
+ #endif
349
+
350
+ // Generate QR code
351
+ auto qr = generate_qr_code(data, options.ec_level);
352
+ if (!qr) {
353
+ return false;
354
+ }
355
+
356
+ #ifdef FASTQR_BENCHMARK
357
+ auto t_qr_gen = std::chrono::high_resolution_clock::now();
358
+ auto dur_qr = std::chrono::duration_cast<std::chrono::microseconds>(t_qr_gen - t_start).count();
359
+ #endif
360
+
361
+ int qr_size = qr->width;
362
+ unsigned char* qr_data = qr->data;
363
+
364
+ // Determine final output size
365
+ int final_size = options.size;
366
+ if (options.optimize_size) {
367
+ // Round up to nearest integer multiple for best performance
368
+ int scale = (options.size + qr_size - 1) / qr_size; // Ceiling division
369
+ final_size = scale * qr_size;
370
+ }
371
+
372
+ // Check if using default black/white colors
373
+ bool is_bw = (options.foreground.r == 0 && options.foreground.g == 0 && options.foreground.b == 0 &&
374
+ options.background.r == 255 && options.background.g == 255 && options.background.b == 255);
375
+
376
+ // Check if we can use indexed PNG (only if no logo)
377
+ bool can_use_indexed = is_bw && options.logo_path.empty();
378
+
379
+ if (can_use_indexed) {
380
+ // FASTEST PATH: 1-bit indexed PNG (like qrencode)
381
+ // Pack 8 pixels into 1 byte
382
+ int scale = final_size / qr_size;
383
+
384
+ #ifdef FASTQR_BENCHMARK
385
+ FILE* debug_log = fopen("/tmp/fastqr_debug.log", "a");
386
+ if (debug_log) {
387
+ fprintf(debug_log, "DEBUG: is_bw=%d, qr_size=%d, final_size=%d, scale=%d, scale*qr=%d\n",
388
+ is_bw, qr_size, final_size, scale, scale*qr_size);
389
+ fclose(debug_log);
390
+ }
391
+ #endif
392
+
393
+ if (scale * qr_size == final_size) {
394
+ // Integer scaling - optimized bit packing
395
+ int bytes_per_row = (final_size + 7) / 8;
396
+ std::vector<unsigned char> packed_data(bytes_per_row * final_size, 0);
397
+
398
+ // Build one template row then replicate vertically
399
+ std::vector<unsigned char> template_row(bytes_per_row);
400
+
401
+ for (int src_y = 0; src_y < qr_size; src_y++) {
402
+ // Build template row for this QR row
403
+ std::memset(template_row.data(), 0, bytes_per_row);
404
+
405
+ for (int src_x = 0; src_x < qr_size; src_x++) {
406
+ if (qr_data[src_y * qr_size + src_x] & 1) {
407
+ // Black pixel - set all bits for scaled pixels
408
+ int dst_x_start = src_x * scale;
409
+ int dst_x_end = dst_x_start + scale;
410
+
411
+ for (int dst_x = dst_x_start; dst_x < dst_x_end; dst_x++) {
412
+ int byte_idx = dst_x >> 3;
413
+ int bit_idx = 7 - (dst_x & 7);
414
+ template_row[byte_idx] |= (1 << bit_idx);
415
+ }
416
+ }
417
+ }
418
+
419
+ // Copy template row to all scaled rows
420
+ int dst_y_start = src_y * scale;
421
+ for (int dy = 0; dy < scale; dy++) {
422
+ std::memcpy(&packed_data[(dst_y_start + dy) * bytes_per_row],
423
+ template_row.data(), bytes_per_row);
424
+ }
425
+ }
426
+
427
+ #ifdef FASTQR_BENCHMARK
428
+ auto t_scale = std::chrono::high_resolution_clock::now();
429
+ auto dur_scale = std::chrono::duration_cast<std::chrono::microseconds>(t_scale - t_qr_gen).count();
430
+ #endif
431
+
432
+ bool result = write_indexed_png(output_path.c_str(), packed_data, final_size, final_size);
433
+
434
+ #ifdef FASTQR_BENCHMARK
435
+ auto t_write = std::chrono::high_resolution_clock::now();
436
+ auto dur_write = std::chrono::duration_cast<std::chrono::microseconds>(t_write - t_scale).count();
437
+ auto dur_total = std::chrono::duration_cast<std::chrono::microseconds>(t_write - t_start).count();
438
+
439
+ FILE* timing_log = fopen("/tmp/fastqr_timing.log", "a");
440
+ if (timing_log) {
441
+ fprintf(timing_log, "TIMING: QR=%ldus, Scale=%ldus, Write=%ldus, Total=%ldus\n",
442
+ dur_qr, dur_scale, dur_write, dur_total);
443
+ fclose(timing_log);
444
+ }
445
+ #endif
446
+
447
+ return result;
448
+ } else {
449
+ // Non-integer scaling - use grayscale
450
+ std::vector<unsigned char> final_image(final_size * final_size);
451
+ double x_ratio = static_cast<double>(qr_size) / final_size;
452
+ double y_ratio = static_cast<double>(qr_size) / final_size;
453
+
454
+ for (int y = 0; y < final_size; y++) {
455
+ int src_y = static_cast<int>(y * y_ratio);
456
+ int src_row_idx = src_y * qr_size;
457
+ for (int x = 0; x < final_size; x++) {
458
+ int src_x = static_cast<int>(x * x_ratio);
459
+ final_image[y * final_size + x] = (qr_data[src_row_idx + src_x] & 1) ? 0 : 255;
460
+ }
461
+ }
462
+
463
+ // Add logo if specified
464
+ if (!options.logo_path.empty()) {
465
+ add_logo_to_image(final_image, final_size, 1, options.logo_path, options.logo_size_percent);
466
+ }
467
+
468
+ return write_grayscale_png(output_path.c_str(), final_image, final_size, final_size);
469
+ }
470
+ } else if (is_bw) {
471
+ // Black/white but with logo - use RGB to preserve logo colors
472
+ std::vector<unsigned char> final_image(final_size * final_size * 3);
473
+ int scale = final_size / qr_size;
474
+
475
+ if (scale * qr_size == final_size) {
476
+ // Integer scaling
477
+ std::vector<unsigned char> scaled_row(final_size * 3);
478
+
479
+ for (int src_y = 0; src_y < qr_size; src_y++) {
480
+ for (int src_x = 0; src_x < qr_size; src_x++) {
481
+ unsigned char val = (qr_data[src_y * qr_size + src_x] & 1) ? 0 : 255;
482
+ int dst_x_start = src_x * scale;
483
+ for (int dx = 0; dx < scale; dx++) {
484
+ int idx = (dst_x_start + dx) * 3;
485
+ scaled_row[idx] = scaled_row[idx + 1] = scaled_row[idx + 2] = val;
486
+ }
487
+ }
488
+
489
+ int dst_y_start = src_y * scale;
490
+ unsigned char* dst_ptr = &final_image[dst_y_start * final_size * 3];
491
+ std::memcpy(dst_ptr, scaled_row.data(), final_size * 3);
492
+ for (int dy = 1; dy < scale; dy++) {
493
+ std::memcpy(dst_ptr + dy * final_size * 3, dst_ptr, final_size * 3);
494
+ }
495
+ }
496
+ } else {
497
+ // Non-integer scaling
498
+ double x_ratio = static_cast<double>(qr_size) / final_size;
499
+ double y_ratio = static_cast<double>(qr_size) / final_size;
500
+
501
+ for (int y = 0; y < final_size; y++) {
502
+ int src_y = static_cast<int>(y * y_ratio);
503
+ int src_row_idx = src_y * qr_size;
504
+ for (int x = 0; x < final_size; x++) {
505
+ int src_x = static_cast<int>(x * x_ratio);
506
+ unsigned char val = (qr_data[src_row_idx + src_x] & 1) ? 0 : 255;
507
+ int idx = (y * final_size + x) * 3;
508
+ final_image[idx] = final_image[idx + 1] = final_image[idx + 2] = val;
509
+ }
510
+ }
511
+ }
512
+
513
+ // Add logo (RGB)
514
+ add_logo_to_image(final_image, final_size, 3, options.logo_path, options.logo_size_percent);
515
+
516
+ return write_rgb_png(output_path.c_str(), final_image, final_size, final_size);
517
+ } else {
518
+ // COLOR PATH: RGB output for custom colors
519
+ bool is_grayscale = (options.foreground.r == options.foreground.g &&
520
+ options.foreground.g == options.foreground.b &&
521
+ options.background.r == options.background.g &&
522
+ options.background.g == options.background.b);
523
+
524
+ if (is_grayscale) {
525
+ // Use grayscale PNG
526
+ std::vector<unsigned char> final_image(final_size * final_size);
527
+ unsigned char fg = options.foreground.r;
528
+ unsigned char bg = options.background.r;
529
+
530
+ int scale = final_size / qr_size;
531
+ if (scale * qr_size == final_size) {
532
+ // Integer scaling
533
+ std::vector<unsigned char> scaled_row(final_size);
534
+
535
+ for (int src_y = 0; src_y < qr_size; src_y++) {
536
+ for (int src_x = 0; src_x < qr_size; src_x++) {
537
+ unsigned char val = (qr_data[src_y * qr_size + src_x] & 1) ? fg : bg;
538
+ int dst_x_start = src_x * scale;
539
+ std::memset(&scaled_row[dst_x_start], val, scale);
540
+ }
541
+
542
+ int dst_y_start = src_y * scale;
543
+ unsigned char* dst_ptr = &final_image[dst_y_start * final_size];
544
+ std::memcpy(dst_ptr, scaled_row.data(), final_size);
545
+ for (int dy = 1; dy < scale; dy++) {
546
+ std::memcpy(dst_ptr + dy * final_size, dst_ptr, final_size);
547
+ }
548
+ }
549
+ } else {
550
+ // Non-integer scaling
551
+ double x_ratio = static_cast<double>(qr_size) / final_size;
552
+ double y_ratio = static_cast<double>(qr_size) / final_size;
553
+
554
+ for (int y = 0; y < final_size; y++) {
555
+ int src_y = static_cast<int>(y * y_ratio);
556
+ int src_row_idx = src_y * qr_size;
557
+ for (int x = 0; x < final_size; x++) {
558
+ int src_x = static_cast<int>(x * x_ratio);
559
+ final_image[y * final_size + x] = (qr_data[src_row_idx + src_x] & 1) ? fg : bg;
560
+ }
561
+ }
562
+ }
563
+
564
+ // Add logo if specified
565
+ if (!options.logo_path.empty()) {
566
+ add_logo_to_image(final_image, final_size, 1, options.logo_path, options.logo_size_percent);
567
+ }
568
+
569
+ return write_grayscale_png(output_path.c_str(), final_image, final_size, final_size);
570
+ } else {
571
+ // Full RGB for non-grayscale colors
572
+ std::vector<unsigned char> final_image(final_size * final_size * 3);
573
+
574
+ int scale = final_size / qr_size;
575
+ if (scale * qr_size == final_size) {
576
+ // Integer scaling
577
+ std::vector<unsigned char> scaled_row(final_size * 3);
578
+
579
+ for (int src_y = 0; src_y < qr_size; src_y++) {
580
+ for (int src_x = 0; src_x < qr_size; src_x++) {
581
+ bool is_black = qr_data[src_y * qr_size + src_x] & 1;
582
+ unsigned char r = is_black ? options.foreground.r : options.background.r;
583
+ unsigned char g = is_black ? options.foreground.g : options.background.g;
584
+ unsigned char b = is_black ? options.foreground.b : options.background.b;
585
+
586
+ int dst_x_start = src_x * scale;
587
+ for (int dx = 0; dx < scale; dx++) {
588
+ int idx = (dst_x_start + dx) * 3;
589
+ scaled_row[idx] = r;
590
+ scaled_row[idx + 1] = g;
591
+ scaled_row[idx + 2] = b;
592
+ }
593
+ }
594
+
595
+ int dst_y_start = src_y * scale;
596
+ unsigned char* dst_ptr = &final_image[dst_y_start * final_size * 3];
597
+ std::memcpy(dst_ptr, scaled_row.data(), final_size * 3);
598
+ for (int dy = 1; dy < scale; dy++) {
599
+ std::memcpy(dst_ptr + dy * final_size * 3, dst_ptr, final_size * 3);
600
+ }
601
+ }
602
+ } else {
603
+ // Non-integer scaling
604
+ double x_ratio = static_cast<double>(qr_size) / final_size;
605
+ double y_ratio = static_cast<double>(qr_size) / final_size;
606
+
607
+ for (int y = 0; y < final_size; y++) {
608
+ int src_y = static_cast<int>(y * y_ratio);
609
+ int src_row_idx = src_y * qr_size;
610
+ for (int x = 0; x < final_size; x++) {
611
+ int src_x = static_cast<int>(x * x_ratio);
612
+ bool is_black = qr_data[src_row_idx + src_x] & 1;
613
+ int idx = (y * final_size + x) * 3;
614
+ final_image[idx] = is_black ? options.foreground.r : options.background.r;
615
+ final_image[idx + 1] = is_black ? options.foreground.g : options.background.g;
616
+ final_image[idx + 2] = is_black ? options.foreground.b : options.background.b;
617
+ }
618
+ }
619
+ }
620
+
621
+ // Add logo if specified
622
+ if (!options.logo_path.empty()) {
623
+ add_logo_to_image(final_image, final_size, 3, options.logo_path, options.logo_size_percent);
624
+ }
625
+
626
+ return write_rgb_png(output_path.c_str(), final_image, final_size, final_size);
627
+ }
628
+ }
629
+ }
630
+
631
+ int generate_to_buffer(const std::string& data, void* buffer, size_t buffer_size, const QROptions& options) {
632
+ // For buffer generation, temporarily write to a temp file then read it back
633
+ // This is a simplified implementation - could be optimized to write directly to memory
634
+ std::string temp_file = "/tmp/fastqr_temp.png";
635
+
636
+ if (!generate(data, temp_file, options)) {
637
+ return -1;
638
+ }
639
+
640
+ FILE* fp = fopen(temp_file.c_str(), "rb");
641
+ if (!fp) return -1;
642
+
643
+ fseek(fp, 0, SEEK_END);
644
+ long file_size = ftell(fp);
645
+ fseek(fp, 0, SEEK_SET);
646
+
647
+ if (static_cast<size_t>(file_size) > buffer_size) {
648
+ fclose(fp);
649
+ std::remove(temp_file.c_str());
650
+ return -1;
651
+ }
652
+
653
+ size_t read_size = fread(buffer, 1, file_size, fp);
654
+ fclose(fp);
655
+ std::remove(temp_file.c_str());
656
+
657
+ return static_cast<int>(read_size);
658
+ }
659
+
660
+ const char* version() {
661
+ return FASTQR_VERSION;
662
+ }
663
+
664
+ } // namespace fastqr
665
+