blob: 131dc1e0d6fb0559b9843b907db7deddf4cc1c57 [file] [log] [blame]
/*
* 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 <JavaNativeFoundation/JavaNativeFoundation.h>
#import <AppKit/AppKit.h>
#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 <rdar://problem/3923927>
// With <rdar://problem/4280514>, the hashing function was improved
// With <rdar://problem/4012223>, 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 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 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; i<length; i++)
{
dashes[i] = (CGFloat)jdashes[i];
}
}
else
{
dashphase = 0;
length = 0;
}
CGContextSetLineDash(cgRef, dashphase, dashes, length);
if (dashes != NULL)
{
free(dashes);
}
(*env)->ReleasePrimitiveArrayCritical(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;
}
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_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; i<numtypes; i++)
{
jint pathType = types[i];
if (needNewSubpath == YES)
{
needNewSubpath = NO;
switch (pathType)
{
case java_awt_geom_PathIterator_SEG_LINETO:
case java_awt_geom_PathIterator_SEG_QUADTO:
case java_awt_geom_PathIterator_SEG_CUBICTO:
//fprintf(stderr, " forced CGContextMoveToPoint (%f, %f)\n", mx, my);
CGContextMoveToPoint(cgRef, mx, my); // force new subpath
break;
}
}
switch (pathType)
{
case java_awt_geom_PathIterator_SEG_MOVETO:
mx = x1 = (CGFloat)coords[index++] + offsetX;
my = y1 = (CGFloat)coords[index++] + offsetY;
CGContextMoveToPoint(cgRef, x1, y1); // start new subpath
//fprintf(stderr, " SEG_MOVETO CGContextMoveToPoint (%f, %f)\n", x1, y1);
break;
case java_awt_geom_PathIterator_SEG_LINETO:
x1 = (CGFloat)coords[index++] + offsetX;
y1 = (CGFloat)coords[index++] + offsetY;
CGContextAddLineToPoint(cgRef, x1, y1);
//fprintf(stderr, " SEG_LINETO CGContextAddLineToPoint (%f, %f)\n", x1, y1);
break;
case java_awt_geom_PathIterator_SEG_QUADTO:
cpx1 = (CGFloat)coords[index++] + offsetX;
cpy1 = (CGFloat)coords[index++] + offsetY;
x1 = (CGFloat)coords[index++] + offsetX;
y1 = (CGFloat)coords[index++]+ offsetY;
CGContextAddQuadCurveToPoint(cgRef, cpx1, cpy1, x1, y1);
//fprintf(stderr, " SEG_QUADTO CGContextAddQuadCurveToPoint (%f, %f), (%f, %f)\n", cpx1, cpy1, x1, y1);
break;
case java_awt_geom_PathIterator_SEG_CUBICTO:
cpx1 = (CGFloat)coords[index++] + offsetX;
cpy1 = (CGFloat)coords[index++] + offsetY;
cpx2 = (CGFloat)coords[index++] + offsetX;
cpy2 = (CGFloat)coords[index++] + offsetY;
x1 = (CGFloat)coords[index++] + offsetX;
y1 = (CGFloat)coords[index++] + offsetY;
CGContextAddCurveToPoint(cgRef, cpx1, cpy1, cpx2, cpy2, x1, y1);
//fprintf(stderr, " SEG_CUBICTO CGContextAddCurveToPoint (%f, %f), (%f, %f), (%f, %f)\n", cpx1, cpy1, cpx2, cpy2, x1, y1);
break;
case java_awt_geom_PathIterator_SEG_CLOSE:
CGContextClosePath(cgRef); // close subpath
needNewSubpath = YES;
//fprintf(stderr, " SEG_CLOSE CGContextClosePath\n");
break;
}
}
}
return renderType;
}
void CompleteCGContext(JNIEnv *env, QuartzSDOps *qsdo)
{
PRINT(" CompleteCGContext")
switch (qsdo->renderType)
{
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_Pattern:
if (CGContextIsPathEmpty(qsdo->cgRef) == 0)
{
//TODO:BG
//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;
}
}