SDL / C++: How to make this function short(er)? - c++

I have this:
void showNumbers(){
nrBtn1 = TTF_RenderText_Blended( fontnrs, "1", sdlcolors[0] );
nrBtn2 = TTF_RenderText_Blended( fontnrs, "2", sdlcolors[1] );
nrBtn3 = TTF_RenderText_Blended( fontnrs, "3", sdlcolors[2] );
nrBtn4 = TTF_RenderText_Blended( fontnrs, "4", sdlcolors[3] );
nrBtn5 = TTF_RenderText_Blended( fontnrs, "5", sdlcolors[4] );
nrBtn6 = TTF_RenderText_Blended( fontnrs, "6", sdlcolors[5] );
nrBtn7 = TTF_RenderText_Blended( fontnrs, "7", sdlcolors[6] );
nrBtn8 = TTF_RenderText_Blended( fontnrs, "8", sdlcolors[7] );
nrBtn9 = TTF_RenderText_Blended( fontnrs, "9", sdlcolors[8] );
SDL_Rect rcnrBtn1 = { 40, 32, 0, 0 };
SDL_Rect rcnrBtn2 = { 70, 32, 0, 0 };
SDL_Rect rcnrBtn3 = { 100, 32, 0, 0 };
SDL_Rect rcnrBtn4 = { 130, 32, 0, 0 };
SDL_Rect rcnrBtn5 = { 160, 32, 0, 0 };
SDL_Rect rcnrBtn6 = { 190, 32, 0, 0 };
SDL_Rect rcnrBtn7 = { 220, 32, 0, 0 };
SDL_Rect rcnrBtn8 = { 250, 32, 0, 0 };
SDL_Rect rcnrBtn9 = { 280, 32, 0, 0 };
SDL_BlitSurface(nrBtn1, NULL, screen, &rcnrBtn1); SDL_FreeSurface(nrBtn1);
SDL_BlitSurface(nrBtn2, NULL, screen, &rcnrBtn2); SDL_FreeSurface(nrBtn2);
SDL_BlitSurface(nrBtn3, NULL, screen, &rcnrBtn3); SDL_FreeSurface(nrBtn3);
SDL_BlitSurface(nrBtn4, NULL, screen, &rcnrBtn4); SDL_FreeSurface(nrBtn4);
SDL_BlitSurface(nrBtn5, NULL, screen, &rcnrBtn5); SDL_FreeSurface(nrBtn5);
SDL_BlitSurface(nrBtn6, NULL, screen, &rcnrBtn6); SDL_FreeSurface(nrBtn6);
SDL_BlitSurface(nrBtn7, NULL, screen, &rcnrBtn7); SDL_FreeSurface(nrBtn7);
SDL_BlitSurface(nrBtn8, NULL, screen, &rcnrBtn8); SDL_FreeSurface(nrBtn8);
SDL_BlitSurface(nrBtn9, NULL, screen, &rcnrBtn9); SDL_FreeSurface(nrBtn9);
}
But for 60 buttons. Is there a way how to do something like:
void showNumbers()
{
SDL_Rect rcnrBtn1 = { 40, 32, 0, 0 };
SDL_Rect rcnrBtn2 = { 70, 32, 0, 0 };
SDL_Rect rcnrBtn3 = { 100, 32, 0, 0 };
SDL_Rect rcnrBtn4 = { 130, 32, 0, 0 };
SDL_Rect rcnrBtn5 = { 160, 32, 0, 0 };
SDL_Rect rcnrBtn6 = { 190, 32, 0, 0 };
SDL_Rect rcnrBtn7 = { 220, 32, 0, 0 };
SDL_Rect rcnrBtn8 = { 250, 32, 0, 0 };
SDL_Rect rcnrBtn9 = { 280, 32, 0, 0 };
for(int x=1; x<=60;x++){
nrBtn+x = TTF_RenderText_Blended( fontnrs, x, sdlcolors[x-1] );
SDL_BlitSurface(nrBtn+x, NULL, screen, &rcnrBtn+x); SDL_FreeSurface(nrBtn+x);
}
}

You need to use an array.
E.g.
SDL_Rect rcnrBtn[60];
for(int x = 0; x < 60; x++) {
rcnrBtn[x].x = 30 * x + 10;
rcnrBtn[x].y = 32;
rcnrBtn[x].w = 100;
rcnrBtn[x].h = 24;
}
Arrays always start at 0, and this particular one ends at 59 giving a total of 60 elements.

You could do something like this :
void showNumbers() {
SDL_Surface* nrBtn = NULL;
int nbr = 1;
int x = 30; // your starting x
int i = 0;
for (; i < 60; ++i) {
nrBtn = TTF_RenderText_Blended(fontnrs, nbr_to_str(nbr), sdlcolors[i]); // You'll have to code nbr_to_str
SDL_Rect rect = {x, 32, 0, 0}; // Are you sure that width and height are 0 ?
SDL_BlitSurface(nrBtn, NULL, screen, &rect);
SDL_FreeSurface(nrBtn);
x += 30;
}
return;
}

It appears your entire function could be replaced with the following array-based variation. Assuming that your nrBtnXX variables are defined outside the function and you want to minimise the scope of changes, you should look at something like:
#define BUTTON_COUNT 60
SDL_Surface *nrBtn[BUTTON_COUNT];
void showNumbers () {
char textNum[3];
for (int i = 0; i < BUTTON_COUNT; i++) {
sprintf (textNum, "%d", i);
nrBtn[i] = TTF_RenderText_Blended( fontnrs, textNum, sdlcolors[i] );
}
SDL_Rect rcnrBtn[BUTTON_COUNT];
for (int i = 0; i < BUTTON_COUNT; i++) {
rcnrBtn[i].x = 40 + i * 30; // use formulae for all these.
rcnrBtn[i].y = 32;
rcnrBtn[i].w = 0;
rcnrBtn[i].h = 0;
}
for (int i = 0; i < BUTTON_COUNT; i++) {
SDL_BlitSurface(nrBtn[i], NULL, screen, &rcnrBtn[i]);
SDL_FreeSurface(nrBtn[i]);
}
}
The idea is to store everything in arrays so that you don't have to deal with individual variables. If the nrBtn variables are required to be a non-array, then I would set up a single array of pointers to them so this approach would still work, something like:
SDL_Surface *nrBtnPtr[] = { &nrBtn1, &nrBtn2 ..., &nrBtn60 };
You should also set the formulae intelligently for the x and y coordinates since you probably don't want a 60x1 matrix for them. Look into making it 12x5 or something equally as compact.

Related

Convert Gdiplus::Region to ID2D1Geometry* for clipping

I am trying to migrate my graphics interface project from Gdiplus to Direct2D.
Currently, I have a code that calculates clipping area for an rendering object:
Graphics g(hdc);
Region regC = Rect(x, y, cx + padding[2] + padding[0], cy + padding[3] + padding[1]);
RecursRegPos(this->parent, &regC);
RecursRegClip(this->parent, &regC);
g.setClip(g);
...
inline void RecursRegClip(Object *parent, Region* reg)
{
if (parent == CARD_PARENT)
return;
if (parent->overflow != OVISIBLE)
{
Rect regC(parent->getAbsoluteX(), parent->getAbsoluteY(), parent->getCx(), parent->getCy()); // Implementation of these function is not necceassary
GraphicsPath path;
path.Reset();
GetRoundRectPath(&path, regC, parent->borderRadius[0]);
// source https://stackoverflow.com/a/71431813/15220214, but if diameter is zero, just plain rect is returned
reg->Intersect(&path);
}
RecursRegClip(parent->parent, reg);
}
inline void RecursRegPos(Object* parent, Rect* reg)
{
if (parent == CARD_PARENT)
return;
reg->X += parent->getX() + parent->padding[0];
reg->Y += parent->getY() + parent->padding[1];
if (parent->overflow == OSCROLL || parent->overflow == OSCROLLH)
{
reg->X -= parent->scrollLeft;
reg->Y -= parent->scrollTop;
}
RecursRegPos(parent->parent, reg);
}
And now I need to convert it to Direct2D. As You may notice, there is no need to create Graphics object to get complete calculated clipping region, so I it would be cool if there is way to just convert Region to ID2D1Geometry*, that, as far, as I understand from msdn article need to create clipping layer.
Also, there is probably way to convert existing code (RecursRegClip, RecursRegPos) to Direct2D, but I am facing problems, because I need to work with path, but current functions get region as an argument.
Update 1
There is Region::GetData method that returns, as I understand array of points, so maybe there is possibility to define either ID2D1PathGeometry or ID2D1GeometrySink by points?
Update 2
Oh, maybe
ID2D1GeometrySink::AddLines(const D2D1_POINT_2F *points, UINT32 pointsCount)
is what do I need?
Unfortunately, GetData of region based on just (0,0,4,4) rectangle returns 36 mystique values:
Region reg(Rect(0, 0, 4, 4));
auto so = reg.GetDataSize();
BYTE* are = new BYTE[so];
UINT fi = 0;
reg.GetData(are, so, &fi);
wchar_t ou[1024]=L"\0";
for (int i = 0; i < fi; i++)
{
wchar_t buf[10] = L"";
_itow_s(are[i], buf, 10, 10);
wcscat_s(ou, 1024, buf);
wcscat_s(ou, 1024, L", ");
}
// ou - 28, 0, 0, 0, 188, 90, 187, 128, 2, 16, 192, 219, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 64, 0, 0, 128, 64,
I rewrote the solution completely, it seems to be working:
// zclip is ID2D1PathGeometry*
inline void Render(ID2D1HwndRenderTarget *target)
{
ID2D1RoundedRectangleGeometry* mask = nullptr;
ID2D1Layer* clip = nullptr;
if(ONE_OF_PARENTS_CLIPS_THIS || THIS_HAS_BORDER_RADIUS)
{
Region reg = Rect(x, y, cx + padding[2] + padding[0], cy + padding[3] + padding[1]);
RecursRegPos(this->parent, &reg);
D2D1_ROUNDED_RECT maskRect;
maskRect.rect.left = reg.X;
maskRect.rect.top = reg.Y;
maskRect.rect.right = reg.X + reg.Width;
maskRect.rect.bottom = reg.Y + reg.Height;
maskRect.radiusX = this->borderRadius[0];
maskRect.radiusY = this->borderRadius[1];
factory->CreateRoundedRectangleGeometry(maskRect, &mask);
RecursGeoClip(this->parent, mask);
target->CreateLayer(NULL, &clip);
if(zclip)
target->PushLayer(D2D1::LayerParameters(D2D1::InfiniteRect(), zclip), clip);
else
target->PushLayer(D2D1::LayerParameters(D2D1::InfiniteRect(), mask), clip);
SafeRelease(&mask);
}
// Draw stuff here
if (clip)
{
target->PopLayer();
SafeRelease(&clip);
SafeRelease(&mask);
SafeRelease(&zclip);
}
}
...
inline void RecursGeoClip(Object* parent, ID2D1Geometry* geo)
{
if (parent == CARD_PARENT)
return;
ID2D1RoundedRectangleGeometry* maskParent = nullptr;
if (parent->overflow != OVISIBLE)
{
Rect regC(parent->getAbsoluteX(), parent->getAbsoluteY(), parent->getCx(), parent->getCy());
ID2D1GeometrySink* sink = nullptr;
ID2D1PathGeometry* path = nullptr;
SafeRelease(&path);
factory->CreatePathGeometry(&path);
D2D1_ROUNDED_RECT maskRect;
maskRect.rect.left = regC.X;
maskRect.rect.top = regC.Y;
maskRect.rect.right = regC.X + regC.Width;
maskRect.rect.bottom = regC.Y + regC.Height;
maskRect.radiusX = parent->borderRadius[0];
maskRect.radiusY = parent->borderRadius[1];
path->Open(&sink);
factory->CreateRoundedRectangleGeometry(maskRect, &maskParent);
geo->CombineWithGeometry(maskParent, D2D1_COMBINE_MODE_INTERSECT, NULL, sink);
sink->Close();
SafeRelease(&sink);
SafeRelease(&this->zclip);
this->zclip = path;
RecursGeoClip(parent->parent, this->zclip);
}
else
RecursGeoClip(parent->parent, geo);
SafeRelease(&maskParent);
}
Now I can enjoy drawing one image and two rectangles in more than 60 fps, instead of 27 (in case of 200x200 image size, higher size - lower fps) with Gdi+ -_- :

Seg Fault when calling SDL_BlitSurface a second time

I'm using SDL and SDL_Image to load images to be used as textures for opengl.
I'm trying to load a spritesheet with multiple images arranged in a horizontal row (in the same image)
void load_spritesheet(std::string key, const char *file_name, int width, int height, int nframes) {
GLuint *texture = new GLuint[nframes];
auto src = IMG_Load(file_name);
auto dstrect = new SDL_Rect{0, 0, width, height};
for(int i = 0; i < nframes; i++) {
auto dst = SDL_CreateRGBSurface(0, width, height, 1, src->format->Rmask, src->format->Gmask, src->format->Bmask, src->format->Amask);
auto rect = new SDL_Rect { i*width, 0, width, height };
SDL_BlitSurface(src, rect, dst, dstrect);
load_gltex(dst, &texture[i]);
SDL_FreeSurface(dst);
}
SPRITESHEET_CACHE[key] = texture;
SDL_FreeSurface(src);
}
I stepped through the code, and on the first iteration of the loop it works fine. On the second iteration I get a seg fault on the call to SDL_BlitSurface, none of the pointers passed in are NULL and none of the surfaces are locked or anything like that. I'm sure that my rectangles are within the bounds of each surface.
Here's some values from gdb at the point right before it segfaults:
print i
1
print *src
{flags = 0, format = 0x847100, w = 416, h = 32, pitch = 1664, pixels = 0x87c5f0, userdata = 0x0, locked = 0, lock_data = 0x0, clip_rect = {x = 0, y = 0, w = 416, h = 32}, map = 0x8537b0, refcount = 1}
print *dst
{flags = 0, format = 0x855ec0, w = 32, h = 32, pitch = 4, pixels = 0x84d1f0, userdata = 0x0, locked = 0, lock_data = 0x0, clip_rect = {x = 0, y = 0, w = 32, h = 32}, map = 0x6bdfc0, refcount = 1}
print *rect
{x = 32, y = 0, w = 32, h = 32}
print *dstrect
{x = 0, y = 0, w = 32, h = 32}
Is it unsafe to call SDL_BlitSurface twice on the same surface or something like that? Thanks.
Ah, the error was caused by improperly setting the depth on the call to SDL_CreateRGBSurface
I was passing in a 1 when I really should've been passing the correct value (32 in this case)
Once I corrected that the segfault went away.
https://wiki.libsdl.org/SDL_CreateRGBSurface#Remarks

How to create .swf file from multi .JPG images using swflib library in Visual C++?

I'm writing a c++ code to create a animate SWF file from multi JPG pictures.
Now, I have pictures like "image_0" to "image_100". I find a swflib library. I think it will help. So far, I can use this library's method to create .SWF file and the size of .SWF file is the sum of pictures in .JPG format.
So, I think I almost done. But ,SWF does not play. I am crazy.
Below this is the code which I modified:
`void CSWFLIBTestProjectDlg::CreateSWFMovie_Bitmap()
{
// Set movie params
SIZE_F movieSize = {100, 100};
int frameRate = 14;
POINT_F pt;
// Create empty .SWF file
CSWFMovie swfMovie;
swfMovie.OpenSWFFile(_T("SWF Sample Movies/Sample2.swf"), movieSize, frameRate);
SWF_RGB bgColor = {255, 255, 255};
swfMovie.SetBackgroundColor(bgColor);
// Define bitmap object
CSWFBitmap bitmap(2, (UCHAR*)"gif_0.jpg");//bm128
swfMovie.DefineObject(&bitmap, -1, true);
// Define custom shape
RECT_F shapeRect = {0, 0, 1000, 1000};
CSWFShape shape(1, shapeRect, 1);
SWF_RGBA lineColor = {0, 0, 0, 255};
shape.AddLineStyle(0, lineColor);
RECT_F bitmapRect = {0, 0, 1246, 622}; // size of the bitmap
RECT_F clipRect = {0, 0, 100, 100}; // where to fill
shape.AddBitmapFillStyle(bitmap.m_ID, SWF_FILLSTYLETYPE_BITMAP_0, bitmapRect, clipRect);
pt.x = 0;
pt.y = 0;
shape.ChangeStyle(1, 1, 0, &pt);
shape.AddLineSegment(100, 0);
shape.AddLineSegment(0, 100);
shape.AddLineSegment(-100, 0);
shape.AddLineSegment(0, -100);
swfMovie.DefineObject(&shape, shape.m_Depth, true);
swfMovie.ShowFrame();
/****************************************
* move
****************************************/
float i;
for (i=0; i<10; i++)
{
shape.Translate(i, 0);
swfMovie.UpdateObject(&shape, shape.m_Depth, NULL, -1);
swfMovie.ShowFrame();
//if (i>200)
//{
// swfMovie.RemoveObject(shape.m_Depth);
//}
}
/****************************************
* add jpg
****************************************/
swfMovie.RemoveObject(shape.m_Depth);
for (int i=1;i<=100;i++)
{
char filename[100];
sprintf(filename,"gif_%d%s",i,".jpg");
CSWFBitmap bitmap2(3, (UCHAR*)filename);//gif_0
swfMovie.DefineObject(&bitmap2, -1, true);//
// Define custom shape
RECT_F shapeRect2 = {0, 0, 1000, 1000};
CSWFShape shape2(1, shapeRect2, 1);
SWF_RGBA lineColor2 = {0, 0, 0, 255};
shape2.AddLineStyle(0, lineColor2);
RECT_F bitmapRect2 = {0, 0, 1246, 622}; // size of the bitmap
RECT_F clipRect2 = {0, 0, 100, 100}; // where to fill
shape2.AddBitmapFillStyle(bitmap2.m_ID, SWF_FILLSTYLETYPE_BITMAP_0, bitmapRect2, clipRect2);
pt.x = 0;
pt.y = 0;
shape2.ChangeStyle(1, 1, 0, &pt);
shape2.AddLineSegment(100, 0);
shape2.AddLineSegment(0, 100);
shape2.AddLineSegment(-100, 0);
shape2.AddLineSegment(0, -100);
swfMovie.UpdateObject(&shape2, shape2.m_Depth, NULL, -1);
//swfMovie.DefineObject(&shape2, shape2.m_Depth, true);
swfMovie.ShowFrame();
//swfMovie.RemoveObject(shape2.m_Depth);
}
// Close .SWF file
swfMovie.CloseSWFFile();
}`
can you tell me what's wrong with my code ? Thanks in advance.
I have found a way to create a .SWF file from a set of image using C++,and a swflib library.you can download it from the lib file website .in which there is a method named :CreateSWFMovie_Bitmap() here is the code i modified
void CSWFLIBTestProjectDlg::CreateSWFMovie_Bitmap()
{
SIZE_F movieSize = {4000, 4000};
int frameRate = 40;
POINT_F pt;
CSWFMovie swfMovie;
swfMovie.OpenSWFFile(_T("SWF Sample Movies/Sample2.swf"), movieSize, frameRate);
SWF_RGB bgColor = {255, 255, 255};
swfMovie.SetBackgroundColor(bgColor);
CSWFBitmap bitmap(2, (UCHAR*)"bm128.jpg");
swfMovie.DefineObject(&bitmap, -1, false);
RECT_F shapeRect = {0, 0, 1000, 1000};//shape大小
CSWFShape shape(1, shapeRect, 1);
SWF_RGBA lineColor = {0, 0, 0, 255};
shape.AddLineStyle(3, lineColor);
RECT_F bitmapRect = {0, 0, 128, 128}; // size of the bitmap
RECT_F clipRect = {0, 0, 100, 100}; // where to fill
shape.AddBitmapFillStyle(bitmap.m_ID, SWF_FILLSTYLETYPE_BITMAP_0, bitmapRect, clipRect);
pt.x = 0;
pt.y = 0;
shape.ChangeStyle(1, 1, 0, &pt);
shape.AddLineSegment(100, 0);
shape.AddLineSegment(0, 100);
shape.AddLineSegment(-100, 0);
shape.AddLineSegment(0, -100);
swfMovie.DefineObject(&shape, shape.m_Depth, true);
swfMovie.ShowFrame();
for (int i=0; i<100; i++)
{
char filename[50]="";
sprintf(filename,"gif_%d%s",i,".jpg");
CSWFBitmap bitmap2(300+i, (UCHAR*)filename);
swfMovie.DefineObject(&bitmap2, -1, false);//false
CSWFShape shape2(7+i,shapeRect,2+i);//depth值也不能一样 id不能重复
shape2.AddLineStyle(3, lineColor);
RECT_F bitmapRect2 = {0, 0, 128, 128};
RECT_F clipRect2 = {0, 0, 100, 100};
shape2.AddBitmapFillStyle(bitmap2.m_ID, SWF_FILLSTYLETYPE_BITMAP_0, bitmapRect2, clipRect2);
pt.x = 0;
pt.y = 0;
shape2.ChangeStyle(1, 1, 0, &pt);
shape2.AddLineSegment(1000, 0);
shape2.AddLineSegment(0, 1000);
shape2.AddLineSegment(-1000, 0);
shape2.AddLineSegment(0, -1000);
shape2.Translate(80, 0);
swfMovie.DefineObject(&shape2, shape2.m_Depth, true);//必须define才能动 可以不update
swfMovie.ShowFrame();
}
}

Wrong display depending on displayed bitmap size

I am having a trouble in a SDI view.
I am using the following code to display a bitmap buffer.
Depending on the width of the bitmap and when the width is becoming large, then a flickering problem occurs.
Additionally, the view is displaying weird data as seen below :
Here is the expected display.
The code I am using for this view is here :
void CTestLargeView::RefreshDisplay()
{
CClientDC dc(this);
CRect cRect;
GetClientRect(&cRect);
LPBITMAPINFO pBmpInfo;
pBmpInfo = (LPBITMAPINFO) new BYTE[sizeof(BITMAPINFOHEADER) + 256 * sizeof(RGBQUAD)];
pBmpInfo->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
pBmpInfo->bmiHeader.biWidth = XSize;
pBmpInfo->bmiHeader.biHeight = YSize;
pBmpInfo->bmiHeader.biPlanes = 1;
pBmpInfo->bmiHeader.biBitCount = 32;
pBmpInfo->bmiHeader.biCompression = BI_RGB;
pBmpInfo->bmiHeader.biSizeImage = XSize * YSize;
pBmpInfo->bmiHeader.biXPelsPerMeter = 0;
pBmpInfo->bmiHeader.biYPelsPerMeter = 0;
pBmpInfo->bmiHeader.biClrUsed = 0;
pBmpInfo->bmiHeader.biClrImportant = 0;
SetStretchBltMode(dc.m_hDC, STRETCH_DELETESCANS);
StretchDIBits(dc.m_hDC,
0,
0,
cRect.Width(),
cRect.Height(),
0,
0,
XSize,
YSize,
Data,
pBmpInfo,
DIB_RGB_COLORS,
SRCCOPY);
delete[] pBmpInfo;
}
void CTestLargeView::OnTimer(UINT_PTR nIDEvent)
{
if (nIDEvent == 150)
RefreshDisplay();
CView::OnTimer(nIDEvent);
}
int CTestLargeView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CView::OnCreate(lpCreateStruct) == -1)
return -1;
SetTimer(150, 33, NULL);
XSize = 32000; // No flickering
//XSize = 32800; // Flickering occurring
YSize = 256;
Data = new int[XSize * YSize];
for (int i = 0 ; i < XSize * YSize ; i++)
Data[i] = RGB(i % 255, i % 255, i % 255);
return 0;
}
Thanks !
Found the problem->
It is caused by the "STRETCH_DELETESCANS". Changing it to STRETCH_HALFTONE is correcting the trouble.

Set console window size on Windows

I know that there is a lot questions about how to set console size. But all found solutions are the same to my and my code doesn't works for me.
Ok, so for setting console window size, I need two functions. They are SetConsoleScreenBufferSize() and SetConsoleWindowInfo(). First version of my function:
bool SetWindowSize(size_t width, size_t height)
{
HANDLE output_handle = ::GetStdHandle(STD_OUTPUT_HANDLE);
if(output_handle == INVALID_HANDLE_VALUE)
return false;
COORD coord = {};
coord.X = static_cast<SHORT>(width);
coord.Y = static_cast<SHORT>(height);
if(::SetConsoleScreenBufferSize(output_handle, coord) == FALSE)
return false;
SMALL_RECT rect = {};
rect.Bottom = coord.X - 1;
rect.Right = coord.Y - 1;
return (::SetConsoleWindowInfo(output_handle, TRUE, &rect) != FALSE);
}
SetConsoleScreenBufferSize() will work not for all values. From documentation:
The specified width and height cannot be less than the width and
height of the console screen buffer's window
Lets try to get current window's size and call our function. To get window size, I need GetConsoleScreenBufferInfo() function. main() test code:
HANDLE output_handle = ::GetStdHandle(STD_OUTPUT_HANDLE);
if(output_handle == INVALID_HANDLE_VALUE)
return 0;
CONSOLE_SCREEN_BUFFER_INFO info = {};
if(::GetConsoleScreenBufferInfo(output_handle, &info) == FALSE)
return 0;
size_t width = info.srWindow.Right - info.srWindow.Left;
size_t height = info.srWindow.Bottom - info.srWindow.Top;
bool suc = SetWindowSize(width + 1, height + 1);
In this case SetConsoleScreenBufferSize() works fine. Next function is SetConsoleWindowInfo(). This function will work in case:
The function fails if the specified window rectangle extends beyond
the boundaries of the console screen buffer. This means that the Top
and Left members of the lpConsoleWindow rectangle (or the calculated
top and left coordinates, if bAbsolute is FALSE) cannot be less than
zero. Similarly, the Bottom and Right members (or the calculated
bottom and right coordinates) cannot be greater than (screen buffer
height – 1) and (screen buffer width – 1), respectively. The function
also fails if the Right member (or calculated right coordinate) is
less than or equal to the Left member (or calculated left coordinate)
or if the Bottom member (or calculated bottom coordinate) is less than
or equal to the Top member (or calculated top coordinate).
In our case, the values of rectangle are the same (because Left and Top are zeroes) as values of info.srWindow rectangle after call of GetConsoleScreenBufferInfo(). But! SetConsoleWindowInfo() fails with next ::GetLastError()
#err,hr ERROR_INVALID_PARAMETER : The parameter is incorrect. unsigned int
If I swap calls of this two functions:
bool SetWindowSize(size_t width, size_t height)
{
HANDLE output_handle = ::GetStdHandle(STD_OUTPUT_HANDLE);
if(output_handle == INVALID_HANDLE_VALUE)
return false;
SMALL_RECT rect = {};
rect.Bottom = static_cast<SHORT>(width);
rect.Right = static_cast<SHORT>(height);
if(::SetConsoleWindowInfo(output_handle, TRUE, &rect) == FALSE)
return false;
COORD coord = {};
coord.X = rect.Bottom + 1;
coord.Y = rect.Right + 1;
return (::SetConsoleScreenBufferSize(output_handle, coord) != FALSE);
}
then I will have the same error.
So, how can I use SetConsoleScreenBufferSize() and SetConsoleWindowInfo() correctly ?
SetConsoleWindowInfo() does not reposition the console window on the screen. The name of this function is misleading. It rather scrolls the current visible portion inside the console window. See this sample here.
If you want to set the position of a console window that runs your programm, use code such as:
HWND hwnd = GetConsoleWindow();
RECT rect = {100, 100, 300, 500};
MoveWindow(hwnd, rect.left, rect.top, rect.right-rect.left, rect.bottom-rect.top,TRUE);
From the TurboVision port:
void TDisplay::setCrtMode( ushort mode )
{
int oldr = getRows();
int oldc = getCols();
int cols = uchar(mode >> 8);
int rows = uchar(mode);
if ( cols == 0 ) cols = oldc;
if ( rows == 0 ) rows = oldr;
checksize(rows, cols);
COORD newSize = { cols, rows };
SMALL_RECT rect = { 0, 0, cols-1, rows-1 };
if ( oldr <= rows )
{
if ( oldc <= cols )
{ // increasing both dimensions
BUFWIN:
SetConsoleScreenBufferSize( TThreads::chandle[cnOutput], newSize );
SetConsoleWindowInfo( TThreads::chandle[cnOutput], True, &rect );
}
else
{ // cols--, rows+
SMALL_RECT tmp = { 0, 0, cols-1, oldr-1 };
SetConsoleWindowInfo( TThreads::chandle[cnOutput], True, &tmp );
goto BUFWIN;
}
}
else
{
if ( oldc <= cols )
{ // cols+, rows--
SMALL_RECT tmp = { 0, 0, oldc-1, rows-1 };
SetConsoleWindowInfo( TThreads::chandle[cnOutput], True, &tmp );
goto BUFWIN;
}
else
{ // cols--, rows--
SetConsoleWindowInfo( TThreads::chandle[cnOutput], True, &rect );
SetConsoleScreenBufferSize( TThreads::chandle[cnOutput], newSize );
}
}
GetConsoleScreenBufferInfo( TThreads::chandle[cnOutput], &TThreads::sbInfo );
}
ushort TDisplay::getRows()
{
GetConsoleScreenBufferInfo( TThreads::chandle[cnOutput], &TThreads::sbInfo );
return TThreads::sbInfo.dwSize.Y;
}
ushort TDisplay::getCols()
{
GetConsoleScreenBufferInfo( TThreads::chandle[cnOutput], &TThreads::sbInfo );
return TThreads::sbInfo.dwSize.X;
}
I solved this issue by making these functions which can get/set the console window/buffer sizes in characters taking into account increasing the buffer size if needed, console font size, window borders and all that jazz.
The variables at play here to understand:
Windows have a client area, which is the coordinates (in pixels) excluding the borders
Windows have a window area, which is the coordinates (in pixels) including the borders
Console has a view area, which is the window size in characters
Console has a screen buffer, which is the scrollable buffer size in characters
Console has a font size, which is the character size in coordinates (pixels)
Console's screen buffer cannot be smaller than the view area
You need to correctly mix and match these around to achieve the desired result.
These functions are plug-n-play so you don't need to worry about none of that though.
All functions return TRUE (1) on success and FALSE (0) on error.
Set Console Window Size
static BOOL SetConsoleSize(int cols, int rows) {
HWND hWnd;
HANDLE hConOut;
CONSOLE_FONT_INFO fi;
CONSOLE_SCREEN_BUFFER_INFO bi;
int w, h, bw, bh;
RECT rect = {0, 0, 0, 0};
COORD coord = {0, 0};
hWnd = GetConsoleWindow();
if (hWnd) {
hConOut = GetStdHandle(STD_OUTPUT_HANDLE);
if (hConOut && hConOut != (HANDLE)-1) {
if (GetCurrentConsoleFont(hConOut, FALSE, &fi)) {
if (GetClientRect(hWnd, &rect)) {
w = rect.right-rect.left;
h = rect.bottom-rect.top;
if (GetWindowRect(hWnd, &rect)) {
bw = rect.right-rect.left-w;
bh = rect.bottom-rect.top-h;
if (GetConsoleScreenBufferInfo(hConOut, &bi)) {
coord.X = bi.dwSize.X;
coord.Y = bi.dwSize.Y;
if (coord.X < cols || coord.Y < rows) {
if (coord.X < cols) {
coord.X = cols;
}
if (coord.Y < rows) {
coord.Y = rows;
}
if (!SetConsoleScreenBufferSize(hConOut, coord)) {
return FALSE;
}
}
return SetWindowPos(hWnd, NULL, rect.left, rect.top, cols*fi.dwFontSize.X+bw, rows*fi.dwFontSize.Y+bh, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOZORDER);
}
}
}
}
}
}
return FALSE;
}
/* usage */
SetConsoleSize(80, 40);
Get Console Window Size
static BOOL GetConsoleSize(int* cols, int* rows) {
HWND hWnd;
HANDLE hConOut;
CONSOLE_FONT_INFO fi;
int w, h;
RECT rect = {0, 0, 0, 0};
hWnd = GetConsoleWindow();
if (hWnd) {
hConOut = GetStdHandle(STD_OUTPUT_HANDLE);
if (hConOut && hConOut != (HANDLE)-1) {
if (GetCurrentConsoleFont(hConOut, FALSE, &fi)) {
if (GetClientRect(hWnd, &rect)) {
w = rect.right-rect.left;
h = rect.bottom-rect.top;
*cols = w / fi.dwFontSize.X;
*rows = h / fi.dwFontSize.Y;
return TRUE;
}
}
}
}
return FALSE;
}
/* usage */
int cols, rows;
GetConsoleSize(&cols, &rows);
Set Console Buffer Size
static BOOL SetConsoleBufferSize(int cols, int rows) {
HANDLE hConOut;
CONSOLE_SCREEN_BUFFER_INFO bi;
COORD coord = {0, 0};
hConOut = GetStdHandle(STD_OUTPUT_HANDLE);
if (hConOut && hConOut != (HANDLE)-1) {
if (GetConsoleScreenBufferInfo(hConOut, &bi)) {
coord.X = cols;
coord.Y = rows;
return SetConsoleScreenBufferSize(hConOut, coord);
}
}
return FALSE;
}
/* usage */
SetConsoleBufferSize(80, 300);
Get Console Buffer Size
static BOOL GetConsoleBufferSize(int* cols, int* rows) {
HANDLE hConOut;
CONSOLE_SCREEN_BUFFER_INFO bi;
hConOut = GetStdHandle(STD_OUTPUT_HANDLE);
if (hConOut && hConOut != (HANDLE)-1) {
if (GetConsoleScreenBufferInfo(hConOut, &bi)) {
*cols = bi.dwSize.X;
*rows = bi.dwSize.Y;
return TRUE;
}
}
return FALSE;
}
/* usage */
int cols, rows;
GetConsoleBufferSize(&cols, &rows);