#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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(*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