Convert Gdiplus::Region to ID2D1Geometry* for clipping - c++

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+ -_- :

Related

L_LineRemoveBitmap returns an empty HRGN

I'm trying to use Leadtools Version 20 to automatically cleanup some images (black border removal, line removal, deskew, ...).
Since some of the APIs only work with black and white images, I create a copy of the image in memory and turn it black and white using L_ColorResBitmap. My plan is to use this black and white image to do the processing and then process the colored image manually. For example I use L_BorderRemoveBitmap to figure out the region that needs to be wiped out and then wipe the same region on the color image or use L_DeskewBitmap to figure out the angle that black and white image needs to be turned and then use L_RotateBitmap to turn the colored image. But when I use L_LineRemoveBitmap, it returns an empty region. I even tried to use the callback function, but inside the callback function region is always NULL.I have made sure the image that is being loaded has a vertical line in it and if I save the black and white version the line is removed, but the correct region is not handed back.Here is a snippet of what I'm doing:
FILEINFO fi;
L_INT PageCount;
L_INT i;
L_UINT uFlags;
BITMAPHANDLE tBmp;
BITMAPHANDLE bwBmp;
BORDERREMOVE br = {sizeof(BORDERREMOVE), BORDER_SINGLE_REGION, BORDER_ALL, 25, 4, 10, NULL, nullptr, sizeof(BITMAPHANDLE)};
RECT r;
LINEREMOVE lr = {sizeof(LINEREMOVE), LINE_SINGLE_REGION, 400, 12, 15, 10, 2, 0, LINEREMOVE_VERTICAL, NULL, nullptr, sizeof(BITMAPHANDLE)};
memset(&fi, 0, sizeof(FILEINFO));
fi.uStructSize = sizeof(FILEINFO);
plo->PageNumber = 0; // plo is a LOADFILEOPTION*
L_FileInfo(FileName, &fi, sizeof(FILEINFO), FILEINFO_TOTALPAGES, plo); // ok
PageCount = fi.TotalPages;
for(i = 0; i < PageCount; i++)
{
memset(&fi, 0, sizeof(FILEINFO));
fi.uStructSize = sizeof(FILEINFO);
plo->PageNumber = i + 1;
memset(&tBmp, 0, sizeof(BITMAPHANDLE));
FileInfo(FileName, &fi, sizeof(FILEINFO), 0, plo); // OK
if(tBmp.Flags.Allocated)
L_FreeBitmap(&tBmp);
L_LoadBitmap(FileName, &tBmp, sizeof(BITMAPHANDLE), fi.BitsPerPixel > 24 ? 24 : fi.BitsPerPixel, ORDER_RGBORGRAY, plo, &fi); // OK
if(tBmp.Flags.Allocated)
{
if (TOP_LEFT != tBmp.ViewPerspective)
L_ChangeBitmapViewPerspective(NULL, &tBmp, sizeof(BITMAPHANDLE), TOP_LEFT);
uFlags = DSKW_PROCESS | DSKW_FILL | DSKW_DOCUMENTANDPICTURE | DSKW_BICUBIC | DSKW_NORMALSPEEDROTATE;
if(1 != fi.BitsPerPixel)
uFlags |= (DSKW_DONT_PERFORM_PREPROCESSING | DSKW_NORMAL_DETECTION);
memset(&BitmapRegion, 0, sizeof(BITMAPHANDLE));
BitmapRegion.uStructSize = sizeof(BITMAPHANDLE);
if(bwBmp.Flags.Allocated)
L_FreeBitmap(&bwBmp);
memset(&bwBmp, 0, sizeof(BITMAPHANDLE));
bwBmp.uStructSize = sizeof(BITMAPHANDLE);
L_CopyBitmap(&bwBmp, &tBmp, bwBmp.uStructSize); // OK
if(1 != tBmp.BitsPerPixel)
L_ColorResBitmap(&bwBmp, &bwBmp, sizeof(BITMAPHANDLE), 1, CRF_FIXEDPALETTE, NULL, NULL, 0, NULL, NULL); // OK
L_BorderRemoveBitmap(&bwBmp, &br, nullptr, nullptr, 0) // OK
if(NULL != br.hRgn)
{
L_SetBitmapRgnHandle(&tBmp, nullptr, br.hRgn, L_RGN_SET); // OK
L_FillBitmap(&tBmp, bkColor); // OK bkColor is White
L_FreeBitmapRgn(&tBmp);
}
L_LineRemoveBitmap(&bwBmp, &lr, lrCB, NULL, 0) // returns OK
if(NULL != lr.hRgn) // not null but empty
{
::GetRgnBox(lr.hRgn, &r); // it is always {0, 0, 0, 0}
L_SetBitmapRgnHandle(&tBmp, nullptr, lr.hRgn, L_RGN_SET);
L_FillBitmap(&tBmp, bkColor); // OK but fills nothing
L_FreeBitmapRgn(&tBmp);
}
// do other stuff and save
}
}
L_INT EXT_CALLBACK lrCB(HRGN hRgn, L_INT iStartRow, L_INT iStartCol, L_INT iLength, L_VOID* pUserData)
{
UNREFERENCED_PARAMETER(pUserData);
if(NULL != hRgn) // always null
{
RECT rcRect;
GetRgnBox(hRgn, &rcRect);
DeleteObject(hRgn);
}
return SUCCESS_REMOVE;
}
Sam,
If you want the Callback to set a Windows region, you need to also set the LINE_CALLBACK_REGION uFlag when defining the LINEREMOVE structure:
LINEREMOVE lr = {
sizeof(LINEREMOVE), // uStructSize
LINE_CALLBACK_REGION| LINE_SINGLE_REGION, // uFlags
400, // Minimum Length
12, // Maximum Width
15, // Wall size
10, // Max percent of line that can be a wall
2, // Maximum Gap
0, // Maximum Line Variance
LINEREMOVE_VERTICAL, // horizontal or vertical
NULL, // hRgn
nullptr, // pBitmapRegion
sizeof(BITMAPHANDLE) // uBitmapStructSize
};
The flag is documented in this page.
After the function finishes processing, you can then take the lines region set in the lr.hRgn property and set it in the original color bitmap using the following code:
L_SetBitmapRgnHandle(&OriginalBitmap, NULL, lr.hRgn, L_RGN_SET);
In addition to the response here, I have sent you a small code snippet in a reply to the email you sent to our support address.

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

GetPixel() not working correctly Windows API C++

I'm writing a program that reads each pixel of a window and store it in an array of bytes as black and white, each bit of the bytes is a black/white value.
But GetPixel() doesn't seem to work the way I expected. Here's the part of the code for reading pixels and storing them:
byte *colors = new byte[250000 / 8 + 1];
ZeroMemory(colors, 250000 / 8 + 1);
HDC hdc = GetDC(hwnd);
HDC memDC = CreateCompatibleDC(hdc);
HBITMAP memBitmap = CreateCompatibleBitmap(hdc, 500, 500);
SelectObject(memDC, memBitmap);
BitBlt(memDC, 0, 0, 500, 500, hdc, 0, 0, SRCCOPY);
for (int y = 0; y < 500; y++) {
for (int x = 0; x < 500; x++) {
COLORREF pxcolor = GetPixel(memDC, x, y);
if (pxcolor == CLR_INVALID) {
MessageBox(hwnd, _T("Oops..."), NULL, NULL);
}
int r = GetRValue(pxcolor);
int g = GetGValue(pxcolor);
int b = GetBValue(pxcolor);
int average = (r + g + b) / 3;
bool colorBW = average >= 128;
int currentIndex = y * 500 + x;
if (colorBW) {
SetBit(colors, currentIndex);
}
}
}
ReleaseDC(hwnd, hdc);
DeleteDC(memDC);
DeleteObject(memBitmap);
delete[] colors;
SetBit():
inline VOID SetBit(byte *bytes, int index, bool state = true) {
byte byteToSet = bytes[index / 8];
int bitNumber = index % 8;
bytes[index / 8] = state ? (byteToSet | (0b1000'0000 >> bitNumber)) : (byteToSet & ((0b1111'1111 >> (bitNumber + 1)) | (0b1111'1111 << (8 - bitNumber - 1))));
}
Every pixel read in by GetPixel() seems to give me 0x000000, or pure black.
My code used to call GetPixel() with the first parameter being hdc, without all the bitmap and memory DC stuff, but that way every pixel returns CLR_INVALID. I came across this question, and the above code is after I have changed it into using memory DCs and bitmaps. But it just went from returning CLR_INVALID to 0x000000 for each pixel.
If I add this line before I use GetPixel():
SetPixel(memDC, x, y, RGB(255, 255, 255));
GetPixel() returns the correct result. Why is it functioning this way?

Why is locked bits returning -842150451 for all pixel values?

I am trying to get the bits in a bitmap, but I keep getting this output (PS. I tested the whole array as well):
-842150451 // Array before lockbits
-842150451 // Array after lockbits
This is my code to get the lockedBits.
BitmapData * getLockedBitmapData()
{
float squareSideLength = 50 * 4;
Bitmap * src = new Bitmap(squareSideLength , squareSideLength);
Graphics * graphics = Graphics::FromImage(solid);
SolidBrush blackBrush(Color(255, 0, 0, 0));
graphics->FillRectangle(&blackBrush, FLOAT_ZERO, FLOAT_ZERO, squareSideLength, squareSideLength);
int srcWidth = src->GetWidth();
int srcHeight = src->GetHeight();
UINT * pixels = new UINT[srcWidth * srcHeight];
// _RPT1(0, "%d\n", pixels[55]);
BitmapData * bitmapData = new BitmapData();
bitmapData->Width = srcWidth;
bitmapData->Height = srcHeight;
bitmapData->Stride = 4 * srcWidth;
bitmapData->PixelFormat = PixelFormat32bppARGB;
bitmapData->Scan0 = (VOID*) pixels;
bitmapData->Reserved = NULL;
src->LockBits(new Rect(0, 0, srcWidth, srcHeight),
ImageLockMode::ImageLockModeRead | ImageLockMode::ImageLockModeWrite,
src->GetPixelFormat(),
bitmapData);
// _RPT1(0, "%d\n", pixels[55]);
return bitmapData;
}
You are using it wrong, it returns a BitmapData. So it needs to be:
BitmapData bitmapData;
Status ret = src->LockBits(new Rect(0, 0, srcWidth, srcHeight),
ImageLockMode::ImageLockModeRead | ImageLockMode::ImageLockModeWrite,
src->GetPixelFormat(),
&bitmapData);
if (ret != Ok) {
// Report error
//...
}
Not not skip error checking.

Get Pixel color fastest way?

I'm trying to make an auto-cliker for an windows app. It works well, but it's incredibly slow!
I'm currently using the method "getPixel" which reloads an array everytime it's called.
Here is my current code:
hdc = GetDC(HWND_DESKTOP);
bx = GetSystemMetrics(SM_CXSCREEN);
by = GetSystemMetrics(SM_CYSCREEN);
start_bx = (bx/2) - (MAX_WIDTH/2);
start_by = (by/2) - (MAX_HEIGHT/2);
end_bx = (bx/2) + (MAX_WIDTH/2);
end_by = (by/2) + (MAX_HEIGHT/2);
for(y=start_by; y<end_by; y+=10)
{
for(x=start_bx; x<end_bx; x+=10)
{
pixel = GetPixel(*hdc, x, y);
if(pixel==RGB(255, 0, 0))
{
SetCursorPos(x,y);
mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);
Sleep(50);
mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
Sleep(25);
}
}
}
So basically, it just scan a range of pixel in the screen and starts a mouse event if it detects a red button.
I know there are other ways to get the pixel color, such as bitblt. But I've made some researches, and I don't understand how I'm supposed to do, in order to scan a color array. I need something which scans screen very fast in order to catch the button.
Could you please help me?
Thanks.
I found a perfect way which is clearly faster than the GetPixel one:
HDC hdc, hdcTemp;
RECT rect;
BYTE* bitPointer;
int x, y;
int red, green, blue, alpha;
while(true)
{
hdc = GetDC(HWND_DESKTOP);
GetWindowRect(hWND_Desktop, &rect);
int MAX_WIDTH = rect.right;
int MAX_HEIGHT = rect.bottom;
hdcTemp = CreateCompatibleDC(hdc);
BITMAPINFO bitmap;
bitmap.bmiHeader.biSize = sizeof(bitmap.bmiHeader);
bitmap.bmiHeader.biWidth = MAX_WIDTH;
bitmap.bmiHeader.biHeight = MAX_HEIGHT;
bitmap.bmiHeader.biPlanes = 1;
bitmap.bmiHeader.biBitCount = 32;
bitmap.bmiHeader.biCompression = BI_RGB;
bitmap.bmiHeader.biSizeImage = MAX_WIDTH * 4 * MAX_HEIGHT;
bitmap.bmiHeader.biClrUsed = 0;
bitmap.bmiHeader.biClrImportant = 0;
HBITMAP hBitmap2 = CreateDIBSection(hdcTemp, &bitmap, DIB_RGB_COLORS, (void**)(&bitPointer), NULL, NULL);
SelectObject(hdcTemp, hBitmap2);
BitBlt(hdcTemp, 0, 0, MAX_WIDTH, MAX_HEIGHT, hdc, 0, 0, SRCCOPY);
for (int i=0; i<(MAX_WIDTH * 4 * MAX_HEIGHT); i+=4)
{
red = (int)bitPointer[i];
green = (int)bitPointer[i+1];
blue = (int)bitPointer[i+2];
alpha = (int)bitPointer[i+3];
x = i / (4 * MAX_HEIGHT);
y = i / (4 * MAX_WIDTH);
if (red == 255 && green == 0 && blue == 0)
{
SetCursorPos(x,y);
mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);
Sleep(50);
mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
Sleep(25);
}
}
}
I hope this could help someone else.
The simple answer is that if this is the method you insist on using then there isn't much to optimize. As others have pointed out in comments, you should probably use a different method for locating the area to click. Have a look at using FindWindow, for example.
If you don't want to change your method, then at least sleep your thread for a bit after each complete screen scan.