Cairo: How to clip text to a rect? - c++

Using Cairo under C++ on a Raspberry Pi, and trying to clip text drawing to inside a given rectangle.
I'd have thought that it would be as simple as this:
cairo_t* cp = cairo_create(psurface);
// set font, etc
cairo_rectangle(cp, 0, 0, 100, 100); // Desired clipping rect
cairo_clip(cp);
cairo_show_text(cp, "pretend that this string is > 100px wide");
cairo_destroy(cp);
but it always causes no text to appear. If I omit the call to cairo_clip() the text does appear (albeit unclipped).
I'm wanting only the last few chars of the string to get clipped.
What's the trick?

Works for me.
#include <cairo.h>
int main()
{
cairo_surface_t *s = cairo_image_surface_create(CAIRO_FORMAT_RGB24, 150, 50);
cairo_t *cr = cairo_create(s);
cairo_set_source_rgb(cr, 1, 0, 0);
cairo_paint(cr);
cairo_rectangle(cr, 0, 0, 100, 100);
cairo_clip(cr);
cairo_move_to(cr, 50, 25);
cairo_set_source_rgb(cr, 0, 0, 0);
cairo_show_text(cr, "pretend that this string is > 100px wide");
cairo_destroy(cr);
cairo_surface_write_to_png(s, "out.png");
cairo_surface_destroy(s);
return 0;
}

Related

How to erase a shape without redrawing the entire surface?

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);
}
}
}

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);

glfw window with no title bar

I am trying to make a way to toggle my window between windowed mode and fullscreen mode. I had done it successfully except for one problem. The title bar is not working! You can’t move the window either. Without this piece of code everything works just fine.
setFullscreen method:
void Window::setFullscreen(bool fullscreen)
{
GLFWmonitor* monitor = glfwGetPrimaryMonitor();
const GLFWvidmode* mode = glfwGetVideoMode(monitor);
if (fullscreen) {
glfwSetWindowMonitor(m_window, monitor, 0, 0, mode->width, mode->height, mode->refreshRate);
glViewport(0, 0, mode->width, mode->height);
}
if (!fullscreen) {
glfwSetWindowMonitor(m_window, nullptr, 0, 0, m_width, m_height, GLFW_DONT_CARE);
glViewport(0, 0, m_width, m_height);
}
}
The result of the code:
#tomasantunes help me figure this out.
in setFullscreen I am setting the window to be at 0, 0 or the top left of the screen. The title bar didn't actually disappear is was just off screen. so if I set the window to be at 100, 100 instead I get the title bar back. This was pretty dumb of me to make a stupid mistake like this.
if (!fullscreen) {
glfwSetWindowMonitor(m_window, nullptr, 0, 0, m_width, m_height, GLFW_DONT_CARE); // I set the position to 0,0 in the 3rd and 4th parameter
glViewport(0, 0, m_width, m_height);
}
Fixed code:
if (!fullscreen) {
glfwSetWindowMonitor(m_window, nullptr, 100, 100, m_width, m_height, GLFW_DONT_CARE); // I set the position to 100, 100
glViewport(0, 0, m_width, m_height);
}
New result:
Not sure if you are still looking for an answer, however, the code I have here fixes that same problem you were initially having.
Before changing to full-screen mode, save the window position and size.
int xPos, yPos, width, height; //have somewhere stored out of function scope.
glfwGetWindowPos(windowPtr, &xPos, &yPos);
glfwGetWindowSize(windowPtr, &width, &height);
and then when changing from full-screen back to windowed mode, apply the saved positions and size properties.
glfwSetWindowMonitor(windowPtr, nullptr, xPos, yPos, width, height, 0); //Refresh rate is ignored without an active monitor.

How to show a PNG image with 25% opacity using GDI+? (MFC)

I am trying to output a PNG image by using GDI+, MFC. I want to output it with 25% opacity. Below is the way to output a PNG image on x=10, y=10:
CDC *pDC =GetDC();
Graphics graphics(pDC->m_hDC);
Image image(L"test1.png", FALSE);
graphics.DrawImage(&image, 10, 10);
But I don't know how to make it translucent. Any idea?
To draw the image with alpha blending, declare Gdiplus::ImageAttributes and Gdiplus::ColorMatrix with required alpha channel:
float alpha = 0.25f;
Gdiplus::ColorMatrix matrix =
{
1, 0, 0, 0, 0,
0, 1, 0, 0, 0,
0, 0, 1, 0, 0,
0, 0, 0, alpha, 0,
0, 0, 0, 0, 1
};
Gdiplus::ImageAttributes attrib;
attrib.SetColorMatrix(&matrix);
graphics.DrawImage(&image,
Gdiplus::Rect(10, 10, image.GetWidth(), image.GetHeight()),
0, 0, image.GetWidth(), image.GetHeight(), Gdiplus::UnitPixel, &attrib);
See also: Using a Color Matrix to Transform a Single Color
Note that GetDC() is usually not used in MFC. If you do use it, be sure to call ReleaseDC(pDC) when pDC is no longer needed. Or simply use CClientDC dc(this) which has automatic cleanup. If painting is done in OnPaint then use CPaintDC which also has automatic cleanup:
void CMyWnd::OnPaint()
{
CPaintDC dc(this);
Gdiplus::Graphics graphics(dc);
...
}

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).