contrek 1.4.0 → 1.4.2

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 (36) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +7 -1
  3. data/Gemfile.lock +4 -1
  4. data/PERFORMANCE.md +40 -4
  5. data/README.md +211 -59
  6. data/contrek.gemspec +1 -0
  7. data/ext/cpp_polygon_finder/PolygonFinder/src/ContrekApi.h +37 -11
  8. data/ext/cpp_polygon_finder/PolygonFinder/src/Tests.cpp +53 -144
  9. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/RawBitmap.cpp +14 -2
  10. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/RawBitmap.h +1 -0
  11. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/streaming/AsciiSource.cpp +69 -0
  12. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/streaming/AsciiSource.h +34 -0
  13. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/streaming/PngSource.cpp +108 -0
  14. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/streaming/PngSource.h +40 -0
  15. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/streaming/RasterSource.h +22 -0
  16. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/bitmaps/streaming/RasterStreamer.h +92 -0
  17. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/FinderUtils.cpp +6 -8
  18. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/PolygonFinder.cpp +8 -2
  19. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/PolygonFinder.h +4 -1
  20. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Finder.cpp +7 -0
  21. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Poolable.h +1 -0
  22. data/ext/cpp_polygon_finder/PolygonFinder/src/polygon/finder/concurrent/Tile.cpp +3 -3
  23. data/ext/cpp_polygon_finder/cpp_polygon_finder.cpp +47 -0
  24. data/lib/contrek/bitmaps/bitmap.rb +2 -2
  25. data/lib/contrek/bitmaps/chunky_bitmap.rb +23 -8
  26. data/lib/contrek/bitmaps/png_bitmap.rb +4 -0
  27. data/lib/contrek/bitmaps/rgba_bitmap.rb +50 -0
  28. data/lib/contrek/bitmaps/streaming/raster_streamer.rb +66 -0
  29. data/lib/contrek/bitmaps/streaming/sources/ascii.rb +33 -0
  30. data/lib/contrek/bitmaps/streaming/sources/base.rb +27 -0
  31. data/lib/contrek/bitmaps/streaming/sources/png.rb +105 -0
  32. data/lib/contrek/finder/concurrent/finder.rb +5 -3
  33. data/lib/contrek/finder/polygon_finder.rb +8 -4
  34. data/lib/contrek/version.rb +1 -1
  35. data/lib/contrek.rb +5 -0
  36. metadata +26 -1
@@ -28,6 +28,8 @@
28
28
  #include "polygon/bitmaps/RawBitmap.h"
29
29
  #include "polygon/bitmaps/RemoteFastPngBitmap.h"
30
30
  #include "polygon/bitmaps/spng.h"
31
+ #include "polygon/bitmaps/streaming/RasterStreamer.h"
32
+ #include "polygon/bitmaps/streaming/PngSource.h"
31
33
  #include "polygon/matchers/Matcher.h"
32
34
  #include "polygon/matchers/RGBMatcher.h"
33
35
  #include "polygon/matchers/RGBNotMatcher.h"
@@ -321,70 +323,29 @@ double get_peak_rss() {
321
323
  This approach allows for processing large PNG files on systems where memory
322
324
  would otherwise be insufficient.
323
325
  */
326
+
324
327
  void stream_png_image(const std::string& filepath, uint32_t stripe_height, bool generate_svg = false, bool generate_png = false) {
325
328
  std::vector<ProcessResult*> result_clones;
326
329
  Options options = {};
327
330
  VerticalMerger vmerger(0, options);
328
331
 
329
- // opens image to stream
330
- FILE* fp = fopen(filepath.c_str(), "rb");
331
- if (!fp) {
332
- std::cerr << "Unable open file: " << filepath << std::endl;
333
- return;
334
- }
335
-
336
- // exams image
337
- spng_ctx *ctx = spng_ctx_new(0);
338
- spng_set_png_file(ctx, fp);
339
- struct spng_ihdr ihdr;
340
- if (spng_get_ihdr(ctx, &ihdr)) {
341
- fclose(fp);
342
- spng_ctx_free(ctx);
343
- return;
344
- }
345
- uint32_t total_width = ihdr.width;
346
- uint32_t total_height = ihdr.height;
332
+ PngSource source(filepath);
333
+ RasterStreamer streamer(source, stripe_height);
347
334
 
348
335
  // allocates stripe buffer
349
- RawBitmap stripe_bitmap;
350
- std::cout << total_width << " " << stripe_height << std::endl;
351
- stripe_bitmap.define(total_width, stripe_height, 4, true);
336
+ RawBitmap stripe_bitmap(source.width(), stripe_height);
352
337
  RGBNotMatcher not_matcher(-1);
353
338
 
354
- if (spng_decode_image(ctx, NULL, 0, SPNG_FMT_RGBA8, SPNG_DECODE_PROGRESSIVE)) {
355
- fclose(fp);
356
- spng_ctx_free(ctx);
357
- return;
358
- }
359
-
360
- size_t row_size = static_cast<size_t>(total_width) * 4;
361
339
  int stripe_count = 0;
340
+
362
341
  // main stripes loop
363
- for (uint32_t current_y_offset = 0; current_y_offset < total_height; current_y_offset += stripe_height) {
364
- uint32_t uncovered_height = total_height - current_y_offset;
365
-
366
- // copy previous last line to the next new one (each contigue stripe must share one pixel scanline)
367
- if (current_y_offset > 0) {
368
- unsigned char* last_row_prev = const_cast<unsigned char*>(stripe_bitmap.get_row_ptr(stripe_height - 1));
369
- unsigned char* first_row_curr = const_cast<unsigned char*>(stripe_bitmap.get_row_ptr(0));
370
- std::memcpy(first_row_curr, last_row_prev, row_size);
371
- }
372
- // clears the part of the stripe wont be overwritten by png data
373
- if (uncovered_height < stripe_height)
374
- { stripe_bitmap.draw_filled_rectangle(0, 1, total_width, stripe_height - 1, 255, 255, 255);
375
- }
376
- // decoding data directly in the stripe buffer
377
- uint32_t lines_to_read = std::min(stripe_height, total_height - current_y_offset);
378
- for (uint32_t y = (current_y_offset == 0 ? 0 : 1); y < lines_to_read; y++) {
379
- unsigned char* row_ptr = const_cast<unsigned char*>(stripe_bitmap.get_row_ptr(y));
380
- int ret = spng_decode_row(ctx, row_ptr, row_size);
381
- if (ret != 0 && ret != SPNG_EOI) break;
382
- }
342
+ streamer.each(stripe_bitmap, [&](Bitmap& bitmap, uint32_t buffer_rows, std::size_t) {
383
343
  // stripe contour tracing
384
344
  Options options = {
345
+ {"processing_height", (int) buffer_rows},
385
346
  {"versus", Identifier{"a"}},
386
347
  };
387
- PolygonFinder polygon_finder(&stripe_bitmap, &not_matcher, nullptr, options);
348
+ PolygonFinder polygon_finder(&bitmap, &not_matcher, nullptr, options);
388
349
  ProcessResult *result = polygon_finder.process_info();
389
350
  if (result) {
390
351
  std::cout << "stripe " << stripe_count << ": found polygons " << result->groups << std::endl;
@@ -392,7 +353,7 @@ void stream_png_image(const std::string& filepath, uint32_t stripe_height, bool
392
353
  vmerger.add_tile(*result);
393
354
  }
394
355
  stripe_count++;
395
- }
356
+ });
396
357
 
397
358
  std::cout << "Merging polygons ..." << std::endl;
398
359
  ProcessResult *merged_result = vmerger.process_info();
@@ -400,8 +361,7 @@ void stream_png_image(const std::string& filepath, uint32_t stripe_height, bool
400
361
  if (merged_result) {
401
362
  std::cout << "Founds total polygons: " << merged_result->groups << std::endl;
402
363
  if (generate_png) {
403
- RawBitmap full_bitmap;
404
- full_bitmap.define(total_width, total_height, 4, true);
364
+ RawBitmap full_bitmap(source.width(), source.height());
405
365
  full_bitmap.fill(255, 255, 255);
406
366
  merged_result->draw_on_bitmap(full_bitmap);
407
367
  std::cout << "Saving whole png ..." << std::endl;
@@ -413,18 +373,18 @@ void stream_png_image(const std::string& filepath, uint32_t stripe_height, bool
413
373
  merged_result->save_svg("whole.svg");
414
374
  std::cout << "Svg saved!" << std::endl;
415
375
  }
376
+ delete merged_result;
416
377
  }
417
- delete merged_result;
378
+
418
379
  // frees memory
419
380
  for (auto c : result_clones) {
420
381
  delete c;
421
382
  }
422
- spng_ctx_free(ctx);
423
- fclose(fp);
424
383
  }
425
384
 
426
385
  void Tests::test_i() {
427
386
  stream_png_image("../images/graphs_1024x1024.png", 300, true);
387
+ // stream_png_image("../images/test_20480x20480.png", 2000, true);
428
388
  std::cout << "Memory usage peak: " << get_peak_rss() << " MB" << std::endl;
429
389
  }
430
390
 
@@ -439,104 +399,53 @@ double get_current_rss_mb() {
439
399
  return (resident * page_size) / (1024.0 * 1024.0);
440
400
  }
441
401
 
442
- void stream_progressive_png_image(const std::string& filepath, uint32_t stripe_height) {
443
- std::vector<ProcessResult*> result_clones;
444
- Options voptions = {
445
- {"bounds", true},
446
- };
447
- // opens image to stream
448
- FILE* fp = fopen(filepath.c_str(), "rb");
449
- if (!fp) {
450
- std::cerr << "Unable open file: " << filepath << std::endl;
451
- return;
452
- }
453
-
454
- // exams image
455
- spng_ctx *ctx = spng_ctx_new(0);
456
- spng_set_png_file(ctx, fp);
457
- struct spng_ihdr ihdr;
458
- if (spng_get_ihdr(ctx, &ihdr)) {
459
- fclose(fp);
460
- spng_ctx_free(ctx);
461
- return;
462
- }
463
- uint32_t total_width = ihdr.width;
464
- uint32_t total_height = ihdr.height;
465
-
466
- // allocates stripe buffer
467
- RawBitmap stripe_bitmap;
468
- stripe_bitmap.define(total_width, stripe_height, 4, true);
469
- RGBNotMatcher not_matcher(-1);
470
- if (spng_decode_image(ctx, NULL, 0, SPNG_FMT_RGBA8, SPNG_DECODE_PROGRESSIVE)) {
471
- fclose(fp);
472
- spng_ctx_free(ctx);
473
- return;
474
- }
475
-
476
- // allocates streaming svg buffer
477
- std::string output_path = "streaming_buffer.svg";
478
- std::ofstream shared_stream(output_path, std::ios::out | std::ios::binary);
479
- if (!shared_stream) {
480
- std::cerr << "Error: Unable creating output streaming file!" << std::endl;
481
- }
482
- std::vector<char> buffer(4 * 1024 * 1024); // Buffer (4MB)
483
- shared_stream.rdbuf()->pubsetbuf(buffer.data(), buffer.size());
484
-
485
- SvgStreamingMerger vmerger(0, voptions, &shared_stream, total_width, total_height);
486
- // GeoJsonStreamingMerger vmerger(0, &varguments, &shared_stream, 4294901760);
487
- try {
488
- size_t row_size = static_cast<size_t>(total_width) * 4;
489
- int stripe_count = 0;
490
- // main stripes loop
491
- for (uint32_t current_y_offset = 0; current_y_offset < total_height; current_y_offset += stripe_height) {
492
- uint32_t uncovered_height = total_height - current_y_offset;
493
-
494
- // copy previous last line to the next new one (each contigue stripe must share one pixel scanline)
495
- if (current_y_offset > 0) {
496
- unsigned char* last_row_prev = const_cast<unsigned char*>(stripe_bitmap.get_row_ptr(stripe_height - 1));
497
- unsigned char* first_row_curr = const_cast<unsigned char*>(stripe_bitmap.get_row_ptr(0));
498
- std::memcpy(first_row_curr, last_row_prev, row_size);
499
- }
500
- // clears the part of the stripe wont be overwritten by png data
501
- if (uncovered_height < stripe_height)
502
- { stripe_bitmap.draw_filled_rectangle(0, 1, total_width, stripe_height - 1, 255, 255, 255);
503
- }
504
- // decoding data directly in the stripe buffer
505
- uint32_t lines_to_read = std::min(stripe_height, total_height - current_y_offset);
506
- for (uint32_t y = (current_y_offset == 0 ? 0 : 1); y < lines_to_read; y++) {
507
- unsigned char* row_ptr = const_cast<unsigned char*>(stripe_bitmap.get_row_ptr(y));
508
- int ret = spng_decode_row(ctx, row_ptr, row_size);
509
- if (ret != 0 && ret != SPNG_EOI) break;
510
- }
511
- // stripe contour tracing
512
- Options options = {
402
+ void stream_progressive_png_image(const std::string& filepath,uint32_t stripe_height) {
403
+ PngSource source(filepath);
404
+ RasterStreamer streamer(source, stripe_height);
405
+ RawBitmap bitmap(source.width(), stripe_height);
406
+ RGBNotMatcher not_matcher(-1);
407
+
408
+ std::ofstream stream("streaming_buffer.svg");
409
+
410
+ SvgStreamingMerger merger(
411
+ 0,
412
+ {{"bounds", true}},
413
+ &stream,
414
+ source.width(),
415
+ source.height()
416
+ );
417
+
418
+ uint32_t processed_rows = 0;
419
+ bool first = true;
420
+ int stripe_count = 0;
421
+ streamer.each(
422
+ bitmap,
423
+ [&](Bitmap& bitmap, uint32_t buffer_rows, std::size_t) {
424
+
425
+ PolygonFinder finder(&bitmap,&not_matcher,nullptr,
426
+ {
427
+ {"processing_height", (int) buffer_rows},
513
428
  {"versus", Identifier{"a"}},
514
429
  {"bounds", true},
515
430
  {"compress", Options{
516
431
  {"uniq", true},
517
432
  {"linear", true},
518
433
  }},
519
- };
520
-
521
- PolygonFinder polygon_finder(&stripe_bitmap, &not_matcher, nullptr, options);
522
- ProcessResult *result = polygon_finder.process_info();
523
- if (result) {
524
- std::cout << "stripe " << stripe_count << ": found polygons " << result->groups << std::endl;
525
- vmerger.add_tile(*result, !(current_y_offset + stripe_height < total_height));
526
- delete result;
527
- std::cout << "-> RSS before merge: " << get_current_rss_mb() << " MB" << std::endl;
528
434
  }
529
- stripe_count++;
435
+ );
436
+ ProcessResult* result = finder.process_info();
437
+ if (result) {
438
+ std::cout << "stripe " << stripe_count++ << ": found polygons " << result->groups << std::endl;
439
+ processed_rows += buffer_rows - (first ? 0 : RasterStreamer::OVERLAP);
440
+ merger.add_tile(*result,processed_rows == source.height());
441
+ delete result;
530
442
  }
531
- ProcessResult *merged_result = vmerger.process_info();
532
- std::cout << "total found polygons " << merged_result->groups << std::endl;
533
- delete merged_result;
534
- } catch (const std::exception& e) {
535
- std::cerr << "\n[ERROR] Processing exception: " << e.what() << std::endl;
536
- if (shared_stream.is_open()) shared_stream.close();
443
+ first = false;
444
+
537
445
  }
538
- spng_ctx_free(ctx);
539
- fclose(fp);
446
+ );
447
+
448
+ delete merger.process_info();
540
449
  }
541
450
 
542
451
  void Tests::test_l() {
@@ -18,7 +18,15 @@
18
18
 
19
19
  RawBitmap::RawBitmap() : Bitmap("", 0),
20
20
  width(0),
21
- height(0) {
21
+ height(0),
22
+ bpp(0) {
23
+ }
24
+
25
+ RawBitmap::RawBitmap(uint width, uint height) : Bitmap("", 0),
26
+ width(0),
27
+ height(0),
28
+ bpp(0) {
29
+ define(width, height, 4, false);
22
30
  }
23
31
 
24
32
  int RawBitmap::w() {
@@ -34,7 +42,11 @@ char RawBitmap::value_at(int, int) {
34
42
  }
35
43
 
36
44
  const unsigned char* RawBitmap::get_row_ptr(int y) const {
37
- return image.get() + (static_cast<size_t>(y) * static_cast<size_t>(width) * 4);
45
+ return image.get() + (
46
+ static_cast<std::size_t>(y) *
47
+ static_cast<std::size_t>(width) *
48
+ static_cast<std::size_t>(bpp)
49
+ );
38
50
  }
39
51
 
40
52
  int RawBitmap::get_bytes_per_pixel() const {
@@ -19,6 +19,7 @@ class RGBMatcher;
19
19
  class RawBitmap : public Bitmap {
20
20
  public:
21
21
  explicit RawBitmap();
22
+ RawBitmap(uint width, uint height);
22
23
  int h();
23
24
  int w();
24
25
  char value_at(int x, int y);
@@ -0,0 +1,69 @@
1
+ /*
2
+ * AsciiSource.cpp
3
+ *
4
+ * Copyright (c) 2025-2026 Emanuele Cesaroni
5
+ *
6
+ * Licensed under the GNU Affero General Public License v3 (AGPLv3).
7
+ * See the LICENSE file in this directory for the full license text.
8
+ */
9
+
10
+ #include "AsciiSource.h"
11
+ #include <cstring>
12
+ #include <stdexcept>
13
+
14
+ AsciiSource::AsciiSource(Bitmap& bitmap)
15
+ : bitmap_(bitmap),
16
+ current_row_(0) {
17
+ }
18
+
19
+ uint32_t AsciiSource::width() const {
20
+ return static_cast<uint32_t>(
21
+ bitmap_.w()
22
+ );
23
+ }
24
+
25
+ uint32_t AsciiSource::height() const {
26
+ return static_cast<uint32_t>(
27
+ bitmap_.h()
28
+ );
29
+ }
30
+
31
+ uint32_t AsciiSource::get_bytes_per_pixel() const {
32
+ return static_cast<uint32_t>(
33
+ bitmap_.get_bytes_per_pixel()
34
+ );
35
+ }
36
+
37
+ bool AsciiSource::read_next_row(
38
+ unsigned char* destination,
39
+ std::size_t row_size
40
+ ) {
41
+ if (current_row_ >= height()) {
42
+ return false;
43
+ }
44
+
45
+ const unsigned char* row =
46
+ bitmap_.get_row_ptr(current_row_);
47
+
48
+ const std::size_t expected_row_size =
49
+ static_cast<std::size_t>(width()) *
50
+ static_cast<std::size_t>(
51
+ get_bytes_per_pixel()
52
+ );
53
+
54
+ if (expected_row_size != row_size) {
55
+ throw std::runtime_error(
56
+ "Invalid row size"
57
+ );
58
+ }
59
+
60
+ std::memcpy(
61
+ destination,
62
+ row,
63
+ row_size
64
+ );
65
+
66
+ ++current_row_;
67
+
68
+ return true;
69
+ }
@@ -0,0 +1,34 @@
1
+ /*
2
+ * AsciiSource.h
3
+ *
4
+ * Copyright (c) 2025-2026 Emanuele Cesaroni
5
+ *
6
+ * Licensed under the GNU Affero General Public License v3 (AGPLv3).
7
+ * See the LICENSE file in this directory for the full license text.
8
+ */
9
+
10
+ #pragma once
11
+
12
+ #include <cstddef>
13
+ #include <cstdint>
14
+
15
+ #include "RasterSource.h"
16
+ #include "../Bitmap.h"
17
+
18
+ class AsciiSource : public RasterSource {
19
+ public:
20
+ explicit AsciiSource(Bitmap& bitmap);
21
+
22
+ uint32_t width() const override;
23
+ uint32_t height() const override;
24
+ uint32_t get_bytes_per_pixel() const override;
25
+
26
+ bool read_next_row(
27
+ unsigned char* destination,
28
+ std::size_t row_size
29
+ ) override;
30
+
31
+ private:
32
+ Bitmap& bitmap_;
33
+ uint32_t current_row_;
34
+ };
@@ -0,0 +1,108 @@
1
+ /*
2
+ * PngSource.cpp
3
+ *
4
+ * Copyright (c) 2025-2026 Emanuele Cesaroni
5
+ *
6
+ * Licensed under the GNU Affero General Public License v3 (AGPLv3).
7
+ * See the LICENSE file in this directory for the full license text.
8
+ */
9
+
10
+ #include "PngSource.h"
11
+
12
+ #include <stdexcept>
13
+ #include <string>
14
+
15
+ PngSource::PngSource(const std::string& filepath)
16
+ : fp_(nullptr),
17
+ ctx_(nullptr),
18
+ width_(0),
19
+ height_(0),
20
+ current_row_(0) {
21
+ fp_ = std::fopen(filepath.c_str(),"rb");
22
+ if (!fp_) {
23
+ throw std::runtime_error("Unable to open PNG file");
24
+ }
25
+
26
+ ctx_ = spng_ctx_new(0);
27
+ if (!ctx_) {
28
+ std::fclose(fp_);
29
+ fp_ = nullptr;
30
+ throw std::runtime_error("Unable to create spng context");
31
+ }
32
+
33
+ int ret = spng_set_png_file(ctx_,fp_);
34
+ if (ret != 0) {
35
+ spng_ctx_free(ctx_);
36
+ ctx_ = nullptr;
37
+ std::fclose(fp_);
38
+ fp_ = nullptr;
39
+ throw std::runtime_error(std::string("spng_set_png_file failed: ") + spng_strerror(ret));
40
+ }
41
+
42
+ spng_ihdr ihdr{};
43
+ ret = spng_get_ihdr(ctx_, &ihdr);
44
+
45
+ if (ret != 0) {
46
+ spng_ctx_free(ctx_);
47
+ ctx_ = nullptr;
48
+ std::fclose(fp_);
49
+ fp_ = nullptr;
50
+ throw std::runtime_error(std::string("spng_get_ihdr failed: ") + spng_strerror(ret));
51
+ }
52
+
53
+ width_ = static_cast<uint32_t>(ihdr.width);
54
+ height_ = static_cast<uint32_t>(ihdr.height);
55
+ ret = spng_decode_image(
56
+ ctx_,
57
+ nullptr,
58
+ 0,
59
+ SPNG_FMT_RGBA8,
60
+ SPNG_DECODE_PROGRESSIVE
61
+ );
62
+
63
+ if (ret != 0) {
64
+ spng_ctx_free(ctx_);
65
+ ctx_ = nullptr;
66
+ std::fclose(fp_);
67
+ fp_ = nullptr;
68
+ throw std::runtime_error(std::string("spng_decode_image failed: ") + spng_strerror(ret));
69
+ }
70
+ }
71
+
72
+ PngSource::~PngSource()
73
+ { if (ctx_) {
74
+ spng_ctx_free(ctx_);
75
+ }
76
+ if (fp_) {
77
+ std::fclose(fp_);
78
+ }
79
+ }
80
+
81
+ uint32_t PngSource::width() const {
82
+ return width_;
83
+ }
84
+
85
+ uint32_t PngSource::height() const {
86
+ return height_;
87
+ }
88
+
89
+ uint32_t PngSource::get_bytes_per_pixel() const {
90
+ return 4;
91
+ }
92
+
93
+ bool PngSource::read_next_row(unsigned char* destination, std::size_t row_size) {
94
+ if (current_row_ >= height_) {
95
+ return false;
96
+ }
97
+ const std::size_t expected_row_size = static_cast<std::size_t>(width_) * 4;
98
+ if (row_size != expected_row_size) {
99
+ throw std::runtime_error("Invalid row size");
100
+ }
101
+ const int ret = spng_decode_row(ctx_,destination,row_size);
102
+
103
+ if (ret != 0 && ret != SPNG_EOI) {
104
+ throw std::runtime_error(std::string("spng_decode_row failed: ") + spng_strerror(ret));
105
+ }
106
+ ++current_row_;
107
+ return true;
108
+ }
@@ -0,0 +1,40 @@
1
+ /*
2
+ * PngSource.h
3
+ *
4
+ * Copyright (c) 2025-2026 Emanuele Cesaroni
5
+ *
6
+ * Licensed under the GNU Affero General Public License v3 (AGPLv3).
7
+ * See the LICENSE file in this directory for the full license text.
8
+ */
9
+
10
+ #pragma once
11
+
12
+ #include <cstddef>
13
+ #include <cstdint>
14
+ #include <cstdio>
15
+ #include <string>
16
+
17
+ extern "C" {
18
+ #include "../spng.h"
19
+ }
20
+
21
+ #include "RasterSource.h"
22
+
23
+ class PngSource : public RasterSource {
24
+ public:
25
+ explicit PngSource(const std::string& filepath);
26
+ ~PngSource() override;
27
+
28
+ uint32_t width() const override;
29
+ uint32_t height() const override;
30
+ uint32_t get_bytes_per_pixel() const override;
31
+ bool read_next_row(unsigned char* destination, std::size_t row_size) override;
32
+
33
+ private:
34
+ FILE* fp_;
35
+ spng_ctx* ctx_;
36
+
37
+ uint32_t width_;
38
+ uint32_t height_;
39
+ uint32_t current_row_;
40
+ };
@@ -0,0 +1,22 @@
1
+ /*
2
+ * RasterSource.cpp
3
+ *
4
+ * Copyright (c) 2025-2026 Emanuele Cesaroni
5
+ *
6
+ * Licensed under the GNU Affero General Public License v3 (AGPLv3).
7
+ * See the LICENSE file in this directory for the full license text.
8
+ */
9
+
10
+ #pragma once
11
+
12
+ #include <cstddef>
13
+ #include <cstdint>
14
+
15
+ class RasterSource {
16
+ public:
17
+ virtual ~RasterSource() = default;
18
+ virtual uint32_t width() const = 0;
19
+ virtual uint32_t height() const = 0;
20
+ virtual uint32_t get_bytes_per_pixel() const = 0;
21
+ virtual bool read_next_row(unsigned char* destination, std::size_t row_size) = 0;
22
+ };
@@ -0,0 +1,92 @@
1
+ /*
2
+ * RasterStreamer.h
3
+ *
4
+ * Copyright (c) 2025-2026 Emanuele Cesaroni
5
+ *
6
+ * Licensed under the GNU Affero General Public License v3 (AGPLv3).
7
+ * See the LICENSE file in this directory for the full license text.
8
+ */
9
+
10
+ #pragma once
11
+
12
+ #include <algorithm>
13
+ #include <cstddef>
14
+ #include <cstdint>
15
+ #include <cstring>
16
+ #include <stdexcept>
17
+ #include <utility>
18
+ #include <vector>
19
+
20
+ #include "RasterSource.h"
21
+ #include "../RawBitmap.h"
22
+
23
+ class RasterStreamer {
24
+ public:
25
+ static constexpr uint32_t OVERLAP = 1;
26
+
27
+ RasterStreamer(RasterSource& source, uint32_t stripe_height)
28
+ : source_(source),
29
+ stripe_height_(stripe_height) {
30
+ if (stripe_height_ <= OVERLAP) {
31
+ throw std::invalid_argument("stripe_height must be greater than 1");
32
+ }
33
+ }
34
+ uint32_t stripe_height() const { return stripe_height_; }
35
+
36
+ template <typename Callback>
37
+ void each(Bitmap& buffer, Callback&& callback)
38
+ { const uint32_t width = source_.width();
39
+ const uint32_t height = source_.height();
40
+ const uint32_t bytes_per_pixel = source_.get_bytes_per_pixel();
41
+ const std::size_t row_size = static_cast<std::size_t>(width) * static_cast<std::size_t>(bytes_per_pixel);
42
+ uint32_t source_row = 0;
43
+ uint32_t previous_buffer_rows = 0;
44
+ bool first_stripe = true;
45
+
46
+ while (source_row < height) {
47
+ std::vector<unsigned char> overlap_row;
48
+ if (!first_stripe) {
49
+ overlap_row.resize(row_size);
50
+ const unsigned char* previous_last_row = buffer.get_row_ptr(previous_buffer_rows - 1);
51
+ std::memcpy(overlap_row.data(), previous_last_row, row_size);
52
+ }
53
+
54
+ const uint32_t overlap_rows = first_stripe ? 0 : OVERLAP;
55
+ const uint32_t available_rows = stripe_height_ - overlap_rows;
56
+ const uint32_t remaining_rows = height - source_row;
57
+ const uint32_t rows_to_read = std::min(available_rows, remaining_rows);
58
+ const uint32_t buffer_rows = overlap_rows + rows_to_read;
59
+
60
+ if (!first_stripe) {
61
+ unsigned char* first_row = const_cast<unsigned char*>(buffer.get_row_ptr(0));
62
+ std::memcpy(first_row, overlap_row.data(), row_size);
63
+ }
64
+ uint32_t rows_read = 0;
65
+ for (uint32_t i = 0; i < rows_to_read; ++i) {
66
+ const uint32_t buffer_y = overlap_rows + i;
67
+ unsigned char* destination = const_cast<unsigned char*>(buffer.get_row_ptr(buffer_y));
68
+ if (!source_.read_next_row(destination, row_size)) {
69
+ break;
70
+ }
71
+ ++source_row;
72
+ ++rows_read;
73
+ }
74
+ if (rows_read == 0) {
75
+ break;
76
+ }
77
+ const uint32_t actual_buffer_rows = overlap_rows + rows_read;
78
+ if (actual_buffer_rows != buffer_rows) {
79
+ throw std::runtime_error("Raster source ended before expected image height!");
80
+ }
81
+ const std::size_t buffer_size = static_cast<std::size_t>(buffer_rows) * row_size;
82
+
83
+ std::forward<Callback>(callback)(buffer, buffer_rows, buffer_size);
84
+ previous_buffer_rows = buffer_rows;
85
+ first_stripe = false;
86
+ }
87
+ }
88
+
89
+ private:
90
+ RasterSource& source_;
91
+ uint32_t stripe_height_;
92
+ };