How to erase a shape without redrawing the entire surface? - c++

On the screen below I have the image with the painted translucent rectangle and with the painted opaque rectangle - which cuts the area from translucent rectangle. Now it works because I redraw the surface and the translucent rectangle each time in the cycle, but working with slow computers this redrawing causes glaring. If I stop redrawing all this stuff, I get this result, but the glaring disappears:
The commented lines in code below cause the result at the picture
I want just to erase the the translucent rectangle without redrawing the entire surface. I want to dynamically select the area (without redrawing the entire surface each time) for making a screenshot like on the Windows 11
void OcctGtkViewer::buttonclick(int *x, int *y, int *width, int *height)
{
cairo_surface_t *surface = cairo_xlib_surface_create(xdisplay, xroot, DefaultVisual(xdisplay, scr), DisplayWidth(xdisplay, scr), DisplayHeight(xdisplay, scr));
cairo_t *cr = cairo_create(surface);
const char *backgroundImagePath = "/tmp/test.png";
cairo_surface_write_to_png(
surface,
backgroundImagePath);
cairo_surface_t *surfaceTmp = cairo_image_surface_create_from_png(backgroundImagePath);
cairo_set_source_rgba(cr, 0, 0, 0, 0.2);
cairo_rectangle(cr, 0, 0, DisplayWidth(xdisplay, scr), DisplayHeight(xdisplay, scr));
cairo_fill(cr);
cairo_surface_t *surfaceTmpp = cairo_image_surface_create_from_png(backgroundImagePath);
printf("select an area\n");
while (1)
{
event.type == MotionNotify && isPressed == true)
{
int tmp_x = xevent.xmotion.x_root;
int tmp_y = xevent.xmotion.y_root;
cairo_set_source_surface(cr, surfaceTmp, 1, 1);
// cairo_paint(cr);
cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD); // The default is CAIRO_FILL_RULE_WINDING.
// cairo_set_source_rgba(cr, 0, 0, 0, 0.2);
// cairo_rectangle(cr, 0, 0, DisplayWidth(xdisplay, scr), DisplayHeight(xdisplay, scr));
cairo_rectangle(cr, init_x, init_y, tmp_x - init_x, tmp_y - init_y); // set rectangle
cairo_fill(cr);
}
}
}

Related

How to draw a selected part of one surface above another image surface?

I want to draw the area of one surface above another surface, but nothing appears on the screen. I want to create area selection mechanics for making a screenshot. That means that I should clear the entire surface before next mouse movement in order not to place one selection above another.
void OcctGtkViewer::buttonclick(int *x, int *y, int *width, int *height)
{
bool isPressed = false;
int init_x, init_y, fin_x, fin_y;
int prevInitX, prevInitY, prevFinX, prevFinY;
Display *xdisplay = XOpenDisplay(0);
XEvent xevent;
::Window xroot = gdk_x11_window_get_xid(gtk_widget_get_window((GtkWidget *)myGLArea->gobj()));
int scr = DefaultScreen(xdisplay);
cairo_surface_t *surface = cairo_xlib_surface_create(xdisplay, xroot, DefaultVisual(xdisplay, scr), DisplayWidth(xdisplay, scr), DisplayHeight(xdisplay, scr));
cairo_t *cr = cairo_create(surface);
const char *backgroundImagePath = "/tmp/test.png";
const char *backgroundImagePath2 = "/tmp/test2.png";
cairo_surface_write_to_png(
surface,
backgroundImagePath);
cairo_surface_t *surfaceClearImage = cairo_image_surface_create_from_png(backgroundImagePath);
cairo_set_source_rgba(cr, 0, 0, 0, 0.2);
cairo_rectangle(cr, 0, 0, DisplayWidth(xdisplay, scr), DisplayHeight(xdisplay, scr));
cairo_fill(cr);
cairo_surface_write_to_png(
surface,
backgroundImagePath2);
cairo_surface_t *surfaceOverlay = cairo_image_surface_create_from_png(backgroundImagePath2);
cr = cairo_create(surfaceOverlay);
XGrabPointer(xdisplay, xroot, 1, PointerMotionMask | ButtonPressMask | ButtonReleaseMask, GrabModeAsync, GrabModeAsync, None, None, CurrentTime);
while (1)
{
XNextEvent(xdisplay, &xevent);
if (xevent.type == MotionNotify && isPressed == true)
{
int tmp_x = xevent.xmotion.x;
int tmp_y = xevent.xmotion.y;
cairo_save(cr);
cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
cairo_paint(cr);
cairo_restore(cr);
cairo_set_source_surface(cr, surfaceClearImage, 0, 0);
cairo_rectangle(cr, init_x, init_y, tmp_x - init_x, tmp_y - init_y); // set rectangle
cairo_fill(cr);
XSync(xdisplay, true);
}
}
}
The result I want to achieve:
If I delete these lines of code: `
cairo_surface_t *surfaceOverlay = cairo_image_surface_create_from_png(backgroundImagePath2);
cr = cairo_create(surfaceOverlay);`
I get this result:

How to cut a part of a shape with the help of another painted shape above?

On the screen below I have the image with the painted translucent rectangle and with the painted opaque rectangle. My purpose is to cut the area of the opaque rectangle - delete pixels in the translucent rectangle in order to see the initial image.
cairo_surface_t *surface = cairo_xlib_surface_create(xdisplay, xroot, DefaultVisual(xdisplay, scr), DisplayWidth(xdisplay, scr), DisplayHeight(xdisplay, scr));
cairo_t *cr = cairo_create(surface);
cairo_surface_write_to_png(
surface,
"test.png");
cairo_surface_t *surfaceTmp = cairo_image_surface_create_from_png("./test.png");
if (xevent.type == MotionNotify && isPressed == true)
{
int tmp_x = xevent.xmotion.x_root;
int tmp_y = xevent.xmotion.y_root;
cairo_save(cr);
cairo_set_source_surface(cr, surfaceTmp, 1, 1);
cairo_paint(cr);
cairo_set_source_rgba(cr, 0, 0, 0, 0.2);
cairo_rectangle(cr, 0, 0, DisplayWidth(xdisplay, scr), DisplayHeight(xdisplay, scr));
cairo_fill(cr);
cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
cairo_rectangle(cr, init_x, init_y, tmp_x - init_x, tmp_y - init_y); // set rectangle
cairo_fill(cr);
cairo_restore(cr);
}
Why do I have this black rectangle? I thought that CAIRO_OPERATOR_CLEAR should delete the shape part beneath.
The desired outcome:
Maybe, since cairo's drawings change pixels directly (=not buffered), once you draw something, there remain no underlying original pixels that can be recovered afterwards. If you'd like to hole the rectangle, try the fill rule: CAIRO_FILL_RULE_EVEN_ODD.
cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD); // The default is CAIRO_FILL_RULE_WINDING.
cairo_set_source_rgba(cr, 0, 0, 0, 0.2);
cairo_rectangle(cr, 0, 0, DisplayWidth(xdisplay, scr), DisplayHeight(xdisplay, scr));
//cairo_fill(cr);
//cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
cairo_rectangle(cr, init_x, init_y, tmp_x - init_x, tmp_y - init_y); // set rectangle
cairo_fill(cr);

How to erace (delete) the filled cairo shape?

I want to paint a rectangle to select the area according to mouse coordinates. So, the rectangle should be created and deleted every frame (the if statement below executes every frame). I was trying to call these functions, but nothing happened - shapes were painting one above another.
cairo_surface_destroy(surface);
cairo_destroy(cr);
if (xevent.type == MotionNotify && isPressed == true)
{
int tmp_x = xevent.xmotion.x_root;
int tmp_y = xevent.xmotion.y_root;
int scr = DefaultScreen(xdisplay);
cairo_surface_t *surface = cairo_xlib_surface_create(xdisplay, xroot, DefaultVisual(xdisplay, scr), DisplayWidth(xdisplay, scr), DisplayHeight(xdisplay, scr));
cairo_t *cr = cairo_create(surface);
cairo_rectangle(cr, init_x, init_y, tmp_x - init_x, tmp_y - init_y); /* set rectangle */
cairo_set_source_rgba(cr, 0.3, 0.4, 0.6, 0.1); /* set fill color */
cairo_fill(cr);
cairo_destroy(cr);
}

SDL 2.0 enlarge texture

In order to make buttons, I create and render texture that way:
typedef struct{
SDL_Rect pos;
SDL_Texture* texture;
int hovered;
} button;
button getButton(int x, int y, char * label, TTF_Font* font, SDL_Color color){
button btn;
btn.hovered = false;
btn.pos.x = x;
btn.pos.y = y;
SDL_Surface* surface = TTF_RenderText_Solid(font, label, color);
btn.pos.w = surface->w;
btn.pos.h = surface->h;
btn.texture = SDL_CreateTextureFromSurface(renderer, surface);
SDL_FreeSurface(surface);
return btn;
}
void drawButton(button btn){
SDL_RenderCopyEx( renderer, btn.texture, NULL, &btn.pos, 0, NULL, SDL_FLIP_NONE);
if(btn.hovered){
SDL_SetRenderDrawColor(renderer, 255, 255, 255, 0x00);
SDL_RenderDrawRect(renderer, &btn.pos);
}
The problem is that I get texture which size equals one of label. How can I increase texture pixel size without stretching it i.e. add blank spaces to the side of it?
Something like
void drawButton(button btn){
SDL_RenderCopyEx( renderer, btn.texture, NULL, &btn.pos, 0, NULL, SDL_FLIP_NONE);
if(btn.hovered){
int padding = 10;
SDL_Rect pos = {btn.pos.x - padding, btn.pos.y - padding,
btn.pos.w + 2*padding, btn.pos.h + 2*padding };
SDL_SetRenderDrawColor(renderer, 255, 255, 255, 0x00);
SDL_RenderDrawRect(renderer, &pos);
}
}
That way only the size of the rectangle changes, obviously I just pulled 10 for the size of padding out of thin air, you'll want to pick something appropriate yourself.
Found a way to do it. To enlarge the texture you create surface representing background of button, then combine them:
button getButton(int x, int y, char * label, TTF_Font* font, SDL_Color color){
button btn;
btn.hovered = false;
btn.pos.x = x;
btn.pos.y = y;
SDL_Surface* surface = TTF_RenderText_Solid(font, label, color);
SDL_Surface* back = SDL_CreateRGBSurface(0, surface->w+10, surface->h+10,
32, 0, 0, 0, 0);// create a black background
SDL_Rect t = {5, 5, back->w, back->w}; // place in a background to place label
SDL_BlitSurface(surface, NULL, back, &t); // combining surfaces
btn.pos.w = back->w;
btn.pos.h = back->h;
btn.texture = SDL_CreateTextureFromSurface(renderer, back);
SDL_FreeSurface(surface);
return btn;
}

Slight undesired transparency from FillRectangle

I have a window created with the WS_EX_LAYERED window style. I am currently drawing onto a memory bitmap using GDI+, and using UpdateLayeredWindow to update the graphical content of my layered window.
Here's a snippet of my code:
void Redraw(HWND hWnd, int width, int height) {
static bool floppy = true;
floppy = !floppy;
HDC hScreenDC = GetDC(HWND_DESKTOP);
HDC hMemDC = CreateCompatibleDC(hScreenDC);
HBITMAP hBmp = CreateCompatibleBitmap(hScreenDC, width, height);
HGDIOBJ hObj = SelectObject(hMemDC, hBmp);
Graphics gfx(hMemDC);
SolidBrush b(Color(254, (floppy ? 255 : 0), (floppy ? 0 : 255), 0));
gfx.FillRectangle(&b, Rect(0, 0, width, height));
BLENDFUNCTION blend;
blend.BlendOp = AC_SRC_OVER;
blend.BlendFlags = 0;
blend.SourceConstantAlpha = 255;
blend.AlphaFormat = AC_SRC_ALPHA;
POINT src = { 0, 0 };
SIZE size;
size.cx = width;
size.cy = height;
Assert(UpdateLayeredWindow(
hWnd,
hScreenDC,
NULL,
&size,
hMemDC,
&src,
RGB(0, 0, 0),
&blend,
ULW_ALPHA
));
SelectObject(hMemDC, hObj);
DeleteObject(hBmp);
DeleteDC(hMemDC);
ReleaseDC(HWND_DESKTOP, hScreenDC);
}
When creating my SolidBrush, I specified the value of 254 for the alpha component. This results in a 99.6% opaque fill, which is not what I want.
When I specify 255 as the alpha component, there appears to be no fill; my window becomes completely transparent. This is an issue because I wish to draw shapes that are 100% opaque, but I also wish to draw some that aren't.
There seems to be some qwerks with FillRectangle. This becomes apparent when we observe that using FillEllipse with a SolidBrush whose alpha component is 255, results in the shape being rendered perfectly (opaque).
Here are two work-arounds that I came up with, which each solve the issue for me:
Call FillRectangle twice
SolidBrush b(Color(254, 255, 0, 0));
gfx.FillRectangle(&b, Rect(0, 0, width, height));
gfx.FillRectangle(&b, Rect(0, 0, width, height));
Since the same area is being filled twice, they will blend and create RGB(255, 0, 0) regardless of the content behind the window (it's now 100% opaque). I do not prefer this method, as it requires every rectangle to be drawn twice.
Use FillPolygon instead
Just as with FillEllipse, FillPolygon doesn't seem to have the colour issue, unless you call it like so:
SolidBrush b(Color(255, 255, 0, 0));
Point points[4];
points[0] = Point(0, 0);
points[1] = Point(width, 0);
points[2] = Point(width, height);
points[4] = Point(0, height);
gfx.FillPolygon(&b, points, 4); //don't copy and paste - this won't work
The above code will result in a 100% transparent window. I am guessing that this is either due to some form of optimisation that passes the call to FillRectangle instead. Or - most likely - there is some problem with FillPolygon, which is called by FillRectangle. However, if you add an extra Point to the array, you can get around it:
SolidBrush b(Color(255, 255, 0, 0));
Point points[5];
points[0] = Point(0, 0);
points[1] = Point(0, 0); //<-
points[2] = Point(width, 0);
points[3] = Point(width, height);
points[4] = Point(0, height);
gfx.FillPolygon(&b, points, 5);
The above code will indeed draw a 100% opaque shape, which fixes my problem.
UpdateLayeredWindow() requires a bitmap with pre-multiplied alpha:
Note that the APIs use premultiplied alpha, which means that the red,
green and blue channel values in the bitmap must be premultiplied with
the alpha channel value. For example, if the alpha channel value is x,
the red, green and blue channels must be multiplied by x and divided
by 0xff prior to the call.
You can use Bitmap::ConvertFormat() to convert a bitmap to pre-multiplied (the format is PixelFormat32bppPARGB).