Image Rotate


#21

Ok. What toolchain first Embitz/Simulator/online ? (I will add others later)


#22

Simulator (Code::Blocks)


#23

I am adding examples to PokittoLib repo, to both Sim and Hardware targets. Expect to see updates soon.


#25

I got it a little faster, I don’t understand other methods about removing almost all of the angle stuff to outside of the loops, but this way might be fast enough.

/* Pokitto drawBitmap example - draws a Pokitto icon by @trelemar */

#include "Pokitto.h" // include Pokitto library
#include "pokitto_icon.h" // include the Pokitto icon graphics file

Pokitto::Core mygame; //create Pokitto application instance


#define HALFTIMES 128
#define TIMES 256
#define TIMES2 65536

int sine[361];
int cosine[361];

int main () {
    mygame.begin(); // start the application
    mygame.display.load565Palette(sprite_pal); //load the palette for the image
    mygame.display.bgcolor=0;

    float angle=0;
    for(int t=0; t<=360; t++){
        sine[t]=sin(angle)*TIMES;
        cosine[t]=cos(angle)*TIMES;
        angle+=(0.0174533); // radians
    }
    int ang=0;


    int width = sprite[0];
    int height = sprite[1];
    int halfWidth = width>>1;
    int halfHeight = height>>1;
    int col;

    /* the "while" loop runs as long as the program is running */
    while (mygame.isRunning()) {

        int SIN = sine[ang];  // pre-calculated look-up
        int COS = cosine[ang];

        int sizeH = height;// * cos(ang*0.0174533) + height * cos(90-ang*0.0174533);
        int sizeW = width;// * sin(ang*0.0174533) + height * sin(90-ang*0.0174533);


        /* mygame.update() is processed whenever it is time to update the screen */
        if (mygame.update()) {

            for(int y=0; y<sizeH; y++){

                int y1 = (y-halfHeight)*TIMES;
                int ay1 = y1 * SIN + HALFTIMES;
                int ay2 = y1 * COS + HALFTIMES;

                for(int x=0; x<sizeW; x++){
                    int x1 = (width-x-halfWidth)*TIMES; // multiply to avoid using floats
                    int ux = (x1 * COS - ay1) / TIMES2;  // undo above multiplications to get x,y pixels
                    int uy = (x1 * SIN + ay2) / TIMES2;

                    if(ux>-halfWidth && ux <halfWidth && uy >-halfHeight && uy <halfHeight){
                        // grab pixel from 4bbp bitmap
                        uint16_t i = (uy+halfHeight)*(width>>1) + (width-(ux+halfWidth)>>1);
                        uint8_t pixel = sprite[i+2];
                        if (ux&1) col =  pixel>>4;
                        else col =  pixel & 0x0F;
                        mygame.display.drawPixel(x,y,col);
                    }else{
                        mygame.display.drawPixel(x,y,1);
                    }
                }
            }

//        if(ang++>=359) ang=0;

        ang-=1;
        if(ang<1) ang=359;

        }
    }

    return 0; // this is "good programming manners". Program informs it ended without errors
}


#26

If anyone would like to try to fixed-point the following, it might be faster, even if the output is a little uglier.

/* Pokitto drawBitmap example - draws a Pokitto icon by @trelemar */

#include "Pokitto.h" // include Pokitto library
#include "pokitto_icon.h" // include the Pokitto icon graphics file

Pokitto::Core mygame; //create Pokitto application instance

float sine[361];
float cosine[361];

int main () {
    mygame.begin(); // start the application
    mygame.display.load565Palette(sprite_pal); //load the palette for the image
    mygame.display.bgcolor=0;

    float angle=0;
    for(int t=0; t<=360; t++){
        sine[t]=sin(angle);
        cosine[t]=cos(angle);
        angle+=(0.0174533); // radians
    }

    int dstW = mygame.display.width;
	int dstH = mygame.display.height;
    int srcW = sprite[0];
	int srcH = sprite[1];
    int fDstCX = dstW/2;
	int fDstCY = dstH/2;
    int fSrcCX = srcW/2;
	int fSrcCY = srcH/2;
    int fAngle = 0;
	float fScale = 2.5;

    int col;

    /* the "while" loop runs as long as the program is running */
    while (mygame.isRunning()) {

        /* mygame.update() is processed whenever it is time to update the screen */
        if (mygame.update()) {

        float SIN = sine[fAngle];  // pre-calculated look-up
        float COS = cosine[fAngle];

        float duCol = SIN * (1.0 / fScale);
        float dvCol = COS * (1.0 / fScale);
        float duRow = dvCol;
        float dvRow = -duCol;

        float startingu = fSrcCX - (fDstCX * dvCol + fDstCY * duCol);
        float startingv = fSrcCY - (fDstCX * dvRow + fDstCY * duRow);

        float rowu = startingu;
        float rowv = startingv;

            for(int y=0; y<dstH; y++){

                float u = rowu;
                float v = rowv;

                for(int x=0; x<dstW; x++){

                    if ( u >=0 && u < srcW && v >=0 && v < srcH ){
                        uint16_t i = int(v)*(srcW/2) + (srcW-int(u)>>1);
                        uint8_t pixel = sprite[i+2];
                        if (int(u)&1) col =  pixel & 0x0F;
                        else col =  pixel >> 4;

                        mygame.display.drawPixel(x,y,col);
                    }

                    u += duRow;
                    v += dvRow;

                }

                rowu = rowu + duCol;
                rowv = rowv + dvCol;

            }

//        if(ang++>=359) ang=0;

        fAngle-=1;
        if(fAngle<1) fAngle=359;

        }
    }

    return 0; // this is "good programming manners". Program informs it ended without errors
}

#27

I had to guess what the contents of pokitto_icon.h were but I managed to get it to compile after adding some brackets.

Then I simply dropped in my FixedPoints library, tweaked the includes, changed a few casts and behond, one massive speed up:

FixedPointRotate.bin (41.4 KB)


#28

Cool, it looks about the same speed as the one further up though. Perhaps the framerate is the bottleneck rather than the rotation code.

Question is, can we use this for anything? I imagine rotating a tile map would be stupidly slow.


#29

It’s possible that there’s some kind of cap.
I don’t know what the bottleneck might be.

It would probably go faster if it weren’t using degrees though.
Brads would be better (possibly).


#30

You can remove any FPS throttling by using this after Pokitto::Core::begin():
Pokitto::Core::setFrameRate(100); // No limits!


#31

The inner loop can be optimized by not making a function call (mygame.display.drawPixel()). That involves pushing and popping registers and other overhead which can be costly in the inner loop.


#32

OK, so, remove the framerate limitation, switch to 8bit mode and 8bit image to simplify the pixelreading and writing…

Would it go any faster as an actual screen mode?


/* Pokitto drawBitmap example - draws a Pokitto icon by @trelemar */
#include "Pokitto.h" // include Pokitto library
#include "pokitto_icon.h" // include the Pokitto icon graphics file

#include "FixedPoints.h"
#include "FixedPointsCommon.h"

#include <cstddef>
#include <cstdint>
#include <cmath>

using Fract = SQ15x16;

//Fract sine[361];
//Fract cosine[361];

Fract angleTable[64];

constexpr const float floatPi = 3.141592654f;
constexpr const double doublePi = 3.141592654;

void generateAngleTable()
{
	const double factor = doublePi / 128.0;
	for(unsigned int a = 0; a < 64; ++a)
	{
		angleTable[a] = static_cast<Fract>(std::sin(static_cast<double>(a) * factor));
	}
}

Fract lookupAngle(std::uint_fast8_t index)
{
    return (index == 64) ? 1 : angleTable[(index & (0x3F))];
}

Fract Sin(std::uint8_t brads)
{
	const std::uint_fast8_t fastBrads = brads;
	const std::uint_fast8_t quarter = ((fastBrads >> 6) & 0x03);
	const std::uint_fast8_t index = ((fastBrads >> 0) & 0x3F);
	switch (quarter)
	{
		case 0: return lookupAngle(index);
		case 1: return lookupAngle(64 - index);
		case 2: return -lookupAngle(index);
		case 3: return -lookupAngle(64 - index);
		default: return 0;
	}
}

Fract Cos(std::uint8_t brads)
{
	const std::uint_fast8_t fastBrads = brads;
	const std::uint_fast8_t quarter = ((fastBrads >> 6) & 0x03);
	const std::uint_fast8_t index = ((fastBrads >> 0) & 0x3F);
	switch (quarter)
	{
		case 0: return lookupAngle(64 - index);
		case 1: return -lookupAngle(index);
		case 2: return -lookupAngle(64 - index);
		case 3: return lookupAngle(index);
		default: return 0;
	}
}

int main(void)
{
    Pokitto::Core::begin(); // start the application
    Pokitto::Display::load565Palette(sprite_pal); //load the palette for the image
    Pokitto::Display::bgcolor = 0;
    Pokitto::Core::setFrameRate(100); // spinal

    /*Fract angle=0;
	for(int t=0; t<=360; t++)
	{
		sine[t] = sin(static_cast<double>(angle));
		cosine[t] = cos(static_cast<double>(angle));
		angle += (0.0174533); // radians
	}*/
	generateAngleTable();

	unsigned int dstW = Pokitto::Display::getWidth();
	unsigned int dstH = Pokitto::Display::getHeight();
	unsigned int fDstCX = dstW/2;
	unsigned int fDstCY = dstH/2;

	unsigned int srcW = sprite[0];
	unsigned int srcH = sprite[1];
	unsigned int fSrcCX = srcW/2;
	unsigned int fSrcCY = srcH/2;

	//const Fract fScale = 2.5;
	const Fract inverseScale = 1.0;// / 2.5;  // spinal - 1:1 scale to see whats happening better
	unsigned int fAngle = 0;
    uint8_t colour = 0; // spinal
	/* the "while" loop runs as long as the program is running */
	while (Pokitto::Core::isRunning())
	{

		/* Pokitto::Core::update() is processed whenever it is time to update the screen */

		if (Pokitto::Core::update())
		{

			Fract s = Sin(fAngle);//sine[fAngle];  // pre-calculated look-up
			Fract c = Cos(fAngle);//cosine[fAngle];

			Fract duCol = s * inverseScale;
			Fract dvCol = c * inverseScale;
			Fract duRow = dvCol;
			Fract dvRow = -duCol;

			Fract startingu = fSrcCX - (fDstCX * dvCol + fDstCY * duCol);
			Fract startingv = fSrcCY - (fDstCX * dvRow + fDstCY * duRow);

			Fract rowu = startingu;
			Fract rowv = startingv;

            int pix=0; // spinal
			for(unsigned int y = 0; y < dstH; ++y)
			{
				Fract u = rowu;
				Fract v = rowv;

				for(unsigned int x = 0; x < dstW; ++x)
				{

					if ( u >=0 && u < srcW && v >=0 && v < srcH )
					{
						const int16_t iv = static_cast<int16_t>(v);
						const int16_t iu = static_cast<int16_t>(u);

						unsigned int i = iu + srcW * iv; // spinal
						colour = sprite[i + 2]; // spinal
					}else{ // spinal
                        colour = 0; // spinal
					}
					Pokitto::Display::screenbuffer[pix++] = colour; // spinal

					u += duRow;
					v += dvRow;
				}

				rowu = rowu + duCol;
				rowv = rowv + dvCol;

			}
			--fAngle;
		}
	}

	return 0;
}

#33

It would be interesting to see how it affects to performance. There is less calculations in 8-bit mode but more data to read and write. Depends on how fast the memory transfer is.

Turning the rotator to the actual screen mode would certainly speed up as there is no buffer in-between, but you have to deal with flickering if you ever draw anything over the bitmap.


#34

Well, this didn’t work how I expected…


/* Pokitto drawBitmap example - draws a Pokitto icon by @trelemar */
#include "Pokitto.h" // include Pokitto library
#include "pokitto_icon.h" // include the Pokitto icon graphics file

#include "FixedPoints.h"
#include "FixedPointsCommon.h"

#include <cstddef>
#include <cstdint>
#include <cmath>

using Fract = SQ15x16;



	unsigned int dstW = Pokitto::Display::getWidth();
	unsigned int dstH = Pokitto::Display::getHeight();
	unsigned int fDstCX = dstW/2;
	unsigned int fDstCY = dstH/2;

	unsigned int srcW = sprite[0];
	unsigned int srcH = sprite[1];
	unsigned int fSrcCX = srcW/2;
	unsigned int fSrcCY = srcH/2;

	//const Fract fScale = 2.5;
	Fract inverseScale = 1.0;// / 2.5;
	unsigned int fAngle = 0;
    uint8_t colour = 0;

    Fract s, c, duCol, dvCol, duRow, dvRow, startingu, startingv, rowu, rowv;



//Fract sine[361];
//Fract cosine[361];

Fract angleTable[64];

constexpr const float floatPi = 3.141592654f;
constexpr const double doublePi = 3.141592654;

void generateAngleTable()
{
	const double factor = doublePi / 128.0;
	for(unsigned int a = 0; a < 64; ++a)
	{
		angleTable[a] = static_cast<Fract>(std::sin(static_cast<double>(a) * factor));
	}
}

Fract lookupAngle(std::uint_fast8_t index)
{
    return (index == 64) ? 1 : angleTable[(index & (0x3F))];
}

Fract Sin(std::uint8_t brads)
{
	const std::uint_fast8_t fastBrads = brads;
	const std::uint_fast8_t quarter = ((fastBrads >> 6) & 0x03);
	const std::uint_fast8_t index = ((fastBrads >> 0) & 0x3F);
	switch (quarter)
	{
		case 0: return lookupAngle(index);
		case 1: return lookupAngle(64 - index);
		case 2: return -lookupAngle(index);
		case 3: return -lookupAngle(64 - index);
		default: return 0;
	}
}

Fract Cos(std::uint8_t brads)
{
	const std::uint_fast8_t fastBrads = brads;
	const std::uint_fast8_t quarter = ((fastBrads >> 6) & 0x03);
	const std::uint_fast8_t index = ((fastBrads >> 0) & 0x3F);
	switch (quarter)
	{
		case 0: return lookupAngle(64 - index);
		case 1: return -lookupAngle(index);
		case 2: return -lookupAngle(64 - index);
		case 3: return lookupAngle(index);
		default: return 0;
	}
}

void recalculate(int scanLine=0){

    s = Sin(fAngle);//sine[fAngle];  // pre-calculated look-up
    c = Cos(fAngle);//cosine[fAngle];

    duCol = s * inverseScale;
    dvCol = c * inverseScale;
    duRow = dvCol;
    dvRow = -duCol;

    startingu = fSrcCX - (fDstCX * dvCol + fDstCY * duCol);
    startingv = fSrcCY - (fDstCX * dvRow + fDstCY * duRow);

    rowu = startingu;
    rowv = startingv;

    rowv += scanLine*dvCol;

}


int main(void)
{
    Pokitto::Core::begin(); // start the application
    Pokitto::Display::load565Palette(sprite_pal); //load the palette for the image
    Pokitto::Display::bgcolor = 0;
    Pokitto::Core::setFrameRate(100); // No limits!

    /*Fract angle=0;
	for(int t=0; t<=360; t++)
	{
		sine[t] = sin(static_cast<double>(angle));
		cosine[t] = cos(static_cast<double>(angle));
		angle += (0.0174533); // radians
	}*/
	generateAngleTable();

	/* the "while" loop runs as long as the program is running */
	while (Pokitto::Core::isRunning())
	{

		/* Pokitto::Core::update() is processed whenever it is time to update the screen */

		if (Pokitto::Core::update())
		{

            recalculate();

            int pix=0;
			for(unsigned int y = 0; y < dstH; ++y)
			{
				inverseScale = 1.0/dstH*y;
                recalculate(y);

				Fract u = rowu;
				Fract v = rowv;

				for(unsigned int x = 0; x < dstW; ++x)
				{

					if ( u >=0 && u < srcW && v >=0 && v < srcH )
					{
						const int16_t iv = static_cast<int16_t>(v);
						const int16_t iu = static_cast<int16_t>(u);

						unsigned int i = iu + srcW * iv;
						colour = sprite[i + 2];
					}else{
                        colour = 0;
					}
					Pokitto::Display::screenbuffer[pix++] = colour;

					u += duRow;
					v += dvRow;
				}

				rowu = rowu + duCol;
				rowv = rowv + dvCol;

			}
			--fAngle;
		}
	}

	return 0;
}