Pixel drawing app


(Scott R) #21

You could use everyXFrames () to draw the cursor.

I’m sure @Pharap will have a better technical solution though :stuck_out_tongue:


(Scott) #22

As @Keyboard_Camper said, I’d use arduboy.everyXFrames() to flip the cursor at the desired rate. If you set the frame rate to 30 FPS, to change the cursor colour every ½ second you would do it every 15 frames.

I’d keep a global variable for the cursor colour and use it in a function to draw the cursor.

#include <Arduboy2.h>

Arduboy2 arduboy;

byte cursorColour;

void setup() {
  arduboy.begin();
  arduboy.setFrameRate(30);
  arduboy.clear();
}

void loop() {
  //Prevent the Arduboy from running too fast
  if(!arduboy.nextFrame()) {
    return;
  }

  int x = 5, y = 10; // set a fixed x, y location just for testing
  
  if (arduboy.everyXFrames(15)) {
    cursorColour = cursorColour == BLACK ? WHITE : BLACK;
    drawCursor(x, y, cursorColour);
  }

  // rest of loop() code

  arduboy.display();
}

void drawCursor(int x, int y, byte colour) {
  // draw whatever cursor shape is desired at the coordinates
  // given in the arguments and with the given colour

  // For example:
  arduboy.drawLine(x, y - 3, x, y - 1, colour);
  arduboy.drawLine(x, y + 1, x, y + 3, colour);
  arduboy.drawLine(x - 3, y, x - 1, y, colour);
  arduboy.drawLine(x + 1, y, x + 3, y, colour);
}

(Giuseppe Costanza) #23

I’m almost there, but still there’s something broken.
The blinking is somehow covered by an always white pixel and the cursor get sometime painted even when Button A is not pressed.
Code here https://github.com/Gusepo/arduboy_1st_test/blob/master/1stgame.ino


(Scott) #24

I don’t see you using the justpressed variable for anything useful and I’m not sure what you intend to use it for. If you want to detect a button press once until it’s released, the Arduboy2 library has a justPressed() function which is aided by the pollButtons() function.

You call pollButtons() at the start of the loop() after the nextFrame() test. You can then use justPressed() to detect a button press only once.

If you use the pressed() function, it will return true for as long as the button is held down, which may be over multiple frames.


(Pharap) #25

It’s mainly about what’s common practice.
&& is the most commonly used variant, to the point that most C++ programmers have probably never even encountered and.

and (and its relatives, e.g. or) are actually caleld the alternative operator representations because they are intended to be alternatives for when &&, || etc are not available for technical reasons.
Those alternative operators only exist because a long time ago there were computers that didn’t have certain symbols (e.g. &, |, ^) on their keyboard or possibly in their text encoding.

Technically a framerate independent solution would be best,
but considering the circumstances that’s probably going to be both too much of a learning curve and not worth the effort (it’s a blinking cursor, not the physics of a virtual world),
so in this case I think arduboy.everyXFrames is a suitable option.

loop is automatically an infinite loop.
When the loop function ends, it starts from the beginning again.

Using everyXFrame would be good enough for the timing,
but the complicated bit would be saving and restoring part of the screen buffer.


(Scott) #26

Technically it’s not. Unless you specifically write loop() to never exit (which isn’t a good idea), it only performs its function once. loop() keeps starting from the beginning only because main() (which is “hidden” in the Arduino “O/S”) calls it repeatedly.

And note that because of this, you have to be careful with any local variables created in the loop() function. They will be destroyed and cease to exist each time loop() executes from the beginning. If you create a local variable near the start of loop(), then set a value in it somewhere later, it will no longer contain that value when loop() starts again. It will actually be a new instance of that variable.


(Scott) #27

Which I’ve already written example code for.


(Pharap) #28

If you don’t write your own main, the function will indefinitely be called again after exiting,
hence it’s automatic in the sense that it’s the default behaviour for the environment.
The programmer has to add something extra to change the behaviour.

That’s true of any function.

It’s not the actual copying that’s the difficult part,
it’s the when to save and restore that’s difficult.


(Scott) #29

Not really. You restore before moving the cursor to a new location then save the new area that you’re going to move to before drawing the cursor in the new position.


(Scott R) #30

@Giuseppe I’m not sure if it’s been pointed out but I noticed your git you are using the older Arduboy library rather than the newer Arduboy2 library.


(Pharap) #31

That only covers a non-blinking cursor though.
The blinking complicates matters.

You have to factor in whether or not the cursor was blinking before moving and how to handle the A button being pressed (i.e. whether to draw to the screen, or the buffer, or both.)


(Pharap) #32

I’ve made a basic prototype.
Hopefully all of this makes sense,
but if there’s anything you don’t understand yet then don’t hesitate to ask.


(Giuseppe Costanza) #33

Thanks! I’m studying it.
I don’t understand what’s happens inside

if(arduboy.everyXFrames(15))

I will try to modify to have just 1 pixel cursor blinking and I’d like also to allow to keep A button clicked and draw. I will try in the weekend and keep everyone posted.


(Giuseppe Costanza) #34

I just found something similar here https://github.com/ccaroon/arduino/blob/master/arduboy/sketch-a-sketch/src/main.ino

But I have to say that controls are not so intuitive


(Pharap) #35

This is the part that allows the cursor to ‘blink’/‘flicker’.

arduboy.everyXFrames(15) is true on every 15th frame.
By default the framerate is 60 fps, 60 / 15 = 4, so it’s true every 1/4 of a second.
Hence the code inside that block (from line 195 to 207) is only run once every 1/4 of a second.

When the code is run, the code checks if the cursor is active cursorActive.

If the cursor is active (i.e. the cursor has been drawn on the screen) then restoreCursorArea is called, which draws the saved data in cursorBuffer onto the screen, overwriting the cursor.

If the cursor is not active (i.e. the screen shows what the user has drawn, the cursor has not been drawn yet) then saveCursorArea is called, which saves the pixels on screen to the cursorBuffer, and then the cursor is drawn on the screen (and cursorActive is set to true).

If you are going to modify this code then you will only need to modify saveCursorArea and restoreCursorArea.

I presume you are doing this as a learning excercise?
If so I won’t tell you any more, I will let you figure it out.
(But if you get stuck, we will be happy to answer any questions.)

I’ve never seen this before.
I’m guessing it’s very old because it uses the original Arduboy library (instead of Arduboy2).


(Giuseppe Costanza) #36

Thanks, yes I’m doing it for learning / fun.
I have in mind a simple drawing or “coloring” game.


(hartmann1301) #37

Hi, I once wrote a paint app for my arduboy like thing. It has different brushes and tool and ended up being a bit hard to understand, but the simple painting is really easy if you do it right.

I think @MLXXXp already said it, the key is using invert twice. This way you do not have to save and restore the pixels where you paint your brush at.

Personally I do not recomment a one pixel brush because it is very hard to see. I also do not like a blinking cursor because it is totally anoying. The following code should work with any kind of bursh and for blinking uncomment the marked lines in the code.

What it does:
BattleTankExample2

I know there are already some code examples, but I think this is a bit better, easier and shorter:

#include <Arduboy2.h>
Arduboy2 arduboy;

// 'brush', 7x7px this should look a bit like a pen
const uint8_t brush [] PROGMEM = { 0x70, 0x48, 0x5c, 0x3e, 0x1f, 0x0e, 0x04 };

// int16_t so I do not have to care about overflows when using constrain()
int16_t xPos = 0;
int16_t yPos = 0;

void invertBrush() {
  arduboy.drawBitmap(xPos, yPos, brush, 7, 7, INVERT);
}

void initScreen() {
  arduboy.fillScreen(WHITE);
}

void moveBrush() {
  if (arduboy.pressed(UP_BUTTON))
    yPos--;

  if (arduboy.pressed(DOWN_BUTTON))
    yPos++;

  if (arduboy.pressed(LEFT_BUTTON))
    xPos--;

  if (arduboy.pressed(RIGHT_BUTTON))
    xPos++;

  xPos = constrain(xPos, 0, WIDTH - 1);
  yPos = constrain(yPos, -7, HEIGHT - 1);
}

void paint() {
  if (arduboy.pressed(A_BUTTON))
    arduboy.drawPixel(xPos, yPos + 7, BLACK);

  if (arduboy.pressed(B_BUTTON))
    initScreen();
}

void setup() {
  arduboy.begin();
  arduboy.setFrameRate(20); // the more frame the faster the brush moves

  initScreen(); // clear the screen the first time

  invertBrush(); // draw the brush at the current postition to the screen
}

void loop() {
  if (!arduboy.nextFrame())
    return;

  invertBrush(); // erase the brush at the last position

  moveBrush(); // now you can move the brush

  paint(); // here happens all the painting and erasing

  invertBrush(); // draw the brush at the new position

  // if you like your brush blinking uncomment this and the next
  //if (arduboy.everyXFrames(2))
  //  invertBrush();
 
  arduboy.display();

  //if (arduboy.everyXFrames(2))
  //  invertBrush();
}

(Giuseppe Costanza) #38

This is really nice, thanks for sharing.


(Giuseppe Costanza) #39

Can you explain what happen here?
What’s “constrain”?

xPos = constrain(xPos, 0, WIDTH - 1);


(Scott) #40

It constrains a value to a given range if it falls outside that range
https://www.arduino.cc/reference/en/language/functions/math/constrain/