0
$\begingroup$

I'm trying to create a simple painting application. And I was wondering about an efficient way to draw pixels on the screen.

I have a pixel struct:

struct Pixel {
 unsigned char r, g, b, a;
};

And I have two methods for converting between pixel coordinates and normalized pixel coordinates. w and h are image width and height respectively.

int getPixelIndex(float2 norm, int w, int h) {
   return (int)(norm.x*h)*w + norm.y*h;
}

float2 getPixelNormCoords(int index, int w, int h) {
   float2 norm;
   norm.x = (index % w) / (float)(w-1);
   norm.y = (index / h) / (float)(h-1);
   return norm;
}

Now if I want to draw a red pixel under the cursor I could do it like this:

int pixelIndex = getPixelIndex(cursorNorm.x, cursorNorm.y, w, h);

pixels[pixelIndex] = {255, 0, 0, 255};

However what if I want to fill more pixels around the cursor, to create different brushes. Say I want to create a square brush with a given offset d. One hacky way to do this would be:

float delta = 10e-4;
for (float i = -d; i < d; i+=delta) {
  for (float j = -d; j < d; j+=delta) {
    int pixelIndex = getPixelIndex(cursorNorm.x+i, cursorNorm.y+j, w, h);
    pixels[pixelIndex] = {255, 0, 0, 255};
  }
}

Another way would be to iterate over each pixel:

for (int i = 0; i < size(pixels); i++) {
   float2 norm = getPixelNormCoords(i, w, h);

   if (fabs(norm.x-cursorNorm.x) < d && fabs(norm.y-cursorNorm.y) < d)
      pixels[i] = {255, 0, 0, 255};
}

However this way seems to be extremely costly.

Are there more efficient and less hacky methods to accomplish what I'm trying to do?

$\endgroup$
1
$\begingroup$

I don't know, why you consider your first approach hacky, but an alternative would be to analyze the shape of your brush and how it interacts with your underlying data structure. For example for your rectangle:

Let's assume the brush's sides are parallel to the sides of the image. Then you can calculate how man pixels are covered by your offset d. Let's call be the number of pixels between two corners n. Then you can determine the index of the upper left pixel of your rectangle and fill the next n indices since they represent the left border of your rectangle. Then add h to the index of the upper left point to get to the pixel right of it. Fill the next n pixels. Repeat this n times and you have finished filling your rectangle. Here is some untested code that should demonstrate the approach:

float left_x = cursorNorm.x - d;
float upper_y = cursorNorm.y - d;
float lower_y = cursorNorm.y + d;

int index_upper_left = getPixelIndex(left_x, upper_y, w, h);
int index_lower_left = getPixelIndex(left_x, lower_y, w, h);

int n = index_lower_left - index_upper_left;

for (int i=0; i < n; ++i)
    for (int j=0; j < n; ++j)
        int index = index_upper_left + j*h + i
        pixels[index] = {255, 0, 0, 255};

Since this is all symmetric, you can vary this to start at the center point, run i and j just up to n/2 and do something like that:

for (int i=0; i < n/2; ++i)
    for (int j=0; j < n/2; ++j)
        int index_left = index_center - j*h
        int index_right = index_center + j*h

        pixels[index_left + i] = {255, 0, 0, 255};
        pixels[index_left - i] = {255, 0, 0, 255};
        pixels[index_right + i] = {255, 0, 0, 255};
        pixels[index_right - i] = {255, 0, 0, 255};

More complex shapes like circles get a little bit more complicated but you can calculate for each affected column of pixels the index of the upper and lower affected pixel and fill between them. Remember that you can abuse symmetry here. Meaning if you found the upper pixel of a column, the lower pixel has the same y distance to the y-value of the center point. Additionally, the bounds to the left and to the right of the center are also identical. A helpful algorithm to find the bounds might be Bresenham’s circle drawing algorithm.

| improve this answer | |
$\endgroup$
  • $\begingroup$ Thanks this is very helpful! $\endgroup$ – Lenny White Aug 2 at 14:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.