I'm attempting to apply an a rotation matrix in C++ that rotates all points of square a specified degree around a specified origin. The catch is that it is based in the win32 console, so each point has to correspond with a pair of ints, rather than floating point values. As you can see below, the rotating square's overall shape is consistent with the desired result, but there are a number of 'holes' in it.
Here's my source code:
#include <iostream>
#include <cmath>
using namespace std;
enum {W = 50, H = 50, S = 25}; //Width, Height, Square size
struct Vector2i
{
int x;
int y;
Vector2i() {}
Vector2i(int _x, int _y) : x(_x), y(_y) {}
};
struct Square
{
bool Data[W][H];
Vector2i Origin = Vector2i(W / 2, H / 2);
void clear() {
for (int y = 0; y < H; ++y) {
for (int x = 0; x < W; ++x)
Data[x][y] = false;
}
}
void setSquare() {
for (int y = H / 2 - S / 2; y < H / 2 + S / 2; ++y) {
for (int x = W / 2 - S / 2; x < W / 2 + S / 2; ++x)
Data[x][y] = true;
}
}
void draw() {
for (int y = 0; y < H; ++y) {
for (int x = 0; x < W; ++x) {
if (y == Origin.y && x == Origin.x) std::cout << '+'; //Marks the origin
else if (Data[x][y]) std::cout << 'X';
else std::cout << '.';
}
std::cout << '\n';
}
}
};
Vector2i newPos(Vector2i old, double theta) {
theta *= 3.14159265d / 180.d; //Converting from degrees to radians
int X = ceil(cos(theta) * old.x - sin(theta) * old.y);
int Y = ceil(sin(theta) * old.x + cos(theta) * old.y);
return Vector2i(X, Y);
}
int main()
{
cout << "Enter an angle (in degrees): ";
double angle = 0;
cin >> angle;
Square One;
One.clear();
One.setSquare();
One.draw();
Square Two;
Two.clear();
///Draw the rotated square as the second square
for (int y = 0; y < H; ++y) {
for (int x = 0; x < W; ++x) {
if (One.Data[x][y]) {
Vector2i finalVec = newPos(Vector2i(x - One.Origin.x,
y - One.Origin.y), angle);
Two.Data[finalVec.x + One.Origin.x][finalVec.y + One.Origin.y] = true;
}
}
}
///Copy the second square back into the first
for (int y = 0; y < H; ++y) {
for (int x = 0; x < W; ++x)
One.Data[x][y] = Two.Data[x][y];
}
One.draw();
return 0;
}
Is this due to the accuracy of the newPos() function, or is it the rounding into int values that is causing this?
Additionally, is there a way to fix this or predict where the holes will be?
EDIT: SOLVED!
Going off of infgeoax's suggestion to work backwards, I created a function to calculate the original positions. I'll leave the augmented code here, in case anyone has a similar problem in the future (Thanks for all your help, everyone! [especially infgeoax, for the brainwave]):
#include <iostream>
#include <cmath>
using namespace std;
enum {W = 50, H = 50, S = 25};
struct Vector2i
{
int x;
int y;
Vector2i() {}
Vector2i(int _x, int _y) : x(_x), y(_y) {}
};
struct Square
{
bool Data[W][H];;
Vector2i Origin = Vector2i(W / 2, H / 2);
void clear() {
for (int y = 0; y < H; ++y) {
for (int x = 0; x < W; ++x)
Data[x][y] = false;
}
}
void setSquare() {
for (int y = H / 2 - S / 2; y < H / 2 + S / 2; ++y) {
for (int x = W / 2 - S / 2; x < W / 2 + S / 2; ++x)
Data[x][y] = true;
}
}
void draw() {
for (int y = 0; y < H; ++y) {
for (int x = 0; x < W; ++x) {
if (y == Origin.y && x == Origin.x) std::cout << '+'; //Marks the origin
else if (Data[x][y]) std::cout << 'X';
else std::cout << '.';
}
std::cout << '\n';
}
}
};
Vector2i oldPos(Vector2i new_, float theta) {
theta *= 3.14159265f / 180.f; //Converting from degrees to radians
return Vector2i(new_.x * cosf(theta) + new_.y * sinf(theta) + 0.5f,
new_.y * cosf(theta) - new_.x * sinf(theta) + 0.5f);
}
int main()
{
cout << "Enter an angle (in degrees): ";
float angle = 0;
cin >> angle;
Square One;
One.clear();
One.setSquare();
One.draw();
Square Two;
Two.clear();
for (int y = 0; y < H; ++y) {
for (int x = 0; x < W; ++x) {
Vector2i vec = oldPos(Vector2i(x - One.Origin.x, y - One.Origin.y), angle);
vec.x += One.Origin.x;
vec.y += One.Origin.y;
if (vec.x >= 0 && vec.x < W && vec.y >= 0 && vec.y < H)
Two.Data[x][y] = One.Data[vec.x][vec.y];
}
}
Two.draw();
return 0;
}
Well your problem has nothing to do with whether or not your are developing a console or GUI application. Images are stored and processed as matrices of pixels. When you rotate the image, the resulting position for a specific pixel is usually not integers.
The idea is to go the other way around.
You calculate the four corners of the rotated sqaure.
For each position(pixel) in the rotated square, you calculate its color by rotating it back to the original square.
Related
Faced the following problem: I have a grid and a beam, in the form of a circle. At this stage, you just need to draw them.
Grid::render():
for (int i = 0; i < cellsInColumn; i++) {
for (int j = 0; j < cellsInRow; j++) {
SDL_Rect outlineRect = { this->x + this->bord_x + (cellWidth*j), this->y+this->bord_y, this->cellWidth, this->cellHeight };
SDL_RenderDrawRect( this->rend, &outlineRect );
}
y+=cellHeight;
}
Beam::render():
for (int w = 0; w < radius * 2; w++) {
for (int h = 0; h < radius * 2; h++) {
double dx = radius - w;
double dy = radius - h;
if ((dx*dx + dy*dy) <= (radius * radius)) {
SDL_RenderDrawPoint(this->rend, x + dx, y + dy);
}
}
}
But my screen seems to have "eaten" the top line of the grid. It turned out that the top of the grid, along with the "beam", was drawn under the title bar.
bord_y == 0
bord_y == 70
Question for the connoisseurs: how do I now draw the grid and the circle? Does the SDL know how many pixels are in the title bar, or should this indent be "by eye"? If it knows, where is this information stored?
UPD:
Grid and beam values are set in the following function:
void setStartValues(int screenWidth, int screenHeight){
Grid::setBord(screenWidth, screenHeight);
Grid::setCellSize(screenHeight);
Beam::setValues(Grid::getCellHeight(), Grid::getBord());
}
And here are all the getters and setters that are used above:
void setBord(int scrW, int scrH) {
this->bord_x = this->cellsInRow <= this->cellsInColumn? (scrW-scrH)/2 : (scrW-scrH)/6;
this->bord_y = 0;
}
void setCellSize(int scrH) {
this->cellWidth = this->cellHeight = scrH/cellsInColumn;
}
double getCellHeight() {
return this->cellHeight;
}
double getBord() {
return this->bord_x;
}
void setValues(double cellH, double bord) { //Beam
this->x = cellH/2 + bord;
this->y = cellH/2;
this->radius = cellH/4;
}
I'm trying to create my own CFD in C++. I have watched some videos on youtube about the Lattice Boltzmann method, but I cant get my simulations to look like the simulations performed in the videos with lattice Boltzmann implemented in Python.
I use SDL2 to create an image on my screen. I am not trying to create anything fast. Just something that will make pretty simulations on the CPU.
Here is my class for each cell:
//cell class
class cell {
public:
double Fi[nL] = {0,0,0,0,0,0,0,0,0};
double density = 0;
double momentumX = 0;
double momentumY = 0;
double velocityX = 0;
double velocityY = 0;
double Fieq[nL] = {0,0,0,0,0,0,0,0,0};
//obstacle
bool obstacle = false;
void densityOperator() {
for (int i = 0; i < nL; i++) {
density += Fi[i];
}
}
void momentumOperator() {
for (int i = 0; i < nL; i++) {
momentumX += Fi[i] * cX[i];
momentumY += Fi[i] * cY[i];
}
}
void velocityOperator() {
for (int i = 0; i < nL; i++) {
if (density == 0) {
density += 0.001;
}
velocityX += momentumX / density; // prolly very slow
velocityY += momentumY / density;
//velocityX += cX[i];
//velocityY += cY[i];
}
}
void FieqOperator() {
for (int i = 0; i < nL; i++) {
Fieq[i] = weights[i] * density *
(
1 +
(cX[i] * velocityX + cY[i] * velocityY) / Cs +
pow((cX[i] * velocityX + cY[i] * velocityY), 2) / (2 * pow(Cs, 4)) -
(velocityX * velocityX + velocityY * velocityY) / (2 * pow(Cs, 2))
);
}
}
void FiOperator() {
for (int i = 0; i < nL; i++) {
Fi[i] = Fi[i] - (timestep / tau) * (Fi[i] - Fieq[i]);
}
}
void addRightVelocity() {
Fi[0] = 1.f;
Fi[1] = 1.f;
Fi[2] = 1.f;
Fi[3] = 6.f;
Fi[4] = 1.f;
Fi[5] = 1.f;
Fi[6] = 1.f;
Fi[7] = 1.f;
Fi[8] = 1.f;
}
};
Please note that im am using a vector for my cells instead of a 2d array. I am using a index function to go from x,y to 1d cordinate.
int index(int x, int y) {
return x * nY + y;
}
Variables:
//box
const int nX = 400;
const int nY = 100;
//viscosity
float tau = 0.5; // 0.53
//time delta time per iteration
float timestep = 1;
//distance between cells
float dist = 1000;
//Speed of sound
float Cs = 1 / sqrt(3) * (dist / timestep);
//viscociti
float v = pow(Cs, 2) * (tau - timestep / 2); // tau will need to be much smaller
//time steps
int nT = 3000;
//lattice speeds and weights
const int nL = 9;
//Ci vector direction, discrete velocity
int cX[9] = { 0, 0, 1, 1, 1, 0, -1, -1, -1 };
int cY[9] = { 0, 1, 1, 0, -1, -1, -1, 0 , 1 };
//weights, based on navier stokes
float weights[9] = { 4 / 9, 1 / 9, 1 / 36, 1 / 9, 1 / 36, 1 / 9, 1 / 36, 1 / 4, 1 / 36 };
//opposite populations
int cO[9] = { 0, 5, 6, 7, 8, 1, 2, 3, 4 };
My main function:
int main() {
//init vector cells
for (int x = 0; x < nX; x++) {
for (int y = 0; y < nY; y++) {
cell cellUnit;
cells.push_back(cellUnit);
TempCells.push_back(cellUnit);
}
}
//SDL
//SDL
//-------------------------------------------------------------
SDL_Window* window = nullptr;
SDL_Renderer* renderer = nullptr;
SDL_Init(SDL_INIT_VIDEO);
SDL_CreateWindowAndRenderer(nX* 3, nY * 3, 0, &window, &renderer);
SDL_RenderSetScale(renderer, 3, 3);
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderClear(renderer);
//-------------------------------------------------------------//
//Circle Object Gen
for (int x = 0; x < nX; x++) {
for (int y = 0; y < nY; y++) {
//cicle position
int circleX = 5;
int circleY = 50;
//circle radius
float radius = 10;
//distance bewtween cell and circle pos
float distance = sqrt(pow(circleX - x, 2) + pow(circleY - y, 2));
if (distance < radius) {
cells[index(x,y)].obstacle = true;
}
else {
cells[index(x, y)].obstacle = false;
}
}
}
//add velocity
for (int x = 0; x < nX; x++) {
for (int y = 0; y < nY; y++) {
cells[index(x, y)].addRightVelocity();
//random velocity
for (int i = 0; i < nL; i++) {
cells[index(x,y)].Fi[i] += (rand() % 200) / 100;
}
}
}
for (int t = 0; t < nT; t++) {
//SDL
//--------------------------------------------------------------
//clear renderer
if (t % 20 == 0) {
SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
SDL_RenderClear(renderer);
}
//--------------------------------------------------------------
//streaming:
//because we will loop over the same populations we do not want to switch the same population twice
for (int x = 0; x < nX; x++) {
for (int y = 0; y < nY; y++) {
if (x == 0) {
cells[index(x, y)].Fi[3] += 0.4;
}
//for populations
for (int i = 0; i < nL; i++) {
//boundary
//checs if cell is object or air
if (cells[index(x, y)].obstacle == false) {
//air
//targetet cell
int cellX = x + cX[i];
int cellY = y + cY[i];
//out of bounds check + rearange to other side
if (cellX < 0) {
//left to right
cellX = nX;
}
if (cellX >= nX) {
//right to left
cellX = 0;
continue;
}
if (cellY < 0) {
//top to buttom
cellY = nY;
}
if (cellY >= nY) {
//bottom to top
cellY = 0;
}
//if neighborinig cell is object --> collision with object
if (cells[index(cellX, cellY)].obstacle == true) {
//Boundary handling https://youtu.be/jfk4feD7rFQ?t=2821
TempCells[index(x,y)].Fi[cO[i]] = cells[index(x, y)].Fi[i];
}
//if not then stream to neighbor air cell with oposite population
TempCells[index(cellX, cellY)].Fi[cO[i]] = cells[index(x, y)].Fi[i];
}
else {
//wall
//SDL GRAPICHS
if (t % 20 == 0) {
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderDrawPoint(renderer, x, y);
}
}
}
}
}
for (int x = 0; x < nX; x++) {
for (int y = 0; y < nY; y++) {
for (int i = 0; i < nL; i++) {
cells[index(x, y)].Fi[i] = TempCells[index(x, y)].Fi[cO[i]];
}
}
}
//collision:
for (int x = 0; x < nX; x++) {
for (int y = 0; y < nY; y++) {
//density:
cells[index(x, y)].densityOperator();
//momentum:
cells[index(x, y)].momentumOperator();
//velocity:
cells[index(x, y)].velocityOperator();
//Fieq + new new Fi:
for (int i = 0; i < nL; i++) {
cells[index(x, y)].FieqOperator();
}
//SDL Graphics
if (t % 20 == 0) {
if (cells[index(x, y)].obstacle == false) {
SDL_SetRenderDrawColor(renderer, cells[index(x, y)].density, cells[index(x, y)].density , 255 , 255);
SDL_RenderDrawPoint(renderer, x, y);
}
}
}
}
for (int x = 0; x < nX; x++) {
for (int y = 0; y < nY; y++) {
cells[index(x, y)].FiOperator();
}
}
//SDL Graphics
if (t % 20 == 0 ) {
SDL_RenderPresent(renderer);
}
}
return 0;
}
I do realize my code might be a bit messy and not easy to understand at first. And it is definitely not optimal.
If anyone has any experience in programming their own LBM in c++ i would like to hear your input.
It seams like my simulations is working but i do not get those bueatiful animations like in, https://youtu.be/ZUXmO4hu-20?t=3394
Thanks for any help.
Edit:
I have edited my script to reset, density, velocity X Y and Momentum X Y
Simulation visualised by density, pink is higher, loops if density exceeds color range of 255
Simulation visualised by density
Simulation visualised by density
I am new to opencv and I am performing unsharp masking by using using this criteria Image+(K*(Image-low pass filter)),however ,the resultant image may have values <0 or >255,i need to write a loop to scale that down.
I tried to write one but seemingly its in correct.
Here are the errors.
a) (k * (float)(src.at(y, x) - res.at(y, x))) can be negative, it is incorrect to do (uchar)(k * (float)(src.at(y, x) - res.at(y, x))).
b) src.at(y,x) + (k * (float)(src.at(y, x) - res.at(y, x))) can be greater than 255 and can be smaller than 0.
Can someone help me fix this,thanks in advance
#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <iostream>
#include <stdlib.h>
#include <math.h>
using namespace std;
using namespace cv;
//the pixel lying outside the image i.e. (x – j, y – k) are reflected back into the image
int reflect(int M, int x)
{
if (x < 0)
{
return -x - 1;
}
if (x >= M)
{
return 2 * M - x - 1;
}
return x;
}
int circular(int M, int x)
{
if (x < 0)
return x + M;
if (x >= M)
return x - M;
return x;
}
void noBorderProcessing(Mat src, Mat res, float Kernel[][3])
{
float sum;
for (int y = 1; y < src.rows - 1; y++) {
for (int x = 1; x < src.cols - 1; x++) {
sum = 0.0;
for (int k = -1; k <= 1; k++) {
for (int j = -1; j <= 1; j++) {
sum = sum + Kernel[j + 1][k + 1] * src.at<uchar>(y - j, x - k);
}
}
res.at<uchar>(y, x) = sum;
}
}
}
void refletedIndexing(Mat src, Mat res, float Kernel[][3])
{
float sum, x1, y1;
for (int y = 0; y < src.rows; y++) {
for (int x = 0; x < src.cols; x++) {
sum = 0.0;
for (int k = -1; k <= 1; k++) {
for (int j = -1; j <= 1; j++) {
x1 = reflect(src.cols, x - j);
y1 = reflect(src.rows, y - k);
sum = sum + Kernel[j + 1][k + 1] * src.at<uchar>(y1, x1);
}
}
res.at<uchar>(y, x) = sum;
}
}
}
//coordinates that exceed the bounds of the image wrap around to the opposite side
void circularIndexing(Mat src, Mat res, float Kernel[][3])
{
float sum, x1, y1;
for (int y = 0; y < src.rows; y++) {
for (int x = 0; x < src.cols; x++) {
sum = 0.0;
for (int k = -1; k <= 1; k++) {
for (int j = -1; j <= 1; j++) {
x1 = circular(src.cols, x - j);
y1 = circular(src.rows, y - k);
sum = sum + Kernel[j + 1][k + 1] * src.at<uchar>(y1, x1);
}
}
res.at<uchar>(y, x) = sum;
}
}
}
int main()
{
Mat src, res,dst;
/// Load an image
src = cv::imread("Images4DZ/Gray_Win.bmp", cv::IMREAD_ANYDEPTH);
//low pass filtering
float Kernel[3][3] = {
{1 / 9.0, 1 / 9.0, 1 / 9.0},
{1 / 9.0, 1 / 9.0, 1 / 9.0},
{1 / 9.0, 1 / 9.0, 1 / 9.0}
};
res = src.clone();
for (int y = 0; y < src.rows; y++)
for (int x = 0; x < src.cols; x++)
res.at<uchar>(y, x) = 0.0;
circularIndexing(src, res, Kernel);
//Unsharpen Masking
dst = cv::Mat::zeros(res.rows, res.cols, CV_8UC1);
float k = 0.5;
for (int y = 0; y < res.rows; y++) {
for (int x = 0; x < res.cols; x++) {
dst.at<uchar>(y, x) = src.at<uchar>(y,x) + (uchar)(k * (float)(src.at<uchar>(y, x) - res.at<uchar>(y, x)));
}
}
imshow("Source Image", src);
imshow("Low Pass Filter", res);
imshow("Unsharpen Masking", dst);
waitKey();
return 0;
}
Hello I'm currently trying to rotate blocks that are within a square. But when I start to rotate them it starts to create weird spaces between blocks that I don't want. Could you help me to fix the problem of spaces beetween blocks? Here are some code and screenshots how does it look.
https://imgur.com/a/BLuO7FF
I have already checked if all angles and radiuses are calculated correctly and I don't see any problem there.
World.h
#pragma once
#include <SFML/Graphics.hpp>
class World
{
public:
World(sf::Vector2f Wpos);
float AngleToRadian(int angle);
void RotateWorld();
void draw(sf::RenderWindow &window);
sf::Texture tx;
sf::Sprite** Block;
sf::Vector2f Pos;
sf::Vector2i Size;
float** radius;
float** angle;
};
World.cpp
#include "World.h"
#include <SFML/Graphics.hpp>
#include <iostream>
#include <cmath>
#define PI 3.14159
World::World(sf::Vector2f Wpos)
{
Pos = Wpos;
Size = sf::Vector2i(10, 10);
Block = new sf::Sprite*[Size.y];
radius = new float*[Size.y];
angle = new float*[Size.y];
for (int i = 0; i < Size.y; i++)
{
Block[i] = new sf::Sprite[Size.x];
radius[i] = new float[Size.x];
angle[i] = new float[Size.x];
}
tx.loadFromFile("Img/Block.png");
sf::Vector2i off(Size.x * tx.getSize().x / 2, Size.y * tx.getSize().y / 2); //tx size is 32px x 32px
for (int y = 0; y < Size.y; y++)
{
for (int x = 0; x < Size.x; x++)
{
Block[y][x].setTexture(tx);
Block[y][x].setOrigin(tx.getSize().x / 2, tx.getSize().y / 2);
Block[y][x].setPosition(x*tx.getSize().x + Wpos.x - off.x + Block[y][x].getOrigin().x, y*tx.getSize().y + Wpos.y - off.y + Block[y][x].getOrigin().y);
radius[y][x] = sqrt(pow(Pos.x - Block[y][x].getPosition().x, 2) + pow(Pos.y - Block[y][x].getPosition().y, 2));
angle[y][x] = (atan2(Block[y][x].getPosition().y - Pos.y, Block[y][x].getPosition().x - Pos.x) * 180.0) / PI;
if ((atan2(Block[y][x].getPosition().y - Pos.y, Block[y][x].getPosition().x - Pos.x) * 180.0) / PI < 0)
{
angle[y][x] += 360;
}
//angle[y][x] = round(angle[y][x]);
/*radius[y][x] = round(radius[y][x]);*/
}
}
}
void World::RotateWorld()
{
float dx = 0, dy = 0;
if (sf::Keyboard::isKeyPressed(sf::Keyboard::E))
{
for (int y = 0; y < Size.y; y++)
{
for (int x = 0; x < Size.x; x++)
{
Block[y][x].rotate(1);
if (angle[y][x] >= 360)
{
angle[y][x] = 0;
}
angle[y][x]++;
dx = cos(AngleToRadian(angle[y][x])) * radius[y][x];
dy = sin(AngleToRadian(angle[y][x])) * radius[y][x];
Block[y][x].setPosition(Pos.x + dx, Pos.y + dy);
}
}
}
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Q))
{
for (int y = 0; y < Size.y; y++)
{
for (int x = 0; x < Size.x; x++)
{
Block[y][x].rotate(-1);
if (angle[y][x] >= 360)
{
angle[y][x] = 0;
}
angle[y][x]--;
dx = cos(AngleToRadian(angle[y][x])) * radius[y][x];
dy = sin(AngleToRadian(angle[y][x])) * radius[y][x];
Block[y][x].setPosition(Pos.x + dx, Pos.y + dy);
}
}
}
}
I expected it to rotate withouth any spaces between. I would be really thankfull if someone would help me.
I would try with setting the origin of the sf::Sprite using it's getGlobalBounds() method instead of the sf::Texture size getter.
The difference seems minor and something like that might be the case.
Block[y][x].setTexture(tx);
Block[y][x].setOrigin(Block[y][x].getGlobalBouds().width / 2, Block[y][x].getGlobalBouds().height / 2);
Block[y][x].setPosition(x*Block[y][x].getGlobalBouds().width + Wpos.x - off.x + Block[y][x].getOrigin().x, y*Block[y][x].getGlobalBouds().height + Wpos.y - off.y + Block[y][x].getOrigin().y);
My goal is simple: I want to create a rendering system in C++ that can draw thousands of bitmaps on screen. I have been trying to use threads to speed up the process but to no avail. In most cases, I have actually slowed down performance by using multiple threads. I am using this project as an educational exercise by not using hardware acceleration. That said, my question is this:
What is the best way to use several threads to accept a massive list of images to be drawn onto the screen and render them at break-neck speeds? I know that I won’t be able to create a system that can rival hardware accelerated graphics, but I believe that my idea is still feasible because the operation is so simple: copying pixels from one memory location to another.
My renderer design uses three core blitting operations: position, rotation, and scale of a bitmap image. I have it set up to only rotate an image when needed, and only scale an image when needed.
I have gone through several designs for this system. All of them too slow to get the job done (300 64x64 bitmaps at barely 60fps).
Here are the designs I have tried:
Immediately drawing a source bitmap on a destination bitmap for every image on screen (moderate speed).
Creating workers that accept a draw instruction and immediately begin working on it while other workers receive their instructions also (slowest).
Workers that receive packages of several instructions at a time (slower).
Saving all drawing instructions up and then parting them up in one swoop to several workers while other tasks (in theory) are being done (slowest).
Here is the bitmap class I am using to blit bitmaps onto each other:
class Bitmap
{
public:
Bitmap(int w, int h)
{
width = w;
height = h;
size = w * h;
pixels = new unsigned int[size];
}
virtual ~Bitmap()
{
if (pixels != 0)
{
delete[] pixels;
pixels = 0;
}
}
void blit(Bitmap *bmp, float x, float y, float rot, float sclx,
float scly)
{
// Position only
if (rot == 0 && sclx == 1 && scly == 1)
{
blitPos(bmp, x, y);
return;
}
// Rotate only
else if (rot != 0 && sclx == 1 && scly == 1)
{
blitRot(bmp, x, y, rot);
return;
}
// Scale only
else if (rot == 0 && (sclx != 1 || scly != 1))
{
blitScl(bmp, x, y, sclx, scly);
return;
}
/////////////////////////////////////////////////////////////////////////////
// If it is not one of those, you have to do all three... :D
/////////////////////////////////////////////////////////////////////////////
// Create a bitmap that is scaled to the new size.
Bitmap tmp((int)(bmp->width * sclx), (int)(bmp->height * scly));
// Find how much each pixel steps:
float step_x = (float)bmp->width / (float)tmp.width;
float step_y = (float)bmp->height / (float)tmp.height;
// Fill the scaled image with pixels!
float inx = 0;
int xOut = 0;
while (xOut < tmp.width)
{
float iny = 0;
int yOut = 0;
while (yOut < tmp.height)
{
unsigned int sample = bmp->pixels[
(int)(std::floor(inx) + std::floor(iny) * bmp->width)
];
tmp.drawPixel(xOut, yOut, sample);
iny += step_y;
yOut++;
}
inx += step_x;
xOut++;
}
blitRot(&tmp, x, y, rot);
}
void drawPixel(int x, int y, unsigned int color)
{
if (x > width || y > height || x < 0 || y < 0)
return;
if (color == 0x00000000)
return;
int index = x + y * width;
if (index >= 0 && index <= size)
pixels[index] = color;
}
unsigned int getPixel(int x, int y)
{
return pixels[x + y * width];
}
void clear(unsigned int color)
{
std::fill(&pixels[0], &pixels[size], color);
}
private:
void blitPos(Bitmap *bmp, float x, float y)
{
// Don't draw if coordinates are already past edges
if (x > width || y > height || y + bmp->height < 0 || x + bmp->width < 0)
return;
int from;
int to;
int destfrom;
int destto;
for (int i = 0; i < bmp->height; i++)
{
from = i * bmp->width;
to = from + bmp->width;
//////// Caps
// Bitmap is being drawn past the right edge
if (x + bmp->width > width)
{
int cap = bmp->width - ((x + bmp->width) - width);
to = from + cap;
}
// Bitmap is being drawn past the left edge
else if (x + bmp->width < bmp->width)
{
int cap = bmp->width + x;
from += (bmp->width - cap);
to = from + cap;
}
//////// Destination Maths
if (x < 0)
{
destfrom = (y + i) * width;
destto = destfrom + (bmp->width + x);
}
else
{
destfrom = x + (y + i) * width;
destto = destfrom + bmp->width;
}
// Bitmap is being drawn past either top or bottom edges
if (y + i > height - 1)
{
continue;
}
if (destfrom > size || destfrom < 0)
{
continue;
}
memcpy(&pixels[destfrom], &bmp->pixels[from], sizeof(unsigned int) * (to - from));
}
}
void blitRot(Bitmap *bmp, float x, float y, float rot)
{
float sine = std::sin(-rot);
float cosine = std::cos(-rot);
int x1 = (int)(-bmp->height * sine);
int y1 = (int)(bmp->height * cosine);
int x2 = (int)(bmp->width * cosine - bmp->height * sine);
int y2 = (int)(bmp->height * cosine + bmp->width * sine);
int x3 = (int)(bmp->width * cosine);
int y3 = (int)(bmp->width * sine);
int minx = (int)std::min(0, std::min(x1, std::min(x2, x3)));
int miny = (int)std::min(0, std::min(y1, std::min(y2, y3)));
int maxx = (int)std::max(0, std::max(x1, std::max(x2, x3)));
int maxy = (int)std::max(0, std::max(y1, std::max(y2, y3)));
int w = maxx - minx;
int h = maxy - miny;
int srcx;
int srcy;
int dest_x;
int dest_y;
unsigned int color;
for (int sy = miny; sy < maxy; sy++)
{
for (int sx = minx; sx < maxx; sx++)
{
srcx = sx * cosine + sy * sine;
srcy = sy * cosine - sx * sine;
dest_x = x + sx;
dest_y = y + sy;
if (dest_x <= width - 1 && dest_y <= height - 1
&& dest_x >= 0 && dest_y >= 0)
{
color = 0;
// Only grab a pixel if it is inside of the src image
if (srcx < bmp->width && srcy < bmp->height && srcx >= 0 &&
srcy >= 0)
color = bmp->getPixel(srcx, srcy);
// Only this pixel if it is not completely transparent:
if (color & 0xFF000000)
// Only if the pixel is somewhere between 0 and the bmp size
if (0 < srcx < bmp->width && 0 < srcy < bmp->height)
drawPixel(x + sx, y + sy, color);
}
}
}
}
void blitScl(Bitmap *bmp, float x, float y, float sclx, float scly)
{
// Create a bitmap that is scaled to the new size.
int finalwidth = (int)(bmp->width * sclx);
int finalheight = (int)(bmp->height * scly);
// Find how much each pixel steps:
float step_x = (float)bmp->width / (float)finalwidth;
float step_y = (float)bmp->height / (float)finalheight;
// Fill the scaled image with pixels!
float inx = 0;
int xOut = 0;
float iny;
int yOut;
while (xOut < finalwidth)
{
iny = 0;
yOut = 0;
while (yOut < finalheight)
{
unsigned int sample = bmp->pixels[
(int)(std::floor(inx) + std::floor(iny) * bmp->width)
];
drawPixel(xOut + x, yOut + y, sample);
iny += step_y;
yOut++;
}
inx += step_x;
xOut++;
}
}
public:
int width;
int height;
int size;
unsigned int *pixels;
};
Here is some code showing the latest method I have tried: saving up all instructions and then giving them to workers once they have all been received:
class Instruction
{
public:
Instruction() {}
Instruction(Bitmap* out, Bitmap* in, float x, float y, float rot,
float sclx, float scly)
: outbuffer(out), inbmp(in), x(x), y(y), rot(rot),
sclx(sclx), scly(scly)
{ }
~Instruction()
{
outbuffer = nullptr;
inbmp = nullptr;
}
public:
Bitmap* outbuffer;
Bitmap* inbmp;
float x, y, rot, sclx, scly;
};
Layer Class:
class Layer
{
public:
bool empty()
{
return instructions.size() > 0;
}
public:
std::vector<Instruction> instructions;
int pixel_count;
};
Worker Thread Class:
class Worker
{
public:
void start()
{
done = false;
work_thread = std::thread(&Worker::processData, this);
}
void processData()
{
while (true)
{
controller.lock();
if (done)
{
controller.unlock();
break;
}
if (!layers.empty())
{
for (int i = 0; i < layers.size(); i++)
{
for (int j = 0; j < layers[i].instructions.size(); j++)
{
Instruction* inst = &layers[i].instructions[j];
inst->outbuffer->blit(inst->inbmp, inst->x, inst->y, inst->rot, inst->sclx, inst->scly);
}
}
layers.clear();
}
controller.unlock();
}
}
void finish()
{
done = true;
}
public:
bool done;
std::thread work_thread;
std::mutex controller;
std::vector<Layer> layers;
};
Finally, the Render Manager Class:
class RenderManager
{
public:
RenderManager()
{
workers.reserve(std::thread::hardware_concurrency());
for (int i = 0; i < 1; i++)
{
workers.emplace_back();
workers.back().start();
}
}
void layer()
{
layers.push_back(current_layer);
current_layer = Layer();
}
void blit(Bitmap* out, Bitmap* in, float x, float y, float rot, float sclx, float scly)
{
current_layer.instructions.emplace_back(out, in, x, y, rot, sclx, scly);
}
void processInstructions()
{
if (layers.empty())
layer();
lockall();
int index = 0;
for (int i = 0; i < layers.size(); i++)
{
// Evenly distribute the layers in a round-robin fashion
Layer l = layers[i];
workers[index].layers.push_back(layers[i]);
index++;
if (index >= workers.size()) index = 0;
}
layers.clear();
unlockall();
}
void lockall()
{
for (int i = 0; i < workers.size(); i++)
{
workers[i].controller.lock();
}
}
void unlockall()
{
for (int i = 0; i < workers.size(); i++)
{
workers[i].controller.unlock();
}
}
void finish()
{
// Wait until every worker is done rendering
lockall();
// At this point, we know they have nothing more to draw
unlockall();
}
void endRendering()
{
for (int i = 0; i < workers.size(); i++)
{
// Send each one an exit code
workers[i].finish();
}
// Let the workers finish and then return
for (int i = 0; i < workers.size(); i++)
{
workers[i].work_thread.join();
}
}
private:
std::vector<Worker> workers;
std::vector<Layer> layers;
Layer current_layer;
};
Here is a screenshot of what the 3rd method I tried, and it's results:
Sending packages of draw instructions
What would really be helpful is that if someone could simply point me in the right direction in regards to what method I should try. I have tried these four methods and have failed, so I stand before those who have done greater things than I for help. The least intelligent person in the room is the one that does not ask questions because his pride does not permit it. Please keep in mind though, this is my first question ever on Stack Overflow.