Pixel drawing app


(Giuseppe Costanza) #1

Hello,
I’m working at my 1st Arduboy project. I’d like to create a simple pixel drawing app where user can make pixels black or white moving the cursor.
To store the pixel vales I thought of using a 2D array with x,y coordinates of each pixel, the problem is that I run out of memory if I try to create an array with more than 200 rows. To store the value of any single pixel I would need 128x64 rows.

Can you suggest better ways of doing it?

Thanks

You find the sample code on github https://github.com/Gusepo/arduboy_1st_test/blob/master/ast%20game.c

Giuseppe


(Kea Oliver) #2

You have accidently hit upon one of the biggest constraints with the arduboy. You could store the pixel values of an array use single bits, but honestly that would still use a TON of your available memory. There are other ways to compress the data even more but they would be pretty complicated and would vary in efficiency as you draw.


(Scott) #3

Why not just write and read the pixels to/from the RAM screen buffer itself?

You can use arduboy.drawPixel() to write a pixel and arduboy.getPixel() to read one.


(Pharap) #4

This is precisely what I was going to suggest.

But don’t forget to avoid calling arduboy.clear() or it will erase anything that’s been drawn.

With games that’s usually what you want, but for a drawing program it isn’t what you want to do.


Also please remember to use .ino or .cpp rather than .c.
The Arduboy2 object is a C++ class, you can’t access it from C code.

If you’re using the Arduino IDE to compile your code then your main code file needs to be a .ino file.
(If you’re using PlatformIO then you don’t need an .ino.)


(Scott) #5

And something else to consider:

If you use this technique and want to show a cursor, you’ll have to:

  • Save (at least) the contents of the screen screen buffer area that your cursor will occupy, to another variable (array or otherwise) in RAM.
  • Draw the cursor.
  • If a pixel is to change at the cursor, change it but also save info that will tell what colour the pixel has changed to.
  • Before moving the cursor:
    • Restore the saved area back to the screen buffer.
    • If a pixel was changed, set it to the saved colour.

Repeat all the steps whenever the cursor location is changed.

Keep in mind that drawing functions only change the contents of the RAM screen buffer. To actually see the changes, you have to call arduboy.display() where appropriate. Alternatively, you can continually call arduboy.display() once at the end of each frame, whether a change has been made or not.


(Giuseppe Costanza) #6

Thanks all for your advices.
I still do not understand a couple of things:

  • if I use arduboy.drawPixel() I still need to store somehow all the coordinates of painted pixels to render them after I clear screen
  • I want the user to paint moving the cursor and clicking a button. If I avoid using arduboy.clear() the screen will get painted even if users doesn’t click the button.
    @MLXXXp are you suggesting to leave the screen painted (avoiding arduboy.clear) and subtract the cursor data everytime the user moves the arrows? Can I do it without clearing the screen?

Thanks again

Giuseppe


(Kea Oliver) #7

If you use arduboy.drawPixel() and dont clear the screen you wont need to store all the co-ordinates because you are drawing directly. You would need to write your own code to display and change the pixels for the cursor though.


(Scott) #8

That’s why @Pharap said not to clear the screen, so you don’t loose the settings of all the pixels in the screen buffer.

Yes, that’s what the steps I posted instruct you to do.

Don’t subtract the cursor data. Save the pixel data in the buffer for the area where you want to place the cursor, then draw the cursor in that area. Restore that saved data to “erase” the cursor before repeating the steps to “move” the cursor to a new location.


(Kea Oliver) #9

You will need to write your own code to handle the cursor.

So when you move the cursor you will go through a few steps.

1)write the stored pixels that were under the cursor (this will erase the cursor)
2)store the pixels the cursor will cover when it moves
3)write the pixels to draw the cursor.

When you press a button to draw a pixel you need to make sure that that pixel is changed both on screen AND in the stored pixels, so when you move your cursor and the old data is restored, your change is saved.

Thats about as clear as I can make it without examples and there are people much better than me for that.


(Scott) #10

To anticipate the next question:
How do I save the cursor area?

This method wastes a lot of RAM, because it stores only one pixel per byte, but it doesn’t require manipulating individual bits to store each pixel. For a cursor that occupies an 8 x 8 square of pixels this will use 64 bytes of RAM for saving. If you end up short on RAM, pixels could be stored in individual bits, at the expense of a larger and slower program. You could also directly access the screen buffer and save the area bytes at a time but this requires knowledge of the screen buffer layout and isn’t as portable.

constexpr int cursorSize = 8; // assumes a square cursor area

// where the pixels in the cursor area are saved
bool cursorArea[cursorSize][cursorSize];

// x and y are the coordinates of the top left corner of the cursor area
void saveCursorArea(int x, int y) {
  for (int i = 0; i < cursorSize; i++) {
    for (int j = 0; j < cursorSize; j++) {
      cursorArea[i][j] = arduboy.getPixel(x + i, y + j);
    }
  }
}

void restoreCursorArea(int x, int y) {
  for (int i = 0; i < cursorSize; i++) {
    for (int j = 0; j < cursorSize; j++) {
      arduboy.drawPixel(x + i, y + j, cursorArea[i][j]);
    }
  }
}

I haven’t actually tested this code other than making sure it compiles without errors. There could be bugs.


(Giuseppe Costanza) #11

Thanks very much!
I wasn’t expecting to get the code :slight_smile:

Anyway I was thinking of starting with 1 pixel cursor to make things easier, storing cursor x and y position in a different var before moving the arrows should make the trick.

I will post the code once I’ve done


(Scott) #12

To be able to see the position of a 1 pixel cursor, you’ll need to flash it on and off. That’s a whole new discussion. :wink:


(Kea Oliver) #13

I would recommend a cross shape, a clear center pixel (that will be the one you paint to) and one pixel above, below and to either side like a cross.


(Pharap) #14

That isn’t much of an issue.
Redrawing the screen would use extra CPU cycles,
but you won’t need many CPU cycles for a drawing program.

If it’s a problem then you can just wait until the user presses a button before calling arduboy.display().


(Scott) #15

Being able to see the cursor in all situations requires some thought. If you make it all white, it will disappear in large white areas (and similarly for all black).

You could use all white but put a black boarder around it, like a mouse pointer does. This obscures more of the actual drawn pixels underneath.

You could make the cursor the inverse of the pixels it covers (exclusive “or”). The cursor may become hard to see if the drawn pixel pattern is complex.

You could flash the cursor. This is probably the best but the hardest to accomplish.


(Giuseppe Costanza) #16

Thanks for the interesting discussion, here my progress.


Still nothing really working properly but I see potentials!

Anyway I don’t understand why it always piant white even when mouse is not pressed and some pixels become randomly black…


(Scott) #17

Line 60:

arduboy.drawPixel(ballx, ballx, BLACK);

should it be? ballx, bally
(Same for line 52?)


(Giuseppe Costanza) #18

You’re right. It is working now!


(Pharap) #19

Sorry to bring up coding style so soon but…

This:

if(arduboy.pressed(A_BUTTON)) {
  arduboy.drawPixel(ballx, bally, WHITE);
} else {
  if (cursorArea==0) {
    arduboy.drawPixel(ballx, ballx, BLACK);
 }
}

Would make more sense as:

if(arduboy.pressed(A_BUTTON)) {
  arduboy.drawPixel(ballx, bally, WHITE);
} else if (cursorArea == 0) {
  arduboy.drawPixel(ballx, ballx, BLACK);
}

And of course you can (and should) use && instead of and.
E.g.

if(arduboy.pressed(UP_BUTTON) and bally > 0) {
  bally = bally - cursorshift;
}

Could be:

if(arduboy.pressed(UP_BUTTON) && bally > 0) {
  bally = bally - cursorshift;
}

I can think of a handful of other improvements,
but I don’t want to overload you with suggestions so soon.


The srand(7/8); part gives away that you’ve been reading Crait’s tutorials.

You might want to read this thread:


(Giuseppe Costanza) #20

Thanks for the advices, why && is better than “and”?
Does it perform better or is a matter of readability?

Regarding srand I forgot to canceling it from the original code, I guess I don’t need a random function in my code…

Any idea on how to make the blinking cursor? Should I use a timeout or infinite loop.