/* * Copyright (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code 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 * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ #import "QuartzSurfaceData.h" #import "java_awt_BasicStroke.h" #import "java_awt_AlphaComposite.h" #import "java_awt_geom_PathIterator.h" #import "java_awt_image_BufferedImage.h" #import "sun_awt_SunHints.h" #import "sun_java2d_CRenderer.h" #import "sun_java2d_OSXSurfaceData.h" #import "sun_lwawt_macosx_CPrinterSurfaceData.h" #import "ImageSurfaceData.h" #import #import #import "ThreadUtilities.h" //#define DEBUG #if defined DEBUG #define PRINT(msg) {fprintf(stderr, "%s\n", msg);} #else #define PRINT(msg) {} #endif #define kOffset (0.5f) BOOL gAdjustForJavaDrawing; #pragma mark #pragma mark --- Color Cache --- // Creating and deleting CGColorRefs can be expensive, therefore we have a color cache. // The color cache was first introduced with // With , the hashing function was improved // With , the color cache became global (per process) instead of per surface. // Must be power of 2. 1024 is the least power of 2 number that makes SwingSet2 run without any non-empty cache misses #define gColorCacheSize 1024 struct _ColorCacheInfo { UInt32 keys[gColorCacheSize]; CGColorRef values[gColorCacheSize]; }; static struct _ColorCacheInfo colorCacheInfo; static pthread_mutex_t gColorCacheLock = PTHREAD_MUTEX_INITIALIZER; // given a UInt32 color, it tries to find that find the corresponding CGColorRef in the hash cache. If the CGColorRef // doesn't exist or there is a collision, it creates a new one CGColorRef and put's in the cache. Then, // it sets with current fill/stroke color for the CGContext passed in (qsdo->cgRef). void setCachedColor(QuartzSDOps *qsdo, UInt32 color) { static const CGFloat kColorConversionMultiplier = 1.0f/255.0f; pthread_mutex_lock(&gColorCacheLock); static CGColorSpaceRef colorspace = NULL; if (colorspace == NULL) { colorspace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB); } CGColorRef cgColor = NULL; // The colors passed have low randomness. That means we need to scramble the bits of the color // to produce a good hash key. After some analysis, it looks like Thomas's Wang integer hasing algorithm // seems a nice trade off between performance and effectivness. UInt32 index = color; index += ~(index << 15); index ^= (index >> 10); index += (index << 3); index ^= (index >> 6); index += ~(index << 11); index ^= (index >> 16); index = index & (gColorCacheSize - 1); // The bits are scrambled, we just need to make sure it fits inside our table UInt32 key = colorCacheInfo.keys[index]; CGColorRef value = colorCacheInfo.values[index]; if ((key == color) && (value != NULL)) { //fprintf(stderr, "+");fflush(stderr);//hit cgColor = value; } else { if (value != NULL) { //fprintf(stderr, "!");fflush(stderr);//miss and replace - double ouch CGColorRelease(value); } //fprintf(stderr, "-");fflush(stderr);// miss CGFloat alpha = ((color>>24)&0xff)*kColorConversionMultiplier; CGFloat red = ((color>>16)&0xff)*kColorConversionMultiplier; CGFloat green = ((color>>8)&0xff)*kColorConversionMultiplier; CGFloat blue = ((color>>0)&0xff)*kColorConversionMultiplier; const CGFloat components[] = {red, green, blue, alpha, 1.0f}; value = CGColorCreate(colorspace, components); colorCacheInfo.keys[index] = color; colorCacheInfo.values[index] = value; cgColor = value; } CGContextSetStrokeColorWithColor(qsdo->cgRef, cgColor); CGContextSetFillColorWithColor(qsdo->cgRef, cgColor); pthread_mutex_unlock(&gColorCacheLock); } #pragma mark #pragma mark --- Gradient --- // this function MUST NOT be inlined! void gradientLinearPaintEvaluateFunction(void *info, const CGFloat *in, CGFloat *out) { StateShadingInfo *shadingInfo = (StateShadingInfo *)info; CGFloat *colors = shadingInfo->colors; CGFloat range = *in; CGFloat c1, c2; jint k; //fprintf(stderr, "range=%f\n", range); for (k=0; k<4; k++) { c1 = colors[k]; //fprintf(stderr, " c1=%f", c1); c2 = colors[k+4]; //fprintf(stderr, ", c2=%f", c2); if (c1 == c2) { *out++ = c2; //fprintf(stderr, ", %f", *(out-1)); } else if (c1 > c2) { *out++ = c1 - ((c1-c2)*range); //fprintf(stderr, ", %f", *(out-1)); } else// if (c1 < c2) { *out++ = c1 + ((c2-c1)*range); //fprintf(stderr, ", %f", *(out-1)); } //fprintf(stderr, "\n"); } } // this function MUST NOT be inlined! void gradientCyclicPaintEvaluateFunction(void *info, const CGFloat *in, CGFloat *out) { StateShadingInfo *shadingInfo = (StateShadingInfo *)info; CGFloat length = shadingInfo->length ; CGFloat period = shadingInfo->period; CGFloat offset = shadingInfo->offset; CGFloat periodLeft = offset; CGFloat periodRight = periodLeft+period; CGFloat *colors = shadingInfo->colors; CGFloat range = *in; CGFloat c1, c2; jint k; jint count = 0; range *= length; // put the range within the period if (range < periodLeft) { while (range < periodLeft) { range += period; count++; } range = range-periodLeft; } else if (range > periodRight) { count = 1; while (range > periodRight) { range -= period; count++; } range = periodRight-range; } else { range = range - offset; } range = range/period; // cycle up or down if (count%2 == 0) { for (k=0; k<4; k++) { c1 = colors[k]; c2 = colors[k+4]; if (c1 == c2) { *out++ = c2; } else if (c1 > c2) { *out++ = c1 - ((c1-c2)*range); } else// if (c1 < c2) { *out++ = c1 + ((c2-c1)*range); } } } else { for (k=0; k<4; k++) { c1 = colors[k+4]; c2 = colors[k]; if (c1 == c2) { *out++ = c2; } else if (c1 > c2) { *out++ = c1 - ((c1-c2)*range); } else// if (c1 < c2) { *out++ = c1 + ((c2-c1)*range); } } } } // this function MUST NOT be inlined! void gradientPaintReleaseFunction(void *info) { PRINT(" gradientPaintReleaseFunction") free(info); } static inline void contextQuartzLinearGradientPath(QuartzSDOps* qsdo) { PRINT(" contextQuartzLinearGradientPath"); CGContextRef cgRef = qsdo->cgRef; StateGradientInfo *gradientInfo = qsdo->gradientInfo; CGColorSpaceRef colorspace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB); size_t num_locations = gradientInfo->fractionsLength; CGFloat *locations = (CGFloat *) malloc(sizeof(CGFloat) * num_locations); int i = 0; size_t component_size = num_locations * 4; CGFloat components[component_size]; CGGradientRef gradient = NULL; for (i = 0; i < num_locations; i++) { locations[i] = gradientInfo->fractionsdata[i]; } for (i = 0; i < component_size; i++) { components[i] = gradientInfo->colordata[i]; } CGContextSaveGState(cgRef); gradient = CGGradientCreateWithColorComponents(colorspace, components, locations, num_locations); if (qsdo->isEvenOddFill) { CGContextEOClip(cgRef); } else { CGContextClip(cgRef); } CGContextDrawLinearGradient(cgRef, gradient, gradientInfo->start, gradientInfo->end, kCGGradientDrawsAfterEndLocation); CGContextRestoreGState(cgRef); CGColorSpaceRelease(colorspace); CGGradientRelease(gradient); free(locations); free(gradientInfo->colordata); free(gradientInfo->fractionsdata); } static inline void contextQuartzRadialGradientPath(QuartzSDOps* qsdo) { PRINT(" contextQuartzRadialGradientPath"); CGContextRef cgRef = qsdo->cgRef; StateGradientInfo *gradientInfo = qsdo->gradientInfo; CGColorSpaceRef colorspace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB); size_t num_locations = gradientInfo->fractionsLength; CGFloat *locations = (CGFloat *) malloc(sizeof(CGFloat) * num_locations); int i = 0; size_t component_size = num_locations * 4; CGFloat components[component_size]; CGGradientRef gradient = NULL; CGFloat startRadius = gradientInfo->radius; CGFloat endRadius = gradientInfo->radius; for (i = 0; i < num_locations; i++) { locations[i] = gradientInfo->fractionsdata[i]; } for (i = 0; i < component_size; i++) { components[i] = gradientInfo->colordata[i]; } CGContextSaveGState(cgRef); gradient = CGGradientCreateWithColorComponents(colorspace, components, locations, num_locations); if (qsdo->isEvenOddFill) { CGContextEOClip(cgRef); } else { CGContextClip(cgRef); } CGContextDrawRadialGradient(cgRef, gradient, gradientInfo->start, 0, gradientInfo->end, endRadius, kCGGradientDrawsAfterEndLocation); CGContextRestoreGState(cgRef); CGColorSpaceRelease(colorspace); CGGradientRelease(gradient); free(locations); free(gradientInfo->colordata); free(gradientInfo->fractionsdata); } static inline void contextGradientPath(QuartzSDOps* qsdo) { PRINT(" ContextGradientPath") CGContextRef cgRef = qsdo->cgRef; StateShadingInfo* shadingInfo = qsdo->shadingInfo; CGRect bounds = CGContextGetClipBoundingBox(cgRef); static const CGFloat domain[2] = {0.0f, 1.0f}; static const CGFloat range[8] = {0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f}; CGColorSpaceRef colorspace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB); CGFunctionRef shadingFunc = NULL; CGShadingRef shading = NULL; if (shadingInfo->cyclic == NO) { static const CGFunctionCallbacks callbacks = {0, &gradientLinearPaintEvaluateFunction, &gradientPaintReleaseFunction}; shadingFunc = CGFunctionCreate((void *)shadingInfo, 1, domain, 4, range, &callbacks); shading = CGShadingCreateAxial(colorspace, shadingInfo->start, shadingInfo->end, shadingFunc, 1, 1); } else { //fprintf(stderr, "BOUNDING BOX x1=%f, y1=%f x2=%f, y2=%f\n", bounds.origin.x, bounds.origin.y, bounds.origin.x+bounds.size.width, bounds.origin.y+bounds.size.height); // need to extend the line start-end CGFloat x1 = shadingInfo->start.x; CGFloat y1 = shadingInfo->start.y; CGFloat x2 = shadingInfo->end.x; CGFloat y2 = shadingInfo->end.y; //fprintf(stderr, "GIVEN x1=%f, y1=%f x2=%f, y2=%f\n", x1, y1, x2, y2); if (x1 == x2) { y1 = bounds.origin.y; y2 = y1 + bounds.size.height; } else if (y1 == y2) { x1 = bounds.origin.x; x2 = x1 + bounds.size.width; } else { // find the original line function y = mx + c CGFloat m1 = (y2-y1)/(x2-x1); CGFloat c1 = y1 - m1*x1; //fprintf(stderr, " m1=%f, c1=%f\n", m1, c1); // a line perpendicular to the original one will have the slope CGFloat m2 = -(1/m1); //fprintf(stderr, " m2=%f\n", m2); // find the only 2 possible lines perpendicular to the original line, passing the two top corners of the bounding box CGFloat x1A = bounds.origin.x; CGFloat y1A = bounds.origin.y; CGFloat c1A = y1A - m2*x1A; //fprintf(stderr, " x1A=%f, y1A=%f, c1A=%f\n", x1A, y1A, c1A); CGFloat x1B = bounds.origin.x+bounds.size.width; CGFloat y1B = bounds.origin.y; CGFloat c1B = y1B - m2*x1B; //fprintf(stderr, " x1B=%f, y1B=%f, c1B=%f\n", x1B, y1B, c1B); // find the crossing points of the original line and the two lines we computed above to find the new possible starting points CGFloat x1Anew = (c1A-c1)/(m1-m2); CGFloat y1Anew = m2*x1Anew + c1A; CGFloat x1Bnew = (c1B-c1)/(m1-m2); CGFloat y1Bnew = m2*x1Bnew + c1B; //fprintf(stderr, "NEW x1Anew=%f, y1Anew=%f x1Bnew=%f, y1Bnew=%f\n", x1Anew, y1Anew, x1Bnew, y1Bnew); // select the new starting point if (y1Anew <= y1Bnew) { x1 = x1Anew; y1 = y1Anew; } else { x1 = x1Bnew; y1 = y1Bnew; } //fprintf(stderr, "--- NEW x1=%f, y1=%f\n", x1, y1); // find the only 2 possible lines perpendicular to the original line, passing the two bottom corners of the bounding box CGFloat x2A = bounds.origin.x; CGFloat y2A = bounds.origin.y+bounds.size.height; CGFloat c2A = y2A - m2*x2A; //fprintf(stderr, " x2A=%f, y2A=%f, c2A=%f\n", x2A, y2A, c2A); CGFloat x2B = bounds.origin.x+bounds.size.width; CGFloat y2B = bounds.origin.y+bounds.size.height; CGFloat c2B = y2B - m2*x2B; //fprintf(stderr, " x2B=%f, y2B=%f, c2B=%f\n", x2B, y2B, c2B); // find the crossing points of the original line and the two lines we computed above to find the new possible ending points CGFloat x2Anew = (c2A-c1)/(m1-m2); CGFloat y2Anew = m2*x2Anew + c2A; CGFloat x2Bnew = (c2B-c1)/(m1-m2); CGFloat y2Bnew = m2*x2Bnew + c2B; //fprintf(stderr, "NEW x2Anew=%f, y2Anew=%f x2Bnew=%f, y2Bnew=%f\n", x2Anew, y2Anew, x2Bnew, y2Bnew); // select the new ending point if (y2Anew >= y2Bnew) { x2 = x2Anew; y2 = y2Anew; } else { x2 = x2Bnew; y2 = y2Bnew; } //fprintf(stderr, "--- NEW x2=%f, y2=%f\n", x2, y2); } qsdo->shadingInfo->period = sqrt(pow(shadingInfo->end.x-shadingInfo->start.x, 2.0) + pow(shadingInfo->end.y-shadingInfo->start.y, 2.0)); if ((qsdo->shadingInfo->period != 0)) { // compute segment lengths that we will need for the gradient function qsdo->shadingInfo->length = sqrt(pow(x2-x1, 2.0) + pow(y2-y1, 2.0)); qsdo->shadingInfo->offset = sqrt(pow(shadingInfo->start.x-x1, 2.0) + pow(shadingInfo->start.y-y1, 2.0)); //fprintf(stderr, "length=%f, period=%f, offset=%f\n", qsdo->shadingInfo->length, qsdo->shadingInfo->period, qsdo->shadingInfo->offset); CGPoint newStart = {x1, y1}; CGPoint newEnd = {x2, y2}; static const CGFunctionCallbacks callbacks = {0, &gradientCyclicPaintEvaluateFunction, &gradientPaintReleaseFunction}; shadingFunc = CGFunctionCreate((void *)shadingInfo, 1, domain, 4, range, &callbacks); shading = CGShadingCreateAxial(colorspace, newStart, newEnd, shadingFunc, 0, 0); } } CGColorSpaceRelease(colorspace); if (shadingFunc != NULL) { CGContextSaveGState(cgRef); // rdar://problem/5214320 // Gradient fills of Java GeneralPath don't respect the even odd winding rule (quartz pipeline). if (qsdo->isEvenOddFill) { CGContextEOClip(cgRef); } else { CGContextClip(cgRef); } CGContextDrawShading(cgRef, shading); CGContextRestoreGState(cgRef); CGShadingRelease(shading); CGFunctionRelease(shadingFunc); qsdo->shadingInfo = NULL; } } #pragma mark #pragma mark --- Texture --- // this function MUST NOT be inlined! void texturePaintEvaluateFunction(void *info, CGContextRef cgRef) { JNIEnv* env = [ThreadUtilities getJNIEnvUncached]; StatePatternInfo* patternInfo = (StatePatternInfo*)info; ImageSDOps* isdo = LockImage(env, patternInfo->sdata); makeSureImageIsCreated(isdo); CGContextDrawImage(cgRef, CGRectMake(0.0f, 0.0f, patternInfo->width, patternInfo->height), isdo->imgRef); UnlockImage(env, isdo); } // this function MUST NOT be inlined! void texturePaintReleaseFunction(void *info) { PRINT(" texturePaintReleaseFunction") JNIEnv* env = [ThreadUtilities getJNIEnvUncached]; StatePatternInfo* patternInfo = (StatePatternInfo*)info; (*env)->DeleteGlobalRef(env, patternInfo->sdata); free(info); } static inline void contextTexturePath(JNIEnv* env, QuartzSDOps* qsdo) { PRINT(" ContextTexturePath") CGContextRef cgRef = qsdo->cgRef; StatePatternInfo* patternInfo = qsdo->patternInfo; CGAffineTransform ctm = CGContextGetCTM(cgRef); CGAffineTransform ptm = {patternInfo->sx, 0.0f, 0.0f, -patternInfo->sy, patternInfo->tx, patternInfo->ty}; CGAffineTransform tm = CGAffineTransformConcat(ptm, ctm); CGFloat xStep = (CGFloat)qsdo->patternInfo->width; CGFloat yStep = (CGFloat)qsdo->patternInfo->height; CGPatternTiling tiling = kCGPatternTilingNoDistortion; BOOL isColored = YES; static const CGPatternCallbacks callbacks = {0, &texturePaintEvaluateFunction, &texturePaintReleaseFunction}; CGPatternRef pattern = CGPatternCreate((void*)patternInfo, CGRectMake(0.0f, 0.0f, xStep, yStep), tm, xStep, yStep, tiling, isColored, &callbacks); CGColorSpaceRef colorspace = CGColorSpaceCreatePattern(NULL); static const CGFloat alpha = 1.0f; CGContextSaveGState(cgRef); CGContextSetFillColorSpace(cgRef, colorspace); CGContextSetFillPattern(cgRef, pattern, &alpha); CGContextSetRGBStrokeColor(cgRef, 0.0f, 0.0f, 0.0f, 1.0f); CGContextSetPatternPhase(cgRef, CGSizeMake(0.0f, 0.0f)); // rdar://problem/5214320 // Gradient fills of Java GeneralPath don't respect the even odd winding rule (quartz pipeline). if (qsdo->isEvenOddFill) { CGContextEOFillPath(cgRef); } else { CGContextFillPath(cgRef); } CGContextRestoreGState(cgRef); CGColorSpaceRelease(colorspace); CGPatternRelease(pattern); qsdo->patternInfo = NULL; } #pragma mark #pragma mark --- Context Setup --- static inline void setDefaultColorSpace(CGContextRef cgRef) { static CGColorSpaceRef colorspace = NULL; if (colorspace == NULL) { colorspace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB); } CGContextSetStrokeColorSpace(cgRef, colorspace); CGContextSetFillColorSpace(cgRef, colorspace); } void SetUpCGContext(JNIEnv *env, QuartzSDOps *qsdo, SDRenderType renderType) { PRINT(" SetUpCGContext") CGContextRef cgRef = qsdo->cgRef; //fprintf(stderr, "%p ", cgRef); jint *javaGraphicsStates = qsdo->javaGraphicsStates; jfloat *javaFloatGraphicsStates = (jfloat*)(qsdo->javaGraphicsStates); jint changeFlags = javaGraphicsStates[sun_java2d_OSXSurfaceData_kChangeFlagIndex]; BOOL everyThingChanged = qsdo->newContext || (changeFlags == sun_java2d_OSXSurfaceData_kEverythingChangedFlag); BOOL clipChanged = everyThingChanged || ((changeFlags&sun_java2d_OSXSurfaceData_kClipChangedBit) != 0); BOOL transformChanged = everyThingChanged || ((changeFlags&sun_java2d_OSXSurfaceData_kCTMChangedBit) != 0); BOOL paintChanged = everyThingChanged || ((changeFlags&sun_java2d_OSXSurfaceData_kColorChangedBit) != 0); BOOL compositeChanged = everyThingChanged || ((changeFlags&sun_java2d_OSXSurfaceData_kCompositeChangedBit) != 0); BOOL strokeChanged = everyThingChanged || ((changeFlags&sun_java2d_OSXSurfaceData_kStrokeChangedBit) != 0); // BOOL fontChanged = everyThingChanged || ((changeFlags&sun_java2d_OSXSurfaceData_kFontChangedBit) != 0); BOOL renderingHintsChanged = everyThingChanged || ((changeFlags&sun_java2d_OSXSurfaceData_kHintsChangedBit) != 0); //fprintf(stderr, "SetUpCGContext cgRef=%p new=%d changeFlags=%d, everyThingChanged=%d clipChanged=%d transformChanged=%d\n", // cgRef, qsdo->newContext, changeFlags, everyThingChanged, clipChanged, transformChanged); if ((everyThingChanged == YES) || (clipChanged == YES) || (transformChanged == YES)) { everyThingChanged = YES; // in case clipChanged or transformChanged CGContextRestoreGState(cgRef); // restore to the original state CGContextSaveGState(cgRef); // make our local copy of the state setDefaultColorSpace(cgRef); } if ((everyThingChanged == YES) || (clipChanged == YES)) { if (javaGraphicsStates[sun_java2d_OSXSurfaceData_kClipStateIndex] == sun_java2d_OSXSurfaceData_kClipRect) { CGFloat x = javaFloatGraphicsStates[sun_java2d_OSXSurfaceData_kClipXIndex]; CGFloat y = javaFloatGraphicsStates[sun_java2d_OSXSurfaceData_kClipYIndex]; CGFloat w = javaFloatGraphicsStates[sun_java2d_OSXSurfaceData_kClipWidthIndex]; CGFloat h = javaFloatGraphicsStates[sun_java2d_OSXSurfaceData_kClipHeightIndex]; CGContextClipToRect(cgRef, CGRectMake(x, y, w, h)); } else { BOOL eoFill = (javaGraphicsStates[sun_java2d_OSXSurfaceData_kClipWindingRuleIndex] == java_awt_geom_PathIterator_WIND_EVEN_ODD); jint numtypes = javaGraphicsStates[sun_java2d_OSXSurfaceData_kClipNumTypesIndex]; jobject coordsarray = (jobject)((*env)->GetObjectArrayElement(env, qsdo->javaGraphicsStatesObjects, sun_java2d_OSXSurfaceData_kClipCoordinatesIndex)); jobject typesarray = (jobject)((*env)->GetObjectArrayElement(env, qsdo->javaGraphicsStatesObjects, sun_java2d_OSXSurfaceData_kClipTypesIndex)); jfloat* coords = (jfloat*)(*env)->GetDirectBufferAddress(env, coordsarray); jint* types = (jint*)(*env)->GetDirectBufferAddress(env, typesarray); DoShapeUsingCG(cgRef, types, coords, numtypes, NO, qsdo->graphicsStateInfo.offsetX, qsdo->graphicsStateInfo.offsetY); if (CGContextIsPathEmpty(cgRef) == 0) { if (eoFill) { CGContextEOClip(cgRef); } else { CGContextClip(cgRef); } } else { CGContextClipToRect(cgRef, CGRectZero); } } } // for debugging //CGContextResetClip(cgRef); if ((everyThingChanged == YES) || (transformChanged == YES)) { CGFloat a = javaFloatGraphicsStates[sun_java2d_OSXSurfaceData_kCTMaIndex]; CGFloat b = javaFloatGraphicsStates[sun_java2d_OSXSurfaceData_kCTMbIndex]; CGFloat c = javaFloatGraphicsStates[sun_java2d_OSXSurfaceData_kCTMcIndex]; CGFloat d = javaFloatGraphicsStates[sun_java2d_OSXSurfaceData_kCTMdIndex]; CGFloat tx = javaFloatGraphicsStates[sun_java2d_OSXSurfaceData_kCTMtxIndex]; CGFloat ty = javaFloatGraphicsStates[sun_java2d_OSXSurfaceData_kCTMtyIndex]; CGContextConcatCTM(cgRef, CGAffineTransformMake(a, b, c, d, tx, ty)); if (gAdjustForJavaDrawing == YES) { // find the offsets in the device corrdinate system CGAffineTransform ctm = CGContextGetCTM(cgRef); if ((qsdo->graphicsStateInfo.ctm.a != ctm.a) || (qsdo->graphicsStateInfo.ctm.b != ctm.b) || (qsdo->graphicsStateInfo.ctm.c != ctm.c) || (qsdo->graphicsStateInfo.ctm.d != ctm.d)) { qsdo->graphicsStateInfo.ctm = ctm; // In CG affine xforms y' = bx+dy+ty // We need to flip both y coefficeints to flip the offset point into the java coordinate system. ctm.b = -ctm.b; ctm.d = -ctm.d; ctm.tx = 0.0f; ctm.ty = 0.0f; CGPoint offsets = {kOffset, kOffset}; CGAffineTransform inverse = CGAffineTransformInvert(ctm); offsets = CGPointApplyAffineTransform(offsets, inverse); qsdo->graphicsStateInfo.offsetX = offsets.x; qsdo->graphicsStateInfo.offsetY = offsets.y; } } else { qsdo->graphicsStateInfo.offsetX = 0.0f; qsdo->graphicsStateInfo.offsetY = 0.0f; } } // for debugging //CGContextResetCTM(cgRef); if ((everyThingChanged == YES) || (compositeChanged == YES)) { jint alphaCompositeRule = javaGraphicsStates[sun_java2d_OSXSurfaceData_kCompositeRuleIndex]; CGFloat alphaCompositeValue = javaFloatGraphicsStates[sun_java2d_OSXSurfaceData_kCompositeValueIndex]; NSCompositingOperation op; switch (alphaCompositeRule) { case java_awt_AlphaComposite_CLEAR: op = NSCompositeClear; break; case java_awt_AlphaComposite_SRC: op = NSCompositeCopy; break; case java_awt_AlphaComposite_SRC_OVER: op = NSCompositeSourceOver; break; case java_awt_AlphaComposite_DST_OVER: op = NSCompositeDestinationOver; break; case java_awt_AlphaComposite_SRC_IN: op = NSCompositeSourceIn; break; case java_awt_AlphaComposite_DST_IN: op = NSCompositeDestinationIn; break; case java_awt_AlphaComposite_SRC_OUT: op = NSCompositeSourceOut; break; case java_awt_AlphaComposite_DST_OUT: op = NSCompositeDestinationOut; break; case java_awt_AlphaComposite_DST: // Alpha must be set to 0 because we're using the kCGCompositeSover rule op = NSCompositeSourceOver; alphaCompositeValue = 0.0f; break; case java_awt_AlphaComposite_SRC_ATOP: op = NSCompositeSourceAtop; break; case java_awt_AlphaComposite_DST_ATOP: op = NSCompositeDestinationAtop; break; case java_awt_AlphaComposite_XOR: op = NSCompositeXOR; break; default: op = NSCompositeSourceOver; alphaCompositeValue = 1.0f; break; } NSGraphicsContext *context = [NSGraphicsContext graphicsContextWithGraphicsPort:cgRef flipped:NO]; //CGContextSetCompositeOperation(cgRef, op); [context setCompositingOperation:op]; CGContextSetAlpha(cgRef, alphaCompositeValue); } if ((everyThingChanged == YES) || (renderingHintsChanged == YES)) { jint antialiasHint = javaGraphicsStates[sun_java2d_OSXSurfaceData_kHintsAntialiasIndex]; // jint textAntialiasHint = javaGraphicsStates[sun_java2d_OSXSurfaceData_kHintsTextAntialiasIndex]; jint renderingHint = javaGraphicsStates[sun_java2d_OSXSurfaceData_kHintsRenderingIndex]; jint interpolationHint = javaGraphicsStates[sun_java2d_OSXSurfaceData_kHintsInterpolationIndex]; // jint textFractionalMetricsHint = javaGraphicsStates[sun_java2d_OSXSurfaceData_kHintsFractionalMetricsIndex]; // 10-10-02 VL: since CoreGraphics supports only an interpolation quality attribute we have to map // both interpolationHint and renderingHint to an attribute value that best represents their combination. // (See Radar 3071704.) We'll go for the best quality. CG maps interpolation quality values as follows: // kCGInterpolationNone - nearest_neighbor // kCGInterpolationLow - bilinear // kCGInterpolationHigh - Lanczos (better than bicubic) CGInterpolationQuality interpolationQuality = kCGInterpolationDefault; // First check if the interpolation hint is suggesting to turn off interpolation: if (interpolationHint == sun_awt_SunHints_INTVAL_INTERPOLATION_NEAREST_NEIGHBOR) { interpolationQuality = kCGInterpolationNone; } else if ((interpolationHint >= sun_awt_SunHints_INTVAL_INTERPOLATION_BICUBIC) || (renderingHint >= sun_awt_SunHints_INTVAL_RENDER_QUALITY)) { // Use >= just in case Sun adds some hint values in the future - this check wouldn't fall apart then: interpolationQuality = kCGInterpolationHigh; } else if (interpolationHint == sun_awt_SunHints_INTVAL_INTERPOLATION_BILINEAR) { interpolationQuality = kCGInterpolationLow; } else if (renderingHint == sun_awt_SunHints_INTVAL_RENDER_SPEED) { interpolationQuality = kCGInterpolationNone; } // else interpolationHint == -1 || renderingHint == sun_awt_SunHints_INTVAL_CSURFACE_DEFAULT --> kCGInterpolationDefault CGContextSetInterpolationQuality(cgRef, interpolationQuality); qsdo->graphicsStateInfo.interpolation = interpolationQuality; // antialiasing BOOL antialiased = (antialiasHint == sun_awt_SunHints_INTVAL_ANTIALIAS_ON); CGContextSetShouldAntialias(cgRef, antialiased); qsdo->graphicsStateInfo.antialiased = antialiased; } if ((everyThingChanged == YES) || (strokeChanged == YES)) { qsdo->graphicsStateInfo.simpleStroke = YES; CGFloat linewidth = javaFloatGraphicsStates[sun_java2d_OSXSurfaceData_kStrokeWidthIndex]; jint linejoin = javaGraphicsStates[sun_java2d_OSXSurfaceData_kStrokeJoinIndex]; jint linecap = javaGraphicsStates[sun_java2d_OSXSurfaceData_kStrokeCapIndex]; CGFloat miterlimit = javaFloatGraphicsStates[sun_java2d_OSXSurfaceData_kStrokeLimitIndex]; jobject dasharray = ((*env)->GetObjectArrayElement(env, qsdo->javaGraphicsStatesObjects, sun_java2d_OSXSurfaceData_kStrokeDashArrayIndex)); CGFloat dashphase = javaFloatGraphicsStates[sun_java2d_OSXSurfaceData_kStrokeDashPhaseIndex]; if (linewidth == 0.0f) { linewidth = (CGFloat)-109.05473e+14; // Don't ask ! } CGContextSetLineWidth(cgRef, linewidth); CGLineCap cap; switch (linecap) { case java_awt_BasicStroke_CAP_BUTT: qsdo->graphicsStateInfo.simpleStroke = NO; cap = kCGLineCapButt; break; case java_awt_BasicStroke_CAP_ROUND: qsdo->graphicsStateInfo.simpleStroke = NO; cap = kCGLineCapRound; break; case java_awt_BasicStroke_CAP_SQUARE: default: cap = kCGLineCapSquare; break; } CGContextSetLineCap(cgRef, cap); CGLineJoin join; switch (linejoin) { case java_awt_BasicStroke_JOIN_ROUND: qsdo->graphicsStateInfo.simpleStroke = NO; join = kCGLineJoinRound; break; case java_awt_BasicStroke_JOIN_BEVEL: qsdo->graphicsStateInfo.simpleStroke = NO; join = kCGLineJoinBevel; break; case java_awt_BasicStroke_JOIN_MITER: default: join = kCGLineJoinMiter; break; } CGContextSetLineJoin(cgRef, join); CGContextSetMiterLimit(cgRef, miterlimit); if (dasharray != NULL) { qsdo->graphicsStateInfo.simpleStroke = NO; jint length = (*env)->GetArrayLength(env, dasharray); jfloat* jdashes = (jfloat*)(*env)->GetPrimitiveArrayCritical(env, dasharray, NULL); if (jdashes == NULL) { CGContextSetLineDash(cgRef, 0, NULL, 0); return; } CGFloat* dashes = (CGFloat*)malloc(sizeof(CGFloat)*length); if (dashes != NULL) { jint i; for (i=0; iReleasePrimitiveArrayCritical(env, dasharray, jdashes, 0); } else { CGContextSetLineDash(cgRef, 0, NULL, 0); } } BOOL cocoaPaint = (javaGraphicsStates[sun_java2d_OSXSurfaceData_kColorStateIndex] == sun_java2d_OSXSurfaceData_kColorSystem); BOOL complexPaint = (javaGraphicsStates[sun_java2d_OSXSurfaceData_kColorStateIndex] == sun_java2d_OSXSurfaceData_kColorGradient) || (javaGraphicsStates[sun_java2d_OSXSurfaceData_kColorStateIndex] == sun_java2d_OSXSurfaceData_kColorTexture); if ((everyThingChanged == YES) || (paintChanged == YES) || (cocoaPaint == YES) || (complexPaint == YES)) { // rdar://problem/5214320 // Gradient fills of Java GeneralPath don't respect the even odd winding rule (quartz pipeline). // Notice the side effect of the stmt after this if-block. if (renderType == SD_EOFill) { qsdo->isEvenOddFill = YES; } renderType = SetUpPaint(env, qsdo, renderType); } qsdo->renderType = renderType; } void setupGradient(JNIEnv *env, QuartzSDOps* qsdo, jfloat* javaFloatGraphicsStates) { static const CGFloat kColorConversionMultiplier = 1.0f/255.0f; qsdo->gradientInfo = (StateGradientInfo*)malloc(sizeof(StateGradientInfo)); if (qsdo->gradientInfo == NULL) { [JNFException raise:env as:kOutOfMemoryError reason:"Failed to malloc memory for gradient paint"]; } qsdo->graphicsStateInfo.simpleStroke = NO; qsdo->graphicsStateInfo.simpleColor = NO; qsdo->gradientInfo->start.x = javaFloatGraphicsStates[sun_java2d_OSXSurfaceData_kColorx1Index]; qsdo->gradientInfo->start.y = javaFloatGraphicsStates[sun_java2d_OSXSurfaceData_kColory1Index]; qsdo->gradientInfo->end.x = javaFloatGraphicsStates[sun_java2d_OSXSurfaceData_kColorx2Index]; qsdo->gradientInfo->end.y = javaFloatGraphicsStates[sun_java2d_OSXSurfaceData_kColory2Index]; jobject colorArray = ((*env)->GetObjectArrayElement(env, qsdo->javaGraphicsStatesObjects, sun_java2d_OSXSurfaceData_kColorArrayIndex)); if (colorArray != NULL) { jint length = (*env)->GetArrayLength(env, colorArray); jint* jcolorData = (jint*)(*env)->GetPrimitiveArrayCritical(env, colorArray, NULL); qsdo->gradientInfo->colordata = (CGFloat*)malloc(sizeof(CGFloat)*4*length); memset(qsdo->gradientInfo->colordata, 0, sizeof(CGFloat)*4*length); if (jcolorData != NULL) { int i; for (i=0; igradientInfo->colordata[i*4] = ((jcolorData[i]>>16)&0xff)*kColorConversionMultiplier; qsdo->gradientInfo->colordata[i*4+1] = ((jcolorData[i]>>8)&0xff)*kColorConversionMultiplier; qsdo->gradientInfo->colordata[i*4+2] = ((jcolorData[i]>>0)&0xff)*kColorConversionMultiplier; qsdo->gradientInfo->colordata[i*4+3] = ((jcolorData[i]>>24)&0xff)*kColorConversionMultiplier; } } (*env)->ReleasePrimitiveArrayCritical(env, colorArray, jcolorData, 0); } jobject fractionsArray = ((*env)->GetObjectArrayElement(env, qsdo->javaGraphicsStatesObjects, sun_java2d_OSXSurfaceData_kFractionsArrayIndex)); if (fractionsArray != NULL) { jint length = (*env)->GetArrayLength(env, fractionsArray); qsdo->gradientInfo->fractionsLength = length; jfloat* jfractionsData = (jfloat*)(*env)->GetPrimitiveArrayCritical(env, fractionsArray, NULL); if (jfractionsData != NULL) { int i; qsdo->gradientInfo->fractionsdata = (CGFloat *)malloc(sizeof(CGFloat) *length); memset(qsdo->gradientInfo->fractionsdata, 0, sizeof(CGFloat)*length); for (i=0; igradientInfo->fractionsdata[i] = jfractionsData[i]; } (*env)->ReleasePrimitiveArrayCritical(env, fractionsArray, jfractionsData, 0); } } } SDRenderType SetUpPaint(JNIEnv *env, QuartzSDOps *qsdo, SDRenderType renderType) { CGContextRef cgRef = qsdo->cgRef; jint *javaGraphicsStates = qsdo->javaGraphicsStates; jfloat *javaFloatGraphicsStates = (jfloat*)(qsdo->javaGraphicsStates); static const CGFloat kColorConversionMultiplier = 1.0f/255.0f; jint colorState = javaGraphicsStates[sun_java2d_OSXSurfaceData_kColorStateIndex]; switch (colorState) { case sun_java2d_OSXSurfaceData_kColorSimple: { if (qsdo->graphicsStateInfo.simpleColor == NO) { setDefaultColorSpace(cgRef); } qsdo->graphicsStateInfo.simpleColor = YES; // sets the color on the CGContextRef (CGContextSetStrokeColorWithColor/CGContextSetFillColorWithColor) setCachedColor(qsdo, javaGraphicsStates[sun_java2d_OSXSurfaceData_kColorRGBValueIndex]); break; } case sun_java2d_OSXSurfaceData_kColorSystem: { qsdo->graphicsStateInfo.simpleStroke = NO; // All our custom Colors are NSPatternColorSpace so we are complex colors! qsdo->graphicsStateInfo.simpleColor = NO; NSColor *color = nil; /* TODO:BG { color = getColor(javaGraphicsStates[sun_java2d_OSXSurfaceData_kColorIndexValueIndex]); } */ [color set]; break; } case sun_java2d_OSXSurfaceData_kColorGradient: { qsdo->shadingInfo = (StateShadingInfo*)malloc(sizeof(StateShadingInfo)); if (qsdo->shadingInfo == NULL) { [JNFException raise:env as:kOutOfMemoryError reason:"Failed to malloc memory for gradient paint"]; } qsdo->graphicsStateInfo.simpleStroke = NO; qsdo->graphicsStateInfo.simpleColor = NO; renderType = SD_Shade; qsdo->shadingInfo->start.x = javaFloatGraphicsStates[sun_java2d_OSXSurfaceData_kColorx1Index]; qsdo->shadingInfo->start.y = javaFloatGraphicsStates[sun_java2d_OSXSurfaceData_kColory1Index]; qsdo->shadingInfo->end.x = javaFloatGraphicsStates[sun_java2d_OSXSurfaceData_kColorx2Index]; qsdo->shadingInfo->end.y = javaFloatGraphicsStates[sun_java2d_OSXSurfaceData_kColory2Index]; jint c1 = javaGraphicsStates[sun_java2d_OSXSurfaceData_kColorRGBValue1Index]; qsdo->shadingInfo->colors[0] = ((c1>>16)&0xff)*kColorConversionMultiplier; qsdo->shadingInfo->colors[1] = ((c1>>8)&0xff)*kColorConversionMultiplier; qsdo->shadingInfo->colors[2] = ((c1>>0)&0xff)*kColorConversionMultiplier; qsdo->shadingInfo->colors[3] = ((c1>>24)&0xff)*kColorConversionMultiplier; jint c2 = javaGraphicsStates[sun_java2d_OSXSurfaceData_kColorRGBValue2Index]; qsdo->shadingInfo->colors[4] = ((c2>>16)&0xff)*kColorConversionMultiplier; qsdo->shadingInfo->colors[5] = ((c2>>8)&0xff)*kColorConversionMultiplier; qsdo->shadingInfo->colors[6] = ((c2>>0)&0xff)*kColorConversionMultiplier; qsdo->shadingInfo->colors[7] = ((c2>>24)&0xff)*kColorConversionMultiplier; qsdo->shadingInfo->cyclic = (javaGraphicsStates[sun_java2d_OSXSurfaceData_kColorIsCyclicIndex] == sun_java2d_OSXSurfaceData_kColorCyclic); break; } case sun_java2d_OSXSurfaceData_kColorLinearGradient: { renderType = SD_LinearGradient; setupGradient(env, qsdo, javaFloatGraphicsStates); break; } case sun_java2d_OSXSurfaceData_kColorRadialGradient: { renderType = SD_RadialGradient; setupGradient(env, qsdo, javaFloatGraphicsStates); qsdo->gradientInfo->radius = javaFloatGraphicsStates[sun_java2d_OSXSurfaceData_kRadiusIndex]; break; } case sun_java2d_OSXSurfaceData_kColorTexture: { qsdo->patternInfo = (StatePatternInfo*)malloc(sizeof(StatePatternInfo)); if (qsdo->patternInfo == NULL) { [JNFException raise:env as:kOutOfMemoryError reason:"Failed to malloc memory for texture paint"]; } qsdo->graphicsStateInfo.simpleStroke = NO; qsdo->graphicsStateInfo.simpleColor = NO; renderType = SD_Pattern; qsdo->patternInfo->tx = javaFloatGraphicsStates[sun_java2d_OSXSurfaceData_kColortxIndex]; qsdo->patternInfo->ty = javaFloatGraphicsStates[sun_java2d_OSXSurfaceData_kColortyIndex]; qsdo->patternInfo->sx = javaFloatGraphicsStates[sun_java2d_OSXSurfaceData_kColorsxIndex]; if (qsdo->patternInfo->sx == 0.0f) { return SD_Fill; // 0 is an invalid value, fill argb rect } qsdo->patternInfo->sy = javaFloatGraphicsStates[sun_java2d_OSXSurfaceData_kColorsyIndex]; if (qsdo->patternInfo->sy == 0.0f) { return SD_Fill; // 0 is an invalid value, fill argb rect } qsdo->patternInfo->width = javaGraphicsStates[sun_java2d_OSXSurfaceData_kColorWidthIndex]; qsdo->patternInfo->height = javaGraphicsStates[sun_java2d_OSXSurfaceData_kColorHeightIndex]; jobject sData = ((*env)->GetObjectArrayElement(env, qsdo->javaGraphicsStatesObjects, sun_java2d_OSXSurfaceData_kTextureImageIndex)); //deleted next time through SetUpPaint and not before ( radr://3913190 ) if (sData != NULL) { qsdo->patternInfo->sdata = (*env)->NewGlobalRef(env, sData); if (qsdo->patternInfo->sdata == NULL) { renderType = SD_Fill; } } else { renderType = SD_Fill; } break; } } return renderType; } #pragma mark #pragma mark --- Shape Drawing Code --- SDRenderType DoShapeUsingCG(CGContextRef cgRef, jint *types, jfloat *coords, jint numtypes, BOOL fill, CGFloat offsetX, CGFloat offsetY) { //fprintf(stderr, "DoShapeUsingCG fill=%d\n", (jint)fill); SDRenderType renderType = SD_Nothing; if (gAdjustForJavaDrawing != YES) { offsetX = 0.0f; offsetY = 0.0f; } if (fill == YES) { renderType = SD_Fill; } else { renderType = SD_Stroke; } if (numtypes > 0) { BOOL needNewSubpath = NO; CGContextBeginPath(cgRef); // create new path //fprintf(stderr, " CGContextBeginPath\n"); jint index = 0; CGFloat mx = 0.0f, my = 0.0f, x1 = 0.0f, y1 = 0.0f, cpx1 = 0.0f, cpy1 = 0.0f, cpx2 = 0.0f, cpy2 = 0.0f; jint i; mx = (CGFloat)coords[index++] + offsetX; my = (CGFloat)coords[index++] + offsetY; CGContextMoveToPoint(cgRef, mx, my); for (i=1; irenderType) { case SD_Nothing: break; case SD_Stroke: if (CGContextIsPathEmpty(qsdo->cgRef) == 0) { CGContextStrokePath(qsdo->cgRef); } break; case SD_Fill: if (CGContextIsPathEmpty(qsdo->cgRef) == 0) { CGContextFillPath(qsdo->cgRef); } break; case SD_Shade: if (CGContextIsPathEmpty(qsdo->cgRef) == 0) { contextGradientPath(qsdo); } break; case SD_LinearGradient: if (CGContextIsPathEmpty(qsdo->cgRef) == 0) { contextQuartzLinearGradientPath(qsdo); } break; case SD_RadialGradient: if (CGContextIsPathEmpty(qsdo->cgRef) == 0) { contextQuartzRadialGradientPath(qsdo); } break; case SD_Pattern: if (CGContextIsPathEmpty(qsdo->cgRef) == 0) { contextTexturePath(env, qsdo); } break; case SD_EOFill: if (CGContextIsPathEmpty(qsdo->cgRef) == 0) { CGContextEOFillPath(qsdo->cgRef); } break; case SD_Image: break; case SD_Text: break; case SD_CopyArea: break; case SD_Queue: break; case SD_External: break; } if (qsdo->shadingInfo != NULL) { gradientPaintReleaseFunction(qsdo->shadingInfo); qsdo->shadingInfo = NULL; } if (qsdo->gradientInfo != NULL) { gradientPaintReleaseFunction(qsdo->gradientInfo); qsdo->gradientInfo = NULL; } }