New paste Repaste Download
#include <iostream>
#include <algorithm>
#include <string>
#include <vector>
#include <spn_rng.h>
#include <bluenoise.h>
#include <spn_canvas.h>
#include <spn_core.h>
#include <spn_profiler.h>
#include <spn_collision_utils.h>
#include <spn_ui_event.h>
#include <spn_ui_event_translator.h>
#include <rmgui/spn_rmgui_ui_manager.h>
#include <rmgui/spn_rmgui_textbox.h>
#include <rmgui/spn_rmgui_button.h>
#include <rmgui/spn_rmgui_slider.h>
#define MAXRESX 800
#define MAXRESY 600
#define NOISEIMGSIZE 32
spn::Image sourceImage;
spn::Image* workingImage;
spn::rmgui::Button* doFilterButton;
spn::rmgui::Button* showOriginalButton;
spn::rmgui::Button* autoThreshButton;
spn::rmgui::Textbox* kernelSizeTextBox;
spn::rmgui::UiManager* uim;
bool inputEnabled = true;
//allocate temporary buffers
unsigned char* fullyFilteredImagePixels;
unsigned char* greyscaleImagePixels;
unsigned char* smoothedImagePixels;
unsigned char* horizontallyFilteredImagePixels;
unsigned char noisebuffer[NOISEIMGSIZE * NOISEIMGSIZE];
spn::rmgui::Slider* threshSlider;
struct RoiRect{
int x0;
int y0;
int x1;
int y1;
};
RoiRect curRoi;
bool buildingRoi = false;
spn::RandomGen& rng = spn::RandomGen::GetInstance();
bool CheckCollision2(
int left1, int top1, int right1, int bottom1,
int left2, int top2, int right2, int bottom2
)
{
int  x1, x2;
if (bottom2 < top1 || top2 > bottom1) {
return false;
}
else
{
x1 = (left2 > left1) ? left2 : left1;
x2 = (right2 < right1) ? right2 : right1;
if (x1 > x2) {
return false;
}
return true;
}
}
bool IsRoiDiscardable(const RoiRect r, int x, int y, int w, int h) {
return
std::abs((r.x0 - r.x1) * (r.y0 - r.y1)) < 4L
||
!CheckCollision2(r.x0, r.y0, r.x1, r.y1, x, y, x + w, y + h);
}
/*
Thresholding partitions the the image into black and white
Pixels having intensity less than the threshold intensity
will become black
And pixels having intensity greater than or equal to threshold
will become white
The threshold intensity has a minimum value of 0
and a maximum value of 255
Automatic threshold demonstrated here is the
average intensity of the image
Note that the intensity calculation used
here is an approximation
*/
void Threshold(float thresh) {
unsigned char* srcImg = sourceImage.GetCanvas()->GetPixelBuffer();
unsigned char* dstImg = workingImage->GetCanvas()->GetPixelBuffer();
int n = sourceImage.GetCanvas()->GetNumOfPixels();
for (int i = 0; i < n; ++i)
{
int b = *srcImg;
int g = *(srcImg + 1);
int r = *(srcImg + 2);
float intensity = (float)((b + g + r)) / 3.0f;
if (intensity < thresh)
{
*dstImg++ = 0;
*dstImg++ = 0;
*dstImg++ = 0;
*dstImg++ = 255;
}
else
{
*dstImg++ = b;
*dstImg++ = g;
*dstImg++ = r;
*dstImg++ = 255;
}
srcImg += 4;
}
}
void AutoThreshold() {
unsigned char* srcImg = sourceImage.GetCanvas()->GetPixelBuffer();
float sum = 0;
int count = 0;
int n = sourceImage.GetCanvas()->GetNumOfPixels();
for (int i = 0; i < n; ++i)
{
int b = *srcImg;
int g = *(srcImg + 1);
int r = *(srcImg + 2);
float intensity = (float)((b + g + r)) / 3.0f;
sum += intensity;
++count;
srcImg += 4;
}
float averageIntensity = sum / (float)count;
//Threshold(averageIntensity);
threshSlider->SetRangeAndValue(0, 255, averageIntensity);
threshSlider->CalculateKnobPosition();
}
void OnSliderValueChanged(int id, float value) {
int i;
switch (id) {
case 100:
Threshold(value);
break;
}
}
void GreyScale(unsigned char* op, unsigned char* ip, int w, int h, int channels, const RoiRect& roi) {
bool nop = false;
for (int y = 0; y < h; ++y) {
for (int x = 0; x < w; ++x){
if (y < roi.y0 || y > roi.y1 || x < roi.x0 || x > roi.x1) nop=true;
if (nop) {
unsigned char* buffer = ip +
(w * channels * y) +
(x * channels);
unsigned char* outbuffer =
op +
(w * channels * y) +
(x * channels);
*outbuffer = *buffer;
*(outbuffer + 1) = *(buffer + 1);
*(outbuffer + 2) = *(buffer + 2);
*(outbuffer + 3) = 255; //alpha
nop = false;
continue;
}
int i = (w * channels * y) + (x * channels);
unsigned char* src = ip + i;
unsigned char* dst = op + i;
int avg = static_cast<float>(*src + *(src + 1) + *(src + 2)) / 3.0;
int gsv = std::clamp(avg, 0, 255);
*(dst) = gsv;
*(dst+1) = gsv;
*(dst+2) = gsv;
}
}
}
void Sobel(unsigned char* op, unsigned char* ip, int w, int h, int channels, const RoiRect & roi) {
bool nop = false;
for (int y = 1; y < h - 1; y++) {
int row_curr = y * w * channels;
int row_prev = (y - 1) * w * channels;
int row_next = (y + 1) * w * channels;
for (int x = 1; x < w - 1; x++) {
if (y < roi.y0 || y > roi.y1 || x < roi.x0 || x > roi.x1) nop = true;
if (nop) {
unsigned char* buffer = ip +
(w * channels * y) +
(x * channels);
unsigned char* outbuffer =
op +
(w * channels * y) +
(x * channels);
*outbuffer = *buffer;
*(outbuffer + 1) = *(buffer + 1);
*(outbuffer + 2) = *(buffer + 2);
*(outbuffer + 3) = 255; //alpha
nop = false;
continue;
}
int col_left = (x - 1) * channels;
int col_mid = x * channels;
int col_right = (x + 1) * channels;
//sobel matrix multiplication
int gx =
-(ip[row_prev + col_left] +
2 * ip[row_curr + col_left] +
ip[row_next + col_left]) +
(ip[row_prev + col_right] +
2 * ip[row_curr + col_right] +
ip[row_next + col_right]);
int gy =
(ip[row_prev + col_left] +
2 * ip[row_prev + col_mid] +
ip[row_prev + col_right]) -
(ip[row_next + col_left] +
2 * ip[row_next + col_mid] +
ip[row_next + col_right]);
//thresholding
int g = sqrt(gx * gx + gy * gy);
unsigned char edgeVal = (g > 100) ? 255 : 0;
int dest_idx = row_curr + col_mid;
op[dest_idx] = edgeVal;
op[dest_idx + 1] = edgeVal;
op[dest_idx + 2] = edgeVal;
op[dest_idx + 3] = 255;
}
}
}
void Blur(unsigned char* op, unsigned char* ip, int w, int h, int channels, const RoiRect& roi,int kernelSize) {
int halfKernelSize = kernelSize / 2;
//init horizontal and vertical kernels (for box filtering)
float filterValue = 1.0f / (float)kernelSize;
//apply filter horizontally
for (int y = 0; y < h; ++y) {
for (int x = 0; x < w; ++x) {
if (y < roi.y0 || y > roi.y1 || x < roi.x0 || x > roi.x1) continue;
float bs = 0.0;
float gs = 0.0;
float rs = 0.0;
for (int i = -halfKernelSize; i <= halfKernelSize; ++i) {
int sampleLocationX = std::clamp(x + i, 0, w - 1);
unsigned char* buffer = ip +
(w * channels * y) +
(sampleLocationX * channels);
bs += filterValue * *buffer;
gs += filterValue * *(buffer + 1);
rs += filterValue * *(buffer + 2);
}
unsigned char* outbuffer =
horizontallyFilteredImagePixels +
(w * channels * y) +
(x * channels);
*outbuffer = bs;
*(outbuffer + 1) = gs;
*(outbuffer + 2) = rs;
*(outbuffer + 3) = 255; //alpha
}
}
bool nop = false;
//apply filter vertically
for (int y = 0; y < h; ++y) {
for (int x = 0; x < w; ++x) {
if (y < roi.y0 || y > roi.y1 || x < roi.x0 || x > roi.x1) nop = true;
if (nop) {
unsigned char* buffer = ip +
(w * channels * y) +
(x * channels);
unsigned char* outbuffer =
op +
(w * channels * y) +
(x * channels);
*outbuffer = *buffer;
*(outbuffer + 1) = *(buffer + 1);
*(outbuffer + 2) = *(buffer + 2);
*(outbuffer + 3) = 255; //alpha
nop = false;
continue;
}
float bs = 0.0;
float gs = 0.0;
float rs = 0.0;
for (int i = -halfKernelSize; i <= halfKernelSize; ++i) {
int sampleLocationY = std::clamp(y + i, 0, h - 1);
unsigned char* buffer =
horizontallyFilteredImagePixels +
(w * channels * sampleLocationY) +
(x * channels);
bs += filterValue * *buffer;
gs += filterValue * *(buffer + 1);
rs += filterValue * *(buffer + 2);
}
unsigned char* outbuffer =
smoothedImagePixels +
(w * channels * y) +
(x * channels);
*outbuffer = bs;
*(outbuffer + 1) = gs;
*(outbuffer + 2) = rs;
*(outbuffer + 3) = 255; //alpha
}
}
}
void EdgeDetectFilter(bool noFiltering) {
int kernelSize = 3;
try {
kernelSize = std::stoi(kernelSizeTextBox->GetText());
}
catch (...) {
std::cout << "Parsing int failed...\n";
std::cout << "Blurring with kernel size 3\n";
kernelSize = 3;
}
if (kernelSize % 2 == 0) {
std::cout << "Can't work on even kernel size\n";
return;
}
if (kernelSize < 0 || kernelSize > 7) {
std::cout << "Invalid kernel size\n";
return;
}
PROFILE_REST_OF_THE_CURRENT_BLOCK(kernelSize);
const int channels = 4;
int width = sourceImage.GetCanvas()->GetWidth();
int height = sourceImage.GetCanvas()->GetHeight();
if (noFiltering || IsRoiDiscardable(curRoi,0,0,width,height)) {
std::cout << "Restoring the original image\n";
memcpy(workingImage->GetCanvas()->GetPixelBuffer(),
sourceImage.GetCanvas()->GetPixelBuffer(),
width * height * channels);
return;
}
inputEnabled = false;
GreyScale(greyscaleImagePixels,
sourceImage.GetCanvas()->GetPixelBuffer(),
width, height, channels, curRoi);
Blur(smoothedImagePixels,
greyscaleImagePixels, width, height, channels, curRoi, kernelSize);
Sobel(fullyFilteredImagePixels,
smoothedImagePixels, width, height, channels, curRoi);
memcpy(
workingImage->GetCanvas()->GetPixelBuffer(),
fullyFilteredImagePixels, width * height * channels);
inputEnabled = true;
}
void Filter(bool noFiltering)
{
int i, j;
int x, y;
const int channels = 4;
int kernelSize = 3;
unsigned char* srcImg = sourceImage.GetCanvas()->GetPixelBuffer();
unsigned char* dstImg = workingImage->GetCanvas()->GetPixelBuffer();
int width = sourceImage.GetCanvas()->GetWidth();
int height = sourceImage.GetCanvas()->GetHeight();
if (noFiltering) {
std::cout << "Restoring the original image\n";
memcpy(dstImg, srcImg, width * height * channels);
return;
}
try {
kernelSize = std::stoi(kernelSizeTextBox->GetText());
}
catch (...) {
std::cout << "Parsing int failed...\n";
std::cout << "Blurring with kernel size 3\n";
kernelSize = 3;
}
if (kernelSize % 2 == 0) {
std::cout << "Can't work on even kernel size\n";
return;
}
if (kernelSize < 0 || kernelSize > 100) {
std::cout << "Invalid kernel size\n";
return;
}
spn::ProfilerScope prof(kernelSize);
inputEnabled = false;
std::cout << "Kernel size: " << kernelSize << std::endl;
int halfKernelSize = kernelSize / 2;
//init horizontal and vertical kernels (for box filtering)
float filterValue = 1.0f / (float)kernelSize;
float noiseAmount = 20 + rng.GenerateFloat() * 15;
std::cout << "noise amount " << noiseAmount << "\n";
RoiRect& roi = curRoi;
//apply filter horizontally
for (y = 0; y < height; ++y) {
for (x = 0; x < width; ++x) {
if (y < roi.y0 || y > roi.y1 || x < roi.x0 || x > roi.x1) continue;
float bs = 0.0;
float gs = 0.0;
float rs = 0.0;
for (i = -halfKernelSize; i <= halfKernelSize; ++i) {
int sampleLocationX = std::clamp(x + i, 0, width - 1);
unsigned char* buffer = srcImg +
(width * channels * y) +
(sampleLocationX * channels);
bs += filterValue * *buffer;
gs += filterValue * *(buffer + 1);
rs += filterValue * *(buffer + 2);
}
unsigned char* outbuffer =
horizontallyFilteredImagePixels +
(width * channels * y) +
(x * channels);
*outbuffer = bs;
*(outbuffer + 1) = gs;
*(outbuffer + 2) = rs;
*(outbuffer + 3) = 255; //alpha
}
}
bool nop = false;
//apply filter vertically
for (y = 0; y < height; ++y) {
for (x = 0; x < width; ++x) {
if (y < roi.y0 || y > roi.y1 || x < roi.x0 || x > roi.x1) nop=true;
if (nop) {
unsigned char* buffer = srcImg +
(width * channels * y) +
(x * channels);
unsigned char* outbuffer =
fullyFilteredImagePixels +
(width * channels * y) +
(x * channels);
*outbuffer = *buffer;
*(outbuffer + 1) = *(buffer + 1);
*(outbuffer + 2) = *(buffer + 2);
*(outbuffer + 3) = 255; //alpha
nop = false;
continue;
}
float bs = 0.0;
float gs = 0.0;
float rs = 0.0;
for (i = -halfKernelSize; i <= halfKernelSize; ++i) {
int sampleLocationY = std::clamp(y + i, 0, height - 1);
unsigned char* buffer =
horizontallyFilteredImagePixels +
(width * channels * sampleLocationY) +
(x * channels);
bs += filterValue * *buffer;
gs += filterValue * *(buffer + 1);
rs += filterValue * *(buffer + 2);
}
unsigned char* outbuffer =
fullyFilteredImagePixels +
(width * channels * y) +
(x * channels);
*outbuffer = bs;
*(outbuffer + 1) = gs;
*(outbuffer + 2) = rs;
*(outbuffer + 3) = 255; //alpha
}
}
//add noise
for (y = 0; y < height; ++y) {
for (x = 0; x < width; ++x) {
if (y < roi.y0 || y > roi.y1 || x < roi.x0 || x > roi.x1) continue;
unsigned char* buffer = fullyFilteredImagePixels +
(width * channels * y) +
(x * channels);
float brs, grs, rrs;
if (SampleBlueNoise(noisebuffer, NOISEIMGSIZE, x, y)) {
float displ = (2.0 * rng.GenerateFloat() - 1.0) * noiseAmount;
brs = *buffer + displ;
grs = *(buffer + 1) + displ;
rrs = *(buffer + 2) + displ;
brs = std::clamp(brs, 0.0f, 255.0f);
grs = std::clamp(grs, 0.0f, 255.0f);
rrs = std::clamp(rrs, 0.0f, 255.0f);
}
else {
brs = *buffer;
grs = *(buffer + 1);
rrs = *(buffer + 2);
}
//float displ = (2.0 * rng.GenerateFloat() - 1.0) * noiseAmount;
//float brs = *buffer + displ;
//float grs = *(buffer + 1) + displ;
//float rrs = *(buffer + 2) + displ;
//brs = std::clamp(brs, 0.0f, 255.0f);
//grs = std::clamp(grs, 0.0f, 255.0f);
//rrs = std::clamp(rrs, 0.0f, 255.0f);
unsigned char* outbuffer =
fullyFilteredImagePixels +
(width * channels * y) +
(x * channels);
*outbuffer = brs;
*(outbuffer + 1) = grs;
*(outbuffer + 2) = rrs;
*(outbuffer + 3) = 255; //alpha
}
}
//change the dest image to be filtered
memcpy(dstImg, fullyFilteredImagePixels, width * height * channels);
inputEnabled = true;
}
void InitUi() {
using namespace spn::rmgui;
uim = &UiManager::GetInstance();
doFilterButton = uim->CreateWidget<Button>();
doFilterButton->SetPosition(80, MAXRESY-50);
doFilterButton->SetSize(128, 32);
doFilterButton->SetString("Sobel");
doFilterButton->SetCallback([=](int id) {
//Filter(false);
EdgeDetectFilter(false);
});
showOriginalButton = uim->CreateWidget<Button>();
showOriginalButton->SetPosition(80+140, MAXRESY - 50);
showOriginalButton->SetSize(128, 32);
showOriginalButton->SetString("Show Original");
showOriginalButton->SetCallback([=](int id) {
//Filter(true);
EdgeDetectFilter(true);
});
autoThreshButton = uim->CreateWidget<Button>();
autoThreshButton->SetPosition(80 + 140+140, MAXRESY - 50);
autoThreshButton->SetSize(128, 32);
autoThreshButton->SetString("Auto Thresh");
autoThreshButton->SetCallback([=](int id) {
AutoThreshold();
});
kernelSizeTextBox = uim->CreateWidget<Textbox>();
kernelSizeTextBox->SetPosition(10, MAXRESY-50);
kernelSizeTextBox->SetSize(64, 30);
threshSlider = uim->CreateWidget<Slider>();
threshSlider->SetId(100);
threshSlider->SetPosition(MAXRESX-250-2, MAXRESY/2);
threshSlider->SetSize(120, 32);
threshSlider->SetDecimalPlaces(2);
threshSlider->SetRangeAndValue(0, 255, 0);
threshSlider->CalculateKnobPosition();
threshSlider->SetCallback(OnSliderValueChanged);
threshSlider->SetSensitivity(0.1);
threshSlider->SetCStringLabel("Threshold");
}
void InitApp() {
spn::Profiler::GetInstance().Begin(954);
GenerateBlueNoiseMitchell(noisebuffer, NOISEIMGSIZE, 512, 2);
spn::Profiler::GetInstance().End();
sourceImage.CreateFromPng("../examples/res_for_examples/road.png");
workingImage = sourceImage.Clone();
int channels = 4;
int width = sourceImage.GetCanvas()->GetWidth();
int height = sourceImage.GetCanvas()->GetHeight();
//allocate temporary buffers
fullyFilteredImagePixels = new unsigned char[width * height * channels];
horizontallyFilteredImagePixels = new unsigned char[width * height * channels];
greyscaleImagePixels = new unsigned char[width * height * channels];
smoothedImagePixels = new unsigned char[width * height * channels];
curRoi.x0 = 0;
curRoi.y0 = 0;
curRoi.x1 = width;
curRoi.y1 = height;
}
void DestroyApp() {
delete[] fullyFilteredImagePixels;
delete[] horizontallyFilteredImagePixels;
delete[] greyscaleImagePixels;
delete[] smoothedImagePixels;
}
void UpdateAndRender(spn::Canvas* canvas) {
canvas->Clear();
canvas->DrawImage(workingImage, 0, 0);
uim->Display(canvas);
RoiRect& r = curRoi;
int left = r.x0;
int right = r.x1;
int t;
if (right < left) {
t = right;
right = left;
left = t;
}
int top = r.y0;
int bottom = r.y1;
if (top > bottom) {
t = top;
top = bottom;
bottom = t;
}
canvas->SetPrimaryColorUint(0xc0c0c0);
canvas->DrawRectangle(left, top, right, bottom);
}
void HandleInput(const SDL_Event* sdlEvent) {
if (!inputEnabled) {
return;
}
spn::ui::UiEvent uie;
spn::ui::TranslateSdlEvent(sdlEvent, uie);
switch (uie.mouseButton) {
case spn::ui::MouseButton::Right:
buildingRoi = false;
curRoi.x0 = 0;
curRoi.y0 = 0;
curRoi.x1 = sourceImage.GetCanvas()->GetWidth();
curRoi.y1 = sourceImage.GetCanvas()->GetHeight();
break;
case spn::ui::MouseButton::Left:
if (uie.eventType == spn::ui::UiEventType::MouseDown) {
buildingRoi = true;
if (uie.mouseX > sourceImage.GetCanvas()->GetWidth() || uie.mouseY > sourceImage.GetCanvas()->GetHeight()) {
break;
}
curRoi.x0 = uie.mouseX;
curRoi.x1 = uie.mouseX;
curRoi.y0 = uie.mouseY;
curRoi.y1 = uie.mouseY;
}
else if (uie.eventType == spn::ui::UiEventType::MouseDrag) {
if (buildingRoi) {
if (uie.mouseX > sourceImage.GetCanvas()->GetWidth() || uie.mouseY > sourceImage.GetCanvas()->GetHeight()) {
buildingRoi = false;
break;
}
curRoi.x1 = uie.mouseX;
curRoi.y1 = uie.mouseY;
}
}
else if (uie.eventType == spn::ui::UiEventType::MouseUp) {
if (buildingRoi) {
buildingRoi = false;
}
}
}
uim->HandleUiEvent(uie);
}
int main(int argc, char* argv[])
{
spn::SpinachCore sc;
if (!sc.Init(MAXRESX, MAXRESY, "../res/")) {
std::cout << "initialization failed with error "
<< sc.GetInitializationResult()
<< std::endl;
return 1;
}
InitApp();
InitUi();
sc.SetUpdateAndRenderHandler(UpdateAndRender);
sc.SetInputHandler(HandleInput);
sc.SetWindowTitle("Image Processing Example");
sc.GetCanvas()->SetPrimaryColor(255, 255, 0);
sc.GetCanvas()->SetClearColor(0, 0, 0);
sc.SetTargetFramesPerSecond(30);
sc.LockFps(true);
sc.MainLoop();
spn::Profiler::GetInstance().Print();
DestroyApp();
return 0;
}
Filename: example.cpp. Size: 19kb. View raw, , hex, or download this file.

This paste expires on 2026-07-19 18:14:17.549742+00:00. Pasted through web.