[Demo]Bitmap mask drawmode

concept on making a composite draw mode that takes 2 screen buffers and combines them with a 1bit buffer

might be fun for screen transitions, ghostly sprites and other effects
not sure wich mode to use this on since it doubles the framebuffer, mode15 is not possible, any suggestions?

2 Likes

Why not do the masking when drawing the images?

uhm please clarify drawing the image to what? the buffer or the screen?

masking directly to the buffer would be possible though complex having to do it one every sprite that you want it to be applied to also during that you shouldn’t modify the mask or it will lead to weird masking… though that might be something you want to do

Use a 1-bit mask. That is a lot smaller

When drawing images to the buffer.

You could probably achieve the same effect by masking the images when drawing them to the buffer.

To achieve the effect demonstrated in the image you posted you could have have a full-screen mask that’s used as you draw and then you could invert the mask to draw the other image.

So basically instead of drawing to two buffers and then masking, you draw to a single buffer with a mask and then invert the mask before drawing the other parts.
It could use 1/8th of the memory of having two buffers (though it would be a bit slower).

im already lost at to what im doing the 1bit mask is trowing me off, its probebly somewhere in the pokitto libs but cant find any code i understand

this isnt working but im not sure where im going wrong

static uint8_t mask[14*88];
#define MASKWIDTH 110
#define MASKHEIGHT 88
void setMaskPixel(int8_t x, int8_t y, bool white){
    if((x < 0) || (x >= MASKWIDTH) || (y < 0) || (y >= MASKHEIGHT))return;
    
    if(white) mask[x + (y / 8) * MASKWIDTH] = mask[x + (y / 8) * MASKWIDTH]&((uint8_t)y % 8);
    else mask[x + (y / 8) * MASKWIDTH] = mask[x + (y / 8) * MASKWIDTH]|((uint8_t)y % 8);
}
bool getMaskPixel(int8_t x, int8_t y){
    if((x < 0) || (x >= MASKWIDTH) || (y < 0) || (y >= MASKHEIGHT))return 0;
    return (mask[x + (y / 8) * MASKWIDTH] >> ((uint8_t)y% 8)) & 0x1;
}

For now, forget all this manual manipulation, use std::bitset.

(I’m assuming you’re using the online compiler and thus don’t have access to C++11.)

const std::size_t BufferWidth = 110;
const std::size_t BufferHeight = 88;

static std::bitset<(110 * 88) / 8> mask;

inline std::size_t getIndex(int x, int y)
{
	return (y * BufferWidth) + x;
}

inline void setMaskPixel(int x, int y, bool value)
{
    if((x < 0) || (x >= BufferWidth) || (y < 0) || (y >= BufferHeight)) return;
	mask.set(getIndex(x, y), value);
}

inline bool getMaskPixel(int x, int y)
{
    if((x < 0) || (x >= BufferWidth) || (y < 0) || (y >= BufferHeight)) return false;
	return mask[getIndex(x, y)];
}

It should be fast enough.
If it isn’t then you can come back and replace it later.

1 Like

im getting a softlock wen calling setMaskPixel with your code, no errors

Ah, I think the problem is the index mapping.

Try changing getIndex to:

inline std::size_t getIndex(int x, int y)
{
	return (x * (BufferHeight / 8)) + y;
}

(And also make sure you aren’t manipulating mask's data elsewhere without using getIndex as a proxy for the index.)

If the Pokitto had exceptions or I knew what std::terminate does on Pokitto, I’d recommend swapping mask[getIndex(x, y)]; with mask.test(getIndex(x, y));, which would do bounds checking and throw an exception upon error (or without exceptions, call std::terminate… or possibly std::abort).


std::abort, std::terminate, std::exit
The standards committee makes some interesting decisions.

@Pharap thanks for helping out im not sure how these bitset work
but still hiting odd behavior, just testing if the mask worked gave me this

#include "Pokitto.h"

const std::size_t BufferWidth = 110;
const std::size_t BufferHeight = 88;

static std::bitset<(110 * 88) / 8> mask;

inline std::size_t getIndex(int x, int y)
{
    return (x * (BufferHeight / 8)) + y;
}

inline void setMaskPixel(int x, int y, bool value)
{
    if((x < 0) || (x >= BufferWidth) || (y < 0) || (y >= BufferHeight)) return;
    mask.set(getIndex(x, y), value);
}

inline bool getMaskPixel(int x, int y)
{
    if((x < 0) || (x >= BufferWidth) || (y < 0) || (y >= BufferHeight)) return false;
    return mask[getIndex(x, y)];
}

Pokitto::Core game;

int main(){
    game.begin();
    setMaskPixel(30,30,true);
    while(game.isRunning()){
        if(game.update()){
            for(int x =0; x<110; x++){
                for(int y =0; y<88; y++){
                    if(getMaskPixel(x,y))game.display.drawPixel(x,y,5);
                }
            }
        }
    }
}

Oh, hang on. I’ve been getting a bit muddled. Sorry.
I’m trying to do too much at once again, ended up writing a hybrid of the bitset code and the raw-bitset approach.

static std::bitset<110 * 88> mask;

inline std::size_t getIndex(int x, int y)
{
	return (y * BufferWidth) + x;
}

inline void setMaskPixel(int x, int y, bool value)
{
    if((x < 0) || (x >= BufferWidth) || (y < 0) || (y >= BufferHeight)) return;
    mask.set(getIndex(x, y), value);
}

inline bool getMaskPixel(int x, int y)
{
    if((x < 0) || (x >= BufferWidth) || (y < 0) || (y >= BufferHeight)) return false;
    return mask[getIndex(x, y)];
}

This time for real (probably).

I’m also trying to thread some wires and write both Arduino and Pokitto code at the same time, but I’m reasonably convinced those will all work.

Also if you don’t need to handle negative arguments then you could use std::size_t instead of int.

1 Like

ok got it working and im trying to class it
got a wierd question can i inherite from PokittodDisplay and overide drawPixel to gain all the draw functions?

can i pull or remove/overide functions i might not need, idk how this works in memory

You can’t override the methods, they’re all static.
Besides, that wouldn’t work for things like drawBitmapData, which writes directly to the buffer in 4-bpp modes.

thats why im asking if i can pull those off or both inherite from a base draw class or somthing i just need to replace DrawPixel and all the function code is identical wich feels inaficiant to copy this code

How identical?
We’d have a better idea of what you’re trying to do if you just dumped your failed attempt(s) here.

Copying code isn’t inherantly bad if you’re completely replacing the other function.
A function that isn’t used doesn’t get compiled.

AFAIK, what you have to do is to fork the lib and make your changes directly in PokittoDisplay. Classes with static methods don’t follow the open-closed principle and that’s what’s getting in your way here (unless C++>=11 brought some feature in that area that I’m not aware of).

not realy failed but just tedius to pull out what i need though there was a more elegant solution with inheritance (i seriusly never get to use this feature do i?)

heres my current class couldent resolve the bitset so opted for defines instead as the screen should probebly not change size but you could potetialy have more masks for doing whatever you want

#include <bitset>
#define MASKWIDTH 110
#define MASKHEIGHT 88
class mask{//: public Pokitto::Display{
    private:
        inline std::size_t getIndex(int x, int y){return (y * BufferWidth) + x;}
        const std::size_t BufferWidth = MASKWIDTH;
        const std::size_t BufferHeight = MASKHEIGHT;
        std::bitset<(MASKWIDTH * MASKHEIGHT)> BufferMask;
        
        uint8_t clipLine(int16_t *x0, int16_t *y0, int16_t *x1, int16_t *y1);
        void drawFastVLine(int16_t x, int16_t y, int16_t h);
        void drawFastHLine(int16_t x, int16_t y, int16_t w);
        void drawColumn(int16_t x, int16_t sy, int16_t ey);
        void drawRow(int16_t x0, int16_t x1, int16_t y);
    public:
        
        void setPixel(int x, int y, bool value){
            if((x < 0) || (x >= BufferWidth) || (y < 0) || (y >= BufferHeight)) return;
            BufferMask.set(getIndex(x, y), value);
        }
        void drawPixel(int16_t x,int16_t y) {setPixel(x,y, true);}
        bool getPixel(int x, int y){
            if((x < 0) || (x >= BufferWidth) || (y < 0) || (y >= BufferHeight)) return false;
            return BufferMask[getIndex(x, y)];
        }
        void clear(){BufferMask.reset();};
        void drawCircle(int16_t x0, int16_t y0, int16_t r);
        
        
        void drawTriangle(int16_t x0, int16_t y0,int16_t x1, int16_t y1,int16_t x2, int16_t y2);
        void drawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1);
        void fillTriangle(int16_t x0, int16_t y0,int16_t x1, int16_t y1,int16_t x2, int16_t y2);
        
};

void mask::drawCircle(int16_t x0, int16_t y0, int16_t r) {
    int16_t f = 1 - r;
    int16_t ddF_x = 1;
    int16_t ddF_y = -2 * r;
    int16_t x = 0;
    int16_t y = r;

    setPixel(x0, y0 + r ,true);
    setPixel(x0, y0 - r ,true);
    setPixel(x0 + r, y0 ,true);
    setPixel(x0 - r, y0 ,true);

    while (x < y) {
        if (f >= 0) {

            y--;
            ddF_y += 2;
            f += ddF_y;
        }
        x++;
        ddF_x += 2;
        f += ddF_x;

        setPixel(x0 + x, y0 + y ,true);
        setPixel(x0 - x, y0 + y ,true);
        setPixel(x0 + x, y0 - y ,true);
        setPixel(x0 - x, y0 - y ,true);
        setPixel(x0 + y, y0 + x ,true);
        setPixel(x0 - y, y0 + x ,true);
        setPixel(x0 + y, y0 - x ,true);
        setPixel(x0 - y, y0 - x ,true);

    }
}

void mask::drawTriangle(int16_t x0, int16_t y0,
        int16_t x1, int16_t y1,
        int16_t x2, int16_t y2) {
    drawLine(x0, y0, x1, y1);
    drawLine(x1, y1, x2, y2);
    drawLine(x2, y2, x0, y0);
}
void mask::drawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1) {
    if ((uint16_t)x0 >= BufferWidth || (uint16_t)y0 >= BufferHeight || (uint16_t)x1 >= BufferWidth || (uint16_t)y1 >= BufferHeight ) {
        if (clipLine (&x0,&y0,&x1,&y1)==0) return; // line out of bounds
    }
    if (x0 == x1)
        drawColumn(x0,y0,y1);
    else if (y0 == y1)
        drawRow(x0,x1,y0);
    else {
        int e;
        signed int dx,dy,j, temp;
        signed char s1,s2, xchange;
        signed int x,y;

        x = x0;
        y = y0;

        //take absolute value
        if (x1 < x0) {
            dx = x0 - x1;
            s1 = -1;
        }
        else if (x1 == x0) {
            dx = 0;
            s1 = 0;
        }
        else {
            dx = x1 - x0;
            s1 = 1;
        }

        if (y1 < y0) {
            dy = y0 - y1;
            s2 = -1;
        }
        else if (y1 == y0) {
            dy = 0;
            s2 = 0;
        }
        else {
            dy = y1 - y0;
            s2 = 1;
        }

        xchange = 0;

        if (dy>dx) {
            temp = dx;
            dx = dy;
            dy = temp;
            xchange = 1;
        }

        e = ((int)dy<<1) - dx;

        for (j=0; j<=dx; j++) {
            drawPixel(x,y);

            if (e>=0) {
                if (xchange==1) x = x + s1;
                else y = y + s2;
                e = e - ((int)dx<<1);
            }
            if (xchange==1)
                y = y + s2;
            else
                x = x + s1;
            e = e + ((int)dy<<1);
        }
    }
}
uint8_t mask::clipLine(int16_t *x0, int16_t *y0, int16_t *x1, int16_t *y1){
    // Check X bounds
    if (*x1 < *x0) {
        //std::swap (*x1,*x0); // swap so that we dont have to check x1 also
        swapWT(int16_t*, x1, x0);
        //std::swap (*y1,*y0); // y needs to be swaaped also
        swapWT(int16_t*, y1, y0);
    }

    if (*x0 >= BufferWidth) return 0; // whole line is out of bounds

    // Clip against X0 = 0
    if (*x0 < 0) {
        if (*x1 < 0) return 0; // nothing visible
        int32_t dx = (*x1 - *x0);
        int32_t dy = ((*y1 - *y0) << 16); // 16.16 fixed point calculation trick
        int32_t m = dy/dx;
        *y0 = *y0 + ((m*-*x0) >> 16); // get y0 at boundary
        *x0 = 0;
    }

    // Clip against x1 >= width
    if (*x1 >= BufferWidth) {
        int32_t dx = (*x1 - *x0);
        int32_t dy = ((*y1 - *y0) << 16); // 16.16 fixed point calculation trick
        int32_t m = dy / dx;
        *y1 = *y1 + ((m * ((BufferWidth - 1) - *x1)) >> 16); // get y0 at boundary
        *x1 = BufferWidth-1;
    }

    // Check Y bounds
    if (*y1 < *y0) {
        //std::swap (*x1,*x0); // swap so that we dont have to check x1 also
        swapWT(int16_t*, x1, x0);
        //std::swap (*y1,*y0); // y needs to be swaaped also
        swapWT(int16_t*, y1, y0);
    }

    if (*y0 >= BufferHeight) return 0; // whole line is out of bounds

    if (*y0 < 0) {
        if (*y1 < 0) return 0; // nothing visible
        int32_t dx = (*x1 - *x0) << 16;
        int32_t dy = (*y1 - *y0); // 16.16 fixed point calculation trick
        int32_t m = dx / dy;
        *x0 = *x0 + ((m * -(*y0)) >> 16); // get x0 at boundary
        *y0 = 0;
    }

    // Clip against y1 >= height
    if (*y1 >= BufferHeight) {
        int32_t dx = (*x1 - *x0) << 16;
        int32_t dy = (*y1 - *y0); // 16.16 fixed point calculation trick
        int32_t m = dx / dy;
        *x1 = *x1 + ((m * ((BufferHeight - 1) - *y1)) >> 16); // get y0 at boundary
        *y1 = BufferHeight-1;
    }
    return 1; // clipped succesfully
}
void mask::drawColumn(int16_t x, int16_t sy, int16_t ey){
    if ((uint16_t)sy>=BufferHeight && (uint16_t)ey>=BufferHeight) return; //completely out of bounds
    if ((uint16_t)x>=BufferWidth) return; //completely out of bounds
    if (sy>ey) {
            int y=sy;
            sy=ey;
            ey=y; // swap around so that x0 is less than x1
    }
    for (int y=sy; y <= ey; y++) {
        drawPixel(x,y);
    }
}

void mask::drawRow(int16_t x0, int16_t x1, int16_t y){
    if ((uint16_t)x0>=BufferWidth && (uint16_t)x1>=BufferWidth) return; //completely out of bounds
    if ((uint16_t)y>=BufferHeight) return; //completely out of bounds

    if (x0>x1) {
            int x=x0;
            x0=x1;
            x1=x; // swap around so that x0 is less than x1
    }
    for (int x=x0; x <= x1; x++) {
        drawPixel(x,y);
    }
}
void mask::fillTriangle(int16_t x0, int16_t y0,
        int16_t x1, int16_t y1,
        int16_t x2, int16_t y2) {
    int16_t a, b, y, last;

    // Sort coordinates by Y order (y2 >= y1 >= y0)
    if (y0 > y1) {
        swapWT(int16_t,y0, y1);
        swapWT(int16_t,x0, x1);
    }
    if (y1 > y2) {
        swapWT(int16_t,y2, y1);
        swapWT(int16_t,x2, x1);
    }
    if (y0 > y1) {
        swapWT(int16_t,y0, y1);
        swapWT(int16_t,x0, x1);
    }

    if (y0 == y2) { // Handle awkward all-on-same-line case as its own thing
        a = b = x0;
        if (x1 < a) a = x1;
        else if (x1 > b) b = x1;
        if (x2 < a) a = x2;
        else if (x2 > b) b = x2;
        drawFastHLine(a, y0, b - a + 1);
        return;
    }

    int16_t
    dx01 = x1 - x0,
            dy01 = y1 - y0,
            dx02 = x2 - x0,
            dy02 = y2 - y0,
            dx12 = x2 - x1,
            dy12 = y2 - y1,
            sa = 0,
            sb = 0;

    // For upper part of triangle, find scanline crossings for segments
    // 0-1 and 0-2.  If y1=y2 (flat-bottomed triangle), the scanline y1
    // is included here (and second loop will be skipped, avoiding a /0
    // error there), otherwise scanline y1 is skipped here and handled
    // in the second loop...which also avoids a /0 error here if y0=y1
    // (flat-topped triangle).
    if (y1 == y2) last = y1; // Include y1 scanline
    else last = y1 - 1; // Skip it

    for (y = y0; y <= last; y++) {
        a = x0 + sa / dy01;
        b = x0 + sb / dy02;
        sa += dx01;
        sb += dx02;
        /* longhand:
        a = x0 + (x1 - x0) * (y - y0) / (y1 - y0);
        b = x0 + (x2 - x0) * (y - y0) / (y2 - y0);
         */
        if (a > b) swapWT(int16_t,a, b);
        drawFastHLine(a, y, b - a + 1);
    }

    // For lower part of triangle, find scanline crossings for segments
    // 0-2 and 1-2.  This loop is skipped if y1=y2.
    sa = dx12 * (y - y1);
    sb = dx02 * (y - y0);
    for (; y <= y2; y++) {
        a = x1 + sa / dy12;
        b = x0 + sb / dy02;
        sa += dx12;
        sb += dx02;

        if (a > b) swapWT(int16_t,a, b);
        drawFastHLine(a, y, b - a + 1);
    }
}
void mask::drawFastVLine(int16_t x, int16_t y, int16_t h){
    if (h<0) {y += h; h = -h;}
    drawColumn(x,y,y+h);
}

void mask::drawFastHLine(int16_t x, int16_t y, int16_t w){
    if (w<0) {x += w; w = -w;}
    drawRow(x,x+w-1,y);
}

also what the hecks up with drawFastHLine and drawRow its basicly calling drawRow the check seems minimal idk if its even needed

Every tool has a purpose and a cost. Virtual functions are no different.
They have a runtime cost (which on the Pokitto is actually quite tiny) and they have a memory cost.

The Pokitto library just isn’t designed to have display modes implemented through inheritance.
(It could, but there would be tradeoffs for doing so.)

If you were trying to use a const variable in the angle brackets then no, that wouldn’t have worked.
The expression in the angle brackets of a template type must be a constant expression (i.e. as of C++11, constexpr).
const variables are not constant expressions.


I’m slightly confused about what your goal is here.

I thought the objective was to create a display mode where the graphics were masked?
But it looks like this class it trying to write to the bitset as if the bitset was the screen buffer.

Are you planning to make an interface for ‘drawing’ onto the mask as well?

working on drawing sprites to the display buffer now, the hope is to be atleast a little bit mode agnostic (atleast lowres16 and highres16 can get suported atm)