/* * tkUnixCairoPath.c -- * * This file implements path drawing API's using the Cairo rendering engine. * * TODO: implement text drawing using glyphs instead of the "toy" text API. * * Copyright (c) 2005-2008 Mats Bengtsson * */ /* This should go into configure.in but don't know how. */ #ifdef USE_PANIC_ON_PHOTO_ALLOC_FAILURE #undef USE_PANIC_ON_PHOTO_ALLOC_FAILURE #endif #include <cairo.h> #include <cairo-xlib.h> #include <tkUnixInt.h> #include "tkoPath.h" #define TINT_INT_CALCULATION #define Blue255FromXColorPtr(xc) ((xc)->blue >> 8) #define Green255FromXColorPtr(xc) ((xc)->green >> 8) #define Red255FromXColorPtr(xc) ((xc)->red >> 8) #define BlueDoubleFromXColorPtr(xc) ((double) ((xc)->blue >> 8) / 255.0) #define GreenDoubleFromXColorPtr(xc) ((double) ((xc)->green >> 8) / 255.0) #define RedDoubleFromXColorPtr(xc) ((double) ((xc)->red >> 8) / 255.0) MODULE_SCOPE int Tk_PathAntiAlias; MODULE_SCOPE int Tk_PathSurfaceCopyPremultiplyAlpha; MODULE_SCOPE int Tk_PathDepixelize; static union { short set; char little; } kEndianess; /* * @@@ Need to use cairo_image_surface_create_for_data() here since * prior to 1.2 there doesn't exist any cairo_image_surface_get_data() * accessor. */ typedef struct PathSurfaceCairoRecord { unsigned char *data; cairo_format_t format; int width; int height; int stride; /* number of bytes between the start of rows in the buffer */ } PathSurfaceCairoRecord; /* * This is used as a place holder for platform dependent * stuff between each call. */ typedef struct TkPathContext_ { cairo_t *c; cairo_surface_t *surface; PathSurfaceCairoRecord *record; /* NULL except for memory surfaces. * Skip when cairo 1.2 widely spread. */ int widthCode; /* Used to depixelize the strokes: * 0: not integer width * 1: odd integer width * 2: even integer width */ cairo_matrix_t def_matrix; /* For TkPathResetTMatrix() */ } TkPathContext_; static void TkPathPrepareForStroke( TkPathContext ctx, Tk_PathStyle * style); static void CairoSetFill( TkPathContext ctx, Tk_PathStyle * style) { TkPathContext_ *context = (TkPathContext_ *) ctx; /* Patch from Tim Edwards to handle color correctly on 64 bit arch. */ cairo_set_source_rgba(context->c, (double)(GetColorFromPathColor(style->fill)->red) / 0xFFFF, (double)(GetColorFromPathColor(style->fill)->green) / 0xFFFF, (double)(GetColorFromPathColor(style->fill)->blue) / 0xFFFF, style->fillOpacity); cairo_set_fill_rule(context->c, (style->fillRule == WindingRule) ? CAIRO_FILL_RULE_WINDING : CAIRO_FILL_RULE_EVEN_ODD); } TkPathContext TkPathInit( Tk_Window tkwin, Drawable d) { cairo_t *c; cairo_surface_t *surface; TkPathContext_ *context = (TkPathContext_ *) ckalloc((unsigned)(sizeof(TkPathContext_))); Window dummy; int x, y; unsigned int width, height, borderWidth, depth; /* Find size of Drawable */ XGetGeometry(Tk_Display(tkwin), d, &dummy, &x, &y, &width, &height, &borderWidth, &depth); surface = cairo_xlib_surface_create(Tk_Display(tkwin), d, Tk_Visual(tkwin), width, height); c = cairo_create(surface); context->c = c; context->surface = surface; context->record = NULL; context->widthCode = 0; cairo_get_matrix(context->c, &context->def_matrix); return (TkPathContext) context; } TkPathContext TkPathInitSurface( Display * display, int width, int height) { cairo_t *c; cairo_surface_t *surface; unsigned char *data; int stride; /* * @@@ Need to use cairo_image_surface_create_for_data() here since * prior to 1.2 there doesn't exist any cairo_image_surface_get_data() * accessor. */ TkPathContext_ *context = (TkPathContext_ *) ckalloc((unsigned)(sizeof(TkPathContext_))); PathSurfaceCairoRecord *record = (PathSurfaceCairoRecord *) ckalloc((unsigned)(sizeof(PathSurfaceCairoRecord))); stride = 4 * width; /* Round up to nearest multiple of 16 */ stride = (stride + (16 - 1)) & ~(16 - 1); data = (unsigned char *)ckalloc(height * stride); memset(data, '\0', height * stride); surface = cairo_image_surface_create_for_data(data, CAIRO_FORMAT_ARGB32, width, height, stride); record->data = data; record->format = CAIRO_FORMAT_ARGB32; record->width = width; record->height = height; record->stride = stride; c = cairo_create(surface); context->c = c; context->surface = surface; context->record = record; context->widthCode = 0; cairo_get_matrix(context->c, &context->def_matrix); return (TkPathContext) context; } void TkPathPushTMatrix( TkPathContext ctx, TkPathMatrix * m) { TkPathContext_ *context = (TkPathContext_ *) ctx; cairo_matrix_t matrix; if(m == NULL) { return; } cairo_matrix_init(&matrix, m->a, m->b, m->c, m->d, m->tx, m->ty); cairo_transform(context->c, &matrix); } void TkPathResetTMatrix( TkPathContext ctx) { TkPathContext_ *context = (TkPathContext_ *) ctx; context->widthCode = 0; cairo_set_matrix(context->c, &context->def_matrix); } void TkPathSaveState( TkPathContext ctx) { TkPathContext_ *context = (TkPathContext_ *) ctx; cairo_save(context->c); } void TkPathRestoreState( TkPathContext ctx) { TkPathContext_ *context = (TkPathContext_ *) ctx; cairo_restore(context->c); } void TkPathBeginPath( TkPathContext ctx, Tk_PathStyle * style) { TkPathContext_ *context = (TkPathContext_ *) ctx; int nint; double width; cairo_new_path(context->c); if(style->strokeColor == NULL) { context->widthCode = 0; } else { width = style->strokeWidth; nint = (int)(width + 0.5); context->widthCode = fabs(width - nint) > 0.01 ? 0 : 2 - nint % 2; } } void TkPathMoveTo( TkPathContext ctx, double x, double y) { TkPathContext_ *context = (TkPathContext_ *) ctx; if(Tk_PathDepixelize) { x = TK_PATH_DEPIXELIZE(context->widthCode, x); y = TK_PATH_DEPIXELIZE(context->widthCode, y); } cairo_move_to(context->c, x, y); } void TkPathLineTo( TkPathContext ctx, double x, double y) { TkPathContext_ *context = (TkPathContext_ *) ctx; if(Tk_PathDepixelize) { x = TK_PATH_DEPIXELIZE(context->widthCode, x); y = TK_PATH_DEPIXELIZE(context->widthCode, y); } cairo_line_to(context->c, x, y); } void TkPathQuadBezier( TkPathContext ctx, double ctrlX, double ctrlY, double x, double y) { TkPathContext_ *context = (TkPathContext_ *) ctx; double cx, cy; double x31, y31, x32, y32; if(Tk_PathDepixelize) { x = TK_PATH_DEPIXELIZE(context->widthCode, x); y = TK_PATH_DEPIXELIZE(context->widthCode, y); } cairo_get_current_point(context->c, &cx, &cy); /* * Conversion of quadratic bezier curve to * cubic bezier curve: (mozilla/svg) * Unchecked! Must be an approximation! */ x31 = cx + (ctrlX - cx) * 2 / 3; y31 = cy + (ctrlY - cy) * 2 / 3; x32 = ctrlX + (x - ctrlX) / 3; y32 = ctrlY + (y - ctrlY) / 3; cairo_curve_to(context->c, x31, y31, x32, y32, x, y); } void TkPathCurveTo( TkPathContext ctx, double x1, double y1, double x2, double y2, double x, double y) { TkPathContext_ *context = (TkPathContext_ *) ctx; if(Tk_PathDepixelize) { x = TK_PATH_DEPIXELIZE(context->widthCode, x); y = TK_PATH_DEPIXELIZE(context->widthCode, y); } cairo_curve_to(context->c, x1, y1, x2, y2, x, y); } void TkPathArcTo( TkPathContext ctx, double rx, double ry, double phiDegrees, /* The rotation angle in degrees! */ char largeArcFlag, char sweepFlag, double x, double y) { TkPathContext_ *context = (TkPathContext_ *) ctx; if(Tk_PathDepixelize) { x = TK_PATH_DEPIXELIZE(context->widthCode, x); y = TK_PATH_DEPIXELIZE(context->widthCode, y); } TkPathArcToUsingBezier(ctx, rx, ry, phiDegrees, largeArcFlag, sweepFlag, x, y); } void TkPathRectangle( TkPathContext ctx, double x, double y, double width, double height) { TkPathContext_ *context = (TkPathContext_ *) ctx; if(Tk_PathDepixelize) { x = TK_PATH_DEPIXELIZE(context->widthCode, x); y = TK_PATH_DEPIXELIZE(context->widthCode, y); } cairo_rectangle(context->c, x, y, width, height); } void TkPathOval( TkPathContext ctx, double cx, double cy, double rx, double ry) { TkPathContext_ *context = (TkPathContext_ *) ctx; if(rx == ry) { cairo_move_to(context->c, cx + rx, cy); cairo_arc(context->c, cx, cy, rx, 0.0, 2 * M_PI); cairo_close_path(context->c); } else { cairo_save(context->c); cairo_translate(context->c, cx, cy); cairo_scale(context->c, rx, ry); cairo_move_to(context->c, 1.0, 0.0); cairo_arc(context->c, 0.0, 0.0, 1.0, 0.0, 2 * M_PI); cairo_close_path(context->c); cairo_restore(context->c); } } static cairo_filter_t convertInterpolationToCairoFilter( int interpolation) { switch (interpolation) { case TK_PATH_IMAGEINTERPOLATION_None: return CAIRO_FILTER_FAST; case TK_PATH_IMAGEINTERPOLATION_Fast: return CAIRO_FILTER_GOOD; case TK_PATH_IMAGEINTERPOLATION_Best: return CAIRO_FILTER_BEST; } return CAIRO_FILTER_GOOD; } void TkPathImage( TkPathContext ctx, Tk_Image image, Tk_PhotoHandle photo, double x, double y, double width0, double height0, double fillOpacity, XColor * tintColor, double tintAmount, int interpolation, TkPathRect * srcRegion) { TkPathContext_ *context = (TkPathContext_ *) ctx; Tk_PhotoImageBlock block; cairo_surface_t *surface; cairo_format_t format; unsigned char *data = NULL; unsigned char *ptr = NULL; unsigned char *srcPtr, *dstPtr; int srcR, srcG, srcB, srcA; /* The source pixel offsets. */ int dstR, dstG, dstB, dstA; /* The destination pixel offsets. */ int pitch; int iwidth, iheight; int i, j; double width, height; cairo_filter_t filter; /* Return value? */ Tk_PhotoGetImage(photo, &block); iwidth = block.width; iheight = block.height; pitch = block.pitch; width = (width0 == 0.0) ? (double)iwidth : width0; height = (height0 == 0.0) ? (double)iheight : height0; /* * @format: the format of pixels in the buffer * @width: the width of the image to be stored in the buffer * @height: the eight of the image to be stored in the buffer * @stride: the number of bytes between the start of rows * in the buffer. Having this be specified separate from @width * allows for padding at the end of rows, or for writing * to a subportion of a larger image. */ /* * cairo_format_t * @CAIRO_FORMAT_ARGB32: each pixel is a 32-bit quantity, with * alpha in the upper 8 bits, then red, then green, then blue. * The 32-bit quantities are stored native-endian. Pre-multiplied * alpha is used. (That is, 50% transparent red is 0x80800000, * not 0x80ff0000.) */ if(block.pixelSize == 4) { format = CAIRO_FORMAT_ARGB32; /* * The offset array contains the offsets from the address of a * pixel to the addresses of the bytes containing the red, green, * blue and alpha (transparency) components. * * We need to copy pixel data from the source using the photo offsets * to cairos ARGB format which is in *native* endian order; Switch! */ srcR = block.offset[0]; srcG = block.offset[1]; srcB = block.offset[2]; srcA = block.offset[3]; dstR = 1; dstG = 2; dstB = 3; dstA = 0; if(!kEndianess.set) { kEndianess.set = 1; } if(kEndianess.little) { dstR = 3 - dstR, dstG = 3 - dstG, dstB = 3 - dstB, dstA = 3 - dstA; } data = (unsigned char *)ckalloc(pitch * iheight); ptr = data; if(tintColor && tintAmount > 0.0) { #ifdef TINT_INT_CALCULATION /* calculate with integer arithmetic */ uint32_t tintR, tintG, tintB, uAmount, uRemain; if(tintAmount > 1.0) tintAmount = 1.0; uAmount = (uint32_t) (tintAmount * 256.0); uRemain = 256 - uAmount; tintR = Red255FromXColorPtr(tintColor); tintG = Green255FromXColorPtr(tintColor); tintB = Blue255FromXColorPtr(tintColor); for(i = 0; i < iheight; i++) { srcPtr = block.pixelPtr + i * pitch; dstPtr = ptr + i * pitch; for(j = 0; j < iwidth; j++) { /* extract */ uint32_t r = *(srcPtr + srcR); uint32_t g = *(srcPtr + srcG); uint32_t b = *(srcPtr + srcB); uint32_t a = *(srcPtr + srcA); /* transform */ uint32_t lumAmount = ((r * 6966 + g * 23436 + b * 2366) * uAmount) >> 23; /* 0-256 */ r = (uRemain * r + lumAmount * tintR); g = (uRemain * g + lumAmount * tintG); b = (uRemain * b + lumAmount * tintB); if(a != 255) { /* Cairo expects RGB premultiplied by alpha */ r = r * a / 255; g = g * a / 255; b = b * a / 255; } /* fix range */ r = r > 0xFFFF ? 0xFFFF : r; g = g > 0xFFFF ? 0xFFFF : g; b = b > 0xFFFF ? 0xFFFF : b; /* and put back */ *(dstPtr + dstR) = r >> 8; *(dstPtr + dstG) = g >> 8; *(dstPtr + dstB) = b >> 8; *(dstPtr + dstA) = a; srcPtr += 4; dstPtr += 4; } } #else double tintR, tintG, tintB; if(tintAmount > 1.0) tintAmount = 1.0; tintR = RedDoubleFromXColorPtr(tintColor); tintG = GreenDoubleFromXColorPtr(tintColor); tintB = BlueDoubleFromXColorPtr(tintColor); for(i = 0; i < iheight; i++) { srcPtr = block.pixelPtr + i * pitch; dstPtr = ptr + i * pitch; for(j = 0; j < iwidth; j++) { /* extract */ int r = *(srcPtr + srcR); int g = *(srcPtr + srcG); int b = *(srcPtr + srcB); int a = *(srcPtr + srcA); /* transform */ int lum = (int)(0.2126 * r + 0.7152 * g + 0.0722 * b); r = (int)((1.0 - tintAmount) * r + tintAmount * lum * tintR); g = (int)((1.0 - tintAmount) * g + tintAmount * lum * tintG); b = (int)((1.0 - tintAmount) * b + tintAmount * lum * tintB); if(a != 255) { /* Cairo expects RGB premultiplied by alpha */ r = r * a / 255; g = g * a / 255; b = b * a / 255; } /* fix range */ r = r < 0 ? 0 : r > 255 ? 255 : r; g = g < 0 ? 0 : g > 255 ? 255 : g; b = b < 0 ? 0 : b > 255 ? 255 : b; /* and put back */ *(dstPtr + dstR) = r; *(dstPtr + dstG) = g; *(dstPtr + dstB) = b; *(dstPtr + dstA) = a; srcPtr += 4; dstPtr += 4; } } #endif } else { for(i = 0; i < iheight; i++) { srcPtr = block.pixelPtr + i * pitch; dstPtr = ptr + i * pitch; for(j = 0; j < iwidth; j++) { unsigned int alpha = *(srcPtr + srcA); *(dstPtr + dstA) = alpha; if(alpha == 255) { *(dstPtr + dstR) = *(srcPtr + srcR); *(dstPtr + dstG) = *(srcPtr + srcG); *(dstPtr + dstB) = *(srcPtr + srcB); } else { /* Cairo expects RGB premultiplied by alpha */ *(dstPtr + dstR) = alpha * *(srcPtr + srcR) / 255; *(dstPtr + dstG) = alpha * *(srcPtr + srcG) / 255; *(dstPtr + dstB) = alpha * *(srcPtr + srcB) / 255; } srcPtr += 4; dstPtr += 4; } } } } else if(block.pixelSize == 3) { /* Could do something about this? */ fprintf(stderr, "TkPathImage: unaccepted pixel format: 1 pixel is 3 bytes\n"); return; } else { fprintf(stderr, "TkPathImage: unaccepted pixel format: 1 pixel is %d bytes\n", block.pixelSize); return; } surface = cairo_image_surface_create_for_data(ptr, format, (int)iwidth, (int)iheight, pitch); /* stride */ filter = convertInterpolationToCairoFilter(interpolation); if(width == (double)iwidth && height == (double)iheight && !srcRegion) { cairo_set_source_surface(context->c, surface, x, y); cairo_pattern_set_filter(cairo_get_source(context->c), filter); cairo_paint_with_alpha(context->c, fillOpacity); } else if(srcRegion) { /* crop x0, y0 positions: */ int xcrop = srcRegion->x1; int ycrop = srcRegion->y1; double xscale, yscale, xoffs, yoffs; cairo_matrix_t matrix; cairo_pattern_t *pattern; width = (width0 == 0.0) ? srcRegion->x2 - srcRegion->x1 : width0; height = (height0 == 0.0) ? srcRegion->y2 - srcRegion->y1 : height0; /* scale image: */ xscale = width / (srcRegion->x2 - srcRegion->x1); yscale = height / (srcRegion->y2 - srcRegion->y1); xoffs = xcrop * xscale; yoffs = ycrop * yscale; pattern = cairo_pattern_create_for_surface(surface); cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT); cairo_translate(context->c, (x - xoffs), (y - yoffs)); cairo_matrix_init_scale(&matrix, 1.0 / xscale, 1.0 / yscale); cairo_pattern_set_matrix(pattern, &matrix); cairo_set_source(context->c, pattern); cairo_pattern_set_filter(cairo_get_source(context->c), filter); cairo_rectangle(context->c, xoffs, yoffs, width, height); cairo_fill(context->c); cairo_pattern_destroy(pattern); } else { cairo_save(context->c); cairo_translate(context->c, x, y); cairo_scale(context->c, width / iwidth, height / iheight); cairo_set_source_surface(context->c, surface, 0, 0); cairo_pattern_set_filter(cairo_get_source(context->c), filter); cairo_paint_with_alpha(context->c, fillOpacity); cairo_restore(context->c); } cairo_surface_destroy(surface); if(data) { ckfree((char *)data); } } void TkPathClosePath( TkPathContext ctx) { TkPathContext_ *context = (TkPathContext_ *) ctx; cairo_close_path(context->c); } int TkPathTextConfig( Tcl_Interp * interp, Tk_PathTextStyle * textStylePtr, char *utf8, void **customPtr) { return TCL_OK; } static cairo_font_slant_t convertTkFontSlant2CairoFontSlant( enum TkFontSlant slant) { switch (slant) { case TK_PATH_TEXT_SLANT_NORMAL: return CAIRO_FONT_SLANT_NORMAL; case TK_PATH_TEXT_SLANT_ITALIC: return CAIRO_FONT_SLANT_ITALIC; case TK_PATH_TEXT_SLANT_OBLIQUE: return CAIRO_FONT_SLANT_OBLIQUE; } return CAIRO_FONT_SLANT_NORMAL; } static cairo_font_weight_t convertTkFontWeight2CairoFontWeight( enum TkFontWeight weight) { switch (weight) { case TK_PATH_TEXT_WEIGHT_NORMAL: return CAIRO_FONT_WEIGHT_NORMAL; case TK_PATH_TEXT_WEIGHT_BOLD: return CAIRO_FONT_WEIGHT_BOLD; } return CAIRO_FONT_WEIGHT_NORMAL; } static char * ckstrdup( const char *str) { unsigned len = strlen(str); char *newstr = ckalloc(len + 1); if(newstr != NULL) { strcpy(newstr, str); } return newstr; } static char * linebreak( char *str, char **nextp) { char *ret; if(str == NULL) { str = *nextp; } str += strspn(str, "\r\n"); if(*str == '\0') { return NULL; } ret = str; str += strcspn(str, "\r\n"); if(*str) { int ch = *str; *str++ = '\0'; if((ch == '\r') && (*str == '\n')) { str++; } } *nextp = str; return ret; } static void multiline_show_text( TkPathContext ctx, double x, double y, double dy, char *utf8) { TkPathContext_ *context = (TkPathContext_ *) ctx; char *token, *savep; char *str = ckstrdup(utf8); for(token = linebreak(str, &savep); token; token = linebreak(NULL, &savep), y += dy) { cairo_move_to(context->c, x, y); cairo_show_text(context->c, token); } ckfree(str); } static void multiline_text_path( TkPathContext ctx, double x, double y, double dy, char *utf8) { TkPathContext_ *context = (TkPathContext_ *) ctx; char *token, *savep; char *str = ckstrdup(utf8); for(token = linebreak(str, &savep); token; token = linebreak(NULL, &savep), y += dy) { cairo_move_to(context->c, x, y); cairo_text_path(context->c, token); } ckfree(str); } void TkPathTextDraw( TkPathContext ctx, Tk_PathStyle * style, Tk_PathTextStyle * textStylePtr, double x, double y, int fillOverStroke, char *utf8, void *custom) { TkPathContext_ *context = (TkPathContext_ *) ctx; cairo_font_extents_t fontExtents; int hasStroke = (style->strokeColor != NULL); int hasFill = (GetColorFromPathColor(style->fill) != NULL); cairo_select_font_face(context->c, textStylePtr->fontFamily, convertTkFontSlant2CairoFontSlant(textStylePtr->fontSlant), convertTkFontWeight2CairoFontWeight(textStylePtr->fontWeight)); cairo_set_font_size(context->c, textStylePtr->fontSize); cairo_font_extents(context->c, &fontExtents); if(hasStroke && hasFill) { multiline_text_path(ctx, x, y, fontExtents.ascent + fontExtents.descent, utf8); if(fillOverStroke) { TkPathPrepareForStroke(ctx, style); cairo_stroke_preserve(context->c); CairoSetFill(ctx, style); cairo_fill(context->c); } else { TkPathFillAndStroke(ctx, style); } } else if(hasFill) { CairoSetFill(ctx, style); multiline_show_text(ctx, x, y, fontExtents.ascent + fontExtents.descent, utf8); } else if(hasStroke) { multiline_text_path(ctx, x, y, fontExtents.ascent + fontExtents.descent, utf8); TkPathStroke(ctx, style); } } void TkPathTextFree( Tk_PathTextStyle * textStylePtr, void *custom) { /* Empty. */ } TkPathRect TkPathTextMeasureBbox( Display * display, Tk_PathTextStyle * textStylePtr, char *utf8, double *lineSpacing, void *custom) { cairo_t *c; cairo_surface_t *surface; cairo_text_extents_t extents; cairo_font_extents_t fontExtents; TkPathRect r; int lc; char *token, *savep; double x; char *str = ckstrdup(utf8); /* * @@@ Not very happy about this but it seems that there is no way to * measure text without having a surface (drawable) in cairo. */ surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 10, 10); c = cairo_create(surface); cairo_select_font_face(c, textStylePtr->fontFamily, convertTkFontSlant2CairoFontSlant(textStylePtr->fontSlant), convertTkFontWeight2CairoFontWeight(textStylePtr->fontWeight)); cairo_set_font_size(c, textStylePtr->fontSize); cairo_font_extents(c, &fontExtents); r.x2 = 0.0; for(lc = 0, token = linebreak(str, &savep); token; lc++, token = linebreak(NULL, &savep)) { cairo_text_extents(c, token, &extents); x = extents.x_bearing + extents.width; if(x > r.x2) r.x2 = x; } r.y1 = -fontExtents.ascent; r.x1 = 0.0; r.y2 = lc * (fontExtents.ascent + fontExtents.descent) - fontExtents.ascent; if(lineSpacing != NULL) { *lineSpacing = fontExtents.ascent + fontExtents.descent; } cairo_destroy(c); cairo_surface_destroy(surface); ckfree(str); return r; } void TkPathSurfaceErase( TkPathContext ctx, double dx, double dy, double dwidth, double dheight) { TkPathContext_ *context = (TkPathContext_ *) ctx; unsigned char *data, *dst; int i; int x, y, width, height; int xend, yend; int stride; int bwidth; /* * Had to do it directly on the bits. Assuming CAIRO_FORMAT_ARGB32 * cairos ARGB format is in *native* endian order; Switch! * Be careful not to address the bitmap outside its limits. */ data = context->record->data; stride = context->record->stride; x = (int)(dx + 0.5); y = (int)(dy + 0.5); width = (int)(dwidth + 0.5); height = (int)(dheight + 0.5); x = MAX(0, MIN(context->record->width, x)); y = MAX(0, MIN(context->record->height, y)); width = MAX(0, width); height = MAX(0, height); xend = MIN(x + width, context->record->width); yend = MIN(y + height, context->record->height); bwidth = 4 * (xend - x); for(i = y; i < yend; i++) { dst = data + i * stride + 4 * x; memset(dst, '\0', bwidth); } } void TkPathSurfaceToPhoto( Tcl_Interp * interp, TkPathContext ctx, Tk_PhotoHandle photo) { TkPathContext_ *context = (TkPathContext_ *) ctx; cairo_surface_t *surface = context->surface; Tk_PhotoImageBlock block; unsigned char *data; unsigned char *pixel; int width, height; int stride; /* Bytes per row. */ width = cairo_image_surface_get_width(surface); height = cairo_image_surface_get_height(surface); data = context->record->data; stride = context->record->stride; Tk_PhotoGetImage(photo, &block); pixel = (unsigned char *)attemptckalloc(height * stride); if(pixel == NULL) { return; } if(Tk_PathSurfaceCopyPremultiplyAlpha) { if(!kEndianess.set) { kEndianess.set = 1; } if(kEndianess.little) { TkPathCopyBitsPremultipliedAlphaBGRA(data, pixel, width, height, stride); } else { TkPathCopyBitsPremultipliedAlphaARGB(data, pixel, width, height, stride); } } else { if(!kEndianess.set) { kEndianess.set = 1; } if(kEndianess.little) { TkPathCopyBitsBGRA(data, pixel, width, height, stride); } else { TkPathCopyBitsARGB(data, pixel, width, height, stride); } } block.pixelPtr = pixel; block.width = width; block.height = height; block.pitch = stride; block.pixelSize = 4; block.offset[0] = 0; block.offset[1] = 1; block.offset[2] = 2; block.offset[3] = 3; Tk_PhotoPutBlock(interp, photo, &block, 0, 0, width, height, TK_PHOTO_COMPOSITE_OVERLAY); ckfree((char *)pixel); } void TkPathClipToPath( TkPathContext ctx, int fillRule) { /* Clipping to path is done by default. */ /* Note: cairo_clip does not consume the current path */ /* cairo_clip(context->c); */ } void TkPathReleaseClipToPath( TkPathContext ctx) { /* cairo_reset_clip(context->c); */ } static void TkPathPrepareForStroke( TkPathContext ctx, Tk_PathStyle * style) { TkPathContext_ *context = (TkPathContext_ *) ctx; TkPathDash *dashPtr; /* Patch from Tim Edwards to handle color correctly on 64 bit arch. */ cairo_set_source_rgba(context->c, (double)(style->strokeColor->red) / 0xFFFF, (double)(style->strokeColor->green) / 0xFFFF, (double)(style->strokeColor->blue) / 0xFFFF, style->strokeOpacity); cairo_set_line_width(context->c, style->strokeWidth); switch (style->capStyle) { case CapNotLast: case CapButt: cairo_set_line_cap(context->c, CAIRO_LINE_CAP_BUTT); break; case CapRound: cairo_set_line_cap(context->c, CAIRO_LINE_CAP_ROUND); break; default: cairo_set_line_cap(context->c, CAIRO_LINE_CAP_SQUARE); break; } switch (style->joinStyle) { case JoinMiter: cairo_set_line_join(context->c, CAIRO_LINE_JOIN_MITER); break; case JoinRound: cairo_set_line_join(context->c, CAIRO_LINE_JOIN_ROUND); break; default: cairo_set_line_join(context->c, CAIRO_LINE_JOIN_BEVEL); break; } cairo_set_miter_limit(context->c, style->miterLimit); dashPtr = style->dashPtr; if((dashPtr != NULL) && (dashPtr->number != 0)) { int i; double *dashes = (double *)ckalloc(dashPtr->number * sizeof(double)); for(i = 0; i < dashPtr->number; i++) { dashes[i] = dashPtr->array[i] * style->strokeWidth; } cairo_set_dash(context->c, dashes, dashPtr->number, style->offset); ckfree((char *)dashes); } } void TkPathStroke( TkPathContext ctx, Tk_PathStyle * style) { TkPathContext_ *context = (TkPathContext_ *) ctx; TkPathPrepareForStroke(ctx, style); cairo_stroke(context->c); } void TkPathFill( TkPathContext ctx, Tk_PathStyle * style) { TkPathContext_ *context = (TkPathContext_ *) ctx; CairoSetFill(ctx, style); cairo_fill(context->c); } void TkPathFillAndStroke( TkPathContext ctx, Tk_PathStyle * style) { TkPathContext_ *context = (TkPathContext_ *) ctx; CairoSetFill(ctx, style); cairo_fill_preserve(context->c); TkPathStroke(ctx, style); } void TkPathEndPath( TkPathContext ctx) { /* Empty ??? */ } void TkPathFree( TkPathContext ctx) { TkPathContext_ *context = (TkPathContext_ *) ctx; cairo_destroy(context->c); cairo_surface_destroy(context->surface); if(context->record) { ckfree((char *)context->record->data); ckfree((char *)context->record); } ckfree((char *)context); } int TkPathDrawingDestroysPath( void) { return 1; } int TkPathPixelAlign( void) { return 0; } int TkPathGetCurrentPosition( TkPathContext ctx, TkPathPoint * pt) { TkPathContext_ *context = (TkPathContext_ *) ctx; cairo_get_current_point(context->c, &(pt->x), &(pt->y)); return TCL_OK; } int TkPathBoundingBox( TkPathContext ctx, TkPathRect * rPtr) { return TCL_ERROR; } static int GetCairoExtend( int method) { cairo_extend_t extend; switch (method) { case TK_PATH_GRADIENTMETHOD_Pad: extend = CAIRO_EXTEND_PAD; break; case TK_PATH_GRADIENTMETHOD_Repeat: extend = CAIRO_EXTEND_REPEAT; break; case TK_PATH_GRADIENTMETHOD_Reflect: extend = CAIRO_EXTEND_REFLECT; break; default: extend = CAIRO_EXTEND_NONE; break; } return extend; } void TkPathPaintLinearGradient( TkPathContext ctx, TkPathRect * bbox, TkLinearGradientFill * fillPtr, int fillRule, double fillOpacity, TkPathMatrix * mPtr) { TkPathContext_ *context = (TkPathContext_ *) ctx; int i; int nstops; TkPathRect *tPtr; /* The transition line. */ TkGradientStop *stop; TkGradientStopArray *stopArrPtr; cairo_pattern_t *pattern; stopArrPtr = fillPtr->stopArrPtr; tPtr = fillPtr->transitionPtr; nstops = stopArrPtr->nstops; /* * The current path is consumed by filling. * Need therfore to save the current context and restore after. */ cairo_save(context->c); pattern = cairo_pattern_create_linear(tPtr->x1, tPtr->y1, tPtr->x2, tPtr->y2); /* * We need to do like this since this is how SVG defines gradient drawing * in case the transition vector is in relative coordinates. */ if(fillPtr->units == TK_PATH_GRADIENTUNITS_BoundingBox) { cairo_translate(context->c, bbox->x1, bbox->y1); cairo_scale(context->c, bbox->x2 - bbox->x1, bbox->y2 - bbox->y1); } if(mPtr) { cairo_matrix_t matrix; cairo_matrix_init(&matrix, mPtr->a, mPtr->b, mPtr->c, mPtr->d, mPtr->tx, mPtr->ty); cairo_pattern_set_matrix(pattern, &matrix); } for(i = 0; i < nstops; i++) { stop = stopArrPtr->stops[i]; cairo_pattern_add_color_stop_rgba(pattern, stop->offset, RedDoubleFromXColorPtr(stop->color), GreenDoubleFromXColorPtr(stop->color), BlueDoubleFromXColorPtr(stop->color), stop->opacity * fillOpacity); } cairo_set_source(context->c, pattern); cairo_set_fill_rule(context->c, (fillRule == WindingRule) ? CAIRO_FILL_RULE_WINDING : CAIRO_FILL_RULE_EVEN_ODD); cairo_pattern_set_extend(pattern, GetCairoExtend(fillPtr->method)); cairo_fill(context->c); cairo_pattern_destroy(pattern); cairo_restore(context->c); } void TkPathPaintRadialGradient( TkPathContext ctx, TkPathRect * bbox, TkRadialGradientFill * fillPtr, int fillRule, double fillOpacity, TkPathMatrix * mPtr) { TkPathContext_ *context = (TkPathContext_ *) ctx; int i; int nstops; TkGradientStop *stop; cairo_pattern_t *pattern; TkGradientStopArray *stopArrPtr; TkRadialTransition *tPtr; stopArrPtr = fillPtr->stopArrPtr; nstops = stopArrPtr->nstops; tPtr = fillPtr->radialPtr; /* * The current path is consumed by filling. * Need therfore to save the current context and restore after. */ cairo_save(context->c); pattern = cairo_pattern_create_radial(tPtr->focalX, tPtr->focalY, 0.0, tPtr->centerX, tPtr->centerY, tPtr->radius); if(fillPtr->units == TK_PATH_GRADIENTUNITS_BoundingBox) { cairo_translate(context->c, bbox->x1, bbox->y1); cairo_scale(context->c, bbox->x2 - bbox->x1, bbox->y2 - bbox->y1); } if(mPtr) { cairo_matrix_t matrix; cairo_matrix_init(&matrix, mPtr->a, mPtr->b, mPtr->c, mPtr->d, mPtr->tx, mPtr->ty); cairo_pattern_set_matrix(pattern, &matrix); } for(i = 0; i < nstops; i++) { stop = stopArrPtr->stops[i]; cairo_pattern_add_color_stop_rgba(pattern, stop->offset, RedDoubleFromXColorPtr(stop->color), GreenDoubleFromXColorPtr(stop->color), BlueDoubleFromXColorPtr(stop->color), stop->opacity * fillOpacity); } cairo_set_source(context->c, pattern); cairo_set_fill_rule(context->c, (fillRule == WindingRule) ? CAIRO_FILL_RULE_WINDING : CAIRO_FILL_RULE_EVEN_ODD); cairo_pattern_set_extend(pattern, GetCairoExtend(fillPtr->method)); cairo_fill(context->c); cairo_pattern_destroy(pattern); cairo_restore(context->c); } int TkPathSetup( Tcl_Interp * interp) { return TCL_OK; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */