how to rotate raster 32bits properly? - c++

I'm working on a 2D Graphics Engine, when I use the following code to rotate the images I get 'write access violation' exception for newBits if the image dimensions have even numbers. There is no problem on odd numbered dimensions.
Here is my image rotation code :
bool Graphics::Raster::rotate(float angle)
{
try {
unsigned int xOrigin{ mWidth / 2 };
unsigned int yOrigin{ mHeight / 2 };
std::array<Math::Vector2D, 4> boundingBoxVertices;
Math::Matrix2x2 rotationMatrix;
boundingBoxVertices[0].setX((float)xOrigin * -1.0f);
boundingBoxVertices[0].setY((float)yOrigin);
boundingBoxVertices[1].setX((float)(mWidth - xOrigin) * 1.0f);
boundingBoxVertices[1].setY((float)yOrigin);
boundingBoxVertices[2].setX((float)(mWidth - xOrigin) * 1.0f);
boundingBoxVertices[2].setY((float)(mHeight - yOrigin) * -1.0f);
boundingBoxVertices[3].setX((float)xOrigin * -1.0f);
boundingBoxVertices[3].setY((float)(mHeight - yOrigin) * -1.0f);
int x{ 0 }, y{ 0 }, maxX{ 0 }, minX{ 0 }, maxY{ 0 }, minY{ 0 };
rotationMatrix.setToRotation(angle);
for (size_t i = 0; i < 4; ++i) {
boundingBoxVertices[i] *= rotationMatrix;
boundingBoxVertices[i].round();
x = (int)boundingBoxVertices[i].getX();
y = (int)boundingBoxVertices[i].getY();
if (x < minX) {
minX = x;
}
if (x > maxX) {
maxX = x;
}
if (y < minY) {
minY = y;
}
if (y > maxY) {
maxY = y;
}
}
size_t newWidth = (size_t)(maxX - minX);
size_t newHeight = (size_t)(maxY - minY);
BYTE* newBits{ nullptr };
if (newBits = new BYTE[newWidth * newHeight * 4]{ 0 }) {
int newOrgX = newWidth / 2;
int newOrgY = newHeight / 2;
Math::Vector2D pixVec{ 0.0f, 0.0f };
int oldCoordX{ 0 };
int oldCoordY{ 0 };
int newCoordX{ 0 };
int newCoordY{ 0 };
unsigned int oldIndex{ 0 };
unsigned int newIndex{ 0 };
for (size_t i = 0; i < mWidth * mHeight; ++i) {
oldCoordX = i % mWidth - xOrigin;
oldCoordY = yOrigin - i / mWidth;
pixVec.setX((float)oldCoordX);
pixVec.setY((float)oldCoordY);
pixVec *= rotationMatrix;
pixVec.round();
newCoordX = (unsigned int)(pixVec.getX() + newOrgX);
newCoordY = (unsigned int)(newOrgY - pixVec.getY());
oldIndex = i * 4;
newIndex = (newCoordY * newWidth * 4) + ((newCoordX) * 4);
newBits[newIndex + 0] = m32Bits[oldIndex + 0];
newBits[newIndex + 1] = m32Bits[oldIndex + 1];
newBits[newIndex + 2] = m32Bits[oldIndex + 2];
newBits[newIndex + 3] = m32Bits[oldIndex + 3];
}
if (angle != 0.0f || angle != 90.0f || angle != 180.0f || angle != 270.0f || angle != 360.0f ||
angle != -0.0f || angle != -90.0f || angle != -180.0f || angle != -270.0f || angle != -360.0f ) {
for (size_t i = 0; i < newHeight; ++i) {
for (size_t j = 0; j < newWidth; ++j) {
if (j != 0 && j != newWidth - 1) {
if (newBits[(i * newWidth * 4) + (j * 4) + 0] == 0 &&
newBits[(i * newWidth * 4) + (j * 4) + 1] == 0 &&
newBits[(i * newWidth * 4) + (j * 4) + 2] == 0 &&
newBits[(i * newWidth * 4) + (j * 4) + 3] == 0) {
newBits[(i * newWidth * 4) + (j * 4) + 0] = (newBits[(i * newWidth * 4) + ((j - 1) * 4) + 0] +
newBits[(i * newWidth * 4) + ((j + 1) * 4) + 0]) / 2;
newBits[(i * newWidth * 4) + (j * 4) + 1] = (newBits[(i * newWidth * 4) + ((j - 1) * 4) + 1] +
newBits[(i * newWidth * 4) + ((j + 1) * 4) + 1]) / 2;
newBits[(i * newWidth * 4) + (j * 4) + 2] = (newBits[(i * newWidth * 4) + ((j - 1) * 4) + 2] +
newBits[(i * newWidth * 4) + ((j + 1) * 4) + 2]) / 2;
newBits[(i * newWidth * 4) + (j * 4) + 3] = (newBits[(i * newWidth * 4) + ((j - 1) * 4) + 3] +
newBits[(i * newWidth * 4) + ((j + 1) * 4) + 3]) / 2;
}
}
}
}
}
if (set32Bits(newBits, newWidth, newHeight)) {
delete[] newBits;
return true;
} else {
delete[] newBits;
return false;
}
} else {
throw Error::Exception(L"Resim çevirme işlemi için hafızada yer açılamadı", L"Resim Düzenleme Hatası");
}
} catch (Error::Exception& ex) {
Error::ShowError(ex.getErrorMessage(), ex.getErrorTitle());
return false;
}
}
What am I doing wrong here?
I can't use a third-party to rotate the images, I must use this function.
Thanks in advance.

The problem was a index problem when setting the newBits.
Here is the updated function :
BYTE* Graphics::RotateBits(const BYTE* bits, const int width, const int height, float angle, int* newWidth, int* newHeight)
{
try {
int xOrigin{ width / 2 };
int yOrigin{ height / 2 };
std::array<Math::Vector2D, 4> boundingBoxVertices;
Math::Matrix2x2 rotationMatrix;
boundingBoxVertices[0].setX((float)xOrigin * -1.0f);
boundingBoxVertices[0].setY((float)yOrigin);
boundingBoxVertices[1].setX((float)(width - xOrigin) * 1.0f);
boundingBoxVertices[1].setY((float)yOrigin);
boundingBoxVertices[2].setX((float)(width - xOrigin) * 1.0f);
boundingBoxVertices[2].setY((float)(height - yOrigin) * -1.0f);
boundingBoxVertices[3].setX((float)xOrigin * -1.0f);
boundingBoxVertices[3].setY((float)(height - yOrigin) * -1.0f);
int x{ 0 }, y{ 0 }, maxX{ 0 }, minX{ 0 }, maxY{ 0 }, minY{ 0 };
rotationMatrix.setToRotation(angle);
for (int i = 0; i < 4; ++i) {
boundingBoxVertices[i] *= rotationMatrix;
boundingBoxVertices[i].round();
x = (int)boundingBoxVertices[i].getX();
y = (int)boundingBoxVertices[i].getY();
if (x < minX) {
minX = x;
}
if (x > maxX) {
maxX = x;
}
if (y < minY) {
minY = y;
}
if (y > maxY) {
maxY = y;
}
}
*newWidth = (maxX - minX);
*newHeight = (maxY - minY);
BYTE* newBits = new BYTE[*newWidth * *newHeight * 4]{ 0 };
int newOrgX = *newWidth / 2;
int newOrgY = *newHeight / 2;
Math::Vector2D pixVec{ 0.0f, 0.0f };
int oldCoordX{ 0 };
int oldCoordY{ 0 };
int newCoordX{ 0 };
int newCoordY{ 0 };
int oldIndex{ 0 };
int newIndex{ 0 };
for (int i = 0; i < width * height; ++i) {
oldCoordX = i % width - xOrigin;
oldCoordY = yOrigin - i / width;
pixVec.setX((float)oldCoordX);
pixVec.setY((float)oldCoordY);
pixVec *= rotationMatrix;
pixVec.round();
newCoordX = (int)pixVec.getX() + newOrgX;
newCoordY = newOrgY - (int)pixVec.getY();
oldIndex = i * 4;
newIndex = (newCoordY * *newWidth * 4) + (newCoordX * 4);
if (newIndex >= 0 && newIndex <= *newWidth * *newHeight * 4 - 4) {
newBits[newIndex + 0] = bits[oldIndex + 0];
newBits[newIndex + 1] = bits[oldIndex + 1];
newBits[newIndex + 2] = bits[oldIndex + 2];
newBits[newIndex + 3] = bits[oldIndex + 3];
}
}
if (((int)angle) % 90) {
int index{ 0 };
int prevIndex{ 0 };
int nextIndex{ 0 };
for (int i = 0; i < *newHeight; ++i) {
for (int j = 0; j < *newWidth; ++j) {
if (j != 0 && j != *newWidth - 1) {
index = (i * *newWidth * 4) + (j * 4);
if (newBits[index + 0] == 0 &&
newBits[index + 1] == 0 &&
newBits[index + 2] == 0 &&
newBits[index + 3] == 0) {
prevIndex = (i * *newWidth * 4) + ((j - 1) * 4);
nextIndex = (i * *newWidth * 4) + ((j + 1) * 4);
newBits[index + 0] = (newBits[prevIndex + 0] + newBits[nextIndex + 0]) / 2;
newBits[index + 1] = (newBits[prevIndex + 1] + newBits[nextIndex + 1]) / 2;
newBits[index + 2] = (newBits[prevIndex + 2] + newBits[nextIndex + 2]) / 2;
newBits[index + 3] = (newBits[prevIndex + 3] + newBits[nextIndex + 3]) / 2;
}
}
}
}
}
return newBits;
} catch (Error::Exception& ex) {
Error::ShowError(ex.getErrorMessage(), ex.getErrorTitle());
return nullptr;
} catch (std::exception& ex) {
Error::ShowError((LPCWSTR)ex.what(), L"Bit Düzenleme Hatası");
return nullptr;
}
}

Related

Drawing the top and bottom of a cylinder

I'm trying to create a class that can procedurally create prisms (or cylinders if the precision is high enough) but only the sides of the 3d model are showing (not the top and bottom). This is using openGL and c++. Not going for efficiency, just modifying a previous class that made a sphere.
#define numSlices 2
Prism::Prism() {
init(3);
}
Prism::Prism(int prec) {
init(prec);
}
float Prism::toRadians(float degrees) { return (degrees * 2.0f * 3.14159f) / 360.0f; }
void Prism::init(int prec) {
prec = (prec < 3) ? 3 : prec;
numVertices = (prec + 1) * (numSlices+1);
numIndices = prec * numSlices * 6;
for (int i = 0; i < numVertices; i++) { vertices.push_back(glm::vec3()); }
for (int i = 0; i < numVertices; i++) { texCoords.push_back(glm::vec2()); }
for (int i = 0; i < numVertices; i++) { normals.push_back(glm::vec3()); }
for (int i = 0; i < numVertices; i++) { tangents.push_back(glm::vec3()); }
for (int i = 0; i < numIndices; i++) { indices.push_back(0); }
// calculate triangle vertices
for (int i = 0; i <= numSlices; i++) {
for (int j = 0; j <= prec; j++) {
float y = i;
float x = -(float)cos(toRadians(j * 360.0f / (float)prec));
float z = (float)sin(toRadians(j * 360.0f / (float)prec));
vertices[i * (prec + 1) + j] = glm::vec3(x, y, z);
texCoords[i * (prec + 1) + j] = glm::vec2(((float)j / prec), ((float)i / numSlices));
}
}
// calculate triangle indices
for (int i = 0; i < numSlices; i++) {
for (int j = 0; j < prec; j++) {
indices[6 * (i * prec + j) + 0] = i * (prec + 1) + j;
indices[6 * (i * prec + j) + 1] = i * (prec + 1) + j + 1;
indices[6 * (i * prec + j) + 2] = (i + 1) * (prec + 1) + j;
indices[6 * (i * prec + j) + 3] = i * (prec + 1) + j + 1;
indices[6 * (i * prec + j) + 4] = (i + 1) * (prec + 1) + j + 1;
indices[6 * (i * prec + j) + 5] = (i + 1) * (prec + 1) + j;
}
}
}
Any tips or solutions that stick closely to the code already written would much appreciated.
To render the top and bottom of the cylinder, you can create a "triangle fan" that starts from a vertex at the center of the top/bottom of the cylinder and creates one triangle for every side.
Adapting your code: (untested, I may have made mistakes against winding order)
int bottom_center = vertices.length(); vertices.push_back(glm::vec3(0,0,0));
int top_center = vertices.length(); vertices.push_back(glm::vec3(0,numSlices,0));
// Bottom
for (int j = 0; j < prec; j++) {
int base = 0;
indices.push_back(bottom_center);
indices.push_back(base+j);
indices.push_back(base+j+1);
}
// Top
for (int j = 0; j < prec; j++) {
int base = numSlices * (prec+1);
indices.push_back(top_center);
indices.push_back(base+j);
indices.push_back(base+j+1);
}
See http://www.songho.ca/opengl/gl_cylinder.html for a more worked-out example.

Detect a 2d collision in C++

I need to create a function that returns a bool that is false if there is no collision between two sprites and true if there is, I was thinking for a long time and I can not find an exact solution, the objective is to detect if there is a collision per pixel, that is if two pixels with the alpha value (from rgba) different than 0 (it is visible) coincide in the same place in the space, the function has the following signature :
bool checkPixelCollision(
const Vector2& pixelPos1,
const Vector2& pixelSize1,
const vector<uint8_t> pixel1,
const Vector2& pixelPos2,
const Vector2& pixelSize2,
const vector<uint8_t> pixel2);
Vector2 is a struct with the next form:
struct Vector2
{
float x;
float y;
};
pixelPos1 is the position of the upper left corner of the rectangle that contains sprite 1, pixelSize1 is the size (x = width; y = height) of the rectangle that contains sprite 1, pixel1 is a vector that has the rgba values ​​of each pixel of the sprite, they are stored from 4 to 4 so that i contains the amount of r of the pixel i; i + 1 the amount of g of the pixel i; i + 2 the amount of b of the pixel i; i + 3 the amount of alpha of the pixel i, so that if i + 3 is different from 0 is a visible pixel, the size of pixel1 is given by pixelSize1.x * pixelSize1.y * 4.
The other three parameters of the header are those corresponding to sprite 2. The objective would therefore be to check when there is a collision (either on the side or corner that is) and from there establish a collision rectangle between both rectangles (the coincident area), and set two indexes that travel pixel1 and pixel2 (since each one will have to start from a different position in its corresponding vector).
The problem is that I can not find an optimal and / or easy way to do it and that it works. If anyone knows any way to do it, I would appreciate it very much.
EDIT
Here is my code (it doesn't work)
#include <algorithm>
#include <stdint.h>
#include <vector>
struct Vector2
{
float x;
float y;
};
float clamp(float val, float min, float max) {
return std::max(min, std::min(max, val));
}
bool checkPixelCollision(const Vector2& pixelPos1, const Vector2& pixelSize1, const vector<uint8_t> pixel1, const Vector2& pixelPos2, const Vector2& pixelSize2, const vector<uint8_t> pixel2) {
return check(pixelPos1,pixelSize1,pixel1,pixelPos2,pixelSize2,pixel2)||check(pixelPos2,pixelSize2,pixel2,pixelPos1,pixelSize1,pixel1);
}
bool check(const Vector2& pixelsPos1, const Vector2& pixelsSize1, const vector<uint8_t> pixels1, const Vector2& pixelsPos2, const Vector2& pixelsSize2, const vector<uint8_t> pixels2){
bool res = false;
if (pixelsPos1.x <= pixelsPos2.x + pixelsSize2.x && pixelsPos1.y <= pixelsPos2.y + pixelsSize2.y && pixelsPos1.x >= pixelsPos2.x && pixelsPos1.y >= pixelsPos2.y) {
float i = pixelsSize2.x - (pixelsSize1.y*((pixelsPos1.x - pixelsPos2.x + pixelsSize2.x) / pixelsSize1.x));
float j = pixelsSize2.y - (pixelsSize1.y*((pixelsPos1.y - pixelsPos2.y + pixelsSize2.y) / pixelsSize1.y));
float ifin = fmin(pixelsSize1.x - pixelsSize2.x, pixelsSize1.x);
float jfin = fmin(pixelsSize1.y - pixelsSize2.y, pixelsSize1.y);
float i2 = 0;
float j2 = 0;
while (j<jfin-1) {
int k = floor((pixelsSize2.x*j) + i) * 4 - 1;
int k2 = floor((pixelsSize1.x*j2) + i2) * 4 - 1;
if (pixels1[k2 + 3] != 0 && pixels2[k + 3] != 0) {
res = true;
}
if (i < ifin) {
i = i + 1;
i2 = i2 + 1;
}
else {
i2 = 0;
i = pixelsSize2.x - (pixelsSize1.x*((pixelsPos1.x - pixelsPos2.x + pixelsSize2.x) / pixelsSize1.x));
j = j + 1;
j2 = j2 + 1;
}
}
}
else if (pixelsPos1.x <= pixelsPos2.x + pixelsSize2.x && pixelsPos1.y + pixelsSize1.y >= pixelsPos2.y && pixelsPos1.x >= pixelsPos2.x && pixelsPos1.y + pixelsSize1.y <= pixelsPos2.y + pixelsSize2.y) {
float i = clamp(pixelsSize2.x - (pixelsSize1.x*((pixelsPos1.x - pixelsPos2.x + pixelsSize2.x) / pixelsSize1.x)), 0.0f, pixelsSize2.x);
float jfin = clamp(pixelsSize1.y*((pixelsPos2.y - pixelsPos1.y+pixelsSize1.y) / pixelsSize1.y), 0.0f, pixelsSize1.y);
float ifin = fmin(pixelsSize1.x - pixelsSize2.x, pixelsSize1.x);
float j = 0;
float i2 = 0;
float j2 = clamp(pixelsSize1.y - pixelsSize1.y*((pixelsPos2.y - pixelsPos1.y + pixelsSize1.y) / pixelsSize1.y),0.0f, pixelsSize1.y);
while (j<jfin-1) {
int k = floor((pixelsSize2.x*j) + i) * 4 - 1;
int k2 = floor((pixelsSize1.x*j2) + i2) * 4 - 1;
if (pixels1[k2 + 3] != 0 && pixels2[k + 3] != 0) {
res = true;
}
if (i < ifin) {
i = i + 1;
i2 = i2 + 1;
}
else {
i2 = 0;
i = clamp(pixelsSize2.x - (pixelsSize1.x*((pixelsPos1.x - pixelsPos2.x + pixelsSize2.x) / pixelsSize1.x)),0.0f, pixelsSize2.x);
j = j + 1;
j2 = j2 + 1;
}
}
}
else if (pixelsPos1.x + pixelsSize1.x >= pixelsPos2.x && pixelsPos1.y<= pixelsPos2.y + pixelsSize2.y && pixelsPos1.x + pixelsSize1.x <= pixelsPos2.x + pixelsSize2.x && pixelsPos1.y >= pixelsPos2.y) {
float ifin = clamp(pixelsSize1.x*((pixelsPos2.x - pixelsPos1.x + pixelsSize1.x) / pixelsSize1.x), 0.0f, pixelsSize1.x);
float j = clamp(pixelsSize2.y - (pixelsSize1.y*((pixelsPos1.y - pixelsPos2.y + pixelsSize2.y) / pixelsSize1.y)),0.0f, pixelsSize2.y);
float jfin = fmin(pixelsSize1.y - pixelsSize2.y, pixelsSize1.y);
float i = 0;
float i2 = clamp(pixelsSize1.x - pixelsSize1.x*((pixelsPos2.x - pixelsPos1.x + pixelsSize1.x) / pixelsSize1.x), 0.0f, pixelsSize1.x);
float j2 = 0;
while (j<jfin-1) {
int k = floor((pixelsSize2.x*j) + i) * 4 - 1;
int k2 = floor((pixelsSize1.x*j2) + i2) * 4 - 1;
if (pixels1[k2 + 3] != 0 && pixels2[k + 3] != 0) {
res = true;
}
if (i < ifin) {
i = i + 1;
i2 = i2 + 1;
}
else {
i2 = clamp(pixelsSize1.x - pixelsSize1.x*((pixelsPos2.x - pixelsPos1.x + pixelsSize1.x) / pixelsSize1.x), 0.0f, pixelsSize1.x);
i = 0;
j = j + 1;
j2 = j2 + 1;
}
}
}
else if (pixelsPos1.x + pixelsSize1.x >= pixelsPos2.x && pixelsPos1.y + pixelsSize1.y >= pixelsPos2.y && pixelsPos1.x + pixelsSize1.x <= pixelsPos2.x + pixelsSize2.x && pixelsPos1.y + pixelsSize1.y <= pixelsPos2.y + pixelsSize2.y) {
float jfin = clamp(pixelsSize1.y*((pixelsPos2.y - pixelsPos1.y + pixelsSize1.y) / pixelsSize1.y), 0.0f, pixelsSize1.y);
float j = 0;
float ifin = clamp(pixelsSize1.x*((pixelsPos2.x - pixelsPos1.x + pixelsSize1.x) / pixelsSize1.x), 0.0f, pixelsSize1.x);
float i = 0;
float i2 = clamp(pixelsSize1.x - pixelsSize1.x*((pixelsPos2.x - pixelsPos1.x + pixelsSize1.x) / pixelsSize1.x), 0.0f, pixelsSize1.x);
float j2 = clamp(pixelsSize1.y - pixelsSize1.y*((pixelsPos2.y - pixelsPos1.y + pixelsSize1.y) / pixelsSize1.y), 0.0f, pixelsSize1.y);
while (j<jfin-1) {
int k = floor((pixelsSize2.x*j) + i) * 4 - 1;
int k2 = floor((pixelsSize1.x*j2) + i2) * 4 - 1;
if (pixels1[k2 + 3] != 0 && pixels2[k + 3] != 0) {
res = true;
}
if (i < ifin) {
i = i + 1;
i2 = i2 + 1;
}
else {
i2 = clamp(pixelsSize1.x - pixelsSize1.x*((pixelsPos2.x - pixelsPos1.x + pixelsSize1.x) / pixelsSize1.x), 0.0f, pixelsSize1.x);
i = 0;
j = j + 1;
j2 = j2 + 1;
}
}
}
return res;
}
Start by checking if the bounding rectangles of the two sprites overlap. If they don't, great; no collision is possible. If they do overlap, calculate the overlapping rectangle for each sprite and compare pixel by pixel - if pixel a or pixel b is transparent then there is no collision caused by that pixel, if both pixels are non-transparent there is a collision and you are done. If you finish checking all pixels in the overlapping area and there are no collisions you are also done.

SFML C++ Canny edge detection double edges

So, I decided to create a simple Canny edge detector just as exercise before biting harder topics with image processing.
I tried to follow the typical path of Canny:
1. Grayscaling the image
2. Gaussian filter to blur the noise
3. Edge detection - I use both Sobel and Scharr
4. Edge thinning - I used non-maximum suppression in direction depending on gradient direction - vertical, horizontal, 45 diagonal or 135 diagonal
5. Hysteresis
I somehow managed to get it working with Scharr's detection but I have recurring problem with double or multiple edges, espacially with Sobel. I can't really find a set of parameters which will make it work.
My algorithm for Sobel:
void sobel(sf::Image &image, pixldata **garray, float division)
{
int t1 = 0, t2 = 0, t3 = 0, t4 = 0;
sf::Color color;
sf::Image bufor;
bufor.create(image.getSize().x, image.getSize().y, sf::Color::Cyan);
for (int i = 1;i < image.getSize().y - 1;i++)
{
for (int j = 1;j < image.getSize().x - 1;j++)
{
t1 = (- image.getPixel(j - 1, i - 1).r - 2 * image.getPixel(j - 1, i).r - image.getPixel(j - 1, i + 1).r + image.getPixel(j + 1, i - 1).r + 2 * image.getPixel(j + 1, i).r + image.getPixel(j + 1, i + 1).r) / division;
t2 = (- image.getPixel(j - 1, i).r - 2 * image.getPixel(j - 1, i + 1).r - image.getPixel(j, i + 1).r + image.getPixel(j + 1, i).r + 2 * image.getPixel(j + 1, i - 1).r + image.getPixel(j, i - 1).r) / division;
t3 = (- image.getPixel(j - 1, i + 1).r - 2 * image.getPixel(j, i + 1).r - image.getPixel(j + 1, i + 1).r + image.getPixel(j - 1, i - 1).r + 2 * image.getPixel(j, i - 1).r + image.getPixel(j + 1, i - 1).r) / division;
t4 = (- image.getPixel(j, i + 1).r - 2 * image.getPixel(j + 1, i + 1).r - image.getPixel(j + 1, i).r + image.getPixel(j - 1, i).r + 2 * image.getPixel(j - 1, i - 1).r + image.getPixel(j, i - 1).r) / division;
color.r = (abs(t1) + abs(t2) + abs(t3) + abs(t4));
color.g = (abs(t1) + abs(t2) + abs(t3) + abs(t4));
color.b = (abs(t1) + abs(t2) + abs(t3) + abs(t4));
garray[j][i].gx = t1;
garray[j][i].gy = t3;
garray[j][i].gtrue = sqrt(t1*t1 + t2*t2 + t3*t3 + t4*t4);
garray[j][i].gsimpl = sqrt(t1*t1 + t2*t2);
t1 = abs(t1);
t2 = abs(t2);
t3 = abs(t3);
t4 = abs(t4);
if (t1 > t4 && t1 > t3 && t1 > t2)
garray[j][i].fi = 0;
else if (t2 > t4 && t2 > t3 && t2 > t1)
garray[j][i].fi = 45;
else if (t3 > t4 && t3 > t2 && t3 > t1)
garray[j][i].fi = 90;
else if (t4 > t3 && t4 > t2 && t4 > t1)
garray[j][i].fi = 135;
else
garray[j][i].fi = 0;
if (sqrt(t1*t1 + t2*t2 + t3*t3 + t4*t4) < 0)
{
color.r = 0;
color.g = 0;
color.b = 0;
}
else if (sqrt(t1*t1 + t2*t2 + t3*t3 + t4*t4) > 255)
{
color.r = 255;
color.g = 255;
color.b = 255;
}
else
{
color.r = sqrt(t1*t1 + t2*t2 + t3*t3 + t4*t4);
color.g = sqrt(t1*t1 + t2*t2 + t3*t3 + t4*t4);
color.b = sqrt(t1*t1 + t2*t2 + t3*t3 + t4*t4);
}
bufor.setPixel(j, i, color);
}
}
image.copy(bufor, 0, 0);
}
Code for Scharr differs only in multiplying the pixels' values.
t1 = (-3 * image.getPixel(j - 1, i - 1).r - 10 * image.getPixel(j - 1, i).r - 3 * image.getPixel(j - 1, i + 1).r + 3 * image.getPixel(j + 1, i - 1).r + 10 * image.getPixel(j + 1, i).r + 3 * image.getPixel(j + 1, i + 1).r) / division;
t2 = (-3 * image.getPixel(j - 1, i).r - 10 * image.getPixel(j - 1, i + 1).r - 3 * image.getPixel(j, i + 1).r + 3 * image.getPixel(j + 1, i).r + 10 * image.getPixel(j + 1, i - 1).r + 3 * image.getPixel(j, i - 1).r) / division;
t3 = (-3 * image.getPixel(j - 1, i + 1).r - 10 * image.getPixel(j, i + 1).r - 3 * image.getPixel(j + 1, i + 1).r + 3 * image.getPixel(j - 1, i - 1).r + 10 * image.getPixel(j, i - 1).r + 3 * image.getPixel(j + 1, i - 1).r) / division;
t4 = (-3 * image.getPixel(j, i + 1).r - 10 * image.getPixel(j + 1, i + 1).r - 3 * image.getPixel(j + 1, i).r + 3 * image.getPixel(j - 1, i).r + 10 * image.getPixel(j - 1, i - 1).r + 3 * image.getPixel(j, i - 1).r) / division;
Thinning code:
void intelligentThin(sf::Image &image, int radius, pixldata **garray)
{
int xmax = image.getSize().x;
int ymax = image.getSize().y;
bool judgeandjury = true;
for (int i = 0;i < xmax;i++)
{
int leftBound = 0, rightBound = 0, ceilBound = 0, bottomBound = 0;
if (i < radius)
{
leftBound = 0;
rightBound = i + radius;
}
else if (i >= xmax - radius)
{
leftBound = i - radius;
rightBound = xmax - 1;
}
else
{
leftBound = i - radius;
rightBound = i + radius;
}
for (int j = 0;j < ymax;j++)
{
if (j < radius)
{
ceilBound = 0;
bottomBound = j + radius;
}
else if (j >= ymax - radius)
{
ceilBound = j - radius;
bottomBound = ymax - 1;
}
else
{
ceilBound = j - radius;
bottomBound = j + radius;
}
if (garray[i][j].fi == 0)
{
for (int t = leftBound; t <= rightBound; t++)
{
if ((image.getPixel(t, j).r >= image.getPixel(i, j).r) && (t != i))
{
judgeandjury = false;
}
}
}
else if (garray[i][j].fi == 135)
{
for (int l = leftBound, t = ceilBound; (l <= rightBound && t <= bottomBound); l++, t++)
{
if ((image.getPixel(l, t).r >= image.getPixel(i, j).r) && (t != j))
{
judgeandjury = false;
}
}
}
else if (garray[i][j].fi == 90)
{
for (int t = ceilBound; t <= bottomBound; t++)
{
if ((image.getPixel(i, t).r >= image.getPixel(i, j).r) && (t != j))
{
judgeandjury = false;
}
}
}
else if (garray[i][j].fi == 45)
{
for (int l = rightBound, t = ceilBound; (l >= leftBound && t <= bottomBound); l--, t++)
{
if ((image.getPixel(l, t).r >= image.getPixel(i, j).r) && (t != j))
{
judgeandjury = false;
}
}
}
if (judgeandjury == false)
{
image.setPixel(i, j, sf::Color::Black);
}
judgeandjury = true;
}
leftBound = rightBound = 0;
}
}
Hysteresis code:
void hysteresis(sf::Image &image, int radius, int uplevel, int lowlevel)
{
int xmax = image.getSize().x;
int ymax = image.getSize().y;
bool judgeandjury = false;
sf::Image bufor;
bufor.create(image.getSize().x, image.getSize().y, sf::Color::Cyan);
for (int i = 0;i < xmax;i++)
{
int leftBound = 0, rightBound = 0, ceilBound = 0, bottomBound = 0;
if (i < radius)
{
leftBound = 0;
rightBound = i + radius;
}
else if (i >= xmax - radius)
{
leftBound = i - radius;
rightBound = xmax - 1;
}
else
{
leftBound = i - radius;
rightBound = i + radius;
}
for (int j = 0;j < ymax;j++)
{
int currentPoint = image.getPixel(i, j).r;
if (j < radius)
{
ceilBound = 0;
bottomBound = j + radius;
}
else if (j >= ymax - radius)
{
ceilBound = j - radius;
bottomBound = ymax - 1;
}
else
{
ceilBound = j - radius;
bottomBound = j + radius;
}
if (currentPoint > uplevel)
{
judgeandjury = true;
}
else if (currentPoint > lowlevel)
{
for (int t = leftBound; t <= rightBound; t++)
{
for (int l = ceilBound; l <= bottomBound; l++)
{
if (image.getPixel(t, l).r > uplevel)
{
judgeandjury = true;
}
}
}
}
else judgeandjury = false;
if (judgeandjury == true)
{
bufor.setPixel(i, j, sf::Color::White);
}
else
{
bufor.setPixel(i, j, sf::Color::Black);
}
judgeandjury = false;
currentPoint = 0;
}
leftBound = rightBound = 0;
}
image.copy(bufor, 0, 0);
}
The results are quite unsatisfactionary for Sobel:
Thinning the Sobel
Sobel after hysteresis
With Scharr the results are way better:
Thinned Scharr
Scharr after hysteresis
Set of parameters:
#define thinsize 1
#define scharrDivision 1
#define sobelDivision 1
#define hysteresisRadius 1
#define level 40
#define hysteresisUpperLevelSobel 80
#define hysteresisLowerLevelSobel 60
#define hysteresisUpperLevelScharr 200
#define hysteresisLowerLevelScharr 100
As you can see, there is a problem with Sobel, which generate double edges. Scharr also generates some noise but I think it's acceptable. Of course, it always can get better, if someone could give some advice :)
What is the cause of this behaviour? Does it result from my mistakes or poor algorithms or maybe is it just a case of parameters?
EDIT:
posting main()
sf::Image imydz;
imydz.loadFromFile("lena.jpg");
int x = imydz.getSize().x;
int y = imydz.getSize().y;
pixldata **garray = new pixldata *[x];
for (int i = 0;i < x;i++)
{
garray[i] = new pixldata[y];
}
monochrome(imydz);
gauss(imydz, radius, sigma);
//sobel(imydz, garray, sobelDivision);
scharr(imydz, garray, scharrDivision);
intelligentThin(imydz, thinsize, garray);
hysteresis(imydz, hysteresisRadius, hysteresisUpperLevel, hysteresisLowerLevel);
Second edit - repaired suppression:
sf::Image bufor;
bufor.create(image.getSize().x, image.getSize().y, sf::Color::Black);
for (int i = 1;i < xmax - 1;i++)
{
for (int j = 1;j < ymax - 1;j++)
{
if (garray[i][j].fi == 0)
{
if (((image.getPixel(i, j).r >= image.getPixel(i + 1, j).r) && (image.getPixel(i, j).r > image.getPixel(i - 1, j).r)) ||
((image.getPixel(i, j).r > image.getPixel(i + 1, j).r) && (image.getPixel(i, j).r >= image.getPixel(i - 1, j).r)))
{
judgeandjury = true;
}
else judgeandjury = false;
}
...
if (judgeandjury == false)
{
bufor.setPixel(i, j, sf::Color::Black);
}
else bufor.setPixel(i, j, image.getPixel(i, j));
judgeandjury = false;
}
}
image.copy(bufor, 0, 0);
Repaired Scharr on Lena
It seems strange
Another test image - strange results
Before binarization
Ready gears
I haven't read your whole code in detail, there is much too much code there. But obviously your non-maximum suppression code is wrong. Let's look at what it does for one pixel in the middle of the image, where the gradient is close to 0 degrees:
leftBound = i - radius;
rightBound = i + radius;
// ...
for (int t = leftBound; t <= rightBound; t++)
{
if ((image.getPixel(t, j).r >= image.getPixel(i, j).r) && (t != i))
{
judgeandjury = false; // it's not a maximum: suppress
}
}
// ...
if (judgeandjury == false)
{
image.setPixel(i, j, sf::Color::Black);
}
Here, radius is set to 1 by the calling code. Any other value would be bad, so this is OK. I would remove that as a parameter altogether. Now your loop is:
for (int t = i-1; t <= t+1; t++)
if (t != i)
This means that you hit exactly two values of t. So this should of course be replaced with simpler code that does not loop, it will be more readable.
This is what it now does:
if ( (image.getPixel(i-1, j).r >= image.getPixel(i, j).r)
|| (image.getPixel(i+1, j).r >= image.getPixel(i, j).r)) {
judgeandjury = false; // it's not a maximum: suppress
}
So you suppress the pixel if it is not strictly larger than its neighbors. Looking back at the Wikipedia article, it seems that they suggest the same. But in fact, this is not correct, you want the point to be strictly larger than one of the two neighbors, and larger or equal to the other. This prevents the situation where the gradient happens to be equally strong on two neighboring pixels. The actual maximum can fall right in the middle of two pixels, yielding two pixels on this local maximum gradient with exactly the same value. But let's ignore this case for now, it is possible but not all that likely.
Next, you suppress the maximum... in the input image! This means that, when you reach the next pixel on this line, you will compare its value to this value that was just suppressed. Of course it will be larger, even though it was smaller than the original value at that location. That is, non-maxima will look like maxima because you put a neighboring pixel to 0.
So: write the result of the algorithm to an output image:
if (judgeandjury == true)
{
output.setPixel(i, j, image.getPixel(i, j));
}
...which of course you need to allocate, but you already know that.
Your second problem is in the sobel function, where you compute the gradient magnitude. It clips (saturates) the output. By cutting values of the output above 255 to 255, you create very broad lines along the edges of a constant value. The test of the non-maximum suppression is satisfied at the two edges of this line, but not in the middle, where pixels have the same value as both its neighbors.
To solve this, either:
Use a floating-point buffer to store the gradient magnitude. Here you don’t need to worry about data ranges.
Divide the magnitude by some value such that it will never exceed 255. Now you’re quantifying the magnitude rather than clipping it. Quantizing should be fine in this case.
I strongly recommend that you follow (1). I typically use floating-point—values images for everything, and only convert to 8-bit ints for display. This simplified a lot of things!

sobel filter algorithm (C++) (no libraries)

I am trying to apply the sobel filter algorithm to a given picture (grayscale in this case) given my approach to accessing the pixels of the picture. Since I am accessing them in a way that doesn't use libraries, I am having trouble figuring out how to apply the algorithm given this approach. This first part of the code is just accessing pixel data:
Part 1:
CKingimageDoc* pDoc = GetDocument(); // get picture
int iBitPerPixel = pDoc->_bmp->bitsperpixel; // used to see if grayscale(8 bits) or RGB (24 bits)
int iWidth = pDoc->_bmp->width;
int iHeight = pDoc->_bmp->height;
BYTE *pImg = pDoc->_bmp->point; // pointer used to point at pixels in the image
const int area = iWidth * iHeight;
int Wp = iWidth;
int intensity;
if (iBitPerPixel == 8) ////Grayscale 8 bits image
{
int r = iWidth % 4; // pixels leftover from width (remainder has to be factor of 8 or 24)
int p = (4-r) % 4; // has to be a factor of number of bits in pixel, num leftover to take care of
Wp = iWidth + p;
Part 2 (The actual application of the sobel filter algorithm):
float kernelx[3][3] = { { -1, 0, 1 },
{ -2, 0, 2 },
{ -1, 0, 1 } };
float kernely[3][3] = { { -1, -2, -1 },
{ 0, 0, 0 },
{ 1, 2, 1 } };
double magX = 0.0; // this is your magnitude
for (int a = 0; a < 3; a++) {
for (int b = 0; b < 3; b++) {
magX += pImg[i*Wp + j] * kernelx[a][b]; // where i get confused
}
}
}
Any and all help is greatly appreciated.
You have to use appropriate pixel from neighborhood of center pixel to multiply with kernel entry:
//row, col - coordinates of central pixel for calculation
for (int row = 1; row < height - 1; row++) {
for (int col = 1; col < width - 1; col++) {
double magX = 0.0; // this is your magnitude
for (int a = 0; a < 3; a++) {
for (int b = 0; b < 3; b++) {
magX += pImg[(row - 1 + a) * Wp + col - 1 + b] * kernelx[a][b];
}
}
resultImg[row * Wp + col] = magX;
}
}
I omitted border pixels
CKingimageDoc* pDoc = GetDocument(); // get picture
int iBitPerPixel = pDoc->_bmp->bitsperpixel; // used to see if grayscale(8b) or RGB(24b)
int iWidth = pDoc->_bmp->width;
int iHeight = pDoc->_bmp->height;
BYTE *pImg = pDoc->_bmp->point; // pointer used to point at pixels in the image
const int area = iWidth * iHeight;
BYTE *pImg2 = new BYTE[area];
if (iBitPerPixel == 8) // Grayscale 8bit image
{
int pixel_x;
int pixel_y;
float sobel_x[3][3] =
{ { -1, 0, 1 },
{ -2, 0, 2 },
{ -1, 0, 1 } };
float sobel_y[3][3] =
{ { -1, -2, -1 },
{ 0, 0, 0 },
{ 1, 2, 1 } };
for (int x=1; x < iWidth-1; x++)
{
for (int y=1; y < iHeight-1; y++)
{
pixel_x = (sobel_x[0][0] * pImg[iWidth * (y-1) + (x-1)])
+ (sobel_x[0][1] * pImg[iWidth * (y-1) + x ])
+ (sobel_x[0][2] * pImg[iWidth * (y-1) + (x+1)])
+ (sobel_x[1][0] * pImg[iWidth * y + (x-1)])
+ (sobel_x[1][1] * pImg[iWidth * y + x ])
+ (sobel_x[1][2] * pImg[iWidth * y + (x+1)])
+ (sobel_x[2][0] * pImg[iWidth * (y+1) + (x-1)])
+ (sobel_x[2][1] * pImg[iWidth * (y+1) + x ])
+ (sobel_x[2][2] * pImg[iWidth * (y+1) + (x+1)]);
pixel_y = (sobel_y[0][0] * pImg[iWidth * (y-1) + (x-1)])
+ (sobel_y[0][1] * pImg[iWidth * (y-1) + x ])
+ (sobel_y[0][2] * pImg[iWidth * (y-1) + (x+1)])
+ (sobel_y[1][0] * pImg[iWidth * y + (x-1)])
+ (sobel_y[1][1] * pImg[iWidth * y + x ])
+ (sobel_y[1][2] * pImg[iWidth * y + (x+1)])
+ (sobel_y[2][0] * pImg[iWidth * (y+1) + (x-1)])
+ (sobel_y[2][1] * pImg[iWidth * (y+1) + x ])
+ (sobel_y[2][2] * pImg[iWidth * (y+1) + (x+1)]);
int val = (int)sqrt((pixel_x * pixel_x) + (pixel_y * pixel_y));
if(val < 0) val = 0;
if(val > 255) val = 255;
pImg2[iHeight * y + x] = val;
}
}
}

SDL2.0 screen nullptr on render of Window

Hey so I'm relatively new to the SDL library and just trying to get to grips with it.
I found a C++ conversion for Minecraft4k but it was based on SDL1.x so I'm trying to convert it to SDL2.0
At present the build is successful, but when it gets to;
plot(x, y, rgbmul(col, fxmul(br, ddist)));
It throws a read access violation exception:
screen was nullptr
This is my code;
// C++ port of Minecraft 4k JS (http://jsdo.it/notch/dB1E)
// By The8BitPimp
// See: the8bitpimp.wordpress.com
#include <SDL.h>
#include <math.h>
#include <windows.h>
#include <tchar.h>
#include "plot.h"
#include "llist.h"
const int w = 320;
const int h = 240;
SDL_Surface *screen = nullptr;
const float math_pi = 3.14159265359f;
static inline float math_sin(float x) {
return sinf(x);
}
static inline float math_cos(float x) {
return cosf(x);
}
// the texture map
int texmap[16 * 16 * 16 * 3];
// the voxel map
char map[64 * 64 * 64];
static inline int random(int max) {
return (rand() ^ (rand() << 16)) % max;
}
static inline void plot(int x, int y, int c) {
int *p = (int*)screen->pixels;
p[y * w + x] = c;
}
static void makeTextures(void) {
// each texture
for (int j = 0; j<16; j++) {
int k = 255 - random(96);
// each pixel in the texture
for (int m = 0; m<16 * 3; m++)
for (int n = 0; n<16; n++) {
int i1 = 0x966C4A;
int i2 = 0;
int i3 = 0;
if (j == 4)
i1 = 0x7F7F7F;
if ((j != 4) || (random(3) == 0))
k = 255 - random(96);
if (j == 1)
{
if (m < (((n * n * 3 + n * 81) >> 2) & 0x3) + 18)
i1 = 0x6AAA40;
else if (m < (((n * n * 3 + n * 81) >> 2) & 0x3) + 19)
k = k * 2 / 3;
}
if (j == 7)
{
i1 = 0x675231;
if ((n > 0) && (n < 15) && (((m > 0) && (m < 15)) || ((m > 32) && (m < 47))))
{
i1 = 0xBC9862;
i2 = n - 7;
i3 = (m & 0xF) - 7;
if (i2 < 0)
i2 = 1 - i2;
if (i3 < 0)
i3 = 1 - i3;
if (i3 > i2)
i2 = i3;
k = 196 - random(32) + i2 % 3 * 32;
}
else if (random(2) == 0)
k = k * (150 - (n & 0x1) * 100) / 100;
}
if (j == 5)
{
i1 = 0xB53A15;
if (((n + m / 4 * 4) % 8 == 0) || (m % 4 == 0))
i1 = 0xBCAFA5;
}
i2 = k;
if (m >= 32)
i2 /= 2;
if (j == 8)
{
i1 = 5298487;
if (random(2) == 0)
{
i1 = 0;
i2 = 255;
}
}
// fixed point colour multiply between i1 and i2
i3 =
((((i1 >> 16) & 0xFF) * i2 / 255) << 16) |
((((i1 >> 8) & 0xFF) * i2 / 255) << 8) |
((i1 & 0xFF) * i2 / 255);
// pack the colour away
texmap[n + m * 16 + j * 256 * 3] = i3;
}
}
}
static void makeMap(void) {
// add random blocks to the map
for (int x = 0; x < 64; x++) {
for (int y = 0; y < 64; y++) {
for (int z = 0; z < 64; z++) {
int i = (z << 12) | (y << 6) | x;
float yd = (y - 32.5) * 0.4;
float zd = (z - 32.5) * 0.4;
map[i] = random(16);
float th = random(256) / 256.0f;
if (th > sqrtf(sqrtf(yd * yd + zd * zd)) - 0.8f)
map[i] = 0;
}
}
}
}
static void init(void) {
makeTextures();
makeMap();
}
// fixed point byte byte multiply
static inline int fxmul(int a, int b) {
return (a*b) >> 8;
}
// fixed point 8bit packed colour multiply
static inline int rgbmul(int a, int b) {
int _r = (((a >> 16) & 0xff) * b) >> 8;
int _g = (((a >> 8) & 0xff) * b) >> 8;
int _b = (((a)& 0xff) * b) >> 8;
return (_r << 16) | (_g << 8) | _b;
}
static void render(void) {
float now = (float)(SDL_GetTicks() % 10000) / 10000.f;
float xRot = math_sin(now * math_pi * 2) * 0.4 + math_pi / 2;
float yRot = math_cos(now * math_pi * 2) * 0.4;
float yCos = math_cos(yRot);
float ySin = math_sin(yRot);
float xCos = math_cos(xRot);
float xSin = math_sin(xRot);
float ox = 32.5 + now * 64.0;
float oy = 32.5;
float oz = 32.5;
// for each column
for (int x = 0; x < w; x++) {
// get the x axis delta
float ___xd = ((float)x - (float)w / 2.f) / (float)h;
// for each row
for (int y = 0; y < h; y++) {
// get the y axis delta
float __yd = ((float)y - (float)h / 2.f) / (float)h;
float __zd = 1;
float ___zd = __zd * yCos + __yd * ySin;
float _yd = __yd * yCos - __zd * ySin;
float _xd = ___xd * xCos + ___zd * xSin;
float _zd = ___zd * xCos - ___xd * xSin;
int col = 0;
int br = 255;
float ddist = 0;
float closest = 32.f;
// for each principle axis x,y,z
for (int d = 0; d < 3; d++) {
float dimLength = _xd;
if (d == 1)
dimLength = _yd;
if (d == 2)
dimLength = _zd;
float ll = 1.0f / (dimLength < 0.f ? -dimLength : dimLength);
float xd = (_xd)* ll;
float yd = (_yd)* ll;
float zd = (_zd)* ll;
float initial = ox - floor(ox);
if (d == 1) initial = oy - floor(oy);
if (d == 2) initial = oz - floor(oz);
if (dimLength > 0) initial = 1 - initial;
float dist = ll * initial;
float xp = ox + xd * initial;
float yp = oy + yd * initial;
float zp = oz + zd * initial;
if (dimLength < 0) {
if (d == 0) xp--;
if (d == 1) yp--;
if (d == 2) zp--;
}
// while we are concidering a ray that is still closer then the best so far
while (dist < closest) {
// quantize to the map grid
int tex = map[(((int)zp & 63) << 12) | (((int)yp & 63) << 6) | ((int)xp & 63)];
// if this voxel has a texture applied
if (tex > 0) {
// find the uv coordinates of the intersection point
int u = ((int)((xp + zp) * 16.f)) & 15;
int v = ((int)(yp * 16.f) & 15) + 16;
// fix uvs for alternate directions?
if (d == 1) {
u = ((int)(xp * 16.f)) & 15;
v = (((int)(zp * 16.f)) & 15);
if (yd < 0)
v += 32;
}
// find the colour at the intersection point
int cc = texmap[u + v * 16 + tex * 256 * 3];
// if the colour is not transparent
if (cc > 0) {
col = cc;
ddist = 255 - ((dist / 32 * 255));
br = 255 * (255 - ((d + 2) % 3) * 50) / 255;
// we now have the closest hit point (also terminates this ray)
closest = dist;
}
}
// advance the ray
xp += xd;
yp += yd;
zp += zd;
dist += ll;
}
}
plot(x, y, rgbmul(col, fxmul(br, ddist)));
}
}
}
int main(int argc, char *argv[]) {
SDL_Init(SDL_INIT_EVERYTHING);
SDL_Window *screen;
screen = SDL_CreateWindow(
"Minecraft4k", // window title
SDL_WINDOWPOS_CENTERED, // initial x position
SDL_WINDOWPOS_CENTERED, // initial y position
320, // width, in pixels
240, // height, in pixels
SDL_WINDOW_OPENGL // flags - see below
);
SDL_Renderer* renderer;
renderer = SDL_CreateRenderer(screen, -1, SDL_RENDERER_ACCELERATED);
if (screen == nullptr) {
return 1;
}
init();
bool running = true;
while (running) {
SDL_Event event;
while (SDL_PollEvent(&event)) {
running &= (event.type != SDL_QUIT);
}
SDL_RenderPresent(renderer);
render();
}
SDL_DestroyWindow(screen);
SDL_Quit();
return 0;
}
When I actually run the code I do get a black screen, but the debugger lands on the line
plot(x, y, rgbmul(col, fxmul(br, ddist)));
in ;
static void render(void)
This is all just "for fun" so any information or guidance is appreciated.
You define screen twice (the first time as a global variable, the second time within your main), but you initialize it only once (within your main).
Because of that, the global variable screen actually is set to nullptr and plot fails trying to use it, as the error message states.