/* * tkMacOSXPath.c -- * * This file implements path drawing API's using CoreGraphics on Mac OS X. * * 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 "tkMacOSXInt.h" #include "tkoPath.h" #if !__OBJC__ #error Objective-C compiler required #endif #import <Cocoa/Cocoa.h> #define TINT_INT_CALCULATION /* Seems to work for both Endians. */ #define BlueFloatFromXColorPtr(xc) ((float) ((xc)->blue >> 8) / 255.0) #define GreenFloatFromXColorPtr(xc) ((float) ((xc)->green >> 8) / 255.0) #define RedFloatFromXColorPtr(xc) ((float) ((xc)->red >> 8) / 255.0) #define Blue255FromXColorPtr(xc) ((xc)->blue >> 8) #define Green255FromXColorPtr(xc) ((xc)->green >> 8) #define Red255FromXColorPtr(xc) ((xc)->red >> 8) #ifndef FloatToFixed #define FloatToFixed(a) ((Fixed)((float) (a) * fixed1)) #endif extern int Tk_PathAntiAlias; extern int Tk_PathSurfaceCopyPremultiplyAlpha; extern int Tk_PathDepixelize; const CGFloat kValidDomain[2] = { 0, 1 }; const CGFloat kValidRange[8] = { 0, 1, 0, 1, 0, 1, 0, 1 }; /* * This is used as a place holder for platform dependent stuff between each call. */ typedef struct TkPathContext_ { CGContextRef c; int saveCount; CGrafPtr port; /* QD graphics port, NULL for bitmaps. */ char *data; /* bitmap data, NULL for windows. */ int widthCode; /* Used to depixelize the strokes: * 0: not integer width * 1: odd integer width * 2: even integer width */ /* fields from TK TkMacOSXDrawingContext: */ NSView *view; HIShapeRef clipRgn; CGRect portBounds; int focusLocked; int xOff, yOff; } TkPathContext_; #define MAX_NL 32 typedef struct PathATSUIRecord { ATSUStyle atsuStyle; ATSUTextLayout atsuLayout; UniChar *buffer; /* @@@ Not sure this needs to be cached! */ int nlc; int nl[MAX_NL + 1]; ATSUTextMeasurement dx[MAX_NL]; ATSUTextMeasurement dy[MAX_NL]; } PathATSUIRecord; typedef struct FillInfo { double fillOpacity; GradientStopArray *stopArrPtr; } FillInfo; /* *---------------------------------------------------------------------- * * TkMacOSXDrawableView -- * * This function returns the NSView for a given X drawable. * * Results: * A NSView* or nil. * * Side effects: * None. * *---------------------------------------------------------------------- */ NSView * TkMacOSXDrawableView( MacDrawable * macWin) { NSView *result = nil; if(!macWin) { result = nil; } else if(!macWin->toplevel) { result = macWin->view; } else if(!(macWin->toplevel->flags & TK_EMBEDDED)) { result = macWin->toplevel->view; } else { TkWindow *contWinPtr = TkpGetOtherWindow(macWin->toplevel->winPtr); if(contWinPtr) { result = TkMacOSXDrawableView(contWinPtr->privatePtr); } } return result; } /* *---------------------------------------------------------------------- * * TkpClipDrawableToRect -- * * Clip all drawing into the drawable d to the given rectangle. * If width or height are negative, reset to no clipping. * * Results: * None. * * Side effects: * Subsequent drawing into d is offset and clipped as specified. * *---------------------------------------------------------------------- */ void TkpClipDrawableToRect( Display * display, Drawable d, int x, int y, int width, int height) { MacDrawable *macDraw = (MacDrawable *) d; NSView *view = TkMacOSXDrawableView(macDraw); if(macDraw->drawRgn) { CFRelease(macDraw->drawRgn); macDraw->drawRgn = NULL; } if(width >= 0 && height >= 0) { CGRect drawRect = CGRectMake(x + macDraw->xOff, y + macDraw->yOff, width, height); HIShapeRef drawRgn = HIShapeCreateWithRect(&drawRect); if(macDraw->winPtr && macDraw->flags & TK_CLIP_INVALID) { TkMacOSXUpdateClipRgn(macDraw->winPtr); } if(macDraw->visRgn) { macDraw->drawRgn = HIShapeCreateIntersection(macDraw->visRgn, drawRgn); CFRelease(drawRgn); } else { macDraw->drawRgn = drawRgn; } if(view && view !=[NSView focusView] &&[view lockFocusIfCanDraw]) { drawRect.origin.y =[view bounds].size.height - (drawRect.origin.y + drawRect.size.height); NSRectClip(NSRectFromCGRect(drawRect)); macDraw->flags |= TK_FOCUSED_VIEW; } } else { if(view && (macDraw->flags & TK_FOCUSED_VIEW)) { [view unlockFocus]; macDraw->flags &= ~TK_FOCUSED_VIEW; } } } /* *---------------------------------------------------------------------- * * TkMacOSXGetClipRgn -- * * Get the clipping region needed to restrict drawing to the given * drawable. * * Results: * Clipping region. If non-NULL, CFRelease it when done. * * Side effects: * None. * *---------------------------------------------------------------------- */ HIShapeRef TkMacOSXGetClipRgn( Drawable drawable) { /* Drawable. */ MacDrawable *macDraw = (MacDrawable *) drawable; HIShapeRef clipRgn = NULL; if(macDraw->winPtr && macDraw->flags & TK_CLIP_INVALID) { TkMacOSXUpdateClipRgn(macDraw->winPtr); #ifdef TK_MAC_DEBUG_DRAWING TkMacOSXDbgMsg("%s visRgn ", macDraw->winPtr->pathName); TkMacOSXDebugFlashRegion(drawable, macDraw->visRgn); #endif /* TK_MAC_DEBUG_DRAWING */ } if(macDraw->drawRgn) { clipRgn = HIShapeCreateCopy(macDraw->drawRgn); } else if(macDraw->visRgn) { clipRgn = HIShapeCreateCopy(macDraw->visRgn); } return clipRgn; } /* copied from tk8.5.16/macosx/tkMacOSXSubwindows.c */ NSView * TkpMacOSXDrawableView( MacDrawable * macWin) { NSView *result = nil; if(!macWin) { result = nil; } else if(!macWin->toplevel) { result = macWin->view; } else if(!(macWin->toplevel->flags & TK_EMBEDDED)) { result = macWin->toplevel->view; } else { TkWindow *contWinPtr = TkpGetOtherWindow(macWin->toplevel->winPtr); if(contWinPtr) { result = TkpMacOSXDrawableView(contWinPtr->privatePtr); } } return result; } void PathSetUpCGContext( Drawable d, TkPathContext_ * dcPtr) { CGrafPtr port; MacDrawable *macDraw = (MacDrawable *) d; int dontDraw = 0; dcPtr->c = NULL; dcPtr->view = NULL; dcPtr->clipRgn = NULL; dcPtr->focusLocked = 0; port = TkMacOSXGetDrawablePort(d); dcPtr->clipRgn = TkMacOSXGetClipRgn(d); if(!dontDraw) { dontDraw = dcPtr->clipRgn ? HIShapeIsEmpty(dcPtr->clipRgn) : 0; } if(dontDraw) { goto end; } NSView *view = TkpMacOSXDrawableView(macDraw); if(view) { NSView *fView =[NSView focusView]; if(view != fView) { dcPtr->focusLocked =[view lockFocusIfCanDraw]; dontDraw = !dcPtr->focusLocked; } else { dontDraw = ![view canDraw]; } if(dontDraw) { goto end; } [[view window] disableFlushWindow]; dcPtr->view = view; NSGraphicsContext *currentGraphicsContext =[NSGraphicsContext currentContext]; dcPtr->c = (CGContextRef)[currentGraphicsContext graphicsPort]; dcPtr->portBounds = NSRectToCGRect([view bounds]); if(dcPtr->clipRgn) { } } else { Tcl_Panic("PathSetUpCGContext(): " "no NSView to draw into !"); } /* * Core Graphics defines the origin to be the bottom left * corner of the CGContext and the positive y-axis points up. * Move the origin and flip the y-axis for all subsequent * Core Graphics drawing operations. */ CGContextSaveGState(dcPtr->c); dcPtr->saveCount = 1; CGRect cgbounds = CGContextGetClipBoundingBox(dcPtr->c); dcPtr->portBounds = NSRectToCGRect([view bounds]); dcPtr->portBounds.origin.x += macDraw->xOff; dcPtr->portBounds.origin.y += macDraw->yOff; dcPtr->xOff = macDraw->xOff; dcPtr->yOff = macDraw->yOff; end: if(dontDraw && dcPtr->clipRgn) { CFRelease(dcPtr->clipRgn); dcPtr->clipRgn = NULL; } } void PathReleaseCGContext( TkPathContext_ * dcPtr) { if(dcPtr->c) { CGContextSynchronize(dcPtr->c); [[dcPtr->view window] setViewsNeedDisplay:YES]; [[dcPtr->view window] enableFlushWindow]; if(dcPtr->focusLocked) { [dcPtr->view unlockFocus]; } while(dcPtr->saveCount > 0) { CGContextRestoreGState(dcPtr->c); dcPtr->saveCount--; } } if(dcPtr->clipRgn) { CFRelease(dcPtr->clipRgn); dcPtr->clipRgn = NULL; } } CGColorSpaceRef GetTheColorSpaceRef( void) { static CGColorSpaceRef deviceRGB = NULL; if(deviceRGB == NULL) { deviceRGB = CGColorSpaceCreateDeviceRGB(); } return deviceRGB; } static LookupTable LineCapStyleLookupTable[] = { {CapNotLast, kCGLineCapButt}, {CapButt, kCGLineCapButt}, {CapRound, kCGLineCapRound}, {CapProjecting, kCGLineCapSquare} }; static LookupTable LineJoinStyleLookupTable[] = { {JoinMiter, kCGLineJoinMiter}, {JoinRound, kCGLineJoinRound}, {JoinBevel, kCGLineJoinBevel} }; void PathSetCGContextStyle( CGContextRef c, Tk_PathStyle * style) { Tk_PathDash *dashPtr; int fill = 0, stroke = 0; /** Drawing attribute functions. **/ /* Set the line width in the current graphics state to `width'. */ CGContextSetLineWidth(c, style->strokeWidth); /* Set the line cap in the current graphics state to `cap'. */ CGContextSetLineCap(c, TkPathTableLookup(LineCapStyleLookupTable, 4, style->capStyle)); /* Set the line join in the current graphics state to `join'. */ CGContextSetLineJoin(c, TkPathTableLookup(LineJoinStyleLookupTable, 3, style->joinStyle)); /* Set the miter limit in the current graphics state to `limit'. */ CGContextSetMiterLimit(c, style->miterLimit); /* Set the line dash patttern in the current graphics state. */ dashPtr = style->dashPtr; if((dashPtr != NULL) && (dashPtr->number != 0)) { CGFloat *dashes = (CGFloat *) ckalloc(dashPtr->number * sizeof(CGFloat)); int i; for(i = 0; i < dashPtr->number; i++) dashes[i] = dashPtr->array[i] * style->strokeWidth; CGContextSetLineDash(c, 0.0, dashes, dashPtr->number); ckfree((char *)dashes); } /* Set the current fill colorspace in the context `c' to `DeviceRGB' and * set the components of the current fill color to `(red, green, blue, * alpha)'. */ if(GetColorFromPathColor(style->fill) != NULL) { fill = 1; CGContextSetRGBFillColor(c, RedFloatFromXColorPtr(style->fill->color), GreenFloatFromXColorPtr(style->fill->color), BlueFloatFromXColorPtr(style->fill->color), style->fillOpacity); } /* Set the current stroke colorspace in the context `c' to `DeviceRGB' and * set the components of the current stroke color to `(red, green, blue, * alpha)'. */ if(style->strokeColor != NULL) { stroke = 1; CGContextSetRGBStrokeColor(c, RedFloatFromXColorPtr(style->strokeColor), GreenFloatFromXColorPtr(style->strokeColor), BlueFloatFromXColorPtr(style->strokeColor), style->strokeOpacity); } if(stroke && fill) { CGContextSetTextDrawingMode(c, kCGTextFillStroke); } else if(stroke) { CGContextSetTextDrawingMode(c, kCGTextStroke); } else if(fill) { CGContextSetTextDrawingMode(c, kCGTextFill); } } /* Various ATSUI support functions. */ static OSStatus CreateATSUIStyle( const char *fontFamily, float fontSize, Boolean isBold, Boolean isItalic, ATSUStyle * atsuStylePtr) { OSStatus err = noErr; ATSUStyle style; ATSUFontID atsuFont; Fixed atsuSize; Boolean isUnderline = false; static const ATSUAttributeTag tags[] = { kATSUFontTag, kATSUSizeTag, kATSUQDBoldfaceTag, kATSUQDItalicTag, kATSUQDUnderlineTag }; static const ByteCount sizes[] = { sizeof(ATSUFontID), sizeof(Fixed), sizeof(Boolean), sizeof(Boolean), sizeof(Boolean) }; const ATSUAttributeValuePtr values[] = { &atsuFont, &atsuSize, &isBold, &isItalic, &isUnderline }; *atsuStylePtr = NULL; style = NULL; atsuFont = 0; atsuSize = FloatToFixed(fontSize); { err = ATSUFindFontFromName((Ptr) fontFamily, strlen(fontFamily), kFontFamilyName, kFontNoPlatformCode, kFontNoScriptCode, kFontNoLanguageCode, &atsuFont); } if(err != noErr) { return err; } err = ATSUCreateStyle(&style); if(err != noErr) { if(style) ATSUDisposeStyle(style); return err; } err = ATSUSetAttributes(style, sizeof(tags) / sizeof(tags[0]), tags, sizes, values); if(err != noErr) { if(style) ATSUDisposeStyle(style); return err; } *atsuStylePtr = style; return noErr; } static OSStatus CreateLayoutForString( UniChar * buffer, CFIndex length, ATSUStyle atsuStyle, ATSUTextLayout * layoutPtr) { ATSUTextLayout layout = NULL; OSStatus err = noErr; *layoutPtr = NULL; err = ATSUCreateTextLayoutWithTextPtr(buffer, 0, length, length, 1, (unsigned long *)&length, &atsuStyle, &layout); if(err == noErr) { *layoutPtr = layout; } ATSUSetTransientFontMatching(layout, true); return err; } TkPathContext TkPathInit( Tk_Window tkwin, Drawable d) { TkPathContext_ *context = (TkPathContext_ *) ckalloc(sizeof(TkPathContext_)); bzero(context, sizeof(TkPathContext_)); PathSetUpCGContext(d, context); context->port = TkMacOSXGetDrawablePort(d); context->data = NULL; context->widthCode = 0; return (TkPathContext) context; } TkPathContext TkPathInitSurface( Display * display, int width, int height) { CGContextRef cgContext; TkPathContext_ *context = (TkPathContext_ *) ckalloc((unsigned)(sizeof(TkPathContext_))); size_t bytesPerRow; char *data; /* Move up into own function */ bzero(context, sizeof(TkPathContext_)); bytesPerRow = 4 * width; /* Round up to nearest multiple of 16 */ bytesPerRow = (bytesPerRow + (16 - 1)) & ~(16 - 1); data = ckalloc(height * bytesPerRow); /* Make it RGBA with 32 bit depth. */ cgContext = CGBitmapContextCreate(data, width, height, 8, bytesPerRow, GetTheColorSpaceRef(), kCGImageAlphaPremultipliedLast); if(cgContext == NULL) { ckfree((char *)context); return (TkPathContext) NULL; } CGContextClearRect(cgContext, CGRectMake(0, 0, width, height)); CGContextTranslateCTM(cgContext, 0, height); CGContextScaleCTM(cgContext, 1, -1); context->c = cgContext; context->port = NULL; context->data = data; context->clipRgn = NULL; context->saveCount = 0; context->xOff = context->yOff = 0; return (TkPathContext) context; } void TkPathPushTMatrix( TkPathContext ctx, TMatrix * mPtr) { TkPathContext_ *context = (TkPathContext_ *) ctx; CGAffineTransform transform; if(mPtr == NULL) { return; } /* Return the transform [ a b c d tx ty ]. */ transform = CGAffineTransformMake( (float)mPtr->a, (float)mPtr->b, (float)mPtr->c, (float)mPtr->d, (float)mPtr->tx, (float)mPtr->ty); CGContextConcatCTM(context->c, transform); } void TkPathResetTMatrix( TkPathContext ctx) { TkPathContext_ *context = (TkPathContext_ *) ctx; context->widthCode = 0; while(context->saveCount > 0) { CGContextRestoreGState(context->c); context->saveCount--; } CGContextSaveGState(context->c); context->saveCount++; if(context->data != NULL) { /* surface bitmap */ return; } CGAffineTransform t; t.a = 1.0; t.b = 0.0; t.c = 0.0; t.d = (-1.0); t.tx = 0.0; t.ty = context->portBounds.size.height; CGContextConcatCTM(context->c, t); CGRect r; HIShapeGetBounds(context->clipRgn, &r); HIShapeReplacePathInCGContext(context->clipRgn, context->c); CGContextEOClip(context->c); CGContextTranslateCTM(context->c, context->xOff, context->yOff); CGContextSetShouldAntialias(context->c, Tk_PathAntiAlias); CGContextSetInterpolationQuality(context->c, kCGInterpolationHigh); } void TkPathSaveState( TkPathContext ctx) { TkPathContext_ *context = (TkPathContext_ *) ctx; CGContextSaveGState(context->c); context->saveCount++; } void TkPathRestoreState( TkPathContext ctx) { TkPathContext_ *context = (TkPathContext_ *) ctx; if(context->saveCount > 0) { CGContextRestoreGState(context->c); context->saveCount--; } } void TkPathBeginPath( TkPathContext ctx, Tk_PathStyle * stylePtr) { TkPathContext_ *context = (TkPathContext_ *) ctx; int nint; double width; CGContextBeginPath(context->c); PathSetCGContextStyle(context->c, stylePtr); if(stylePtr->strokeColor == NULL) { context->widthCode = 0; } else { width = stylePtr->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 = PATH_DEPIXELIZE(context->widthCode, x); y = PATH_DEPIXELIZE(context->widthCode, y); } CGContextMoveToPoint(context->c, x, y); } void TkPathLineTo( TkPathContext ctx, double x, double y) { TkPathContext_ *context = (TkPathContext_ *) ctx; if(Tk_PathDepixelize) { x = PATH_DEPIXELIZE(context->widthCode, x); y = PATH_DEPIXELIZE(context->widthCode, y); } CGContextAddLineToPoint(context->c, x, y); } void TkPathLinesTo( TkPathContext ctx, double *pts, int n) { /* TkPathContext_ *context = (TkPathContext_ *) ctx; */ /* Add a set of lines to the context's path. */ /* CGContextAddLines(context->c, const CGPoint points[], size_t count); */ } void TkPathQuadBezier( TkPathContext ctx, double ctrlX, double ctrlY, double x, double y) { TkPathContext_ *context = (TkPathContext_ *) ctx; if(Tk_PathDepixelize) { x = PATH_DEPIXELIZE(context->widthCode, x); y = PATH_DEPIXELIZE(context->widthCode, y); } CGContextAddQuadCurveToPoint(context->c, ctrlX, ctrlY, x, y); } void TkPathCurveTo( TkPathContext ctx, double ctrlX1, double ctrlY1, double ctrlX2, double ctrlY2, double x, double y) { TkPathContext_ *context = (TkPathContext_ *) ctx; if(Tk_PathDepixelize) { x = PATH_DEPIXELIZE(context->widthCode, x); y = PATH_DEPIXELIZE(context->widthCode, y); } CGContextAddCurveToPoint(context->c, ctrlX1, ctrlY1, ctrlX2, ctrlY2, 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; /* @@@ Should we try to use the native arc functions here? */ if(Tk_PathDepixelize) { x = PATH_DEPIXELIZE(context->widthCode, x); y = 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; CGRect r; if(Tk_PathDepixelize) { x = PATH_DEPIXELIZE(context->widthCode, x); y = PATH_DEPIXELIZE(context->widthCode, y); } r = CGRectMake(x, y, width, height); CGContextAddRect(context->c, r); } void TkPathOval( TkPathContext ctx, double cx, double cy, double rx, double ry) { TkPathContext_ *context = (TkPathContext_ *) ctx; CGRect r; r = CGRectMake(cx - rx, cy - ry, 2 * rx, 2 * ry); CGContextAddEllipseInRect(context->c, r); } CGInterpolationQuality convertInterpolationToCGInterpolation( int interpolation) { switch (interpolation) { case kPathImageInterpolationNone: return kCGInterpolationNone; case kPathImageInterpolationFast: return kCGInterpolationLow; case kPathImageInterpolationBest: return kCGInterpolationHigh; default: return kCGInterpolationMedium; } } 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, PathRect * srcRegion) { TkPathContext_ *context = (TkPathContext_ *) ctx; CGImageRef cgImage; CGDataProviderRef provider; CGColorSpaceRef colorspace; CGImageAlphaInfo alphaInfo; size_t size; Tk_PhotoImageBlock block; 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; /* Return value? */ Tk_PhotoGetImage(photo, &block); size = block.pitch * block.height; iheight = block.height; iwidth = block.width; pitch = block.pitch; double width = (width0 == 0.0) ? (double)iwidth : width0; double height = (height0 == 0.0) ? (double)iheight : height0; /* * 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. These are normally 0, 1, 2 and 3. * @@@ There are more cases to consider than these! */ srcR = dstR = block.offset[0]; srcG = dstG = block.offset[1]; srcB = dstB = block.offset[2]; srcA = dstA = block.offset[3]; if(srcA == 3) { alphaInfo = kCGImageAlphaLast; } else if(srcA == 0) { alphaInfo = kCGImageAlphaFirst; } else { /* @@@ What to do here? */ return; } if(block.pixelSize == 4) { if((srcR == dstR) && (srcG == dstG) && (srcB == dstB) && (srcA == dstA) && fillOpacity >= 1.0 && (tintAmount <= 0.0 || tintColor == NULL)) { ptr = (unsigned char *)block.pixelPtr; } else { data = (unsigned char *)ckalloc(pitch * iheight); ptr = data; if(tintColor && tintAmount > 0.0) { #ifdef TINT_INT_CALCULATION uint32_t tintR, tintG, tintB, uAmount, uRemain, uOpacity; if(tintAmount > 1.0) tintAmount = 1.0; uAmount = (uint32_t) (tintAmount * 256.0); uRemain = 256 - uAmount; uOpacity = (uint32_t) (fillOpacity * 256.0); 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); /* 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 * uOpacity) >> 8; srcPtr += 4; dstPtr += 4; } } #else float tintR, tintG, tintB; if(tintAmount > 1.0) tintAmount = 1.0; tintR = RedFloatFromXColorPtr(tintColor); tintG = GreenFloatFromXColorPtr(tintColor); tintB = BlueFloatFromXColorPtr(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); /* 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); /* 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) = *(srcPtr + srcA) * fillOpacity; 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++) { *(dstPtr + dstR) = *(srcPtr + srcR); *(dstPtr + dstG) = *(srcPtr + srcG); *(dstPtr + dstB) = *(srcPtr + srcB); *(dstPtr + dstA) = *(srcPtr + srcA) * fillOpacity; srcPtr += 4; dstPtr += 4; } } } } } else { ptr = (unsigned char *)block.pixelPtr; return; } provider = CGDataProviderCreateWithData(NULL, ptr, size, NULL); colorspace = CGColorSpaceCreateDeviceRGB(); cgImage = CGImageCreate(block.width, block.height, 8, /* bitsPerComponent */ block.pixelSize * 8, /* bitsPerPixel */ block.pitch, /* bytesPerRow */ colorspace, /* colorspace */ alphaInfo, /* alphaInfo */ provider, NULL, interpolation > 0 ? 1 : 0, /* shouldInterpolate */ kCGRenderingIntentDefault); CGDataProviderRelease(provider); CGColorSpaceRelease(colorspace); if(width == 0.0) { width = (double)block.width; } if(height == 0.0) { height = (double)block.height; } CGContextSaveGState(context->c); context->saveCount++; if(srcRegion != NULL) { width = (width0 == 0.0) ? srcRegion->x2 - srcRegion->x1 : width0; height = (height0 == 0.0) ? srcRegion->y2 - srcRegion->y1 : height0; double xscale = width / (srcRegion->x2 - srcRegion->x1); double yscale = height / (srcRegion->y2 - srcRegion->y1); CGContextSetInterpolationQuality(context->c, convertInterpolationToCGInterpolation(interpolation)); CGContextTranslateCTM(context->c, x, y + height); CGContextScaleCTM(context->c, xscale, -yscale); CGContextClipToRect(context->c, CGRectMake(0.0, 0.0, width / xscale, height / yscale)); CGContextDrawTiledImage(context->c, CGRectMake(srcRegion->x1, fmod(srcRegion->y2, iheight), iwidth, iheight), cgImage); } else { /* Flip back to an upright coordinate system since CGContextDrawImage expect this. */ CGContextSetInterpolationQuality(context->c, convertInterpolationToCGInterpolation(interpolation)); CGContextTranslateCTM(context->c, x, y + height); CGContextScaleCTM(context->c, 1, -1); CGContextDrawImage(context->c, CGRectMake(0.0, 0.0, width, height), cgImage); } CGImageRelease(cgImage); CGContextRestoreGState(context->c); context->saveCount--; if(data) { ckfree((char *)data); } } void TkPathClosePath( TkPathContext ctx) { TkPathContext_ *context = (TkPathContext_ *) ctx; CGContextClosePath(context->c); } /* * @@@ Problems: don't want Tcl_Interp, finding matching font not while processing options. * Separate font style from layout??? */ Boolean isItalic( enum FontSlant slant) { switch (slant) { case PATH_TEXT_SLANT_NORMAL: return false; case PATH_TEXT_SLANT_ITALIC: return true; case PATH_TEXT_SLANT_OBLIQUE: return true; default: return false; } } Boolean isBold( enum FontWeight weight) { switch (weight) { case PATH_TEXT_WEIGHT_NORMAL: return false; case PATH_TEXT_WEIGHT_BOLD: return true; default: return false; } } int TkPathTextConfig( Tcl_Interp * interp, Tk_PathTextStyle * textStylePtr, char *utf8, void **customPtr) { PathATSUIRecord *recordPtr; ATSUStyle atsuStyle = NULL; ATSUTextLayout atsuLayout = NULL; CFStringRef cf; UniChar *buffer; CFRange range; CFIndex length; OSStatus err; Tcl_Encoding enc; Tcl_DString ds; if(utf8 == NULL) { return TCL_OK; } TkPathTextFree(textStylePtr, *customPtr); enc = Tcl_GetEncoding(NULL, "utf-8"); Tcl_DStringInit(&ds); Tcl_UtfToExternalDString(enc, utf8, -1, &ds); Tcl_FreeEncoding(enc); cf = CFStringCreateWithCString(NULL, Tcl_DStringValue(&ds), kCFStringEncodingUTF8); Tcl_DStringFree(&ds); length = CFStringGetLength(cf); if(length == 0) { return TCL_OK; } range = CFRangeMake(0, length); err = CreateATSUIStyle(textStylePtr->fontFamily, textStylePtr->fontSize, isBold(textStylePtr->fontWeight), isItalic(textStylePtr->fontSlant), &atsuStyle); if(err != noErr) { Tcl_SetObjResult(interp, Tcl_NewStringObj("font style couldn't be created", -1)); return TCL_ERROR; } buffer = (UniChar *) ckalloc(length * sizeof(UniChar)); CFStringGetCharacters(cf, range, buffer); err = CreateLayoutForString(buffer, length, atsuStyle, &atsuLayout); CFRelease(cf); if(err != noErr) { Tcl_SetObjResult(interp, Tcl_NewStringObj("text layout couldn't be created", -1)); ckfree((char *)buffer); return TCL_ERROR; } recordPtr = (PathATSUIRecord *) ckalloc(sizeof(PathATSUIRecord)); recordPtr->atsuStyle = atsuStyle; recordPtr->atsuLayout = atsuLayout; recordPtr->buffer = buffer; int i, j; recordPtr->nl[0] = 0; for(i = 0, j = 1; i < length; i++) { if((j < MAX_NL) && (buffer[i] == '\n')) { recordPtr->nl[j++] = i + 1; buffer[i] = 0x2028; } } recordPtr->nl[j] = i + 1; recordPtr->nlc = j; *customPtr = (PathATSUIRecord *) recordPtr; return TCL_OK; } static void drawMultilineText( PathATSUIRecord * recordPtr) { int i; for(i = 0; i < recordPtr->nlc; i++) { ATSUDrawText(recordPtr->atsuLayout, recordPtr->nl[i], recordPtr->nl[i + 1] - recordPtr->nl[i] - 1, recordPtr->dx[i], recordPtr->dy[i]); } } void TkPathTextDraw( TkPathContext ctx, Tk_PathStyle * style, Tk_PathTextStyle * textStylePtr, double x, double y, int fillOverStroke, char *utf8, void *custom) { TkPathContext_ *context = (TkPathContext_ *) ctx; PathATSUIRecord *recordPtr = (PathATSUIRecord *) custom; ByteCount iSize = sizeof(CGContextRef); ATSUAttributeTag iTag = kATSUCGContextTag; ATSUAttributeValuePtr iValuePtr = &(context->c); ATSUSetLayoutControls(recordPtr->atsuLayout, 1, &iTag, &iSize, &iValuePtr); CGContextSaveGState(context->c); context->saveCount++; CGContextTranslateCTM(context->c, x, y); CGContextScaleCTM(context->c, 1, -1); if((style->strokeColor != NULL) && (GetColorFromPathColor(style->fill) != NULL)) { CGContextSetTextDrawingMode(context->c, fillOverStroke ? kCGTextStroke : kCGTextFill); drawMultilineText(recordPtr); CGContextSetTextDrawingMode(context->c, fillOverStroke ? kCGTextFill : kCGTextStroke); drawMultilineText(recordPtr); } else { drawMultilineText(recordPtr); } CGContextRestoreGState(context->c); context->saveCount--; } void TkPathTextFree( Tk_PathTextStyle * textStylePtr, void *custom) { PathATSUIRecord *recordPtr = (PathATSUIRecord *) custom; if(recordPtr) { if(recordPtr->atsuStyle) { ATSUDisposeStyle(recordPtr->atsuStyle); } if(recordPtr->atsuLayout) { ATSUDisposeTextLayout(recordPtr->atsuLayout); } if(recordPtr->buffer) { ckfree((char *)recordPtr->buffer); } } } PathRect TkPathTextMeasureBbox( Display * display, Tk_PathTextStyle * textStylePtr, char *utf8, double *lineSpacing, void *custom) { PathATSUIRecord *recordPtr = (PathATSUIRecord *) custom; PathRect r, ri; int i; ATSTrapezoid b; ItemCount numBounds; double x = 0.0; double y = 0.0; double baseX = 0.0; double lineSp = 0; for(i = 0; i < recordPtr->nlc; i++) { b.upperRight.x = b.upperLeft.x = 0; ATSUGetGlyphBounds(recordPtr->atsuLayout, 0, 0, recordPtr->nl[i], recordPtr->nl[i + 1] - recordPtr->nl[i] - 1, kATSUseFractionalOrigins, 1, &b, &numBounds); ri.x1 = MIN(Fix2X(b.upperLeft.x), Fix2X(b.lowerLeft.x)); ri.y1 = MIN(Fix2X(b.upperLeft.y), Fix2X(b.upperRight.y)); ri.x2 = MAX(Fix2X(b.upperRight.x), Fix2X(b.lowerRight.x)); ri.y2 = MAX(Fix2X(b.lowerLeft.y), Fix2X(b.lowerRight.y)); if(i == 0) { baseX = ri.x1; r.x1 = ri.x1; r.y1 = ri.y1; r.x2 = ri.x2; r.y2 = ri.y2; } else { x = ri.x1 - baseX; ri.x1 -= x; ri.y1 += y; ri.x2 -= x; ri.y2 += y; if(r.x1 > ri.x1) r.x1 = ri.x1; if(r.y1 > ri.y1) r.y1 = ri.y1; if(r.x2 < ri.x2) r.x2 = ri.x2; if(r.y2 < ri.y2) r.y2 = ri.y2; } recordPtr->dx[i] = X2Fix(-x); recordPtr->dy[i] = X2Fix(-y); y = r.y2 - r.y1; lineSp += y; } if(lineSpacing != NULL && i > 0) { *lineSpacing = lineSp / i; } return r; } void TkPathSurfaceErase( TkPathContext ctx, double x, double y, double width, double height) { TkPathContext_ *context = (TkPathContext_ *) ctx; CGContextClearRect(context->c, CGRectMake(x, y, width, height)); } void TkPathSurfaceToPhoto( Tcl_Interp * interp, TkPathContext ctx, Tk_PhotoHandle photo) { TkPathContext_ *context = (TkPathContext_ *) ctx; CGContextRef c = context->c; Tk_PhotoImageBlock block; unsigned char *data; unsigned char *pixel; int width, height; int bytesPerRow; width = CGBitmapContextGetWidth(c); height = CGBitmapContextGetHeight(c); data = CGBitmapContextGetData(c); bytesPerRow = CGBitmapContextGetBytesPerRow(c); Tk_PhotoGetImage(photo, &block); pixel = (unsigned char *)attemptckalloc(height * bytesPerRow); if(pixel == NULL) { return; } if(Tk_PathSurfaceCopyPremultiplyAlpha) { TkPathCopyBitsPremultipliedAlphaRGBA(data, pixel, width, height, bytesPerRow); } else { memcpy(pixel, data, height * bytesPerRow); } block.pixelPtr = pixel; block.width = width; block.height = height; block.pitch = bytesPerRow; block.pixelSize = 4; block.offset[0] = 0; block.offset[1] = 1; block.offset[2] = 2; block.offset[3] = 3; /* Should change this to check for errors... */ Tk_PhotoPutBlock(interp, photo, &block, 0, 0, width, height, TK_PHOTO_COMPOSITE_OVERLAY); ckfree((char *)pixel); } void TkPathClipToPath( TkPathContext ctx, int fillRule) { TkPathContext_ *context = (TkPathContext_ *) ctx; /* If you need to grow the clipping path after it’s shrunk, you must save the * graphics state before you clip, then restore the graphics state to restore the current * clipping path. */ CGContextSaveGState(context->c); context->saveCount++; if(fillRule == WindingRule) { CGContextClip(context->c); } else if(fillRule == EvenOddRule) { CGContextEOClip(context->c); } } void TkPathReleaseClipToPath( TkPathContext ctx) { TkPathContext_ *context = (TkPathContext_ *) ctx; CGContextRestoreGState(context->c); context->saveCount--; } void TkPathStroke( TkPathContext ctx, Tk_PathStyle * style) { TkPathContext_ *context = (TkPathContext_ *) ctx; CGContextStrokePath(context->c); } void TkPathFill( TkPathContext ctx, Tk_PathStyle * style) { TkPathContext_ *context = (TkPathContext_ *) ctx; if(style->fillRule == WindingRule) { CGContextFillPath(context->c); } else if(style->fillRule == EvenOddRule) { CGContextEOFillPath(context->c); } } void TkPathFillAndStroke( TkPathContext ctx, Tk_PathStyle * style) { TkPathContext_ *context = (TkPathContext_ *) ctx; if(style->fillRule == WindingRule) { CGContextDrawPath(context->c, kCGPathFillStroke); } else if(style->fillRule == EvenOddRule) { CGContextDrawPath(context->c, kCGPathEOFillStroke); } } void TkPathEndPath( TkPathContext ctx) { /* TkPathContext_ *context = (TkPathContext_ *) ctx; */ /* Empty ??? */ } void TkPathFree( TkPathContext ctx) { TkPathContext_ *context = (TkPathContext_ *) ctx; PathReleaseCGContext(context); if(context->data) { ckfree(context->data); } ckfree((char *)ctx); } int TkPathDrawingDestroysPath( void) { return 1; } int TkPathPixelAlign( void) { return 0; } /* TkPathGetCurrentPosition -- * * Returns the current pen position in untransformed coordinates! */ int TkPathGetCurrentPosition( TkPathContext ctx, PathPoint * ptPtr) { TkPathContext_ *context = (TkPathContext_ *) ctx; CGPoint cgpt; cgpt = CGContextGetPathCurrentPoint(context->c); ptPtr->x = cgpt.x; ptPtr->y = cgpt.y; return TCL_OK; } int TkPathBoundingBox( TkPathContext ctx, PathRect * rPtr) { TkPathContext_ *context = (TkPathContext_ *) ctx; CGRect cgRect; /* This one is not very useful since it includes the control points. */ cgRect = CGContextGetPathBoundingBox(context->c); rPtr->x1 = cgRect.origin.x; rPtr->y1 = cgRect.origin.y; rPtr->x2 = cgRect.origin.x + cgRect.size.width; rPtr->y2 = cgRect.origin.y + cgRect.size.height; return TCL_OK; } /* * Using CGShading for fill gradients. */ static void ShadeEvaluate( void *info, const CGFloat * in, CGFloat * out) { FillInfo *fillInfo = (FillInfo *) info; GradientStopArray *stopArrPtr = fillInfo->stopArrPtr; double fillOpacity = fillInfo->fillOpacity; GradientStop **stopPtrPtr = stopArrPtr->stops; GradientStop *stop1 = NULL, *stop2 = NULL; int nstops = stopArrPtr->nstops; int i = 0; float par = *in; float f1, f2; /* Find the two stops for this point. Tricky! */ while((i < nstops) && ((*stopPtrPtr)->offset < par)) { stopPtrPtr++, i++; } if(i == 0) { /* First stop > 0. */ stop1 = *stopPtrPtr; stop2 = stop1; } else if(i == nstops) { /* We have stepped beyond the last stop; step back! */ stop1 = *(stopPtrPtr - 1); stop2 = stop1; } else { stop1 = *(stopPtrPtr - 1); stop2 = *stopPtrPtr; } /* Interpolate between the two stops. * "If two gradient stops have the same offset value, * then the latter gradient stop controls the color value at the * overlap point." */ if(fabs(stop2->offset - stop1->offset) < 1e-6) { *out++ = RedFloatFromXColorPtr(stop2->color); *out++ = GreenFloatFromXColorPtr(stop2->color); *out++ = BlueFloatFromXColorPtr(stop2->color); *out++ = stop2->opacity * fillOpacity; } else { f1 = (stop2->offset - par) / (stop2->offset - stop1->offset); f2 = (par - stop1->offset) / (stop2->offset - stop1->offset); *out++ = f1 * RedFloatFromXColorPtr(stop1->color) + f2 * RedFloatFromXColorPtr(stop2->color); *out++ = f1 * GreenFloatFromXColorPtr(stop1->color) + f2 * GreenFloatFromXColorPtr(stop2->color); *out++ = f1 * BlueFloatFromXColorPtr(stop1->color) + f2 * BlueFloatFromXColorPtr(stop2->color); *out++ = (f1 * stop1->opacity + f2 * stop2->opacity) * fillOpacity; } } static void ShadeRelease( void *info) { /* Not sure if anything to do here. */ } void TkPathPaintLinearGradient( TkPathContext ctx, PathRect * bbox, LinearGradientFill * fillPtr, int fillRule, double fillOpacity, TMatrix * mPtr) { TkPathContext_ *context = (TkPathContext_ *) ctx; CGShadingRef shading; CGPoint start, end; CGColorSpaceRef colorSpaceRef; CGFunctionRef function; CGFunctionCallbacks callbacks; PathRect *trans = fillPtr->transitionPtr; /* The transition line. */ FillInfo fillInfo; fillInfo.fillOpacity = fillOpacity; fillInfo.stopArrPtr = fillPtr->stopArrPtr; callbacks.version = 0; callbacks.evaluate = ShadeEvaluate; callbacks.releaseInfo = ShadeRelease; colorSpaceRef = CGColorSpaceCreateDeviceRGB(); /* * We need to do like this since this is how SVG defines gradient drawing * in case the transition vector is in relative coordinates. */ CGContextSaveGState(context->c); context->saveCount++; if(fillPtr->units == kPathGradientUnitsBoundingBox) { CGContextTranslateCTM(context->c, bbox->x1, bbox->y1); CGContextScaleCTM(context->c, bbox->x2 - bbox->x1, bbox->y2 - bbox->y1); } function = CGFunctionCreate((void *)&fillInfo, 1, kValidDomain, 4, kValidRange, &callbacks); start = CGPointMake(trans->x1, trans->y1); end = CGPointMake(trans->x2, trans->y2); shading = CGShadingCreateAxial(colorSpaceRef, start, end, function, 1, 1); if(mPtr) { /* @@@ I'm not completely sure of the order of transforms here! */ TkPathPushTMatrix(ctx, mPtr); } CGContextDrawShading(context->c, shading); CGContextRestoreGState(context->c); context->saveCount--; CGShadingRelease(shading); CGFunctionRelease(function); CGColorSpaceRelease(colorSpaceRef); } void TkPathPaintRadialGradient( TkPathContext ctx, PathRect * bbox, RadialGradientFill * fillPtr, int fillRule, double fillOpacity, TMatrix * mPtr) { TkPathContext_ *context = (TkPathContext_ *) ctx; CGShadingRef shading; CGPoint start, end; CGColorSpaceRef colorSpaceRef; CGFunctionRef function; CGFunctionCallbacks callbacks; RadialTransition *tPtr = fillPtr->radialPtr; FillInfo fillInfo; fillInfo.fillOpacity = fillOpacity; fillInfo.stopArrPtr = fillPtr->stopArrPtr; callbacks.version = 0; callbacks.evaluate = ShadeEvaluate; callbacks.releaseInfo = ShadeRelease; colorSpaceRef = CGColorSpaceCreateDeviceRGB(); /* * 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 == kPathGradientUnitsBoundingBox) { CGContextSaveGState(context->c); context->saveCount++; CGContextTranslateCTM(context->c, bbox->x1, bbox->y1); CGContextScaleCTM(context->c, bbox->x2 - bbox->x1, bbox->y2 - bbox->y1); } function = CGFunctionCreate((void *)&fillInfo, 1, kValidDomain, 4, kValidRange, &callbacks); start = CGPointMake(tPtr->focalX, tPtr->focalY); end = CGPointMake(tPtr->centerX, tPtr->centerY); shading = CGShadingCreateRadial(colorSpaceRef, start, 0.0, end, tPtr->radius, function, 1, 1); if(mPtr) { /* @@@ I'm not completely sure of the order of transforms here! */ TkPathPushTMatrix(ctx, mPtr); } CGContextDrawShading(context->c, shading); CGShadingRelease(shading); CGFunctionRelease(function); CGColorSpaceRelease(colorSpaceRef); if(fillPtr->units == kPathGradientUnitsBoundingBox) { CGContextRestoreGState(context->c); context->saveCount--; } } int TkPathSetup( Tcl_Interp * interp) { return TCL_OK; } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */