/*************************************************************************** cpaint_impl.cpp (c) 2000-2009 BenoƮt Minisini This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ***************************************************************************/ #define __CPAINT_IMPL_CPP #include #include #include "gambas.h" #include "gb_common.h" #include "widgets.h" #include "CWindow.h" #include "CDrawingArea.h" #include "CPicture.h" #include "CImage.h" #include "CFont.h" #include "CDraw.h" #include "cpaint_impl.h" typedef struct { cairo_t *context; CFONT *font; } GB_PAINT_EXTRA; #define EXTRA(d) ((GB_PAINT_EXTRA *)d->extra) #define CONTEXT(d) EXTRA(d)->context static bool init_painting(GB_PAINT *d, cairo_surface_t *target, int w, int h) { d->width = w; d->height = h; d->resolutionX = 96; //device->physicalDpiX(); d->resolutionY = 96; //device->physicalDpiY(); /*if (device->paintingActive()) { GB.Error("Device already being painted"); return TRUE; }*/ EXTRA(d)->context = cairo_create(target); cairo_surface_destroy(target); EXTRA(d)->font = NULL; return FALSE; } static int Begin(GB_PAINT *d) { void *device = d->device; cairo_surface_t *target = NULL; int w, h; if (GB.Is(device, CLASS_Picture)) { gPicture *picture = ((CPICTURE *)device)->picture; GdkDrawable *pixmap; if (picture->isVoid()) { GB.Error("Bad picture"); return TRUE; } pixmap = (GdkDrawable *)picture->getPixmap(); w = picture->width(); h = picture->height(); target = cairo_xlib_surface_create(gdk_x11_drawable_get_xdisplay(pixmap), gdk_x11_drawable_get_xid(pixmap), gdk_x11_visual_get_xvisual(gdk_drawable_get_visual(pixmap)), w, h); } else if (GB.Is(device, CLASS_Image)) { gPicture *picture = CIMAGE_get(((CIMAGE *)device)); GdkPixbuf *pixbuf; if (picture->isVoid()) { GB.Error("Bad picture"); return TRUE; } pixbuf = picture->getPixbuf(); w = picture->width(); h = picture->height(); target = cairo_image_surface_create_for_data(picture->data(), CAIRO_FORMAT_ARGB32, w, h, cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, w)); } else if (GB.Is(device, CLASS_DrawingArea)) { gDrawingArea *wid = (gDrawingArea *)((CWIDGET *)device)->widget; GdkDrawable *dr; w = wid->width(); h = wid->height(); if (wid->cached()) { wid->resizeCache(); // Why is it needed? dr = wid->buffer; } else { dr = GTK_LAYOUT(wid->widget)->bin_window; } /*target = cairo_xlib_surface_create(gdk_x11_drawable_get_xdisplay(dr), gdk_x11_drawable_get_xid(dr), gdk_x11_visual_get_xvisual(gdk_drawable_get_visual(dr)), w, h);*/ d->width = w; d->height = h; d->resolutionX = 96; //device->physicalDpiX(); d->resolutionY = 96; //device->physicalDpiY(); EXTRA(d)->context = gdk_cairo_create(dr); EXTRA(d)->font = NULL; return FALSE; } else return TRUE; return init_painting(d, target, w, h); } static void End(GB_PAINT *d) { void *device = d->device; GB.Unref(POINTER(&EXTRA(d)->font)); cairo_destroy(EXTRA(d)->context); if (GB.Is(device, CLASS_DrawingArea)) { gDrawingArea *wid = (gDrawingArea *)((CWIDGET *)device)->widget; if (wid->cached()) wid->setCache(); } } static void Save(GB_PAINT *d) { cairo_save(CONTEXT(d)); } static void Restore(GB_PAINT *d) { cairo_restore(CONTEXT(d)); } // Font is used by X11! static void Paint_Font(GB_PAINT *d, int set, GB_FONT *font) { if (!EXTRA(d)->font) { EXTRA(d)->font = CFONT_create(new gFont()); GB.Ref(EXTRA(d)->font); } if (set) { GB.Ref(*font); GB.Unref(POINTER(&EXTRA(d)->font)); EXTRA(d)->font = (CFONT *)(*font); } else *font = (GB_FONT)EXTRA(d)->font; } /*static void init_path(GB_PAINT *d) { switch (EXTRA(d)->fillRule) { case GB_PAINT_FILL_RULE_WINDING: PATH(d)->setFillRule(Qt::WindingFill); break; case GB_PAINT_FILL_RULE_EVEN_ODD: default: PATH(d)->setFillRule(Qt::OddEvenFill); } }*/ static void Clip(GB_PAINT *d, int preserve) { if (preserve) cairo_clip_preserve(CONTEXT(d)); else cairo_clip(CONTEXT(d)); } static void ResetClip(GB_PAINT *d) { cairo_reset_clip(CONTEXT(d)); } static void ClipExtents(GB_PAINT *d, GB_EXTENTS *ext) { double x1, y1, x2, y2; cairo_clip_extents(CONTEXT(d), &x1, &y1, &x2, &y2); ext->x1 = (float)x1; ext->y1 = (float)y1; ext->x2 = (float)x2; ext->y2 = (float)y2; } static void Fill(GB_PAINT *d, int preserve) { if (preserve) cairo_fill_preserve(CONTEXT(d)); else cairo_fill(CONTEXT(d)); } static void Stroke(GB_PAINT *d, int preserve) { if (preserve) cairo_stroke_preserve(CONTEXT(d)); else cairo_stroke(CONTEXT(d)); } static void PathExtents(GB_PAINT *d, GB_EXTENTS *ext) { double x1, y1, x2, y2; cairo_path_extents(CONTEXT(d), &x1, &y1, &x2, &y2); ext->x1 = (float)x1; ext->y1 = (float)y1; ext->x2 = (float)x2; ext->y2 = (float)y2; } static int PathContains(GB_PAINT *d, float x, float y) { return cairo_in_fill(CONTEXT(d), (double)x, (double)y); } static void Dash(GB_PAINT *d, int set, float **dashes, int *count) { int i; if (set) { if (!*dashes || *count == 0) cairo_set_dash(CONTEXT(d), NULL, 0, 0.0); else { double dd[*count]; for (i = 0; i < *count; i++) dd[i] = (*dashes)[i]; cairo_set_dash(CONTEXT(d), dd, *count, 0.0); } } else { *count = cairo_get_dash_count(CONTEXT(d)); if (*count) { double dd[*count]; cairo_get_dash(CONTEXT(d), dd, NULL); GB.Alloc(POINTER(dashes), sizeof(float) * *count); for (int i = 0; i < *count; i++) (*dashes)[i] = (float)dd[i]; } else { *dashes = NULL; } } } static void DashOffset(GB_PAINT *d, int set, float *offset) { if (set) { int count = cairo_get_dash_count(CONTEXT(d)); double dashes[count]; cairo_get_dash(CONTEXT(d), dashes, NULL); cairo_set_dash(CONTEXT(d), dashes, count, (double)*offset); } else { double v; cairo_get_dash(CONTEXT(d), NULL, &v); *offset = (float)v; } } static void FillRule(GB_PAINT *d, int set, int *value) { if (set) { cairo_fill_rule_t v; switch(*value) { case GB_PAINT_FILL_RULE_EVEN_ODD: v = CAIRO_FILL_RULE_EVEN_ODD; break; case GB_PAINT_FILL_RULE_WINDING: default: v = CAIRO_FILL_RULE_WINDING; } cairo_set_fill_rule(CONTEXT(d), v); } else { switch(cairo_get_fill_rule(CONTEXT(d))) { case CAIRO_FILL_RULE_EVEN_ODD: *value = GB_PAINT_FILL_RULE_EVEN_ODD; break; case CAIRO_FILL_RULE_WINDING: default: *value = GB_PAINT_FILL_RULE_WINDING; } } } static void LineCap(GB_PAINT *d, int set, int *value) { if (set) { cairo_line_cap_t v; switch (*value) { case GB_PAINT_LINE_CAP_ROUND: v = CAIRO_LINE_CAP_ROUND; break; case GB_PAINT_LINE_CAP_SQUARE: v = CAIRO_LINE_CAP_SQUARE; break; case GB_PAINT_LINE_CAP_BUTT: default: v = CAIRO_LINE_CAP_BUTT; } cairo_set_line_cap(CONTEXT(d), v); } else { switch (cairo_get_line_cap(CONTEXT(d))) { case CAIRO_LINE_CAP_ROUND: *value = GB_PAINT_LINE_CAP_ROUND; break; case CAIRO_LINE_CAP_SQUARE: *value = GB_PAINT_LINE_CAP_SQUARE; break; case CAIRO_LINE_CAP_BUTT: default: *value = GB_PAINT_LINE_CAP_BUTT; } } } static void LineJoin(GB_PAINT *d, int set, int *value) { if (set) { cairo_line_join_t v; switch (*value) { case GB_PAINT_LINE_JOIN_ROUND: v = CAIRO_LINE_JOIN_ROUND; break; case GB_PAINT_LINE_JOIN_BEVEL: v = CAIRO_LINE_JOIN_BEVEL; break; case GB_PAINT_LINE_JOIN_MITER: default: v = CAIRO_LINE_JOIN_MITER; } cairo_set_line_join(CONTEXT(d), v); } else { switch (cairo_get_line_join(CONTEXT(d))) { case CAIRO_LINE_JOIN_ROUND: *value = GB_PAINT_LINE_JOIN_ROUND; break; case CAIRO_LINE_JOIN_BEVEL: *value = GB_PAINT_LINE_JOIN_BEVEL; break; case CAIRO_LINE_JOIN_MITER: default: *value = GB_PAINT_LINE_JOIN_MITER; } } } static void LineWidth(GB_PAINT *d, int set, float *value) { if (set) cairo_set_line_width(CONTEXT(d), (double)*value); else *value = (float)cairo_get_line_width(CONTEXT(d)); } static void MiterLimit(GB_PAINT *d, int set, float *value) { if (set) cairo_set_miter_limit(CONTEXT(d), (double)*value); else *value = (float)cairo_get_miter_limit(CONTEXT(d)); } static void Operator(GB_PAINT *d, int set, int *value) { if (set) { cairo_operator_t v; switch (*value) { case GB_PAINT_OPERATOR_CLEAR: v = CAIRO_OPERATOR_CLEAR; break; case GB_PAINT_OPERATOR_SOURCE: v = CAIRO_OPERATOR_SOURCE; break; case GB_PAINT_OPERATOR_IN: v = CAIRO_OPERATOR_IN; break; case GB_PAINT_OPERATOR_OUT: v = CAIRO_OPERATOR_OUT; break; case GB_PAINT_OPERATOR_ATOP: v = CAIRO_OPERATOR_ATOP; break; case GB_PAINT_OPERATOR_DEST: v = CAIRO_OPERATOR_DEST; break; case GB_PAINT_OPERATOR_DEST_OVER: v = CAIRO_OPERATOR_DEST_OVER; break; case GB_PAINT_OPERATOR_DEST_IN: v = CAIRO_OPERATOR_DEST_IN; break; case GB_PAINT_OPERATOR_DEST_OUT: v = CAIRO_OPERATOR_DEST_OUT; break; case GB_PAINT_OPERATOR_DEST_ATOP: v = CAIRO_OPERATOR_DEST_ATOP; break; case GB_PAINT_OPERATOR_XOR: v = CAIRO_OPERATOR_XOR; break; case GB_PAINT_OPERATOR_ADD: v = CAIRO_OPERATOR_ADD; break; case GB_PAINT_OPERATOR_SATURATE: v = CAIRO_OPERATOR_SATURATE; break; case GB_PAINT_OPERATOR_OVER: default: v = CAIRO_OPERATOR_OVER; break; } cairo_set_operator(CONTEXT(d), v); } else { switch (cairo_get_operator(CONTEXT(d))) { case CAIRO_OPERATOR_CLEAR: *value = GB_PAINT_OPERATOR_CLEAR; break; case CAIRO_OPERATOR_SOURCE: *value = GB_PAINT_OPERATOR_SOURCE; break; case CAIRO_OPERATOR_IN: *value = GB_PAINT_OPERATOR_IN; break; case CAIRO_OPERATOR_OUT: *value = GB_PAINT_OPERATOR_OUT; break; case CAIRO_OPERATOR_ATOP: *value = GB_PAINT_OPERATOR_ATOP; break; case CAIRO_OPERATOR_DEST: *value = GB_PAINT_OPERATOR_DEST; break; case CAIRO_OPERATOR_DEST_OVER: *value = GB_PAINT_OPERATOR_DEST_OVER; break; case CAIRO_OPERATOR_DEST_IN: *value = GB_PAINT_OPERATOR_DEST_IN; break; case CAIRO_OPERATOR_DEST_OUT: *value = GB_PAINT_OPERATOR_DEST_OUT; break; case CAIRO_OPERATOR_DEST_ATOP: *value = GB_PAINT_OPERATOR_DEST_ATOP; break; case CAIRO_OPERATOR_XOR: *value = GB_PAINT_OPERATOR_XOR; break; case CAIRO_OPERATOR_ADD: *value = GB_PAINT_OPERATOR_ADD; break; case CAIRO_OPERATOR_SATURATE: *value = GB_PAINT_OPERATOR_SATURATE; break; case CAIRO_OPERATOR_OVER: default: *value = GB_PAINT_OPERATOR_OVER; } } } static void NewPath(GB_PAINT *d) { cairo_new_path(CONTEXT(d)); } static void ClosePath(GB_PAINT *d) { cairo_close_path(CONTEXT(d)); } static void Arc(GB_PAINT *d, float xc, float yc, float radius, float angle, float length) { cairo_new_sub_path(CONTEXT(d)); if (length >= 0.0) cairo_arc(CONTEXT(d), xc, yc, radius, angle, angle + length); else cairo_arc_negative(CONTEXT(d), xc, yc, radius, angle, angle + length); } static void Rectangle(GB_PAINT *d, float x, float y, float width, float height) { cairo_rectangle(CONTEXT(d), x, y, width, height); } static void GetCurrentPoint(GB_PAINT *d, float *x, float *y) { double cx, cy; cairo_get_current_point(CONTEXT(d), &cx, &cy); *x = (float)cx; *y = (float)cy; } static void MoveTo(GB_PAINT *d, float x, float y) { cairo_move_to(CONTEXT(d), x, y); } static void LineTo(GB_PAINT *d, float x, float y) { cairo_line_to(CONTEXT(d), x, y); } static void CurveTo(GB_PAINT *d, float x1, float y1, float x2, float y2, float x3, float y3) { cairo_curve_to(CONTEXT(d), x1, y1, x2, y2, x3, y3); } static void Text(GB_PAINT *d, const char *text, int len, float w, float h, int align) { PangoLayout *layout; CFONT *font; float tw, th, offx, offy; layout = pango_cairo_create_layout(CONTEXT(d)); pango_layout_set_text(layout, text, len); Paint_Font(d, FALSE, (GB_FONT *)&font); gt_add_layout_from_font(layout, font->font); gt_layout_alignment(layout, w, h, &tw, &th, align, &offx, &offy); cairo_rel_move_to(CONTEXT(d), offx, offy); pango_cairo_layout_path(CONTEXT(d), layout); g_object_unref(layout); } static void RichText(GB_PAINT *d, const char *text, int len, float w, float h, int align) { } static void TextExtents(GB_PAINT *d, const char *text, int len, GB_EXTENTS *ext) { } static void Matrix(GB_PAINT *d, int set, GB_TRANSFORM matrix) { cairo_matrix_t *t = (cairo_matrix_t *)matrix; if (set) { if (t) cairo_set_matrix(CONTEXT(d), t); else cairo_identity_matrix(CONTEXT(d)); } else cairo_get_matrix(CONTEXT(d), t); } static void SetBrush(GB_PAINT *d, GB_BRUSH brush) { cairo_set_source(CONTEXT(d), (cairo_pattern_t *)brush); } static void BrushFree(GB_BRUSH brush) { // Should I release the surface associated with an image brush? cairo_pattern_destroy((cairo_pattern_t *)brush); } static void BrushColor(GB_BRUSH *brush, GB_COLOR color) { int r, g, b, a; GB_COLOR_SPLIT(color, r, g, b, a); *brush = (GB_BRUSH)cairo_pattern_create_rgba(r / 255.0, g / 255.0, b / 255.0, a / 255.0); } static cairo_surface_t *gdk_cairo_create_surface_from_pixbuf(const GdkPixbuf *pixbuf) { gint width = gdk_pixbuf_get_width (pixbuf); gint height = gdk_pixbuf_get_height (pixbuf); guchar *gdk_pixels = gdk_pixbuf_get_pixels (pixbuf); int gdk_rowstride = gdk_pixbuf_get_rowstride (pixbuf); int n_channels = gdk_pixbuf_get_n_channels (pixbuf); int cairo_stride; guchar *cairo_pixels; cairo_format_t format; cairo_surface_t *surface; static const cairo_user_data_key_t key = { 0 }; int j; if (n_channels == 3) format = CAIRO_FORMAT_RGB24; else format = CAIRO_FORMAT_ARGB32; cairo_stride = cairo_format_stride_for_width (format, width); cairo_pixels = (uchar *)g_malloc (height * cairo_stride); surface = cairo_image_surface_create_for_data ((unsigned char *)cairo_pixels, format, width, height, cairo_stride); cairo_surface_set_user_data (surface, &key, cairo_pixels, (cairo_destroy_func_t)g_free); for (j = height; j; j--) { guchar *p = gdk_pixels; guchar *q = cairo_pixels; if (n_channels == 3) { guchar *end = p + 3 * width; while (p < end) { #if G_BYTE_ORDER == G_LITTLE_ENDIAN q[0] = p[2]; q[1] = p[1]; q[2] = p[0]; #else q[1] = p[0]; q[2] = p[1]; q[3] = p[2]; #endif p += 3; q += 4; } } else { guchar *end = p + 4 * width; guint t1,t2,t3; #define MULT(d,c,a,t) G_STMT_START { t = c * a + 0x7f; d = ((t >> 8) + t) >> 8; } G_STMT_END while (p < end) { #if G_BYTE_ORDER == G_LITTLE_ENDIAN MULT(q[0], p[2], p[3], t1); MULT(q[1], p[1], p[3], t2); MULT(q[2], p[0], p[3], t3); q[3] = p[3]; #else q[0] = p[3]; MULT(q[1], p[0], p[3], t1); MULT(q[2], p[1], p[3], t2); MULT(q[3], p[2], p[3], t3); #endif p += 4; q += 4; } #undef MULT } gdk_pixels += gdk_rowstride; cairo_pixels += cairo_stride; } return surface; } static void BrushImage(GB_BRUSH *brush, GB_IMAGE image) { gPicture *picture = CIMAGE_get((CIMAGE *)image); cairo_surface_t *surface; cairo_pattern_t *pattern; surface = gdk_cairo_create_surface_from_pixbuf(picture->getPixbuf()); pattern = cairo_pattern_create_for_surface(surface); cairo_surface_destroy(surface); cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT); *brush = (GB_BRUSH)pattern; } static void handle_color_stop(cairo_pattern_t *pattern, int nstop, const double *positions, const GB_COLOR *colors) { int i, r, g, b, a; for (i = 0; i < nstop; i++) { GB_COLOR_SPLIT(colors[i], r, g, b, a); cairo_pattern_add_color_stop_rgba(pattern, positions[i], r / 255.0, g / 255.0, b / 255.0, a / 255.0); } } static void set_pattern_extend(cairo_pattern_t *pattern, int extend) { cairo_extend_t cext; switch (extend) { case GB_PAINT_EXTEND_REPEAT: cext = CAIRO_EXTEND_REPEAT; break; case GB_PAINT_EXTEND_REFLECT: cext = CAIRO_EXTEND_REFLECT; break; case GB_PAINT_EXTEND_PAD: default: cext = CAIRO_EXTEND_PAD; } cairo_pattern_set_extend(pattern, cext); } static void BrushLinearGradient(GB_BRUSH *brush, float x0, float y0, float x1, float y1, int nstop, double *positions, GB_COLOR *colors, int extend) { cairo_pattern_t *pattern; pattern = cairo_pattern_create_linear(x0, y0, x1, y1); handle_color_stop(pattern, nstop, positions, colors); set_pattern_extend(pattern, extend); *brush = (GB_BRUSH)pattern; } static void BrushRadialGradient(GB_BRUSH *brush, float cx0, float cy0, float r0, float cx1, float cy1, float r1, int nstop, double *positions, GB_COLOR *colors, int extend) { cairo_pattern_t *pattern; pattern = cairo_pattern_create_radial(cx0, cy0, r0, cx1, cy1, r1); handle_color_stop(pattern, nstop, positions, colors); set_pattern_extend(pattern, extend); *brush = (GB_BRUSH)pattern; } static void BrushMatrix(GB_BRUSH brush, int set, GB_TRANSFORM matrix) { cairo_matrix_t *t = (cairo_matrix_t *)matrix; cairo_pattern_t *pattern = (cairo_pattern_t *)brush; cairo_matrix_t actual; if (set) { if (t) { actual = *t; cairo_matrix_invert(&actual); } else cairo_matrix_init_identity(&actual); cairo_pattern_set_matrix(pattern, &actual); } else cairo_pattern_get_matrix(pattern, t); } static void TransformCreate(GB_TRANSFORM *matrix) { GB.Alloc(POINTER(matrix), sizeof(cairo_matrix_t)); cairo_matrix_init_identity((cairo_matrix_t *)*matrix); } static void TransformDelete(GB_TRANSFORM *matrix) { GB.Free(POINTER(matrix)); } static void TransformInit(GB_TRANSFORM matrix, float xx, float yx, float xy, float yy, float x0, float y0) { cairo_matrix_init((cairo_matrix_t *)matrix, xx, yx, xy, yy, x0, y0); } static void TransformTranslate(GB_TRANSFORM matrix, float tx, float ty) { cairo_matrix_translate((cairo_matrix_t *)matrix, tx, ty); } static void TransformScale(GB_TRANSFORM matrix, float sx, float sy) { cairo_matrix_scale((cairo_matrix_t *)matrix, sx, sy); } static void TransformRotate(GB_TRANSFORM matrix, float angle) { cairo_matrix_rotate((cairo_matrix_t *)matrix, angle); } static int TransformInvert(GB_TRANSFORM matrix) { cairo_status_t status = cairo_matrix_invert((cairo_matrix_t *)matrix); return status != CAIRO_STATUS_SUCCESS; } static void TransformMultiply(GB_TRANSFORM matrix, GB_TRANSFORM matrix2) { cairo_matrix_multiply((cairo_matrix_t *)matrix, (cairo_matrix_t *)matrix, (cairo_matrix_t *)matrix2); } GB_PAINT_DESC PAINT_Interface = { sizeof(GB_PAINT_EXTRA), Begin, End, Save, Restore, Paint_Font, Clip, ResetClip, ClipExtents, Fill, Stroke, PathExtents, PathContains, Dash, DashOffset, FillRule, LineCap, LineJoin, LineWidth, MiterLimit, Operator, NewPath, ClosePath, Arc, Rectangle, GetCurrentPoint, MoveTo, LineTo, CurveTo, Text, RichText, TextExtents, Matrix, SetBrush, { BrushFree, BrushColor, BrushImage, BrushLinearGradient, BrushRadialGradient, BrushMatrix, }, { TransformCreate, TransformDelete, TransformInit, TransformTranslate, TransformScale, TransformRotate, TransformInvert, TransformMultiply } }; void PAINT_begin(void *device) { DRAW.Paint.Begin(device); } void PAINT_end() { DRAW.Paint.End(); } void PAINT_clip(int x, int y, int w, int h) { GB_PAINT *d = (GB_PAINT *)DRAW.Paint.GetCurrent(); cairo_rectangle(CONTEXT(d), x, y, w, h); cairo_clip(CONTEXT(d)); }