Related
As the title suggests I'm having an issue with my windows console application in c++. So far I've created a class, Console, to represent all of the functions I repeatedly use to construct a windows console.
I implemented a line drawing algorithm and had up to 500 fps when filling the screen with magenta characters and drawing a line in white.
However, I implemented a triangle drawing algorithm next (just three line drawing calls consecutively) and was surprised to find out that the frame rate dropped to about 20. I removed this code again but the bad frame rate persisted. I really don't know why this has happend because I've essentially ended up not changing anything.
For reference, here is the Console code (without header):
Console::Console(int width, int height):
m_handle(GetStdHandle(STD_OUTPUT_HANDLE)),
m_width(width),
m_height(height),
m_screen({ 0, 0, 1, 1 }),
m_title(L"Demo"),
m_buffer(new CHAR_INFO[(size_t)m_width * (size_t)m_height])
{
memset(m_buffer, 0, sizeof(CHAR_INFO) * m_width * m_height);
}
Console::~Console()
{
if (m_buffer)
delete[] m_buffer;
}
int Console::Construct(int char_w, int char_h)
{
if (m_handle == INVALID_HANDLE_VALUE)
return BAD_HANDLE;
if (!SetConsoleWindowInfo(m_handle, true, &m_screen))
return WINDOW_INFO_ERROR;
COORD screen_coord = { (short)m_width, (short)m_height };
if (!SetConsoleScreenBufferSize(m_handle, screen_coord))
return SCREEN_BUFFER_SIZE_ERROR;
CONSOLE_FONT_INFOEX cinfo;
cinfo.cbSize = sizeof(cinfo);
cinfo.dwFontSize.X = (short)char_w;
cinfo.dwFontSize.Y = (short)char_h;
cinfo.FontFamily = FF_DONTCARE;
cinfo.FontWeight = FW_NORMAL;
cinfo.nFont = 0;
wcscpy_s(cinfo.FaceName, L"Consolas");
if (!SetCurrentConsoleFontEx(m_handle, false, &cinfo))
return CONSOLE_FONT_ERROR;
CONSOLE_SCREEN_BUFFER_INFO cbuffinfo;
if (!GetConsoleScreenBufferInfo(m_handle, &cbuffinfo))
return GET_BUFFER_INFO_ERROR;
if (cbuffinfo.dwMaximumWindowSize.X < m_width)
return HORIZONTAL_SIZE_TOO_LARGE_ERROR;
if (cbuffinfo.dwMaximumWindowSize.Y < m_height)
return VERTICAL_SIZE_TOO_LARGE_ERROR;
m_screen = { 0, 0, (short)m_width - 1, (short)m_height - 1 };
if (!SetConsoleWindowInfo(m_handle, true, &m_screen))
return WINDOW_INFO_ERROR;
if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)HandleClose, true))
return CLOSE_HANDLER_ERROR;
return OK;
}
void Console::Start()
{
active = true;
std::thread t(&Console::DoLoop, this);
t.join();
}
void Console::DoLoop()
{
if (!OnCreate())
active = false;
std::chrono::high_resolution_clock::time_point t1, t2;
t1 = std::chrono::high_resolution_clock::now();
t2 = t1;
while (active)
{
t2 = std::chrono::high_resolution_clock::now();
std::chrono::duration<float> diff = t2 - t1;
t1 = t2;
float dt = diff.count();
if (!OnUpdate(dt))
active = false;
wchar_t title_buff[256];
swprintf_s(title_buff, L"Console Application - %s - FPS: %3.2f", m_title.c_str(), 1.0f / dt);
SetConsoleTitle(title_buff);
WriteConsoleOutput(m_handle, m_buffer, { (short)m_width, (short)m_height }, { 0, 0 }, &m_screen);
}
finished.notify_one();
}
void Console::Fill(int x, int y, short glyph, short color)
{
if (x < 0 || y < 0 || x >= m_width || y >= m_height) return;
CHAR_INFO& ci = m_buffer[y * m_width + x];
ci.Char.UnicodeChar = glyph;
ci.Attributes = color;
}
void Console::Fill(int x1, int y1, int x2, int y2, short glyph, short color)
{
if (x1 < 0) x1 = 0;
if (y1 < 0) y1 = 0;
if (x1 >= m_width) return;
if (y1 >= m_height) return;
if (x2 >= m_width) x2 = m_width - 1;
if (y2 >= m_height) y2 = m_height - 1;
if (x2 < 0) return;
if (y2 < 0) return;
for (int x = x1; x <= x2; x++)
{
for (int y = y1; y <= y2; y++)
{
CHAR_INFO& ci = m_buffer[y * m_width + x];
ci.Char.UnicodeChar = glyph;
ci.Attributes = color;
}
}
}
void Console::Clear(short glyph, short color)
{
for (int x = 0; x < m_width; x++)
{
for (int y = 0; y < m_height; y++)
{
CHAR_INFO& ci = m_buffer[y * m_width + x];
ci.Char.UnicodeChar = glyph;
ci.Attributes = color;
}
}
}
void Console::Line(int x1, int y1, int x2, int y2, short glyph, short color)
{
int dx = x2 - x1;
int dy = y2 - y1;
int adx = dx > 0 ? dx : -dx;
int ady = dy > 0 ? dy : -dy;
int dy2 = dy + dy;
int dx2 = dx + dx;
int adx2 = dx2 > 0 ? dx2 : -dx2;
int ady2 = dy2 > 0 ? dy2 : -dy2;
if (adx > ady)
{
if (x1 > x2)
{
int x = x1;
x1 = x2;
x2 = x;
dx = -dx;
dx2 = -dx2;
int y = y1;
y1 = y2;
y2 = y;
dy = -dy;
dy2 = -dy2;
}
int sy = dy > 0 ? 1 : dy < 0 ? -1 : 0;
int err = ady;
for (int x = x1, y = y1; x <= x2; x++)
{
Fill(x, y, glyph, color);
err += ady2;
if (err > adx2)
{
err -= adx2;
y += sy;
}
}
}
else
{
if (y1 > y2)
{
int x = x1;
x1 = x2;
x2 = x;
dx = -dx;
dx2 = -dx2;
int y = y1;
y1 = y2;
y2 = y;
dy = -dy;
dy2 = -dy2;
}
int sx = dx > 0 ? 1 : dx < 0 ? -1 : 0;
int err = adx;
for (int x = x1, y = y1; y <= y2; y++)
{
Fill(x, y, glyph, color);
err += adx2;
if (err > ady2)
{
err -= ady2;
x += sx;
}
}
}
}
void Console::Triangle(int x1, int y1, int x2, int y2, int x3, int y3, short glyph, short color)
{
Line(x1, y1, x2, y2, glyph, color);
Line(x2, y2, x3, y3, glyph, color);
Line(x3, y3, x1, y1, glyph, color);
}
BOOL Console::HandleClose(DWORD evt)
{
if (evt == CTRL_CLOSE_EVENT)
{
active = false;
std::unique_lock<std::mutex> ul(lock);
finished.wait(ul);
}
return true;
}
Here is the very short main code:
class Game : public Console
{
public:
Game(int width, int height):
Console(width, height)
{
}
bool OnCreate() override
{
return true;
}
bool OnUpdate(float dt) override
{
Clear(GLYPH_SOLID, FG_BLACK);
//Triangle(20, 20, 300, 40, 150, 200);
return true;
}
};
int main()
{
Game game(400, 300);
int err = game.Construct(2, 2);
if (err == Console::OK)
game.Start();
}
The error constants are just defined integer codes in the header file.
If any additional code is needed please let me know. Thanks in advance!
EDIT:
I am newbie and having work with connected components labelling algorithm.
My purpose is that I need to find out 3 block of light points and then calculate the coordinates of the central point of each block (kind of image processing).
But after I run the for loop, I got the same coordinate for all the central points of three blocks, and don't know what was going wrong.
Could someone here please help me!
Thanks a lot!
This is my code
for (size_t i = 0; i < 128; i++)
{
for (size_t j = 0; j < 128; j++)
{
if (pInt[i * 128 + j] <= 18000) label[i][j] = 0;
if (pInt[i * 128 + j] > 18000)
{
if (label[i-1][j-1] != 0)
{
label[i][j] = label[i-1][j-1];
}
if (label[i-1][j] != 0)
{
label[i][j] = label[i-1][j];
}
if (label[i-1][j+1] != 0)
{
label[i][j] = label[i-1][j+1];
}
if (label[i][j-1] != 0)
{
label[i][j] = label[i][j-1];
}
if ((label[i - 1][j - 1] = 0) && (label[i - 1][j] = 0) && (label[i - 1][j + 1] = 0) && (label[i][j - 1] = 0))
{
l = l + 1;
label[i][j] = l;
}
}
if (label[i][j] = 1)
{
count1++;
sumx1 = sumx1 + i;
sumy1 = sumy1 + j;
}
if (label[i][j] = 2)
{
count2++;
sumx2 = sumx2 + i;
sumy2 = sumy2 + j;
}
if (label[i][j] = 3)
{
count3++;
sumx3 = sumx3 + i;
sumy3 = sumy3 + j;
}
}
}
float y1 = (float)sumx1 / count1;
float z1 = (float)sumy1 / count1;
float y2 = (float)sumx2 / count2;
float z2 = (float)sumy2 / count2;
float ya = (float)sumx3 / count3;
float za = (float)sumy3 / count3;
printf("three points:\n1(%f, %f)\n2(%f, %f)\na(%f, %f)\n", z1 - 64, 64 - y1, z2 - 64, 64 - y2, za - 64, 64 - ya);
In your if statements you need to use the == operator to compare. The single = is assignment. For example:
if (label[i][j] == 1)
There are 6 places I see where you need to make this change.
I'm writing myself a Newton Fractal Generator. The images all looked like this:
But I actually would like it to look a bit smoother - sure I've done some research and I ran over http://www.hiddendimension.com/FractalMath/Convergent_Fractals_Main.html and this looks rather correct, except that there are at the edges of the basins some issues..
This is my generation loop:
while (i < 6000 && fabs(z.r) < 10000 && !found){
f = computeFunction(z, params, paramc[0]);
d = computeFunction(z, paramsD, paramc[1]);
iterexp = iterexp + exp(-fabs(z.r) - 0.5 / (fabs(subComplex(zo, z).r)));
zo = z;
z = subComplex(z, divComplex(f, d));
i++;
for (int j = 0; j < paramc[0] - 1; j++){
if (compComplex(z, zeros[j], RESOLUTION)){
resType[x + xRes * y] = j;
result[x + xRes * y] = iterexp;
found = true;
break;
}
}
if (compComplex(z, zo, RESOLUTION/100)){
resType[x + xRes * y] = 12;
break;
}
}
The coloration:
const int xRes = res[0];
const int yRes = res[1];
for (int y = 0; y < fraktal->getHeight(); y++){
for (int x = 0; x < fraktal->getWidth(); x++){
int type, it;
double conDiv;
if (genCL && genCL->err == CL_SUCCESS){
conDiv = genCL->result[x + y * xRes];
type = genCL->typeRes[x + y * xRes];
it = genCL->iterations[x + y * xRes];
} else {
type = 3;
conDiv = runNewton(std::complex<double>((double)((x - (double)(xRes / 2)) / zoom[0]), (double)((y - (double)(yRes / 2)) / zoom[1])), type);
}
if (type < 15){
Color col;
col.setColorHexRGB(colors[type]);
col.setColorHSV(col.getHue(), col.getSaturation(), 1-conDiv);
fraktal->setPixel(x, y, col);
} else {
fraktal->setPixel(x, y, conDiv, conDiv, conDiv, 1);
}
}
}
I appreciate any help to actually smooth this ;-)
Thanks,
- fodinabor
I'm trying to implement Bresenham algorithms to have something like :
So in my main, I tested a lot of algorithm something like :
line(ushort x0, ushort y0, ushort x1, ushort y1, const Color couleur)
int dx = abs(x1-x0);
int dy = abs(y1-y0);
int sx,sy;
sx=sy=0;
if(x0 < x1) sx = 1;
else sx = -1;
if(y0 < y1) sy = 1;
else sy = -1;
int err = dx-dy;
while(1)
{
pixel(x0, y0) = couleur;
if(x0 == x1 && y0 == y1) break;
int e2 = 2* err;
if(e2 > -dy)
{
err = err - dy;
x0 = x0 + sx;
}
if(e2 < dy)
{
err = err + dx;
y0 = y0 + sy;
}
}
Or
ushort x=x1;
ushort y=y1;
int longX=x2-x1;
int longY=y2-y1;
if(longY<longX)
{ // 1er Octant
const int c1=2*(longY-longX);
const int c2=2*longY;
int critère=c2-longX;
while(x<=x2)
{
DessinePoint(x,y,couleur);
if(critère>=0)
{ // changement de ligne horizontale
y++;
critère=critère+c1;
}
else
// toujours la même ligne horizontale
critère=critère+c2;
x++; // ligne suivante, et recommence
}
}
else
{ // 2eme Octant
const int c1=2*(longX-longY);
const int c2=2*longX;
int critère=c2-longY;
while(y<=y2)
{
DessinePoint(x,y,couleur);
if(critère>=0)
{ // changement de ligne verticale
x++;
critère=critère+c1;
}
else
// toujours la même ligne verticale
critère=critère+c2;
y++; // ligne suivante, et recommence
}
}
for two octants.
I also tried what we can find in wikipedia, but nothing special.
A last function I tried to implement :
line(ushort xi, ushort yi, ushort xf, ushort yf, const Color couleur)
{
int dx,dy,i,xinc,yinc,cumul,x,y ;
x = xi ;
y = yi ;
dx = xf - xi ;
dy = yf - yi ;
xinc = ( dx > 0 ) ? 1 : -1 ;
yinc = ( dy > 0 ) ? 1 : -1 ;
dx = abs(dx) ;
dy = abs(dy) ;
pixel(x,y)= couleur;
if ( dx > dy )
{
cumul = dx / 2 ;
for ( i = 1 ; i <= dx ; i++ )
{
x += xinc ;
cumul += dy ;
if ( cumul >= dx )
{
cumul -= dx ;
y += yinc ;
}
pixel(x,y) = couleur ;
}
}
else
{
cumul = dy / 2 ;
for ( i = 1 ; i <= dy ; i++ )
{
y += yinc ;
cumul += dx ;
if ( cumul >= dy )
{
cumul -= dy ;
x += xinc ;
}
pixel(x,y) = couleur ;
}
}
So , someone know any solution ?
http://tech-algorithm.com/articles/drawing-line-using-bresenham-algorithm/
void LineBresenham(int x,int y,int x2, int y2, int color)
{
int w = x2 - x ;
int h = y2 - y ;
int dx1 = 0, dy1 = 0, dx2 = 0, dy2 = 0 ;
if (w<0) dx1 = -1 ; else if (w>0) dx1 = 1 ;
if (h<0) dy1 = -1 ; else if (h>0) dy1 = 1 ;
if (w<0) dx2 = -1 ; else if (w>0) dx2 = 1 ;
int longest = abs(w) ;
int shortest = abs(h) ;
if (!(longest>shortest))
{
longest = abs(h) ;
shortest = abs(w) ;
if (h<0) dy2 = -1 ;
else if (h>0) dy2 = 1 ;
dx2 = 0 ;
}
int numerator = longest >> 1 ;
for (int i=0;i<=longest;i++)
{
PlotPixel(x,y,color) ;
numerator += shortest ;
if (!(numerator<longest))
{
numerator -= longest ;
x += dx1 ;
y += dy1 ;
} else {
x += dx2 ;
y += dy2 ;
}
}
}
The octant can be generalized by some extra variables dx0, dy0, dx1, dy1.
if (error < delta)
{
x += dx0;
y += dy0;
} else {
x += dx1;
y += dy1;
}
Depending on the octant, one of dx0, dy0 is zero; also the "delta variables" can have negative values.
I'm trying to fix this triangle rasterizer, but cannot make it work correctly. For some reason it only draws half of the triangles.
void DrawTriangle(Point2D p0, Point2D p1, Point2D p2)
{
Point2D Top, Middle, Bottom;
bool MiddleIsLeft;
if (p0.y < p1.y) // case: 1, 2, 5
{
if (p0.y < p2.y) // case: 1, 2
{
if (p1.y < p2.y) // case: 1
{
Top = p0;
Middle = p1;
Bottom = p2;
MiddleIsLeft = true;
}
else // case: 2
{
Top = p0;
Middle = p2;
Bottom = p1;
MiddleIsLeft = false;
}
}
else // case: 5
{
Top = p2;
Middle = p0;
Bottom = p1;
MiddleIsLeft = true;
}
}
else // case: 3, 4, 6
{
if (p0.y < p2.y) // case: 4
{
Top = p1;
Middle = p0;
Bottom = p2;
MiddleIsLeft = false;
}
else // case: 3, 6
{
if (p1.y < p2.y) // case: 3
{
Top = p1;
Middle = p2;
Bottom = p0;
MiddleIsLeft = true;
}
else // case 6
{
Top = p2;
Middle = p1;
Bottom = p0;
MiddleIsLeft = false;
}
}
}
float xLeft, xRight;
xLeft = xRight = Top.x;
float mLeft, mRight;
// Region 1
if(MiddleIsLeft)
{
mLeft = (Top.x - Middle.x) / (Top.y - Middle.y);
mRight = (Top.x - Bottom.x) / (Top.y - Bottom.y);
}
else
{
mLeft = (Top.x - Bottom.x) / (Top.y - Bottom.y);
mRight = (Middle.x - Top.x) / (Middle.y - Top.y);
}
int finalY;
float Tleft, Tright;
for (int y = ceil(Top.y); y < (int)Middle.y; y++)
{
Tleft=float(Top.y-y)/(Top.y-Middle.y);
Tright=float(Top.y-y)/(Top.y-Bottom.y);
for (int x = ceil(xLeft); x <= ceil(xRight) - 1 ; x++)
{
FrameBuffer::SetPixel(x, y, p0.r,p0.g,p0.b);
}
xLeft += mLeft;
xRight += mRight;
finalY = y;
}
// Region 2
if (MiddleIsLeft)
{
mLeft = (Bottom.x - Middle.x) / (Bottom.y - Middle.y);
}
else
{
mRight = (Middle.x - Bottom.x) / (Middle.y - Bottom.y);
}
for (int y = Middle.y; y <= ceil(Bottom.y) - 1; y++)
{
Tleft=float(Bottom.y-y)/(Bottom.y-Middle.y);
Tright=float(Top.y-y)/(Top.y-Bottom.y);
for (int x = ceil(xLeft); x <= ceil(xRight) - 1; x++)
{
FrameBuffer::SetPixel(x, y, p0.r,p0.g,p0.b);
}
xLeft += mLeft;
xRight += mRight;
}
}
Here is what happens when I use it to draw shapes.
When I disable the second region, all those weird triangles disappear.
The wireframe mode works perfect, so this eliminates all the other possibilities other than the triangle rasterizer.
I kind of got lost in your implementation, but here's what I do (I have a slightly more complex version for arbitrary convex polygons, not just triangles) and I think apart from the Bresenham's algorithm it's very simple (actually the algorithm is simple too):
#include <stddef.h>
#include <limits.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#define SCREEN_HEIGHT 22
#define SCREEN_WIDTH 78
// Simulated frame buffer
char Screen[SCREEN_HEIGHT][SCREEN_WIDTH];
void SetPixel(long x, long y, char color)
{
if ((x < 0) || (x >= SCREEN_WIDTH) ||
(y < 0) || (y >= SCREEN_HEIGHT))
{
return;
}
Screen[y][x] = color;
}
void Visualize(void)
{
long x, y;
for (y = 0; y < SCREEN_HEIGHT; y++)
{
for (x = 0; x < SCREEN_WIDTH; x++)
{
printf("%c", Screen[y][x]);
}
printf("\n");
}
}
typedef struct
{
long x, y;
unsigned char color;
} Point2D;
// min X and max X for every horizontal line within the triangle
long ContourX[SCREEN_HEIGHT][2];
#define ABS(x) ((x >= 0) ? x : -x)
// Scans a side of a triangle setting min X and max X in ContourX[][]
// (using the Bresenham's line drawing algorithm).
void ScanLine(long x1, long y1, long x2, long y2)
{
long sx, sy, dx1, dy1, dx2, dy2, x, y, m, n, k, cnt;
sx = x2 - x1;
sy = y2 - y1;
if (sx > 0) dx1 = 1;
else if (sx < 0) dx1 = -1;
else dx1 = 0;
if (sy > 0) dy1 = 1;
else if (sy < 0) dy1 = -1;
else dy1 = 0;
m = ABS(sx);
n = ABS(sy);
dx2 = dx1;
dy2 = 0;
if (m < n)
{
m = ABS(sy);
n = ABS(sx);
dx2 = 0;
dy2 = dy1;
}
x = x1; y = y1;
cnt = m + 1;
k = n / 2;
while (cnt--)
{
if ((y >= 0) && (y < SCREEN_HEIGHT))
{
if (x < ContourX[y][0]) ContourX[y][0] = x;
if (x > ContourX[y][1]) ContourX[y][1] = x;
}
k += n;
if (k < m)
{
x += dx2;
y += dy2;
}
else
{
k -= m;
x += dx1;
y += dy1;
}
}
}
void DrawTriangle(Point2D p0, Point2D p1, Point2D p2)
{
int y;
for (y = 0; y < SCREEN_HEIGHT; y++)
{
ContourX[y][0] = LONG_MAX; // min X
ContourX[y][1] = LONG_MIN; // max X
}
ScanLine(p0.x, p0.y, p1.x, p1.y);
ScanLine(p1.x, p1.y, p2.x, p2.y);
ScanLine(p2.x, p2.y, p0.x, p0.y);
for (y = 0; y < SCREEN_HEIGHT; y++)
{
if (ContourX[y][1] >= ContourX[y][0])
{
long x = ContourX[y][0];
long len = 1 + ContourX[y][1] - ContourX[y][0];
// Can draw a horizontal line instead of individual pixels here
while (len--)
{
SetPixel(x++, y, p0.color);
}
}
}
}
int main(void)
{
Point2D p0, p1, p2;
// clear the screen
memset(Screen, ' ', sizeof(Screen));
// generate random triangle coordinates
srand((unsigned)time(NULL));
p0.x = rand() % SCREEN_WIDTH;
p0.y = rand() % SCREEN_HEIGHT;
p1.x = rand() % SCREEN_WIDTH;
p1.y = rand() % SCREEN_HEIGHT;
p2.x = rand() % SCREEN_WIDTH;
p2.y = rand() % SCREEN_HEIGHT;
// draw the triangle
p0.color = '1';
DrawTriangle(p0, p1, p2);
// also draw the triangle's vertices
SetPixel(p0.x, p0.y, '*');
SetPixel(p1.x, p1.y, '*');
SetPixel(p2.x, p2.y, '*');
Visualize();
return 0;
}
Output:
*111111
1111111111111
111111111111111111
1111111111111111111111
111111111111111111111111111
11111111111111111111111111111111
111111111111111111111111111111111111
11111111111111111111111111111111111111111
111111111111111111111111111111111111111*
11111111111111111111111111111111111
1111111111111111111111111111111
111111111111111111111111111
11111111111111111111111
1111111111111111111
11111111111111
11111111111
1111111
1*
The original code will only work properly with triangles that have counter-clockwise winding because of the if-else statements on top that determines whether middle is left or right. It could be that the triangles which aren't drawing have the wrong winding.
This stack overflow shows how to Determine winding of a 2D triangles after triangulation
The original code is fast because it doesn't save the points of the line in a temporary memory buffer. Seems a bit over-complicated even given that, but that's another problem.
The following code is in your implementation:
if (p0.y < p1.y) // case: 1, 2, 5
{
if (p0.y < p2.y) // case: 1, 2
{
if (p1.y < p2.y) // case: 1
{
Top = p0;
Middle = p1;
Bottom = p2;
MiddleIsLeft = true;
}
else // case: 2
{
Top = p0;
Middle = p2;
Bottom = p1;
MiddleIsLeft = false;
}
}
This else statement means that p2.y (or Middle) can equal p1.y (or Bottom). If this is true, then when region 2 runs
if (MiddleIsLeft)
{
mLeft = (Bottom.x - Middle.x) / (Bottom.y - Middle.y);
}
else
{
mRight = (Middle.x - Bottom.x) / (Middle.y - Bottom.y);
}
That else line will commit division by zero, which is not possible.