blob: e8580273955285841e1d5767082add32cc4fe7a7 [file] [log] [blame]
/*
* Copyright (c) 1998, 2007, 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.
*/
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "jni.h"
#include "jni_util.h"
#include <jlong.h>
#include "j2d_md.h"
#include "PathConsumer2D.h"
#include "SpanIterator.h"
#include "sun_java2d_pipe_ShapeSpanIterator.h"
#include "java_awt_geom_PathIterator.h"
/*
* This structure holds all of the information needed to trace and
* manage a single line segment of the shape's outline.
*/
typedef struct {
jint curx;
jint cury;
jint lasty;
jint error;
jint bumpx;
jint bumperr;
jbyte windDir;
jbyte pad0;
jbyte pad1;
jbyte pad2;
} segmentData;
/*
* This structure holds all of the information needed to trace out
* the entire span list of a single Shape object.
*/
typedef struct {
PathConsumerVec funcs; /* Native PathConsumer function vector */
char state; /* Path delivery sequence state */
char evenodd; /* non-zero if path has EvenOdd winding rule */
char first; /* non-zero if first path segment */
char adjust; /* normalize to nearest (0.25, 0.25) */
jint lox; /* clip bbox low X */
jint loy; /* clip bbox low Y */
jint hix; /* clip bbox high X */
jint hiy; /* clip bbox high Y */
jfloat curx; /* current path point X coordinate */
jfloat cury; /* current path point Y coordinate */
jfloat movx; /* last moveto X coordinate */
jfloat movy; /* last moveto Y coordinate */
jfloat adjx; /* last X coordinate adjustment */
jfloat adjy; /* last Y coordinate adjustment */
jfloat pathlox; /* lowest X coordinate in path */
jfloat pathloy; /* lowest Y coordinate in path */
jfloat pathhix; /* highest X coordinate in path */
jfloat pathhiy; /* highest Y coordinate in path */
segmentData *segments; /* pointer to array of path segments */
int numSegments; /* number of segments entries in array */
int segmentsSize; /* size of array of path segments */
int lowSegment; /* lower limit of segments in active range */
int curSegment; /* index of next active segment to return */
int hiSegment; /* upper limit of segments in active range */
segmentData **segmentTable; /* pointers to segments being stepped */
} pathData;
#define STATE_INIT 0
#define STATE_HAVE_CLIP 1
#define STATE_HAVE_RULE 2
#define STATE_PATH_DONE 3
#define STATE_SPAN_STARTED 4
static jboolean subdivideLine(pathData *pd, int level,
jfloat x0, jfloat y0,
jfloat x1, jfloat y1);
static jboolean subdivideQuad(pathData *pd, int level,
jfloat x0, jfloat y0,
jfloat x1, jfloat y1,
jfloat x2, jfloat y2);
static jboolean subdivideCubic(pathData *pd, int level,
jfloat x0, jfloat y0,
jfloat x1, jfloat y1,
jfloat x2, jfloat y2,
jfloat x3, jfloat y3);
static jboolean appendSegment(pathData *pd,
jfloat x0, jfloat y0,
jfloat x1, jfloat y1);
static jboolean initSegmentTable(pathData *pd);
static void *ShapeSIOpen(JNIEnv *env, jobject iterator);
static void ShapeSIClose(JNIEnv *env, void *private);
static void ShapeSIGetPathBox(JNIEnv *env, void *private, jint pathbox[]);
static void ShapeSIIntersectClipBox(JNIEnv *env, void *private,
jint lox, jint loy, jint hix, jint hiy);
static jboolean ShapeSINextSpan(void *state, jint spanbox[]);
static void ShapeSISkipDownTo(void *private, jint y);
static jfieldID pSpanDataID;
static SpanIteratorFuncs ShapeSIFuncs = {
ShapeSIOpen,
ShapeSIClose,
ShapeSIGetPathBox,
ShapeSIIntersectClipBox,
ShapeSINextSpan,
ShapeSISkipDownTo
};
static LineToFunc PCLineTo;
static MoveToFunc PCMoveTo;
static QuadToFunc PCQuadTo;
static CubicToFunc PCCubicTo;
static ClosePathFunc PCClosePath;
static PathDoneFunc PCPathDone;
#define PDBOXPOINT(pd, x, y) \
do { \
if (pd->first) { \
pd->pathlox = pd->pathhix = x; \
pd->pathloy = pd->pathhiy = y; \
pd->first = 0; \
} else { \
if (pd->pathlox > x) pd->pathlox = x; \
if (pd->pathloy > y) pd->pathloy = y; \
if (pd->pathhix < x) pd->pathhix = x; \
if (pd->pathhiy < y) pd->pathhiy = y; \
} \
} while (0)
/*
* _ADJUST is the internal macro used to adjust a new endpoint
* and then invoke the "additional" code from the callers below
* which will adjust the control points as needed to match.
*
* When the "additional" code is executed, newadj[xy] will
* contain the adjustment applied to the new endpoint and
* pd->adj[xy] will still contain the previous adjustment
* that was applied to the old endpoint.
*/
#define _ADJUST(pd, x, y, additional) \
do { \
if (pd->adjust) { \
jfloat newx = (jfloat) floor(x + 0.25f) + 0.25f; \
jfloat newy = (jfloat) floor(y + 0.25f) + 0.25f; \
jfloat newadjx = newx - x; \
jfloat newadjy = newy - y; \
x = newx; \
y = newy; \
additional; \
pd->adjx = newadjx; \
pd->adjy = newadjy; \
} \
} while (0)
/*
* Adjust a single endpoint with no control points.
* "additional" code is a null statement.
*/
#define ADJUST1(pd, x1, y1) \
_ADJUST(pd, x1, y1, \
do { \
} while (0))
/*
* Adjust a quadratic curve. The _ADJUST macro takes care
* of the new endpoint and the "additional" code adjusts
* the single quadratic control point by the averge of
* the prior and the new adjustment amounts.
*/
#define ADJUST2(pd, x1, y1, x2, y2) \
_ADJUST(pd, x2, y2, \
do { \
x1 += (pd->adjx + newadjy) / 2; \
y1 += (pd->adjy + newadjy) / 2; \
} while (0))
/*
* Adjust a cubic curve. The _ADJUST macro takes care
* of the new endpoint and the "additional" code adjusts
* the first of the two cubic control points by the same
* amount that the prior endpoint was adjusted and then
* adjusts the second of the two control points by the
* same amount as the new endpoint was adjusted. This
* keeps the tangent lines from xy0 to xy1 and xy3 to xy2
* parallel before and after the adjustment.
*/
#define ADJUST3(pd, x1, y1, x2, y2, x3, y3) \
_ADJUST(pd, x3, y3, \
do { \
x1 += pd->adjx; \
y1 += pd->adjy; \
x2 += newadjx; \
y2 += newadjy; \
} while (0))
#define HANDLEMOVETO(pd, x0, y0, OOMERR) \
do { \
HANDLECLOSE(pd, OOMERR); \
ADJUST1(pd, x0, y0); \
pd->movx = x0; \
pd->movy = y0; \
PDBOXPOINT(pd, x0, y0); \
pd->curx = x0; \
pd->cury = y0; \
} while (0)
#define HANDLELINETO(pd, x1, y1, OOMERR) \
do { \
ADJUST1(pd, x1, y1); \
if (!subdivideLine(pd, 0, \
pd->curx, pd->cury, \
x1, y1)) { \
OOMERR; \
break; \
} \
PDBOXPOINT(pd, x1, y1); \
pd->curx = x1; \
pd->cury = y1; \
} while (0)
#define HANDLEQUADTO(pd, x1, y1, x2, y2, OOMERR) \
do { \
ADJUST2(pd, x1, y1, x2, y2); \
if (!subdivideQuad(pd, 0, \
pd->curx, pd->cury, \
x1, y1, x2, y2)) { \
OOMERR; \
break; \
} \
PDBOXPOINT(pd, x1, y1); \
PDBOXPOINT(pd, x2, y2); \
pd->curx = x2; \
pd->cury = y2; \
} while (0)
#define HANDLECUBICTO(pd, x1, y1, x2, y2, x3, y3, OOMERR) \
do { \
ADJUST3(pd, x1, y1, x2, y2, x3, y3); \
if (!subdivideCubic(pd, 0, \
pd->curx, pd->cury, \
x1, y1, x2, y2, x3, y3)) { \
OOMERR; \
break; \
} \
PDBOXPOINT(pd, x1, y1); \
PDBOXPOINT(pd, x2, y2); \
PDBOXPOINT(pd, x3, y3); \
pd->curx = x3; \
pd->cury = y3; \
} while (0)
#define HANDLECLOSE(pd, OOMERR) \
do { \
if (pd->curx != pd->movx || pd->cury != pd->movy) { \
if (!subdivideLine(pd, 0, \
pd->curx, pd->cury, \
pd->movx, pd->movy)) { \
OOMERR; \
break; \
} \
pd->curx = pd->movx; \
pd->cury = pd->movy; \
} \
} while (0)
#define HANDLEENDPATH(pd, OOMERR) \
do { \
HANDLECLOSE(pd, OOMERR); \
pd->state = STATE_PATH_DONE; \
} while (0)
static pathData *
GetSpanData(JNIEnv *env, jobject sr, int minState, int maxState)
{
pathData *pd = (pathData *) JNU_GetLongFieldAsPtr(env, sr, pSpanDataID);
if (pd == NULL) {
JNU_ThrowNullPointerException(env, "private data");
} else if (pd->state < minState || pd->state > maxState) {
JNU_ThrowInternalError(env, "bad path delivery sequence");
pd = NULL;
}
return pd;
}
static pathData *
MakeSpanData(JNIEnv *env, jobject sr)
{
pathData *pd = (pathData *) JNU_GetLongFieldAsPtr(env, sr, pSpanDataID);
if (pd != NULL) {
JNU_ThrowInternalError(env, "private data already initialized");
return NULL;
}
pd = calloc(1, sizeof(pathData));
if (pd == NULL) {
JNU_ThrowOutOfMemoryError(env, "private data");
} else {
/* Initialize PathConsumer2D struct header */
pd->funcs.moveTo = PCMoveTo;
pd->funcs.lineTo = PCLineTo;
pd->funcs.quadTo = PCQuadTo;
pd->funcs.cubicTo = PCCubicTo;
pd->funcs.closePath = PCClosePath;
pd->funcs.pathDone = PCPathDone;
/* Initialize ShapeSpanIterator fields */
pd->first = 1;
(*env)->SetLongField(env, sr, pSpanDataID, ptr_to_jlong(pd));
}
return pd;
}
JNIEXPORT void JNICALL
Java_sun_java2d_pipe_ShapeSpanIterator_initIDs
(JNIEnv *env, jclass src)
{
pSpanDataID = (*env)->GetFieldID(env, src, "pData", "J");
}
/*
* Class: sun_java2d_pipe_ShapeSpanIterator
* Method: setNormalize
* Signature: (Z)V
*/
JNIEXPORT void JNICALL
Java_sun_java2d_pipe_ShapeSpanIterator_setNormalize
(JNIEnv *env, jobject sr, jboolean adjust)
{
pathData *pd;
pd = MakeSpanData(env, sr);
if (pd == NULL) {
return;
}
pd->adjust = adjust;
}
JNIEXPORT void JNICALL
Java_sun_java2d_pipe_ShapeSpanIterator_setOutputAreaXYXY
(JNIEnv *env, jobject sr, jint lox, jint loy, jint hix, jint hiy)
{
pathData *pd;
pd = GetSpanData(env, sr, STATE_INIT, STATE_INIT);
if (pd == NULL) {
return;
}
pd->lox = lox;
pd->loy = loy;
pd->hix = hix;
pd->hiy = hiy;
pd->state = STATE_HAVE_CLIP;
}
JNIEXPORT void JNICALL
Java_sun_java2d_pipe_ShapeSpanIterator_setRule
(JNIEnv *env, jobject sr, jint rule)
{
pathData *pd;
pd = GetSpanData(env, sr, STATE_HAVE_CLIP, STATE_HAVE_CLIP);
if (pd == NULL) {
return;
}
pd->evenodd = (rule == java_awt_geom_PathIterator_WIND_EVEN_ODD);
pd->state = STATE_HAVE_RULE;
}
JNIEXPORT void JNICALL
Java_sun_java2d_pipe_ShapeSpanIterator_addSegment
(JNIEnv *env, jobject sr, jint type, jfloatArray coordObj)
{
jfloat coords[6];
jfloat x1, y1, x2, y2, x3, y3;
jboolean oom = JNI_FALSE;
pathData *pd;
int numpts = 0;
pd = GetSpanData(env, sr, STATE_HAVE_RULE, STATE_HAVE_RULE);
if (pd == NULL) {
return;
}
(*env)->GetFloatArrayRegion(env, coordObj, 0, 6, coords);
if ((*env)->ExceptionCheck(env)) {
return;
}
switch (type) {
case java_awt_geom_PathIterator_SEG_MOVETO:
x1 = coords[0]; y1 = coords[1];
HANDLEMOVETO(pd, x1, y1, {oom = JNI_TRUE;});
break;
case java_awt_geom_PathIterator_SEG_LINETO:
x1 = coords[0]; y1 = coords[1];
HANDLELINETO(pd, x1, y1, {oom = JNI_TRUE;});
break;
case java_awt_geom_PathIterator_SEG_QUADTO:
x1 = coords[0]; y1 = coords[1];
x2 = coords[2]; y2 = coords[3];
HANDLEQUADTO(pd, x1, y1, x2, y2, {oom = JNI_TRUE;});
break;
case java_awt_geom_PathIterator_SEG_CUBICTO:
x1 = coords[0]; y1 = coords[1];
x2 = coords[2]; y2 = coords[3];
x3 = coords[4]; y3 = coords[5];
HANDLECUBICTO(pd, x1, y1, x2, y2, x3, y3, {oom = JNI_TRUE;});
break;
case java_awt_geom_PathIterator_SEG_CLOSE:
HANDLECLOSE(pd, {oom = JNI_TRUE;});
break;
default:
JNU_ThrowInternalError(env, "bad path segment type");
return;
}
if (oom) {
JNU_ThrowOutOfMemoryError(env, "path segment data");
return;
}
}
JNIEXPORT void JNICALL
Java_sun_java2d_pipe_ShapeSpanIterator_getPathBox
(JNIEnv *env, jobject sr, jintArray spanbox)
{
pathData *pd;
jint coords[4];
pd = GetSpanData(env, sr, STATE_PATH_DONE, STATE_PATH_DONE);
if (pd == NULL) {
return;
}
ShapeSIGetPathBox(env, pd, coords);
(*env)->SetIntArrayRegion(env, spanbox, 0, 4, coords);
}
JNIEXPORT void JNICALL
Java_sun_java2d_pipe_ShapeSpanIterator_intersectClipBox
(JNIEnv *env, jobject ri, jint clox, jint cloy, jint chix, jint chiy)
{
pathData *pd;
pd = GetSpanData(env, ri, STATE_PATH_DONE, STATE_PATH_DONE);
if (pd == NULL) {
return;
}
ShapeSIIntersectClipBox(env, pd, clox, cloy, chix, chiy);
}
JNIEXPORT jboolean JNICALL
Java_sun_java2d_pipe_ShapeSpanIterator_nextSpan
(JNIEnv *env, jobject sr, jintArray spanbox)
{
pathData *pd;
jboolean ret;
jint coords[4];
pd = GetSpanData(env, sr, STATE_PATH_DONE, STATE_SPAN_STARTED);
if (pd == NULL) {
return JNI_FALSE;
}
ret = ShapeSINextSpan(pd, coords);
if (ret) {
(*env)->SetIntArrayRegion(env, spanbox, 0, 4, coords);
}
return ret;
}
JNIEXPORT void JNICALL
Java_sun_java2d_pipe_ShapeSpanIterator_skipDownTo
(JNIEnv *env, jobject sr, jint y)
{
pathData *pd;
pd = GetSpanData(env, sr, STATE_PATH_DONE, STATE_SPAN_STARTED);
if (pd == NULL) {
return;
}
ShapeSISkipDownTo(pd, y);
}
JNIEXPORT jlong JNICALL
Java_sun_java2d_pipe_ShapeSpanIterator_getNativeIterator
(JNIEnv *env, jobject sr)
{
return ptr_to_jlong(&ShapeSIFuncs);
}
JNIEXPORT void JNICALL
Java_sun_java2d_pipe_ShapeSpanIterator_dispose
(JNIEnv *env, jobject sr)
{
pathData *pd = (pathData *) JNU_GetLongFieldAsPtr(env, sr, pSpanDataID);
if (pd == NULL) {
return;
}
if (pd->segments != NULL) {
free(pd->segments);
}
if (pd->segmentTable != NULL) {
free(pd->segmentTable);
}
free(pd);
(*env)->SetLongField(env, sr, pSpanDataID, jlong_zero);
}
#define OUT_XLO 1
#define OUT_XHI 2
#define OUT_YLO 4
#define OUT_YHI 8
#define CALCULATE_OUTCODES(pd, outc, x, y) \
do { \
if (y <= pd->loy) outc = OUT_YLO; \
else if (y >= pd->hiy) outc = OUT_YHI; \
else outc = 0; \
if (x <= pd->lox) outc |= OUT_XLO; \
else if (x >= pd->hix) outc |= OUT_XHI; \
} while (0)
JNIEXPORT void JNICALL
Java_sun_java2d_pipe_ShapeSpanIterator_appendPoly
(JNIEnv *env, jobject sr,
jintArray xArray, jintArray yArray, jint nPoints,
jint ixoff, jint iyoff)
{
pathData *pd;
int i;
jint *xPoints, *yPoints;
jboolean oom = JNI_FALSE;
jfloat xoff = (jfloat) ixoff, yoff = (jfloat) iyoff;
pd = GetSpanData(env, sr, STATE_HAVE_CLIP, STATE_HAVE_CLIP);
if (pd == NULL) {
return;
}
pd->evenodd = JNI_TRUE;
pd->state = STATE_HAVE_RULE;
if (pd->adjust) {
xoff += 0.25f;
yoff += 0.25f;
}
if (xArray == NULL || yArray == NULL) {
JNU_ThrowNullPointerException(env, "polygon data arrays");
return;
}
if ((*env)->GetArrayLength(env, xArray) < nPoints ||
(*env)->GetArrayLength(env, yArray) < nPoints)
{
JNU_ThrowArrayIndexOutOfBoundsException(env, "polygon data arrays");
return;
}
if (nPoints > 0) {
xPoints = (*env)->GetPrimitiveArrayCritical(env, xArray, NULL);
if (xPoints != NULL) {
yPoints = (*env)->GetPrimitiveArrayCritical(env, yArray, NULL);
if (yPoints != NULL) {
jint outc0;
jfloat x, y;
x = xPoints[0] + xoff;
y = yPoints[0] + yoff;
CALCULATE_OUTCODES(pd, outc0, x, y);
pd->movx = pd->curx = x;
pd->movy = pd->cury = y;
pd->pathlox = pd->pathhix = x;
pd->pathloy = pd->pathhiy = y;
pd->first = 0;
for (i = 1; !oom && i < nPoints; i++) {
jint outc1;
x = xPoints[i] + xoff;
y = yPoints[i] + yoff;
if (y == pd->cury) {
/* Horizontal segment - do not append */
if (x != pd->curx) {
/* Not empty segment - track change in X */
CALCULATE_OUTCODES(pd, outc0, x, y);
pd->curx = x;
if (pd->pathlox > x) pd->pathlox = x;
if (pd->pathhix < x) pd->pathhix = x;
}
continue;
}
CALCULATE_OUTCODES(pd, outc1, x, y);
outc0 &= outc1;
if (outc0 == 0) {
oom = !appendSegment(pd, pd->curx, pd->cury, x, y);
} else if (outc0 == OUT_XLO) {
oom = !appendSegment(pd, (jfloat) pd->lox, pd->cury,
(jfloat) pd->lox, y);
}
if (pd->pathlox > x) pd->pathlox = x;
if (pd->pathloy > y) pd->pathloy = y;
if (pd->pathhix < x) pd->pathhix = x;
if (pd->pathhiy < y) pd->pathhiy = y;
outc0 = outc1;
pd->curx = x;
pd->cury = y;
}
}
(*env)->ReleasePrimitiveArrayCritical(env, yArray,
yPoints, JNI_ABORT);
}
(*env)->ReleasePrimitiveArrayCritical(env, xArray,
xPoints, JNI_ABORT);
}
if (!oom) {
HANDLEENDPATH(pd, {oom = JNI_TRUE;});
}
if (oom) {
JNU_ThrowOutOfMemoryError(env, "path segment data");
}
}
JNIEXPORT void JNICALL
Java_sun_java2d_pipe_ShapeSpanIterator_moveTo
(JNIEnv *env, jobject sr, jfloat x0, jfloat y0)
{
pathData *pd;
pd = GetSpanData(env, sr, STATE_HAVE_RULE, STATE_HAVE_RULE);
if (pd == NULL) {
return;
}
HANDLEMOVETO(pd, x0, y0,
{JNU_ThrowOutOfMemoryError(env, "path segment data");});
}
JNIEXPORT void JNICALL
Java_sun_java2d_pipe_ShapeSpanIterator_lineTo
(JNIEnv *env, jobject sr, jfloat x1, jfloat y1)
{
pathData *pd;
pd = GetSpanData(env, sr, STATE_HAVE_RULE, STATE_HAVE_RULE);
if (pd == NULL) {
return;
}
HANDLELINETO(pd, x1, y1,
{JNU_ThrowOutOfMemoryError(env, "path segment data");});
}
JNIEXPORT void JNICALL
Java_sun_java2d_pipe_ShapeSpanIterator_quadTo
(JNIEnv *env, jobject sr,
jfloat xm, jfloat ym, jfloat x1, jfloat y1)
{
pathData *pd;
pd = GetSpanData(env, sr, STATE_HAVE_RULE, STATE_HAVE_RULE);
if (pd == NULL) {
return;
}
HANDLEQUADTO(pd, xm, ym, x1, y1,
{JNU_ThrowOutOfMemoryError(env, "path segment data");});
}
JNIEXPORT void JNICALL
Java_sun_java2d_pipe_ShapeSpanIterator_curveTo
(JNIEnv *env, jobject sr,
jfloat xm, jfloat ym,
jfloat xn, jfloat yn,
jfloat x1, jfloat y1)
{
pathData *pd;
pd = GetSpanData(env, sr, STATE_HAVE_RULE, STATE_HAVE_RULE);
if (pd == NULL) {
return;
}
HANDLECUBICTO(pd, xm, ym, xn, yn, x1, y1,
{JNU_ThrowOutOfMemoryError(env, "path segment data");});
}
JNIEXPORT void JNICALL
Java_sun_java2d_pipe_ShapeSpanIterator_closePath
(JNIEnv *env, jobject sr)
{
pathData *pd;
pd = GetSpanData(env, sr, STATE_HAVE_RULE, STATE_HAVE_RULE);
if (pd == NULL) {
return;
}
HANDLECLOSE(pd, {JNU_ThrowOutOfMemoryError(env, "path segment data");});
}
JNIEXPORT void JNICALL
Java_sun_java2d_pipe_ShapeSpanIterator_pathDone
(JNIEnv *env, jobject sr)
{
pathData *pd;
pd = GetSpanData(env, sr, STATE_HAVE_RULE, STATE_HAVE_RULE);
if (pd == NULL) {
return;
}
HANDLEENDPATH(pd, {JNU_ThrowOutOfMemoryError(env, "path segment data");});
}
JNIEXPORT jlong JNICALL
Java_sun_java2d_pipe_ShapeSpanIterator_getNativeConsumer
(JNIEnv *env, jobject sr)
{
pathData *pd = GetSpanData(env, sr, STATE_HAVE_RULE, STATE_HAVE_RULE);
if (pd == NULL) {
return jlong_zero;
}
return ptr_to_jlong(&(pd->funcs));
}
static jboolean
PCMoveTo(PathConsumerVec *consumer,
jfloat x0, jfloat y0)
{
pathData *pd = (pathData *) consumer;
jboolean oom = JNI_FALSE;
HANDLEMOVETO(pd, x0, y0, {oom = JNI_TRUE;});
return oom;
}
static jboolean
PCLineTo(PathConsumerVec *consumer,
jfloat x1, jfloat y1)
{
pathData *pd = (pathData *) consumer;
jboolean oom = JNI_FALSE;
HANDLELINETO(pd, x1, y1, {oom = JNI_TRUE;});
return oom;
}
static jboolean
PCQuadTo(PathConsumerVec *consumer,
jfloat x1, jfloat y1,
jfloat x2, jfloat y2)
{
pathData *pd = (pathData *) consumer;
jboolean oom = JNI_FALSE;
HANDLEQUADTO(pd, x1, y1, x2, y2, {oom = JNI_TRUE;});
return oom;
}
static jboolean
PCCubicTo(PathConsumerVec *consumer,
jfloat x1, jfloat y1,
jfloat x2, jfloat y2,
jfloat x3, jfloat y3)
{
pathData *pd = (pathData *) consumer;
jboolean oom = JNI_FALSE;
HANDLECUBICTO(pd, x1, y1, x2, y2, x3, y3, {oom = JNI_TRUE;});
return oom;
}
static jboolean
PCClosePath(PathConsumerVec *consumer)
{
pathData *pd = (pathData *) consumer;
jboolean oom = JNI_FALSE;
HANDLECLOSE(pd, {oom = JNI_TRUE;});
return oom;
}
static jboolean
PCPathDone(PathConsumerVec *consumer)
{
pathData *pd = (pathData *) consumer;
jboolean oom = JNI_FALSE;
HANDLEENDPATH(pd, {oom = JNI_TRUE;});
return oom;
}
/*
* REMIND: CDECL needed for WIN32 "qsort"
*/
#ifdef _WIN32
#define CDECL __cdecl
#else
#define CDECL
#endif
#define SUBDIVIDE_MAX 10
#define MAX_FLAT_SQ (1.0 * 1.0)
#define GROW_SIZE 20
#define ERRSTEP_MAX (0x7fffffff)
#define FRACTTOJINT(f) ((jint) ((f) * (double) ERRSTEP_MAX))
#define minmax2(v1, v2, min, max) \
do { \
if (v1 < v2) { \
min = v1; \
max = v2; \
} else { \
min = v2; \
max = v1; \
} \
} while(0)
#define minmax3(v1, v2, v3, min, max) \
do { \
if (v1 < v2) { \
if (v1 < v3) { \
min = v1; \
max = (v2 < v3) ? v3 : v2; \
} else { \
max = v2; \
min = v3; \
} \
} else { \
if (v1 < v3) { \
max = v3; \
min = v2; \
} else { \
max = v1; \
min = (v2 < v3) ? v2 : v3; \
} \
} \
} while (0)
#define minmax4(v1, v2, v3, v4, min, max) \
do { \
if (v1 < v2) { \
if (v3 < v4) { \
max = (v2 < v4) ? v4 : v2; \
min = (v1 < v3) ? v1 : v3; \
} else { \
max = (v2 < v3) ? v3 : v2; \
min = (v1 < v4) ? v1 : v4; \
} \
} else { \
if (v3 < v4) { \
max = (v1 < v4) ? v4 : v1; \
min = (v2 < v3) ? v2 : v3; \
} else { \
max = (v1 < v3) ? v3 : v1; \
min = (v2 < v4) ? v2 : v4; \
} \
} \
} while(0)
static jfloat
ptSegDistSq(jfloat x0, jfloat y0,
jfloat x1, jfloat y1,
jfloat px, jfloat py)
{
jfloat dotprod, projlenSq;
/* Adjust vectors relative to x0,y0 */
/* x1,y1 becomes relative vector from x0,y0 to end of segment */
x1 -= x0;
y1 -= y0;
/* px,py becomes relative vector from x0,y0 to test point */
px -= x0;
py -= y0;
dotprod = px * x1 + py * y1;
if (dotprod <= 0.0) {
/* px,py is on the side of x0,y0 away from x1,y1 */
/* distance to segment is length of px,py vector */
/* "length of its (clipped) projection" is now 0.0 */
projlenSq = 0.0;
} else {
/* switch to backwards vectors relative to x1,y1 */
/* x1,y1 are already the negative of x0,y0=>x1,y1 */
/* to get px,py to be the negative of px,py=>x1,y1 */
/* the dot product of two negated vectors is the same */
/* as the dot product of the two normal vectors */
px = x1 - px;
py = y1 - py;
dotprod = px * x1 + py * y1;
if (dotprod <= 0.0) {
/* px,py is on the side of x1,y1 away from x0,y0 */
/* distance to segment is length of (backwards) px,py vector */
/* "length of its (clipped) projection" is now 0.0 */
projlenSq = 0.0;
} else {
/* px,py is between x0,y0 and x1,y1 */
/* dotprod is the length of the px,py vector */
/* projected on the x1,y1=>x0,y0 vector times the */
/* length of the x1,y1=>x0,y0 vector */
projlenSq = dotprod * dotprod / (x1 * x1 + y1 * y1);
}
}
/* Distance to line is now the length of the relative point */
/* vector minus the length of its projection onto the line */
/* (which is zero if the projection falls outside the range */
/* of the line segment). */
return px * px + py * py - projlenSq;
}
static jboolean
appendSegment(pathData *pd,
jfloat x0, jfloat y0,
jfloat x1, jfloat y1)
{
jbyte windDir;
jint istartx, istarty, ilasty;
jfloat dx, dy, slope;
jfloat ystartbump;
jint bumpx, bumperr, error;
segmentData *seg;
if (y0 > y1) {
jfloat t;
t = x0; x0 = x1; x1 = t;
t = y0; y0 = y1; y1 = t;
windDir = -1;
} else {
windDir = 1;
}
/* We want to iterate at every horizontal pixel center (HPC) crossing. */
/* First calculate next highest HPC we will cross at the start. */
istarty = (jint) ceil(y0 - 0.5f);
/* Then calculate next highest HPC we would cross at the end. */
ilasty = (jint) ceil(y1 - 0.5f);
/* Ignore if we start and end outside clip, or on the same scanline. */
if (istarty >= ilasty || istarty >= pd->hiy || ilasty <= pd->loy) {
return JNI_TRUE;
}
/* We will need to insert this segment, check for room. */
if (pd->numSegments >= pd->segmentsSize) {
segmentData *newSegs;
int newSize = pd->segmentsSize + GROW_SIZE;
newSegs = (segmentData *) calloc(newSize, sizeof(segmentData));
if (newSegs == NULL) {
return JNI_FALSE;
}
if (pd->segments != NULL) {
memcpy(newSegs, pd->segments,
sizeof(segmentData) * pd->segmentsSize);
free(pd->segments);
}
pd->segments = newSegs;
pd->segmentsSize = newSize;
}
dx = x1 - x0;
dy = y1 - y0;
slope = dx / dy;
/*
* The Y coordinate of the first HPC was calculated as istarty. We
* now need to calculate the corresponding X coordinate (both integer
* version for span start coordinate and float version for sub-pixel
* error calculation).
*/
/* First, how far does y bump to get to next HPC? */
ystartbump = istarty + 0.5f - y0;
/* Now, bump the float x coordinate to get X sample at that HPC. */
x0 += ystartbump * dx / dy;
/* Now calculate the integer coordinate that such a span starts at. */
/* NOTE: Span inclusion is based on vertical pixel centers (VPC). */
istartx = (jint) ceil(x0 - 0.5f);
/* What is the lower bound of the per-scanline change in the X coord? */
bumpx = (jint) floor(slope);
/* What is the subpixel amount by which the bumpx is off? */
bumperr = FRACTTOJINT(slope - floor(slope));
/* Finally, find out how far the x coordinate can go before next VPC. */
error = FRACTTOJINT(x0 - (istartx - 0.5f));
seg = &pd->segments[pd->numSegments++];
seg->curx = istartx;
seg->cury = istarty;
seg->lasty = ilasty;
seg->error = error;
seg->bumpx = bumpx;
seg->bumperr = bumperr;
seg->windDir = windDir;
return JNI_TRUE;
}
/*
* Lines don't really need to be subdivided, but this function performs
* the same trivial rejections and reductions that the curve subdivision
* functions perform before it hands the coordinates off to the appendSegment
* function.
*/
static jboolean
subdivideLine(pathData *pd, int level,
jfloat x0, jfloat y0,
jfloat x1, jfloat y1)
{
jfloat miny, maxy;
jfloat minx, maxx;
minmax2(x0, x1, minx, maxx);
minmax2(y0, y1, miny, maxy);
if (maxy <= pd->loy || miny >= pd->hiy || minx >= pd->hix) {
return JNI_TRUE;
}
if (maxx <= pd->lox) {
return appendSegment(pd, maxx, y0, maxx, y1);
}
return appendSegment(pd, x0, y0, x1, y1);
}
static jboolean
subdivideQuad(pathData *pd, int level,
jfloat x0, jfloat y0,
jfloat x1, jfloat y1,
jfloat x2, jfloat y2)
{
jfloat miny, maxy;
jfloat minx, maxx;
minmax3(x0, x1, x2, minx, maxx);
minmax3(y0, y1, y2, miny, maxy);
if (maxy <= pd->loy || miny >= pd->hiy || minx >= pd->hix) {
return JNI_TRUE;
}
if (maxx <= pd->lox) {
return appendSegment(pd, maxx, y0, maxx, y2);
}
if (level < SUBDIVIDE_MAX) {
/* Test if the curve is flat enough for insertion. */
if (ptSegDistSq(x0, y0, x2, y2, x1, y1) > MAX_FLAT_SQ) {
jfloat cx1, cx2;
jfloat cy1, cy2;
cx1 = (x0 + x1) / 2.0f;
cx2 = (x1 + x2) / 2.0f;
x1 = (cx1 + cx2) / 2.0f;
cy1 = (y0 + y1) / 2.0f;
cy2 = (y1 + y2) / 2.0f;
y1 = (cy1 + cy2) / 2.0f;
level++;
return (subdivideQuad(pd, level, x0, y0, cx1, cy1, x1, y1) &&
subdivideQuad(pd, level, x1, y1, cx2, cy2, x2, y2));
}
}
return appendSegment(pd, x0, y0, x2, y2);
}
static jboolean
subdivideCubic(pathData *pd, int level,
jfloat x0, jfloat y0,
jfloat x1, jfloat y1,
jfloat x2, jfloat y2,
jfloat x3, jfloat y3)
{
jfloat miny, maxy;
jfloat minx, maxx;
minmax4(x0, x1, x2, x3, minx, maxx);
minmax4(y0, y1, y2, y3, miny, maxy);
if (maxy <= pd->loy || miny >= pd->hiy || minx >= pd->hix) {
return JNI_TRUE;
}
if (maxx <= pd->lox) {
return appendSegment(pd, maxx, y0, maxx, y3);
}
if (level < SUBDIVIDE_MAX) {
/* Test if the curve is flat enough for insertion. */
if (ptSegDistSq(x0, y0, x3, y3, x1, y1) > MAX_FLAT_SQ ||
ptSegDistSq(x0, y0, x3, y3, x2, y2) > MAX_FLAT_SQ)
{
jfloat ctrx, cx12, cx21;
jfloat ctry, cy12, cy21;
ctrx = (x1 + x2) / 2.0f;
x1 = (x0 + x1) / 2.0f;
x2 = (x2 + x3) / 2.0f;
cx12 = (x1 + ctrx) / 2.0f;
cx21 = (ctrx + x2) / 2.0f;
ctrx = (cx12 + cx21) / 2.0f;
ctry = (y1 + y2) / 2.0f;
y1 = (y0 + y1) / 2.0f;
y2 = (y2 + y3) / 2.0f;
cy12 = (y1 + ctry) / 2.0f;
cy21 = (ctry + y2) / 2.0f;
ctry = (cy12 + cy21) / 2.0f;
level++;
return (subdivideCubic(pd, level, x0, y0, x1, y1,
cx12, cy12, ctrx, ctry) &&
subdivideCubic(pd, level, ctrx, ctry, cx21, cy21,
x2, y2, x3, y3));
}
}
return appendSegment(pd, x0, y0, x3, y3);
}
static int CDECL
sortSegmentsByLeadingY(const void *elem1, const void *elem2)
{
segmentData *seg1 = *(segmentData **)elem1;
segmentData *seg2 = *(segmentData **)elem2;
if (seg1->cury < seg2->cury) {
return -1;
}
if (seg1->cury > seg2->cury) {
return 1;
}
if (seg1->curx < seg2->curx) {
return -1;
}
if (seg1->curx > seg2->curx) {
return 1;
}
if (seg1->lasty < seg2->lasty) {
return -1;
}
if (seg1->lasty > seg2->lasty) {
return 1;
}
return 0;
}
static void *
ShapeSIOpen(JNIEnv *env, jobject iterator)
{
return GetSpanData(env, iterator, STATE_PATH_DONE, STATE_PATH_DONE);
}
static void
ShapeSIClose(JNIEnv *env, void *private)
{
}
static void
ShapeSIGetPathBox(JNIEnv *env, void *private, jint pathbox[])
{
pathData *pd = (pathData *)private;
pathbox[0] = (jint) floor(pd->pathlox);
pathbox[1] = (jint) floor(pd->pathloy);
pathbox[2] = (jint) ceil(pd->pathhix);
pathbox[3] = (jint) ceil(pd->pathhiy);
}
/* Adjust the clip box from the given bounds. Used to constrain
the output to a device clip
*/
static void
ShapeSIIntersectClipBox(JNIEnv *env, void *private,
jint clox, jint cloy, jint chix, jint chiy)
{
pathData *pd = (pathData *)private;
if (clox > pd->lox) {
pd->lox = clox;
}
if (cloy > pd->loy) {
pd->loy = cloy;
}
if (chix < pd->hix) {
pd->hix = chix;
}
if (chiy < pd->hiy) {
pd->hiy = chiy;
}
}
static jboolean
ShapeSINextSpan(void *state, jint spanbox[])
{
pathData *pd = (pathData *)state;
int lo, cur, new, hi;
int num = pd->numSegments;
jint x0, x1, y0, err;
jint loy;
int ret = JNI_FALSE;
segmentData **segmentTable;
segmentData *seg;
if (pd->state != STATE_SPAN_STARTED) {
if (!initSegmentTable(pd)) {
/* REMIND: - throw exception? */
pd->lowSegment = num;
return JNI_FALSE;
}
}
lo = pd->lowSegment;
cur = pd->curSegment;
hi = pd->hiSegment;
num = pd->numSegments;
loy = pd->loy;
segmentTable = pd->segmentTable;
while (lo < num) {
if (cur < hi) {
seg = segmentTable[cur];
x0 = seg->curx;
if (x0 >= pd->hix) {
cur = hi;
continue;
}
if (x0 < pd->lox) {
x0 = pd->lox;
}
if (pd->evenodd) {
cur += 2;
if (cur <= hi) {
x1 = segmentTable[cur - 1]->curx;
} else {
x1 = pd->hix;
}
} else {
int wind = seg->windDir;
cur++;
while (JNI_TRUE) {
if (cur >= hi) {
x1 = pd->hix;
break;
}
seg = segmentTable[cur++];
wind += seg->windDir;
if (wind == 0) {
x1 = seg->curx;
break;
}
}
}
if (x1 > pd->hix) {
x1 = pd->hix;
}
if (x1 <= x0) {
continue;
}
spanbox[0] = x0;
spanbox[1] = loy;
spanbox[2] = x1;
spanbox[3] = loy + 1;
ret = JNI_TRUE;
break;
}
if (++loy >= pd->hiy) {
lo = cur = hi = num;
break;
}
/* Go through active segments and toss which end "above" loy */
cur = new = hi;
while (--cur >= lo) {
seg = segmentTable[cur];
if (seg->lasty > loy) {
segmentTable[--new] = seg;
}
}
lo = new;
if (lo == hi && lo < num) {
/* The current list of segments is empty so we need to
* jump to the beginning of the next set of segments.
* Since the segments are not clipped to the output
* area we need to make sure we don't jump "backwards"
*/
seg = segmentTable[lo];
if (loy < seg->cury) {
loy = seg->cury;
}
}
/* Go through new segments and accept any which start "above" loy */
while (hi < num && segmentTable[hi]->cury <= loy) {
hi++;
}
/* Update and sort the active segments by x0 */
for (cur = lo; cur < hi; cur++) {
seg = segmentTable[cur];
/* First update the x0, y0 of the segment */
x0 = seg->curx;
y0 = seg->cury;
err = seg->error;
if (++y0 == loy) {
x0 += seg->bumpx;
err += seg->bumperr;
x0 -= (err >> 31);
err &= ERRSTEP_MAX;
} else {
jlong steps = loy;
steps -= y0 - 1;
y0 = loy;
x0 += (jint) (steps * seg->bumpx);
steps = err + (steps * seg->bumperr);
x0 += (jint) (steps >> 31);
err = ((jint) steps) & ERRSTEP_MAX;
}
seg->curx = x0;
seg->cury = y0;
seg->error = err;
/* Then make sure the segment is sorted by x0 */
for (new = cur; new > lo; new--) {
segmentData *seg2 = segmentTable[new - 1];
if (seg2->curx <= x0) {
break;
}
segmentTable[new] = seg2;
}
segmentTable[new] = seg;
}
cur = lo;
}
pd->lowSegment = lo;
pd->hiSegment = hi;
pd->curSegment = cur;
pd->loy = loy;
return ret;
}
static void
ShapeSISkipDownTo(void *private, jint y)
{
pathData *pd = (pathData *)private;
if (pd->state != STATE_SPAN_STARTED) {
if (!initSegmentTable(pd)) {
/* REMIND: - throw exception? */
pd->lowSegment = pd->numSegments;
return;
}
}
/* Make sure we are jumping forward */
if (pd->loy < y) {
/* Pretend like we just finished with the span line y-1... */
pd->loy = y - 1;
pd->curSegment = pd->hiSegment; /* no more segments on that line */
}
}
static jboolean
initSegmentTable(pathData *pd)
{
int i, cur, num, loy;
segmentData **segmentTable;
segmentTable = malloc(pd->numSegments * sizeof(segmentData *));
if (segmentTable == NULL) {
return JNI_FALSE;
}
pd->state = STATE_SPAN_STARTED;
for (i = 0; i < pd->numSegments; i++) {
segmentTable[i] = &pd->segments[i];
}
qsort(segmentTable, pd->numSegments, sizeof(segmentData *),
sortSegmentsByLeadingY);
pd->segmentTable = segmentTable;
/* Skip to the first segment that ends below the top clip edge */
cur = 0;
num = pd->numSegments;
loy = pd->loy;
while (cur < num && segmentTable[cur]->lasty <= loy) {
cur++;
}
pd->lowSegment = pd->curSegment = pd->hiSegment = cur;
/* Prepare for next action to increment loy and prepare new segments */
pd->loy--;
return JNI_TRUE;
}