I've got a simple Cairo program that attempts to draw a diagonal consisting of dots in a 600x600 PNG. However, the output appears to be truncated whenever I attempt to render all of the dots using a single call to cairo_stroke().
Specifically, consider the following program:
#include <cairo/cairo.h>
int main(int argc, char **argv)
{
cairo_surface_t *surface =
cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 300, 300);
cairo_t *cr = cairo_create(surface);
cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
cairo_set_source_rgb(cr, 0, 0, 0);
cairo_set_line_width(cr, 5);
for (double x = 0.0; x <= 300; x += 10) {
cairo_move_to(cr, x, x);
cairo_close_path(cr);
cairo_stroke(cr);
}
cairo_surface_write_to_png(surface, "output.png");
cairo_destroy(cr);
cairo_surface_destroy(surface);
return 0;
}
which generates the following correct output:
If I move the
cairo_stroke(cr);
outside of the for loop, then the following incorrect output is generated instead:
.
Can someone else explain why the second attempt fails? I suspect I must be doing something wrong here...
#include <cairo/cairo.h>
int main(int argc, char **argv)
{
cairo_surface_t *surface =
cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 300, 300);
cairo_t *cr = cairo_create(surface);
cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);
cairo_set_source_rgb(cr, 0, 0, 0);
cairo_set_line_width(cr, 5);
for (double x = 0.0; x <= 300; x += 10) {
cairo_move_to(cr, x, x);
cairo_close_path(cr);
}
cairo_stroke(cr); /* moved here */
cairo_surface_write_to_png(surface, "output.png");
cairo_destroy(cr);
cairo_surface_destroy(surface);
return 0;
}
Moving cairo_stroke(cr) outside the for-loop seems to generate equal output with Cairo version 1.10.1.
Compiled as: gcc test.c `pkg-config --libs --cflags gtk+-2.0` -std=c99 -lcairo
Related
I'm trying to find an example of showing an image in GTK 4.0 from an existing memory buffer, where the image is stored in the form of an array of floats of size width x height x 3. I understand I need to use GtkImage, but I don't know how to pass such an array to it.
Just for context, I wanted to implement a prototype for rendering some images, they are all rendered in memory, and as a result, I have an array of such floats, three floats for each pixel. Unfortunately, I could not find any examples for the latest GTK 4.0. There are plenty of examples of loading an image from the disk, but it seems like jumping unnecessary hoops for my case. I used to load these as OpenGL texture and show them on a quad, but it also seems like driving nails with a sledgehammer.
Thank you in advance for any help!
You can use GdkMemoryTexture for an image in memory and use a GtkPicture and set the texture using gtk_picture_set_paintable() to paint it. The following is an example that creates an opaque 8bit RGB image and displays it on the screen:
/* pixel.c
*
* Compile: cc -ggdb pixel.c -o pixel $(pkg-config --cflags --libs gtk4) -o pixel
* Run: ./pixel
*
* Author: Mohammed Sadiq <www.sadiqpk.org>
*
* SPDX-License-Identifier: LGPL-2.1-or-later OR CC0-1.0
*/
#include <gtk/gtk.h>
#define BYTES_PER_R8G8B8 3
#define WIDTH 400
static void
fill_row (GByteArray *array,
guint8 value,
int row_size)
{
guint i;
for (i = 0; i < row_size; i++) {
/* Fill the same values for RGB */
g_byte_array_append (array, &value, 1); /* R */
g_byte_array_append (array, &value, 1); /* G */
g_byte_array_append (array, &value, 1); /* B */
}
}
static void
add_pixel_picture (GtkPicture *picture)
{
g_autoptr(GBytes) bytes = NULL;
GdkTexture *texture;
GByteArray *pixels;
gsize height;
/* Draw something interesting */
pixels = g_byte_array_new ();
for (guint i = 0; i <= 0xff ; i++)
fill_row (pixels, i, WIDTH);
for (guint i = 0; i <= 0xff; i++)
fill_row (pixels, 0xff - i, WIDTH);
height = pixels->len / (WIDTH * BYTES_PER_R8G8B8);
bytes = g_byte_array_free_to_bytes (pixels);
texture = gdk_memory_texture_new (WIDTH, height,
GDK_MEMORY_R8G8B8,
bytes,
WIDTH * BYTES_PER_R8G8B8);
gtk_picture_set_paintable (picture, GDK_PAINTABLE (texture));
}
static void
app_activated_cb (GtkApplication *app)
{
GtkWindow *window;
GtkWidget *picture;
window = GTK_WINDOW (gtk_application_window_new (app));
g_object_set (window,
"width-request", 500,
"height-request", 400,
NULL);
picture = gtk_picture_new ();
gtk_widget_add_css_class (picture, "frame");
g_object_set (picture,
"margin-start", 96,
"margin-end", 96,
"margin-top", 96,
"margin-bottom", 96,
NULL);
gtk_window_set_child (window, picture);
add_pixel_picture (GTK_PICTURE (picture));
gtk_window_present (window);
}
int
main (int argc,
char *argv[])
{
g_autoptr(GtkApplication) app = gtk_application_new (NULL, 0);
g_signal_connect (app, "activate", G_CALLBACK (app_activated_cb), NULL);
return g_application_run (G_APPLICATION (app), argc, argv);
}
Use the GdkMemoryFormat that suites your data, or convert it to one that's supported.
This piece of SDL2 code draws some white pixels on-screen using OpenGL, then grabs the pixels field of the window's SDL_Surface and loops through it, printing out the values of the contents. Even though I just drew a white triangle, the loop shows that there's nothing but zeros in that buffer (the code just prints 0 to standard out over and over).
How can I actually get at the modified pixel buffer, in something like RGB or ARGB or RGBA format?
#include <SDL2/SDL.h>
#include <SDL2/SDL_opengl.h>
#include <GL/glu.h>
int main()
{
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS);
const int WINDOW_WIDTH = 100;
const int WINDOW_HEIGHT = 100;
SDL_Window *window = SDL_CreateWindow("OpenGL Test", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_OPENGL);
SDL_GLContext gl_context = SDL_GL_CreateContext(window);
SDL_GL_SetSwapInterval(1);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
GLenum error = glGetError();
if( error != GL_NO_ERROR )
{
printf( "Error initializing OpenGL! %s\n", gluErrorString(error));
}
glClearColor(0, 0, 0, 1);
int quit = 0;
SDL_Event event;
while (!quit)
{
while (SDL_PollEvent(&event))
{
switch (event.type)
{
case SDL_QUIT:
quit = 1;
break;
}
}
glBegin(GL_TRIANGLES);
glColor3f(255, 255, 255);
glVertex2f(0, 0);
glVertex2f(0, 1);
glVertex2f(1, 0);
glEnd();
SDL_GL_SwapWindow(window);
SDL_Surface *surface = SDL_GetWindowSurface(window);
int pixel_depth = SDL_BYTESPERPIXEL(surface->format->format);
char *pixels = (char*) surface->pixels;
int max_value = 0;
for (int i = 0; i < WINDOW_WIDTH * WINDOW_HEIGHT * pixel_depth; i++)
{
if (pixels[i] > max_value)
{
max_value = pixels[i];
}
}
SDL_FreeSurface(surface);
SDL_Log("%d", max_value);
}
SDL_Quit();
return 0;
}
SDL_GetWindowSurface() doesn't work with OpenGL:
You may not combine this with 3D or the rendering API on this window.
Use glReadPixels() instead.
Use PBO to read data from from the pixel buffer.
glReadBuffer(GL_COLOR_ATTACHMENT0);
writeIndex = (writeIndex + 1) % 2;
readIndex = (readIndex + 1) % 2;
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo[writeIndex]);
// copy from framebuffer to PBO asynchronously. it will be ready in the NEXT frame
glReadPixels(0, 0, SCR_WIDTH, SCR_HEIGHT, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
// now read other PBO which should be already in CPU memory
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo[readIndex]);
unsigned char* Data = (unsigned char *)glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY);
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
I'm trying to use the Cairo graphics library on Linux in C to make a pretty lightweight x11 GUI.
After trying very hard to follow the woefully incomplete guide that cairo gives for x11, this is the best I've got:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <cairo.h>
#include <cairo-xlib.h>
#include <X11/Xlib.h>
#include <X11/extensions/Xrender.h>
#include <X11/extensions/renderproto.h>
//This function should give us a new x11 surface to draw on.
cairo_surface_t* create_x11_surface(int x, int y)
{
Display* d;
Drawable da;
int screen;
cairo_surface_t* sfc;
if((d = XOpenDisplay(NULL)) == NULL)
{
printf("failed to open display\n");
exit(1);
}
screen = DefaultScreen(d);
da = XCreateSimpleWindow(d, DefaultRootWindow(d), 0, 0, x, y, 0, 0, 0);
XSelectInput(d, da, ButtonPressMask | KeyPressMask);
XMapWindow(d, da);
sfc = cairo_xlib_surface_create(d, da, DefaultVisual(d, screen), x, y);
cairo_xlib_surface_set_size(sfc, x, y);
return sfc;
}
int main(int argc, char** argv)
{
//create a new cairo surface in an x11 window as well as a cairo_t* to draw
//on the x11 window with.
cairo_surface_t* surface = create_x11_surface(300, 200);
cairo_t* cr = cairo_create(surface);
while(1)
{
//save the empty drawing for the next time through the loop.
cairo_push_group(cr);
//draw some text
cairo_select_font_face(cr, "serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD);
cairo_set_font_size(cr, 32.0);
cairo_set_source_rgb(cr, 0, 0, 1.0);
cairo_move_to(cr, 10.0, 25.0);
if((argc == 2) && (strnlen(argv[1], 100) < 50))
cairo_show_text(cr, argv[1]);
else
cairo_show_text(cr, "usage: ./p1 <string>");
//put the drawn text onto the screen(?)
cairo_pop_group_to_source(cr);
cairo_paint(cr);
cairo_surface_flush(surface);
//pause for a little bit.
int c = getchar();
//change the text around so we can see the screen update.
for(int i = 0; i < strnlen(argv[1], 100); i++)
{
argv[1][i] = argv[1][i + 1];
}
if(c == 'q')
{
break;
}
}
cairo_surface_destroy(surface);
return 0;
}
On Linux systems that have Cairo installed, it can be compiled with
gcc -o myprog $(pkg-config --cflags --libs cairo x11) -std=gnu99 main.c
And it should be run with a single argument.
For reasons I don't understand at all, inserting the line
cairo_pop_group_to_source(cr);
cairo_paint(cr);
cairo_surface_write_to_png (surface, "hello.png"); //<--------- inserted
cairo_surface_flush(surface);
Puts something on the screen, but there are 2 problems:
Text that I draw with this method is persistent, creating a smearing effect.
I don't want some .png file mediating between my program and an x11 window. Data should be sent directly!
Several issues:
In X11, the X11 server doesn't save what you drew to a window, but instead sends an ExposeEvent to your window that tells it to redraw. This means you get a black window, because you do not handle this event.
getchar only gives you something after a line break, so just typing something won't help.
libX11 buffers stuff and only sends it to the X11 server when you wait for an event (or the buffer fills up). Since you never wait for an event, it never flushes. Calling XFlush explicitly helps.
The group that you push is useless. Just get rid of it.
Your code to move the string one direction to the left easily goes beyond the end of the string. You apparently know this already, because you 'fixed' this with a strnlen.
Here is a little better solution, but it still gives you an initially black window, because you draw to it before it is mapped:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <cairo-xlib.h>
#include <X11/Xlib.h>
//This function should give us a new x11 surface to draw on.
cairo_surface_t* create_x11_surface(Display *d, int x, int y)
{
Drawable da;
int screen;
cairo_surface_t* sfc;
screen = DefaultScreen(d);
da = XCreateSimpleWindow(d, DefaultRootWindow(d), 0, 0, x, y, 0, 0, 0);
XSelectInput(d, da, ButtonPressMask | KeyPressMask);
XMapWindow(d, da);
sfc = cairo_xlib_surface_create(d, da, DefaultVisual(d, screen), x, y);
return sfc;
}
int main(int argc, char** argv)
{
Display *d = XOpenDisplay(NULL);
if (d == NULL) {
fprintf(stderr, "Failed to open display\n");
return 1;
}
//create a new cairo surface in an x11 window as well as a cairo_t* to draw
//on the x11 window with.
cairo_surface_t* surface = create_x11_surface(d, 300, 200);
cairo_t* cr = cairo_create(surface);
char *text = argv[1];
size_t text_len = 0;
if (argc != 2)
text = NULL;
else
text_len = strlen(text);
while(1)
{
// Clear the background
cairo_set_source_rgb(cr, 0, 0, 0);
cairo_paint(cr);
//draw some text
cairo_select_font_face(cr, "serif", CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_BOLD);
cairo_set_font_size(cr, 32.0);
cairo_set_source_rgb(cr, 0, 0, 1.0);
cairo_move_to(cr, 10.0, 25.0);
if (text)
cairo_show_text(cr, text);
else
cairo_show_text(cr, "usage: ./p1 <string>");
cairo_surface_flush(surface);
XFlush(d);
//pause for a little bit.
int c = getchar();
//change the text around so we can see the screen update.
memmove(text, &text[1], text_len);
if (text_len > 0)
text_len--;
printf("got char %c\n", c);
if(c == 'q')
{
break;
}
}
// XXX: Lots of other stuff isn't properly destroyed here
cairo_surface_destroy(surface);
return 0;
}
Edit: Also, why exactly do you feel like cairo only gives you a woefully incomplete guide? It tells you how to get the cairo parts working and it also explains you some parts about X11, even though you should already know those if you want to use cairo-x11. That's none of its business. The guide you linked to even provides a complete, working and self-contained example: https://www.cypherpunk.at/files/2014/11/cairo_xlib_simple.c
I've you would have read the complete text of this "imcomplete guide" you would have seen that there is a link to the full sample: https://www.cypherpunk.at/files/2014/11/cairo_xlib_simple.c .
I am trying to write a little application in order to understand how evas works with X11.
I haven't find a full example in the documentation only some parts that I try to use.
Here is the code:
#include <stdio.h>
#include <stdlib.h>
#include <X11/Xlib.h>
#include <Evas.h>
#include <Evas_Engine_Software_X11.h>
/*
gcc -o evas_software_x11 evas_software_x11.c $(pkg-config --libs --cflags x11 evas)
*/
#define WIDTH 640
#define HEIGHT 480
int main(int argc, char **argv)
{
Evas *canevas;
Evas_Engine_Info_Software_X11 *einfo;
Display * display;
Window win;
display = XOpenDisplay(NULL);
int s;
XEvent e;
s = DefaultScreen(display);
win = XCreateSimpleWindow( display,
RootWindow(display, s),
10,10,WIDTH,HEIGHT,1,
BlackPixel(display, s),
WhitePixel(display, s));
XSelectInput(display, win, ExposureMask | KeyPressMask);
evas_init();
/*Création et configuration du canevas*/
canevas = evas_new();
evas_output_method_set(canevas, evas_render_method_lookup("software_x11"));
evas_output_size_set(canevas, WIDTH, HEIGHT);
evas_output_viewport_set(canevas, 0, 0, WIDTH, HEIGHT);
einfo = NULL;
einfo = (Evas_Engine_Info_Software_X11 *) evas_engine_info_get(canevas);
if(!einfo)
{
printf("einfo not valide\n");
exit(EXIT_FAILURE);
}
einfo->info.display = display;
einfo->info.visual = DefaultVisual(display, DefaultScreen(display));
einfo->info.colormap = DefaultColormap(display, DefaultScreen(display));
einfo->info.drawable = win;
einfo->info.depth = DefaultDepth(display, DefaultScreen(display));
evas_engine_info_set(canevas, (Evas_Engine_Info *) einfo);
/*Création d'un fond et d'un rectangle pour l'exemple*/
Evas_Object * bg, *rect;
bg = evas_object_rectangle_add(canevas);
evas_object_move(bg, 0, 0);
evas_object_resize(bg, WIDTH, HEIGHT);
evas_object_color_set(bg, 0, 128, 0, 128); // 50% opaque vert
eavs_object_show(bg);
rect = evas_object_rectangle_add(canevas);
evas_object_move(rect, 20, 20);
evas_object_resize(rect, 100, 100);
evas_object_color_set(rect, 255, 0, 0, 255); // opaque rouge
eavs_object_show(rect);
evas_render(canevas);
while(1) {
XnextEvent(display, &e);
if (e.type == Expose)
{
}
if (e.type == KeyPress)
break;
}
evas_free(canevas);
evas_shutdown();
XCloseDisplay(d);
return EXIT_SUCCESS;
}
When I compile it I have this error:
gcc -o evas_software_x11 evas_software_x11.c $(pkg-config --libs --cflags evas x11)
evas_software_x11.c: In function ‘main’:
evas_software_x11.c:45:14: erreur: ‘struct <anonymous>’ has no member named ‘display’
einfo->info.display = display;
^
But the part einfo->info.display = display comes from the official documentation :
https://build.enlightenment.org/job/nightly_efl_gcc_x86_64/lastSuccessfulBuild/artifact/doc/html/group__Evas__Output__Method.html#details
Any idea on where I have done an error?
The documentation is not up to date.
The info structure have a member named connection instead of display ( see file
Evas_Engine_Software_X11.h and http://lists.enlightenment.fr/enlightenment-devel/att-27663/expedite_merge_1.diff ).
In SDL2 I want to be able to draw changes to one buffer rather than redraw the whole image to two different buffers as my setup seems to be doing. Below is a really quick test which shows the unwanted behavior:
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <SDL.h>
// Compile: gcc test.c -I/usr/local/include/SDL2 -L/usr/local/lib -lSDL2
void putPixel(SDL_Renderer *renderer, int x, int y)
{
SDL_SetRenderDrawColor(renderer, 255,255,255,255);
SDL_RenderDrawPoint(renderer, x, y);
}
int main(int argc, char* argv[]) {
int width = 640;
int height = 480;
SDL_Window *window = SDL_CreateWindow("Test", 0,0,width,height, 0);
if (window == NULL)
{
return -1;
}
SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
if (renderer == NULL)
{
return -2;
}
SDL_SetRenderDrawBlendMode(renderer,SDL_BLENDMODE_BLEND);
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderClear(renderer);
for(int x=0;x<8;x++)
{
for(int y=0;y<10;y++)
{
putPixel(renderer,40+x*10,50+y);
}
SDL_RenderPresent(renderer);
sleep(1);
}
SDL_Quit();
return 0;
}
The output from this is two alternating screens. It is obviously using a double buffer which means I have to clear and redraw to get the output I want. After each cycle of the for...loop I wanted to add a line to the buffer - there should have been 8 lines at the end of the program running. In this case I got 4 on one buffer and 4 on another. I don't want to redraw the previous lines again either, hence the need for one buffer:
This uses a texture as a buffer and copies this to the screen when done.
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <SDL.h>
// Compile: gcc test.c -I/usr/local/include/SDL2 -L/usr/local/lib -lSDL2
void putPixel(SDL_Renderer *renderer, int x, int y)
{
SDL_SetRenderDrawColor(renderer, 255,255,255,255);
SDL_RenderDrawPoint(renderer, x, y);
}
int main(int argc, char* argv[]) {
int width = 640;
int height = 480;
SDL_Window *window = SDL_CreateWindow("Test", 0,0,width,height, 0);
if (window == NULL)
{
return -1;
}
SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE);
if (renderer == NULL)
{
return -2;
}
SDL_SetRenderDrawBlendMode(renderer,SDL_BLENDMODE_BLEND);
/* Create texture for display */
SDL_Texture *display = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, width, height);
SDL_SetRenderTarget(renderer, display);
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderClear(renderer);
for(int x=0;x<8;x++)
{
SDL_SetRenderTarget(renderer, display);
for(int y=0;y<10;y++)
{
putPixel(renderer,40+x*10,50+y);
}
SDL_SetRenderTarget(renderer, NULL);
SDL_RenderCopy(renderer, display, NULL, NULL);
SDL_RenderPresent(renderer);
sleep(1);
}
SDL_Quit();
return 0;
}
The output from this is below: