[Demo]Bitmap mask drawmode

You can’t really make your drawing functions ‘agnostic’ unless you’re willing to write a template class.
In which case the class would be an adaptor that acts as an intermediary for the display mode.

I’m not sure how well that would work though.

i mean im just trying to keep in mind most of the modes and making desicions that will make it easier down the line to add suport for a mode like the defined buffer size and drawing with display.drawPixel

Are you trying to write a new mode or is this for a specific project?

If you want to get this working for a specific project, it’s better to just worry about that project first rather than worrying about how it might integrate with the rest of the library.

i did it! masking the spriteand drawing it ontop another one just to show it works
maskedDraw.bin (50.0 KB)

heres all the code of this sample
main.cpp (3.2 KB)
mask.h (10.1 KB)

the class is a bit messy and its calling global game from main.cpp wich is a little anoying, but would probebly be resolved if this became part of pokitto library

2 Likes

That’s a very cool demo.

In my opinion could be very useful and should be added to the PokittoLib.

EDIT: have a badge!

https://talk.pokitto.com/uploads/default/original/2X/1/11bc4475754bd38c9a9a88b24fb702ea120d35ed.png

3 Likes

yea totaly, but it needs some work, and defenetly a better programer to spot the unoptimized systems and adding features atm i only got circles, triangles and filled triangles

heres another test emulating a pond of water and ripples
waterTest.bin (52.6 KB)

maybe this one shows it off better
waterTest2.bin (52.9 KB)

2 Likes

That’s very cool!

(you can’t really see it in the video below, but the water is “rippling”)

I can see a lot of use for this masked draw feature

1 Like

i was wondering if anyone can review the code to see where it could be better or more optimised what shapes should be really added to it or any other stuff

I templated it and started reorgansing some of the stuff but then I reliased most of what I was reorganising was probably already like that in the library.

mask.h:

#pragma once

#include <bitset>
#include <cstdint>

template< std::size_t WidthValue, std::size_t HeightValue >
class Mask
{
public:
	// Todo: C++11 - constexpr
	static const std::size_t Width = WidthValue;
	static const std::size_t Height = HeightValue;
	static const std::size_t BufferSize = WidthValue * HeightValue;
	
private:
	// Todo: C++11 - use BufferSize 
	std::bitset<WidthValue * HeightValue> maskBuffer;
	
	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);
	
	// Todo: C++11 - constexpr
	inline std::size_t getIndex(std::size_t x, std::size_t y)
	{
		return (y * Width) + x;
	}
	
public:

	void setPixel(int x, int y, bool value)
	{
		if((x < 0) || (x >= Width) || (y < 0) || (y >= Height))
			return;
			
		maskBuffer.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 >= Width) || (y < 0) || (y >= Height))
			return false;
			
		return maskBuffer[getIndex(x, y)];
	}
	
	void clear()
	{
		maskBuffer.reset();
	}
	
	void drawPixelMasked(int x, int y, uint8_t col)
	{
		if(getPixel(x, y))
			Pokitto::Display::drawPixel(x, y, col);
	}

	void drawSprite(int16_t x, int16_t y, const uint8_t * bitmap);

	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::drawSprite(int16_t x, int16_t y, const uint8_t *bitmap)
{
	uint8_t w = *bitmap;
	uint8_t h = *(bitmap + 1);
	bitmap = bitmap + 2; //add an offset to the pointer to start after the width and height
	int16_t i, j;
	//int8_t byteNum, bitNum, byteWidth = (w + 7) >> 3;

	
	for (j = 0; j < h; j+=1) {
		for (i = 0; i < w; i+=2) {
			uint16_t col = *bitmap>>4; //higher nibble
			drawPixelMasked(x + i, y + j,col);
			col = *bitmap & 0xF; // lower nibble
			drawPixelMasked(x + i + 1, y + j,col);
			bitmap++;
		}
	}
}

////////////////////////////////////////////////
//////////////////draw shapes //////////////////
////////////////////////////////////////////////

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 >= Width || (uint16_t)y0 >= Height || (uint16_t)x1 >= Width || (uint16_t)y1 >= Height ) {
		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 >= Width) 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 >= Width) {
		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 * ((Width - 1) - *x1)) >> 16); // get y0 at boundary
		*x1 = Width-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 >= Height) 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 >= Height) {
		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 * ((Height - 1) - *y1)) >> 16); // get y0 at boundary
		*y1 = Height-1;
	}
	return 1; // clipped succesfully
}

void Mask::drawColumn(int16_t x, int16_t sy, int16_t ey)
{
	if ((uint16_t)sy>=Height && (uint16_t)ey>=Height)
		return; //completely out of bounds
		
	if ((uint16_t)x>=Width)
		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)
{
	// Completely out of bounds
	if ((uint16_t)x0 >= Width && (uint16_t)x1 >= Width)
		return; 
		
	// Completely out of bounds
	if ((uint16_t)y >= Height)
		return;

	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);
	}

	// Handle awkward all-on-same-line case as its own thing
	if (y0 == y2)
	{ 
		a = x0;
		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;
	int16_t dy01 = y1 - y0;
	int16_t dx02 = x2 - x0;
	int16_t dy02 = y2 - y0;
	int16_t dx12 = x2 - x1;
	int16_t dy12 = y2 - y1;
	int16_t sa = 0;
	int16_t sb = 0;
   
	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);
}

main.cpp

#include "Pokitto.h"

//mask needs a game object to draw to it
Pokitto::Core game;
#include "mask.h"

const uint16_t p16_pal[] = {
59421,65184,64736,0,38912,54496,53248,65523,65535,
};

const uint8_t p16[] =
{
40,46,

0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,51,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,51,48,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,51,51,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
3,51,51,0,0,0,0,0,0,0,0,0,0,51,48,0,0,0,0,0,
3,51,51,0,0,0,0,0,0,0,0,3,51,51,51,48,0,0,0,0,
3,51,50,48,0,0,0,0,0,0,3,51,51,51,51,48,0,0,0,0,
3,35,50,48,0,0,0,0,0,68,35,51,51,51,51,0,0,0,0,0,
3,34,34,48,4,68,68,0,68,17,51,51,51,51,48,0,0,0,0,0,
0,50,34,68,87,119,17,84,17,19,51,51,51,51,0,0,0,68,0,0,
0,50,37,87,119,119,113,17,17,19,49,19,51,0,0,0,4,17,64,0,
0,3,81,119,119,119,17,17,17,17,19,48,0,0,0,0,65,17,20,0,
0,4,17,17,17,17,35,50,17,84,48,0,0,0,0,5,17,17,20,0,
0,53,50,17,17,17,56,51,17,19,0,0,0,0,0,81,17,17,17,64,
0,51,130,17,17,17,51,51,17,19,0,0,0,0,85,17,17,17,17,64,
0,51,49,19,81,17,35,50,17,20,0,0,0,4,17,17,17,17,17,64,
0,67,65,17,17,17,17,17,102,21,48,0,0,65,17,17,17,17,17,20,
3,17,17,19,69,17,17,22,102,98,48,0,4,17,17,17,17,17,17,20,
3,98,21,52,102,68,81,38,102,98,48,0,65,17,17,17,17,17,17,20,
3,98,34,36,102,100,34,38,102,98,48,0,66,17,17,17,17,17,17,48,
3,98,34,34,70,100,34,34,102,34,48,4,34,34,17,17,17,17,19,0,
0,66,34,34,70,100,34,34,34,34,48,66,34,34,34,17,17,19,48,0,
0,3,34,34,36,66,34,34,34,34,51,66,34,34,34,34,19,48,0,0,
0,3,34,34,34,34,34,34,34,34,19,3,34,34,34,35,48,0,0,0,
0,3,18,34,34,34,34,34,34,33,19,0,50,34,35,48,0,0,0,0,
0,3,17,18,34,34,34,34,34,17,19,0,3,34,64,0,0,0,0,0,
0,3,17,17,18,34,34,82,17,17,19,0,0,50,35,0,0,0,0,0,
0,3,17,17,17,17,21,17,17,17,19,0,3,82,35,0,0,0,0,0,
0,53,17,65,17,17,21,17,17,17,19,48,53,34,34,48,0,0,0,0,
0,49,17,81,17,17,65,17,17,33,17,48,85,82,83,48,0,0,0,0,
0,49,17,20,17,17,65,17,18,81,17,51,85,83,51,0,0,0,0,0,
0,53,17,20,17,20,17,17,20,17,18,53,85,80,0,0,0,0,0,0,
0,52,17,21,17,19,81,17,65,17,18,51,85,48,0,0,0,0,0,0,
0,52,17,17,65,17,49,84,17,17,34,35,52,67,0,0,0,0,0,0,
3,17,65,20,17,17,20,65,17,17,34,35,3,68,48,0,0,0,0,0,
52,17,20,65,17,17,17,17,17,18,34,35,52,68,48,0,0,0,0,0,
50,17,17,17,17,17,17,17,18,18,34,35,52,51,0,0,0,0,0,0,
50,33,17,17,17,17,17,17,33,33,34,34,51,0,0,0,0,0,0,0,
50,34,34,17,17,17,18,34,18,18,34,34,0,0,0,0,0,0,0,0,
4,34,34,34,34,34,34,34,34,34,34,35,0,0,0,0,0,0,0,0,
3,34,34,34,34,34,34,34,34,34,34,83,0,0,0,0,0,0,0,0,
0,51,34,37,83,51,51,34,34,34,37,48,0,0,0,0,0,0,0,0,
3,53,68,68,48,0,0,51,68,85,67,0,0,0,0,0,0,0,0,0,
49,65,83,51,0,0,0,3,51,34,36,48,0,0,0,0,0,0,0,0,
3,51,48,0,0,0,0,0,3,49,65,48,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,3,51,0,0,0,0,0,0,0,0,0,
};

int main(){
    game.display.invisiblecolor =0;
    game.begin();
    game.display.load565Palette(p16_pal);
    mask mask;
    float r = 0;
    int x =30;
    int y= 40;
    while(game.isRunning()){
        if(game.update()){
            
            mask.clear();
            mask.fillTriangle(x+cos(r)*20,y+sin(r)*20, x+cos(r+2.094395)*20,y+sin(r+2.094395)*20,x+cos(r+4.18879)*20,y+sin(r+4.18879)*20);
            r += 0.1; 

            game.display.drawBitmap(30,10,p16);
            mask.drawSprite(20,10,p16);
        }
    }
}

I remember @FManga mentioning a system where setPixel could be a function pointer, in which case this is one of the things that would benefit from such a system.

There’s also a way that could be done using templates insted of function pointers, but the function used wouldn’t be changeable at runtime.

This would be trivial to implement and would allow using the built-in primitives to draw on the mask. It would also make it easy to add a proxy to mask drawing to the screenbuffer.
The only problem is it prevents optimization. For the highest speed, functions like drawRow/drawBitmapData should not call drawPixel, but instead write 4 bytes at a time directly to the framebuffer.