Img2Num C++ (Internal Developer Docs) dev
API Documentation
Loading...
Searching...
No Matches
image_utils.cpp
1#include "internal/image_utils.h"
2
3#include "img2num.h"
4#include "internal/fft_iterative.h"
5#include "internal/Image.h"
6#include "internal/PixelConverters.h"
7#include "internal/RGBAPixel.h"
8
9#include <algorithm>
10#include <cmath>
11#include <cstdint>
12#include <cstring>
13#include <limits>
14#include <vector>
15
16// M_PI is not defined by default on MSVC
17#ifndef M_PI
18#define M_PI 3.14159265358979323846
19#endif
20
21uint8_t quantize(uint8_t value, uint8_t region_size) {
22 if (region_size == 0) {
23 return value;
24 }
25
26 uint8_t bucket = value / region_size; // Narrowing to colour boundary with
27 // Range [0 : num_thresholds - 1].
28
29 uint8_t bucket_boundary = (bucket * region_size);
30 uint8_t bucket_midpoint =
31 bucket_boundary + (region_size / 2); // Map to threshold region's midpoint.
32
33 // In case of bucket_midpoint overflow: revert to a smaller bucket than the
34 // largest possible value.
35 bool overflow = bucket_midpoint < bucket_boundary;
36 if (overflow) {
37 bucket_midpoint = ((bucket - 1) * region_size) +
38 (region_size / 2); // Correction by reducing the bucket value belongs to.
39 }
40
41 return bucket_midpoint;
42}
43
44namespace img2num {
45// image: pointer to RGBA data
46// width, height: dimensions
47// sigma: standard deviation of Gaussian blur
48void gaussian_blur_fft(uint8_t* image, size_t width, size_t height, double sigma_pixels) {
49 if (!image || width == 0 || height == 0 || sigma_pixels <= 0)
50 return;
51
52 const size_t Npix = width * height;
53
54 // Compute padded dimensions (next power of two)
55 const size_t W = fft::next_power_of_two(width);
56 const size_t H = fft::next_power_of_two(height);
57 const size_t Npix_padded = W * H;
58
59 // Frequency coordinates helper (DC at corner)
60 auto freq_coord = [](int k, int dim) -> double {
61 return (k <= dim / 2) ? double(k) / dim : double(k - dim) / dim;
62 };
63
64 // Precompute Gaussian factor in frequency domain
65 const double two_pi2_sigma2 = 2.0 * M_PI * M_PI * sigma_pixels * sigma_pixels;
66
67 for (int channel = 0; channel < 3; channel++) {
68 // Allocate padded buffer
69 std::vector<fft::cd> data(Npix_padded, {0.0, 0.0});
70
71 // Copy original image channel into padded buffer
72 for (size_t y = 0; y < height; y++)
73 for (size_t x = 0; x < width; x++)
74 data[y * W + x] = fft::cd(image[(y * width + x) * 4 + channel], 0.0);
75
76 // Forward 2D FFT
77 fft::iterative_fft_2d(data, W, H, false);
78
79 // Apply Gaussian filter in frequency domain
80 for (size_t y = 0; y < H; y++) {
81 double fy2 = freq_coord(y, H) * freq_coord(y, H);
82 for (size_t x = 0; x < W; x++) {
83 double fx2 = freq_coord(x, W) * freq_coord(x, W);
84 double gain = std::exp(-two_pi2_sigma2 * (fx2 + fy2));
85 data[y * W + x] *= gain;
86 }
87 }
88
89 // Inverse 2D FFT
90 fft::iterative_fft_2d(data, W, H, true);
91
92 // Copy back only the original width/height and clamp
93 for (size_t y = 0; y < height; y++)
94 for (size_t x = 0; x < width; x++) {
95 double v = data[y * W + x].real();
96 v = std::clamp(v, 0.0, 255.0);
97 image[(y * width + x) * 4 + channel] = static_cast<uint8_t>(std::lrint(v));
98 }
99 }
100
101 // Alpha channel remains unchanged
102}
103
104// Called from JS. `ptr` points to RGBA bytes.
105void invert_image(uint8_t* ptr, int width, int height) {
107 img.loadFromBuffer(ptr, width, height, ImageLib::RGBA_CONVERTER<uint8_t>);
108
109 for (ImageLib::RGBAPixel<uint8_t>& p : img) {
110 p.red = 255 - p.red;
111 p.blue = 255 - p.blue;
112 p.green = 255 - p.green;
113 }
114
115 const auto& modified = img.getData();
116 std::memcpy(ptr, modified.data(), modified.size() * sizeof(ImageLib::RGBAPixel<uint8_t>));
117}
118
119void threshold_image(uint8_t* ptr, const int width, const int height, const int num_thresholds) {
120 if (num_thresholds <= 0) {
121 return;
122 }
123 const uint8_t REGION_SIZE(255 / num_thresholds); // Size of buckets per colour
124
126 img.loadFromBuffer(ptr, width, height, ImageLib::RGBA_CONVERTER<uint8_t>);
127
128 const auto imgWidth {img.getWidth()}, imgHeight {img.getHeight()};
129 for (ImageLib::RGBAPixel<uint8_t>& p : img) {
130 p.red = quantize(p.red, REGION_SIZE);
131 p.green = quantize(p.green, REGION_SIZE);
132 p.blue = quantize(p.blue, REGION_SIZE);
133 }
134
135 const auto& modified = img.getData();
136 std::memcpy(ptr, modified.data(), modified.size() * sizeof(ImageLib::RGBAPixel<uint8_t>));
137}
138
140 uint8_t* ptr, const int width, const int height, const int num_thresholds
141) {
142 if (num_thresholds <= 0) {
143 return;
144 }
146 img.loadFromBuffer(ptr, width, height, ImageLib::RGBA_CONVERTER<uint8_t>);
147
148 const auto imgWidth {img.getWidth()}, imgHeight {img.getHeight()};
149 for (ImageLib::RGBAPixel<uint8_t>& p : img) {
150 const bool R {p.red < num_thresholds};
151 const bool G {p.green < num_thresholds};
152 const bool B {p.blue < num_thresholds};
153 if (R && B && G) {
154 p.setGray(0);
155 }
156 }
157
158 const auto& modified = img.getData();
159 std::memcpy(ptr, modified.data(), modified.size() * sizeof(ImageLib::RGBAPixel<uint8_t>));
160}
161} // namespace img2num
Core image processing functions for img2num project.
void black_threshold_image(uint8_t *ptr, const int width, const int height, const int num_thresholds)
Apply black-thresholding to an image.
void gaussian_blur_fft(uint8_t *image, size_t width, size_t height, double sigma)
Apply a Gaussian blur to an image using FFT.
void threshold_image(uint8_t *ptr, const int width, const int height, const int num_thresholds)
Apply a thresholding operation to an image.
void invert_image(uint8_t *ptr, int width, int height)
Invert the pixel values of an image.