/* * Copyright (c) 2005, 2014, 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. */ package sun.java2d.loops; import java.awt.geom.Path2D; import java.awt.geom.PathIterator; import java.awt.geom.QuadCurve2D; import sun.awt.SunHints; import java.util.*; /* This is the java implementation of the native code from * src/share/native/sun/java2d/loops/ProcessPath.[c,h] * This code is written to be as much similar to the native * as it possible. So, it sometimes does not follow java naming conventions. * * It's important to keep this code synchronized with native one. See more * comments, description and high level scheme of the rendering process in the * ProcessPath.c */ public class ProcessPath { /* Public interfaces and methods for drawing and filling general paths */ public static abstract class DrawHandler { public int xMin; public int yMin; public int xMax; public int yMax; public float xMinf; public float yMinf; public float xMaxf; public float yMaxf; public int strokeControl; public DrawHandler(int xMin, int yMin, int xMax, int yMax, int strokeControl) { setBounds(xMin, yMin, xMax, yMax, strokeControl); } public void setBounds(int xMin, int yMin, int xMax, int yMax) { this.xMin = xMin; this.yMin = yMin; this.xMax = xMax; this.yMax = yMax; /* Setting up fractional clipping box * * We are using following float -> int mapping: * * xi = floor(xf + 0.5) * * So, fractional values that hit the [xmin, xmax) integer interval * will be situated inside the [xmin-0.5, xmax - 0.5) fractional * interval. We are using EPSF constant to provide that upper * boundary is not included. */ xMinf = xMin - 0.5f; yMinf = yMin - 0.5f; xMaxf = xMax - 0.5f - EPSF; yMaxf = yMax - 0.5f - EPSF; } public void setBounds(int xMin, int yMin, int xMax, int yMax, int strokeControl) { this.strokeControl = strokeControl; setBounds(xMin, yMin, xMax, yMax); } public void adjustBounds(int bxMin, int byMin, int bxMax, int byMax) { if (xMin > bxMin) bxMin = xMin; if (xMax < bxMax) bxMax = xMax; if (yMin > byMin) byMin = yMin; if (yMax < byMax) byMax = yMax; setBounds(bxMin, byMin, bxMax, byMax); } public DrawHandler(int xMin, int yMin, int xMax, int yMax) { this(xMin, yMin, xMax, yMax, SunHints.INTVAL_STROKE_DEFAULT); } public abstract void drawLine(int x0, int y0, int x1, int y1); public abstract void drawPixel(int x0, int y0); public abstract void drawScanline(int x0, int x1, int y0); } public interface EndSubPathHandler { public void processEndSubPath(); } public static final int PH_MODE_DRAW_CLIP = 0; public static final int PH_MODE_FILL_CLIP = 1; public static abstract class ProcessHandler implements EndSubPathHandler { DrawHandler dhnd; int clipMode; public ProcessHandler(DrawHandler dhnd, int clipMode) { this.dhnd = dhnd; this.clipMode = clipMode; } public abstract void processFixedLine(int x1, int y1, int x2, int y2, int [] pixelInfo, boolean checkBounds, boolean endSubPath); } public static EndSubPathHandler noopEndSubPathHandler = new EndSubPathHandler() { public void processEndSubPath() { } }; public static boolean fillPath(DrawHandler dhnd, Path2D.Float p2df, int transX, int transY) { FillProcessHandler fhnd = new FillProcessHandler(dhnd); if (!doProcessPath(fhnd, p2df, transX, transY)) { return false; } FillPolygon(fhnd, p2df.getWindingRule()); return true; } public static boolean drawPath(DrawHandler dhnd, EndSubPathHandler endSubPath, Path2D.Float p2df, int transX, int transY) { return doProcessPath(new DrawProcessHandler(dhnd, endSubPath), p2df, transX, transY); } public static boolean drawPath(DrawHandler dhnd, Path2D.Float p2df, int transX, int transY) { return doProcessPath(new DrawProcessHandler(dhnd, noopEndSubPathHandler), p2df, transX, transY); } /* Private implementation of the rendering process */ /* Boundaries used for skipping huge path segments */ private static final float UPPER_BND = Float.MAX_VALUE/4.0f; private static final float LOWER_BND = -UPPER_BND; /* Precision (in bits) used in forward differencing */ private static final int FWD_PREC = 7; /* Precision (in bits) used for the rounding in the midpoint test */ private static final int MDP_PREC = 10; private static final int MDP_MULT = 1 << MDP_PREC; private static final int MDP_HALF_MULT = MDP_MULT >> 1; /* Boundaries used for clipping large path segments (those are inside * [UPPER/LOWER]_BND boundaries) */ private static final int UPPER_OUT_BND = 1 << (30 - MDP_PREC); private static final int LOWER_OUT_BND = -UPPER_OUT_BND; /* Calculation boundaries. They are used for switching to the more slow but * allowing larger input values method of calculation of the initial values * of the scan converted line segments inside the FillPolygon */ private static final float CALC_UBND = 1 << (30 - MDP_PREC); private static final float CALC_LBND = -CALC_UBND; /* Following constants are used for providing open boundaries of the * intervals */ public static final int EPSFX = 1; public static final float EPSF = ((float)EPSFX)/MDP_MULT; /* Bit mask used to separate whole part from the fraction part of the * number */ private static final int MDP_W_MASK = -MDP_MULT; /* Bit mask used to separate fractional part from the whole part of the * number */ private static final int MDP_F_MASK = MDP_MULT - 1; /* * Constants for the forward differencing * of the cubic and quad curves */ /* Maximum size of the cubic curve (calculated as the size of the bounding * box of the control points) which could be rendered without splitting */ private static final int MAX_CUB_SIZE = 256; /* Maximum size of the quad curve (calculated as the size of the bounding * box of the control points) which could be rendered without splitting */ private static final int MAX_QUAD_SIZE = 1024; /* Default power of 2 steps used in the forward differencing. Here DF prefix * stands for DeFault. Constants below are used as initial values for the * adaptive forward differencing algorithm. */ private static final int DF_CUB_STEPS = 3; private static final int DF_QUAD_STEPS = 2; /* Shift of the current point of the curve for preparing to the midpoint * rounding */ private static final int DF_CUB_SHIFT = FWD_PREC + DF_CUB_STEPS*3 - MDP_PREC; private static final int DF_QUAD_SHIFT = FWD_PREC + DF_QUAD_STEPS*2 - MDP_PREC; /* Default amount of steps of the forward differencing */ private static final int DF_CUB_COUNT = (1< (LINE_MAX)) { if (c[a1] < (LINE_MIN)) { if (c[a2] < (LINE_MIN)) { return CRES_INVISIBLE; }; res = CRES_MIN_CLIPPED; t = (LINE_MIN); } else { if (c[a2] > (LINE_MAX)) { return CRES_INVISIBLE; }; res = CRES_MAX_CLIPPED; t = (LINE_MAX); } c[b1] = CLIP(c[a1], c[b1], c[a2], c[b2], t); c[a1] = (float)t; } return res; } /* Integer version of the method above */ private static int TESTANDCLIP(int LINE_MIN, int LINE_MAX, int[] c, int a1, int b1, int a2, int b2) { double t; int res = CRES_NOT_CLIPPED; if (c[a1] < (LINE_MIN) || c[a1] > (LINE_MAX)) { if (c[a1] < (LINE_MIN)) { if (c[a2] < (LINE_MIN)) { return CRES_INVISIBLE; }; res = CRES_MIN_CLIPPED; t = (LINE_MIN); } else { if (c[a2] > (LINE_MAX)) { return CRES_INVISIBLE; }; res = CRES_MAX_CLIPPED; t = (LINE_MAX); } c[b1] = CLIP(c[a1], c[b1], c[a2], c[b2], t); c[a1] = (int)t; } return res; } /* Following method is used for clipping and clumping filled shapes. * An example of this process is shown on the picture below: * ----+ ----+ * |/ | |/ | * + | + | * /| | I | * / | | I | * | | | ===> I | * \ | | I | * \| | I | * + | + | * |\ | |\ | * | ----+ | ----+ * boundary boundary * * We can only perform clipping in case of right side of the output area * because all segments passed out the right boundary don't influence on the * result of scan conversion algorithm (it correctly handles half open * contours). * * This is java implementation of the macro from ProcessGeneralPath.c. * To keep the logic of the java code similar to the native one * array and set of indexes are used to point out the data. * */ private static int CLIPCLAMP(float LINE_MIN, float LINE_MAX, float[] c, int a1, int b1, int a2, int b2, int a3, int b3) { c[a3] = c[a1]; c[b3] = c[b1]; int res = TESTANDCLIP(LINE_MIN, LINE_MAX, c, a1, b1, a2, b2); if (res == CRES_MIN_CLIPPED) { c[a3] = c[a1]; } else if (res == CRES_MAX_CLIPPED) { c[a3] = c[a1]; res = CRES_MAX_CLIPPED; } else if (res == CRES_INVISIBLE) { if (c[a1] > LINE_MAX) { res = CRES_INVISIBLE; } else { c[a1] = LINE_MIN; c[a2] = LINE_MIN; res = CRES_NOT_CLIPPED; } } return res; } /* Integer version of the method above */ private static int CLIPCLAMP(int LINE_MIN, int LINE_MAX, int[] c, int a1, int b1, int a2, int b2, int a3, int b3) { c[a3] = c[a1]; c[b3] = c[b1]; int res = TESTANDCLIP(LINE_MIN, LINE_MAX, c, a1, b1, a2, b2); if (res == CRES_MIN_CLIPPED) { c[a3] = c[a1]; } else if (res == CRES_MAX_CLIPPED) { c[a3] = c[a1]; res = CRES_MAX_CLIPPED; } else if (res == CRES_INVISIBLE) { if (c[a1] > LINE_MAX) { res = CRES_INVISIBLE; } else { c[a1] = LINE_MIN; c[a2] = LINE_MIN; res = CRES_NOT_CLIPPED; } } return res; } private static class DrawProcessHandler extends ProcessHandler { EndSubPathHandler processESP; public DrawProcessHandler(DrawHandler dhnd, EndSubPathHandler processESP) { super(dhnd, PH_MODE_DRAW_CLIP); this.dhnd = dhnd; this.processESP = processESP; } public void processEndSubPath() { processESP.processEndSubPath(); } void PROCESS_LINE(int fX0, int fY0, int fX1, int fY1, boolean checkBounds, int[] pixelInfo) { int X0 = fX0 >> MDP_PREC; int Y0 = fY0 >> MDP_PREC; int X1 = fX1 >> MDP_PREC; int Y1 = fY1 >> MDP_PREC; /* Handling lines having just one pixel */ if (((X0^X1) | (Y0^Y1)) == 0) { if (checkBounds && (dhnd.yMin > Y0 || dhnd.yMax <= Y0 || dhnd.xMin > X0 || dhnd.xMax <= X0)) return; if (pixelInfo[0] == 0) { pixelInfo[0] = 1; pixelInfo[1] = X0; pixelInfo[2] = Y0; pixelInfo[3] = X0; pixelInfo[4] = Y0; dhnd.drawPixel(X0, Y0); } else if ((X0 != pixelInfo[3] || Y0 != pixelInfo[4]) && (X0 != pixelInfo[1] || Y0 != pixelInfo[2])) { dhnd.drawPixel(X0, Y0); pixelInfo[3] = X0; pixelInfo[4] = Y0; } return; } if (!checkBounds || (dhnd.yMin <= Y0 && dhnd.yMax > Y0 && dhnd.xMin <= X0 && dhnd.xMax > X0)) { /* Switch off first pixel of the line before drawing */ if (pixelInfo[0] == 1 && ((pixelInfo[1] == X0 && pixelInfo[2] == Y0) || (pixelInfo[3] == X0 && pixelInfo[4] == Y0))) { dhnd.drawPixel(X0, Y0); } } dhnd.drawLine(X0, Y0, X1, Y1); if (pixelInfo[0] == 0) { pixelInfo[0] = 1; pixelInfo[1] = X0; pixelInfo[2] = Y0; pixelInfo[3] = X0; pixelInfo[4] = Y0; } /* Switch on last pixel of the line if it was already * drawn during rendering of the previous segments */ if ((pixelInfo[1] == X1 && pixelInfo[2] == Y1) || (pixelInfo[3] == X1 && pixelInfo[4] == Y1)) { if (checkBounds && (dhnd.yMin > Y1 || dhnd.yMax <= Y1 || dhnd.xMin > X1 || dhnd.xMax <= X1)) { return; } dhnd.drawPixel(X1, Y1); } pixelInfo[3] = X1; pixelInfo[4] = Y1; } void PROCESS_POINT(int fX, int fY, boolean checkBounds, int[] pixelInfo) { int _X = fX>> MDP_PREC; int _Y = fY>> MDP_PREC; if (checkBounds && (dhnd.yMin > _Y || dhnd.yMax <= _Y || dhnd.xMin > _X || dhnd.xMax <= _X)) return; /* * (_X,_Y) should be inside boundaries * * assert(dhnd.yMin <= _Y && * dhnd.yMax > _Y && * dhnd.xMin <= _X && * dhnd.xMax > _X); * */ if (pixelInfo[0] == 0) { pixelInfo[0] = 1; pixelInfo[1] = _X; pixelInfo[2] = _Y; pixelInfo[3] = _X; pixelInfo[4] = _Y; dhnd.drawPixel(_X, _Y); } else if ((_X != pixelInfo[3] || _Y != pixelInfo[4]) && (_X != pixelInfo[1] || _Y != pixelInfo[2])) { dhnd.drawPixel(_X, _Y); pixelInfo[3] = _X; pixelInfo[4] = _Y; } } /* Drawing line with subpixel endpoints * * (x1, y1), (x2, y2) - fixed point coordinates of the endpoints * with MDP_PREC bits for the fractional part * * pixelInfo - structure which keeps drawing info for avoiding * multiple drawing at the same position on the * screen (required for the XOR mode of drawing) * * pixelInfo[0] - state of the drawing * 0 - no pixel drawn between * moveTo/close of the path * 1 - there are drawn pixels * * pixelInfo[1,2] - first pixel of the path * between moveTo/close of the * path * * pixelInfo[3,4] - last drawn pixel between * moveTo/close of the path * * checkBounds - flag showing necessity of checking the clip * */ public void processFixedLine(int x1, int y1, int x2, int y2, int[] pixelInfo, boolean checkBounds, boolean endSubPath) { /* Checking if line is inside a (X,Y),(X+MDP_MULT,Y+MDP_MULT) box */ int c = ((x1 ^ x2) | (y1 ^ y2)); int rx1, ry1, rx2, ry2; if ((c & MDP_W_MASK) == 0) { /* Checking for the segments with integer coordinates having * the same start and end points */ if (c == 0) { PROCESS_POINT(x1 + MDP_HALF_MULT, y1 + MDP_HALF_MULT, checkBounds, pixelInfo); } return; } if (x1 == x2 || y1 == y2) { rx1 = x1 + MDP_HALF_MULT; rx2 = x2 + MDP_HALF_MULT; ry1 = y1 + MDP_HALF_MULT; ry2 = y2 + MDP_HALF_MULT; } else { /* Neither dx nor dy can be zero because of the check above */ int dx = x2 - x1; int dy = y2 - y1; /* Floor of x1, y1, x2, y2 */ int fx1 = x1 & MDP_W_MASK; int fy1 = y1 & MDP_W_MASK; int fx2 = x2 & MDP_W_MASK; int fy2 = y2 & MDP_W_MASK; /* Processing first endpoint */ if (fx1 == x1 || fy1 == y1) { /* Adding MDP_HALF_MULT to the [xy]1 if f[xy]1 == [xy]1 * will not affect the result */ rx1 = x1 + MDP_HALF_MULT; ry1 = y1 + MDP_HALF_MULT; } else { /* Boundary at the direction from (x1,y1) to (x2,y2) */ int bx1 = (x1 < x2) ? fx1 + MDP_MULT : fx1; int by1 = (y1 < y2) ? fy1 + MDP_MULT : fy1; /* intersection with column bx1 */ int cross = y1 + ((bx1 - x1)*dy)/dx; if (cross >= fy1 && cross <= fy1 + MDP_MULT) { rx1 = bx1; ry1 = cross + MDP_HALF_MULT; } else { /* intersection with row by1 */ cross = x1 + ((by1 - y1)*dx)/dy; rx1 = cross + MDP_HALF_MULT; ry1 = by1; } } /* Processing second endpoint */ if (fx2 == x2 || fy2 == y2) { /* Adding MDP_HALF_MULT to the [xy]2 if f[xy]2 == [xy]2 * will not affect the result */ rx2 = x2 + MDP_HALF_MULT; ry2 = y2 + MDP_HALF_MULT; } else { /* Boundary at the direction from (x2,y2) to (x1,y1) */ int bx2 = (x1 > x2) ? fx2 + MDP_MULT : fx2; int by2 = (y1 > y2) ? fy2 + MDP_MULT : fy2; /* intersection with column bx2 */ int cross = y2 + ((bx2 - x2)*dy)/dx; if (cross >= fy2 && cross <= fy2 + MDP_MULT) { rx2 = bx2; ry2 = cross + MDP_HALF_MULT; } else { /* intersection with row by2 */ cross = x2 + ((by2 - y2)*dx)/dy; rx2 = cross + MDP_HALF_MULT; ry2 = by2; } } } PROCESS_LINE(rx1, ry1, rx2, ry2, checkBounds, pixelInfo); } } /* Performing drawing of the monotonic in X and Y quadratic curves with * sizes less than MAX_QUAD_SIZE by using forward differencing method of * calculation. See comments to the DrawMonotonicQuad in the * ProcessGeneralPath.c */ private static void DrawMonotonicQuad(ProcessHandler hnd, float[] coords, boolean checkBounds, int[] pixelInfo) { int x0 = (int)(coords[0]*MDP_MULT); int y0 = (int)(coords[1]*MDP_MULT); int xe = (int)(coords[4]*MDP_MULT); int ye = (int)(coords[5]*MDP_MULT); /* Extracting fractional part of coordinates of first control point */ int px = (x0 & (~MDP_W_MASK)) << DF_QUAD_SHIFT; int py = (y0 & (~MDP_W_MASK)) << DF_QUAD_SHIFT; /* Setting default amount of steps */ int count = DF_QUAD_COUNT; /* Setting default shift for preparing to the midpoint rounding */ int shift = DF_QUAD_SHIFT; int ax = (int)((coords[0] - 2*coords[2] + coords[4])*QUAD_A_MDP_MULT); int ay = (int)((coords[1] - 2*coords[3] + coords[5])*QUAD_A_MDP_MULT); int bx = (int)((-2*coords[0] + 2*coords[2])*QUAD_B_MDP_MULT); int by = (int)((-2*coords[1] + 2*coords[3])*QUAD_B_MDP_MULT); int ddpx = 2*ax; int ddpy = 2*ay; int dpx = ax + bx; int dpy = ay + by; int x1, y1; int x2 = x0; int y2 = y0; int maxDD = Math.max(Math.abs(ddpx),Math.abs(ddpy)); int dx = xe - x0; int dy = ye - y0; int x0w = x0 & MDP_W_MASK; int y0w = y0 & MDP_W_MASK; /* Perform decreasing step in 2 times if slope of the first forward * difference changes too quickly (more than a pixel per step in X or Y * direction). We can perform adjusting of the step size before the * rendering loop because the curvature of the quad curve remains the * same along all the curve */ while (maxDD > DF_QUAD_DEC_BND) { dpx = (dpx<<1) - ax; dpy = (dpy<<1) - ay; count <<= 1; maxDD >>= 2; px <<=2; py <<=2; shift += 2; } while(count-- > 1) { px += dpx; py += dpy; dpx += ddpx; dpy += ddpy; x1 = x2; y1 = y2; x2 = x0w + (px >> shift); y2 = y0w + (py >> shift); /* Checking that we are not running out of the endpoint and bounding * violating coordinate. The check is pretty simple because the * curve passed to the DrawCubic already split into the * monotonic in X and Y pieces */ /* Bounding x2 by xe */ if (((xe-x2)^dx) < 0) { x2 = xe; } /* Bounding y2 by ye */ if (((ye-y2)^dy) < 0) { y2 = ye; } hnd.processFixedLine(x1, y1, x2, y2, pixelInfo, checkBounds, false); } /* We are performing one step less than necessary and use actual * (xe,ye) endpoint of the curve instead of calculated. This prevent us * from running above the curve endpoint due to the accumulated errors * during the iterations. */ hnd.processFixedLine(x2, y2, xe, ye, pixelInfo, checkBounds, false); } /* * Checking size of the quad curves and split them if necessary. * Calling DrawMonotonicQuad for the curves of the appropriate size. * Note: coords array could be changed */ private static void ProcessMonotonicQuad(ProcessHandler hnd, float[] coords, int[] pixelInfo) { float[] coords1 = new float[6]; float tx, ty; float xMin, yMin, xMax, yMax; xMin = xMax = coords[0]; yMin = yMax = coords[1]; for (int i = 2; i < 6; i += 2) { xMin = (xMin > coords[i])? coords[i] : xMin; xMax = (xMax < coords[i])? coords[i] : xMax; yMin = (yMin > coords[i + 1])? coords[i + 1] : yMin; yMax = (yMax < coords[i + 1])? coords[i + 1] : yMax; } if (hnd.clipMode == PH_MODE_DRAW_CLIP) { /* In case of drawing we could just skip curves which are * completely out of bounds */ if (hnd.dhnd.xMaxf < xMin || hnd.dhnd.xMinf > xMax || hnd.dhnd.yMaxf < yMin || hnd.dhnd.yMinf > yMax) { return; } } else { /* In case of filling we could skip curves which are above, * below and behind the right boundary of the visible area */ if (hnd.dhnd.yMaxf < yMin || hnd.dhnd.yMinf > yMax || hnd.dhnd.xMaxf < xMin) { return; } /* We could clamp x coordinates to the corresponding boundary * if the curve is completely behind the left one */ if (hnd.dhnd.xMinf > xMax) { coords[0] = coords[2] = coords[4] = hnd.dhnd.xMinf; } } if (xMax - xMin > MAX_QUAD_SIZE || yMax - yMin > MAX_QUAD_SIZE) { coords1[4] = coords[4]; coords1[5] = coords[5]; coords1[2] = (coords[2] + coords[4])/2.0f; coords1[3] = (coords[3] + coords[5])/2.0f; coords[2] = (coords[0] + coords[2])/2.0f; coords[3] = (coords[1] + coords[3])/2.0f; coords[4] = coords1[0] = (coords[2] + coords1[2])/2.0f; coords[5] = coords1[1] = (coords[3] + coords1[3])/2.0f; ProcessMonotonicQuad(hnd, coords, pixelInfo); ProcessMonotonicQuad(hnd, coords1, pixelInfo); } else { DrawMonotonicQuad(hnd, coords, /* Set checkBounds parameter if curve intersects * boundary of the visible area. We know that the * curve is visible, so the check is pretty * simple */ hnd.dhnd.xMinf >= xMin || hnd.dhnd.xMaxf <= xMax || hnd.dhnd.yMinf >= yMin || hnd.dhnd.yMaxf <= yMax, pixelInfo); } } /* * Split quadratic curve into monotonic in X and Y parts. Calling * ProcessMonotonicQuad for each monotonic piece of the curve. * Note: coords array could be changed */ private static void ProcessQuad(ProcessHandler hnd, float[] coords, int[] pixelInfo) { /* Temporary array for holding parameters corresponding to the extreme * in X and Y points */ double params[] = new double[2]; int cnt = 0; double param; /* Simple check for monotonicity in X before searching for the extreme * points of the X(t) function. We first check if the curve is * monotonic in X by seeing if all of the X coordinates are strongly * ordered. */ if ((coords[0] > coords[2] || coords[2] > coords[4]) && (coords[0] < coords[2] || coords[2] < coords[4])) { /* Searching for extreme points of the X(t) function by solving * dX(t) * ---- = 0 equation * dt */ double ax = coords[0] - 2*coords[2] + coords[4]; if (ax != 0) { /* Calculating root of the following equation * ax*t + bx = 0 */ double bx = coords[0] - coords[2]; param = bx/ax; if (param < 1.0 && param > 0.0) { params[cnt++] = param; } } } /* Simple check for monotonicity in Y before searching for the extreme * points of the Y(t) function. We first check if the curve is * monotonic in Y by seeing if all of the Y coordinates are strongly * ordered. */ if ((coords[1] > coords[3] || coords[3] > coords[5]) && (coords[1] < coords[3] || coords[3] < coords[5])) { /* Searching for extreme points of the Y(t) function by solving * dY(t) * ----- = 0 equation * dt */ double ay = coords[1] - 2*coords[3] + coords[5]; if (ay != 0) { /* Calculating root of the following equation * ay*t + by = 0 */ double by = coords[1] - coords[3]; param = by/ay; if (param < 1.0 && param > 0.0) { if (cnt > 0) { /* Inserting parameter only if it differs from * already stored */ if (params[0] > param) { params[cnt++] = params[0]; params[0] = param; } else if (params[0] < param) { params[cnt++] = param; } } else { params[cnt++] = param; } } } } /* Processing obtained monotonic parts */ switch(cnt) { case 0: break; case 1: ProcessFirstMonotonicPartOfQuad(hnd, coords, pixelInfo, (float)params[0]); break; case 2: ProcessFirstMonotonicPartOfQuad(hnd, coords, pixelInfo, (float)params[0]); param = params[1] - params[0]; if (param > 0) { ProcessFirstMonotonicPartOfQuad(hnd, coords, pixelInfo, /* Scale parameter to match with * rest of the curve */ (float)(param/(1.0 - params[0]))); } break; } ProcessMonotonicQuad(hnd,coords,pixelInfo); } /* * Bite the piece of the quadratic curve from start point till the point * corresponding to the specified parameter then call ProcessQuad for the * bitten part. * Note: coords array will be changed */ private static void ProcessFirstMonotonicPartOfQuad(ProcessHandler hnd, float[] coords, int[] pixelInfo, float t) { float[] coords1 = new float[6]; coords1[0] = coords[0]; coords1[1] = coords[1]; coords1[2] = coords[0] + t*(coords[2] - coords[0]); coords1[3] = coords[1] + t*(coords[3] - coords[1]); coords[2] = coords[2] + t*(coords[4] - coords[2]); coords[3] = coords[3] + t*(coords[5] - coords[3]); coords[0] = coords1[4] = coords1[2] + t*(coords[2] - coords1[2]); coords[1] = coords1[5] = coords1[3] + t*(coords[3] - coords1[3]); ProcessMonotonicQuad(hnd, coords1, pixelInfo); } /* Performing drawing of the monotonic in X and Y cubic curves with sizes * less than MAX_CUB_SIZE by using forward differencing method of * calculation. See comments to the DrawMonotonicCubic in the * ProcessGeneralPath.c */ private static void DrawMonotonicCubic(ProcessHandler hnd, float[] coords, boolean checkBounds, int[] pixelInfo) { int x0 = (int)(coords[0]*MDP_MULT); int y0 = (int)(coords[1]*MDP_MULT); int xe = (int)(coords[6]*MDP_MULT); int ye = (int)(coords[7]*MDP_MULT); /* Extracting fractional part of coordinates of first control point */ int px = (x0 & (~MDP_W_MASK)) << DF_CUB_SHIFT; int py = (y0 & (~MDP_W_MASK)) << DF_CUB_SHIFT; /* Setting default boundary values for checking first and second forward * difference for the necessity of the restepping. See comments to the * boundary values in ProcessQuad for more info. */ int incStepBnd = DF_CUB_INC_BND; int decStepBnd = DF_CUB_DEC_BND; /* Setting default amount of steps */ int count = DF_CUB_COUNT; /* Setting default shift for preparing to the midpoint rounding */ int shift = DF_CUB_SHIFT; int ax = (int)((-coords[0] + 3*coords[2] - 3*coords[4] + coords[6])*CUB_A_MDP_MULT); int ay = (int)((-coords[1] + 3*coords[3] - 3*coords[5] + coords[7])*CUB_A_MDP_MULT); int bx = (int)((3*coords[0] - 6*coords[2] + 3*coords[4])*CUB_B_MDP_MULT); int by = (int)((3*coords[1] - 6*coords[3] + 3*coords[5])*CUB_B_MDP_MULT); int cx = (int)((-3*coords[0] + 3*coords[2])*(CUB_C_MDP_MULT)); int cy = (int)((-3*coords[1] + 3*coords[3])*(CUB_C_MDP_MULT)); int dddpx = 6*ax; int dddpy = 6*ay; int ddpx = dddpx + bx; int ddpy = dddpy + by; int dpx = ax + (bx>>1) + cx; int dpy = ay + (by>>1) + cy; int x1, y1; int x2 = x0; int y2 = y0; /* Calculating whole part of the first point of the curve */ int x0w = x0 & MDP_W_MASK; int y0w = y0 & MDP_W_MASK; int dx = xe - x0; int dy = ye - y0; while (count > 0) { /* Perform decreasing step in 2 times if necessary */ while (Math.abs(ddpx) > decStepBnd || Math.abs(ddpy) > decStepBnd) { ddpx = (ddpx<<1) - dddpx; ddpy = (ddpy<<1) - dddpy; dpx = (dpx<<2) - (ddpx>>1); dpy = (dpy<<2) - (ddpy>>1); count <<=1; decStepBnd <<=3; incStepBnd <<=3; px <<=3; py <<=3; shift += 3; } /* Perform increasing step in 2 times if necessary. * Note: we could do it only in even steps */ while ((count & 1) == 0 && shift > DF_CUB_SHIFT && Math.abs(dpx) <= incStepBnd && Math.abs(dpy) <= incStepBnd) { dpx = (dpx>>2) + (ddpx>>3); dpy = (dpy>>2) + (ddpy>>3); ddpx = (ddpx + dddpx)>>1; ddpy = (ddpy + dddpy)>>1; count >>=1; decStepBnd >>=3; incStepBnd >>=3; px >>=3; py >>=3; shift -= 3; } count--; /* Performing one step less than necessary and use actual (xe,ye) * curve's endpoint instead of calculated. This prevent us from * running above the curve endpoint due to the accumulated errors * during the iterations. */ if (count > 0) { px += dpx; py += dpy; dpx += ddpx; dpy += ddpy; ddpx += dddpx; ddpy += dddpy; x1 = x2; y1 = y2; x2 = x0w + (px >> shift); y2 = y0w + (py >> shift); /* Checking that we are not running out of the endpoint and * bounding violating coordinate. The check is pretty simple * because the curve passed to the DrawCubic already split * into the monotonic in X and Y pieces */ /* Bounding x2 by xe */ if (((xe-x2)^dx) < 0) { x2 = xe; } /* Bounding y2 by ye */ if (((ye-y2)^dy) < 0) { y2 = ye; } hnd.processFixedLine(x1, y1, x2, y2, pixelInfo, checkBounds, false); } else { hnd.processFixedLine(x2, y2, xe, ye, pixelInfo, checkBounds, false); } } } /* * Checking size of the cubic curves and split them if necessary. * Calling DrawMonotonicCubic for the curves of the appropriate size. * Note: coords array could be changed */ private static void ProcessMonotonicCubic(ProcessHandler hnd, float[] coords, int[] pixelInfo) { float[] coords1 = new float[8]; float tx, ty; float xMin, xMax; float yMin, yMax; xMin = xMax = coords[0]; yMin = yMax = coords[1]; for (int i = 2; i < 8; i += 2) { xMin = (xMin > coords[i])? coords[i] : xMin; xMax = (xMax < coords[i])? coords[i] : xMax; yMin = (yMin > coords[i + 1])? coords[i + 1] : yMin; yMax = (yMax < coords[i + 1])? coords[i + 1] : yMax; } if (hnd.clipMode == PH_MODE_DRAW_CLIP) { /* In case of drawing we could just skip curves which are * completely out of bounds */ if (hnd.dhnd.xMaxf < xMin || hnd.dhnd.xMinf > xMax || hnd.dhnd.yMaxf < yMin || hnd.dhnd.yMinf > yMax) { return; } } else { /* In case of filling we could skip curves which are above, * below and behind the right boundary of the visible area */ if (hnd.dhnd.yMaxf < yMin || hnd.dhnd.yMinf > yMax || hnd.dhnd.xMaxf < xMin) { return; } /* We could clamp x coordinates to the corresponding boundary * if the curve is completely behind the left one */ if (hnd.dhnd.xMinf > xMax) { coords[0] = coords[2] = coords[4] = coords[6] = hnd.dhnd.xMinf; } } if (xMax - xMin > MAX_CUB_SIZE || yMax - yMin > MAX_CUB_SIZE) { coords1[6] = coords[6]; coords1[7] = coords[7]; coords1[4] = (coords[4] + coords[6])/2.0f; coords1[5] = (coords[5] + coords[7])/2.0f; tx = (coords[2] + coords[4])/2.0f; ty = (coords[3] + coords[5])/2.0f; coords1[2] = (tx + coords1[4])/2.0f; coords1[3] = (ty + coords1[5])/2.0f; coords[2] = (coords[0] + coords[2])/2.0f; coords[3] = (coords[1] + coords[3])/2.0f; coords[4] = (coords[2] + tx)/2.0f; coords[5] = (coords[3] + ty)/2.0f; coords[6]=coords1[0]=(coords[4] + coords1[2])/2.0f; coords[7]=coords1[1]=(coords[5] + coords1[3])/2.0f; ProcessMonotonicCubic(hnd, coords, pixelInfo); ProcessMonotonicCubic(hnd, coords1, pixelInfo); } else { DrawMonotonicCubic(hnd, coords, /* Set checkBounds parameter if curve intersects * boundary of the visible area. We know that * the curve is visible, so the check is pretty * simple */ hnd.dhnd.xMinf > xMin || hnd.dhnd.xMaxf < xMax || hnd.dhnd.yMinf > yMin || hnd.dhnd.yMaxf < yMax, pixelInfo); } } /* * Split cubic curve into monotonic in X and Y parts. Calling * ProcessMonotonicCubic for each monotonic piece of the curve. * * Note: coords array could be changed */ private static void ProcessCubic(ProcessHandler hnd, float[] coords, int[] pixelInfo) { /* Temporary array for holding parameters corresponding to the extreme * in X and Y points */ double params[] = new double[4]; double eqn[] = new double[3]; double res[] = new double[2]; int cnt = 0; /* Simple check for monotonicity in X before searching for the extreme * points of the X(t) function. We first check if the curve is * monotonic in X by seeing if all of the X coordinates are strongly * ordered. */ if ((coords[0] > coords[2] || coords[2] > coords[4] || coords[4] > coords[6]) && (coords[0] < coords[2] || coords[2] < coords[4] || coords[4] < coords[6])) { /* Searching for extreme points of the X(t) function by solving * dX(t) * ---- = 0 equation * dt */ eqn[2] = -coords[0] + 3*coords[2] - 3*coords[4] + coords[6]; eqn[1] = 2*(coords[0] - 2*coords[2] + coords[4]); eqn[0] = -coords[0] + coords[2]; int nr = QuadCurve2D.solveQuadratic(eqn, res); /* Following code also correctly works in degenerate case of * the quadratic equation (nr = -1) because we do not need * splitting in this case. */ for (int i = 0; i < nr; i++) { if (res[i] > 0 && res[i] < 1) { params[cnt++] = res[i]; } } } /* Simple check for monotonicity in Y before searching for the extreme * points of the Y(t) function. We first check if the curve is * monotonic in Y by seeing if all of the Y coordinates are strongly * ordered. */ if ((coords[1] > coords[3] || coords[3] > coords[5] || coords[5] > coords[7]) && (coords[1] < coords[3] || coords[3] < coords[5] || coords[5] < coords[7])) { /* Searching for extreme points of the Y(t) function by solving * dY(t) * ----- = 0 equation * dt */ eqn[2] = -coords[1] + 3*coords[3] - 3*coords[5] + coords[7]; eqn[1] = 2*(coords[1] - 2*coords[3] + coords[5]); eqn[0] = -coords[1] + coords[3]; int nr = QuadCurve2D.solveQuadratic(eqn, res); /* Following code also correctly works in degenerate case of * the quadratic equation (nr = -1) because we do not need * splitting in this case. */ for (int i = 0; i < nr; i++) { if (res[i] > 0 && res[i] < 1) { params[cnt++] = res[i]; } } } if (cnt > 0) { /* Sorting parameter values corresponding to the extreme points * of the curve */ Arrays.sort(params, 0, cnt); /* Processing obtained monotonic parts */ ProcessFirstMonotonicPartOfCubic(hnd, coords, pixelInfo, (float)params[0]); for (int i = 1; i < cnt; i++) { double param = params[i] - params[i-1]; if (param > 0) { ProcessFirstMonotonicPartOfCubic(hnd, coords, pixelInfo, /* Scale parameter to match with rest of the curve */ (float)(param/(1.0 - params[i - 1]))); } } } ProcessMonotonicCubic(hnd,coords,pixelInfo); } /* * Bite the piece of the cubic curve from start point till the point * corresponding to the specified parameter then call ProcessCubic for the * bitten part. * Note: coords array will be changed */ private static void ProcessFirstMonotonicPartOfCubic(ProcessHandler hnd, float[] coords, int[] pixelInfo, float t) { float[] coords1 = new float[8]; float tx, ty; coords1[0] = coords[0]; coords1[1] = coords[1]; tx = coords[2] + t*(coords[4] - coords[2]); ty = coords[3] + t*(coords[5] - coords[3]); coords1[2] = coords[0] + t*(coords[2] - coords[0]); coords1[3] = coords[1] + t*(coords[3] - coords[1]); coords1[4] = coords1[2] + t*(tx - coords1[2]); coords1[5] = coords1[3] + t*(ty - coords1[3]); coords[4] = coords[4] + t*(coords[6] - coords[4]); coords[5] = coords[5] + t*(coords[7] - coords[5]); coords[2] = tx + t*(coords[4] - tx); coords[3] = ty + t*(coords[5] - ty); coords[0]=coords1[6]=coords1[4] + t*(coords[2] - coords1[4]); coords[1]=coords1[7]=coords1[5] + t*(coords[3] - coords1[5]); ProcessMonotonicCubic(hnd, coords1, pixelInfo); } /* Note: * For more easy reading of the code below each java version of the macros * from the ProcessPath.c preceded by the commented origin call * containing verbose names of the parameters */ private static void ProcessLine(ProcessHandler hnd, float x1, float y1, float x2, float y2, int[] pixelInfo) { float xMin, yMin, xMax, yMax; int X1, Y1, X2, Y2, X3, Y3, res; boolean clipped = false; float x3,y3; float c[] = new float[]{x1, y1, x2, y2, 0, 0}; boolean lastClipped; xMin = hnd.dhnd.xMinf; yMin = hnd.dhnd.yMinf; xMax = hnd.dhnd.xMaxf; yMax = hnd.dhnd.yMaxf; // // TESTANDCLIP(yMin, yMax, y1, x1, y2, x2, res); // res = TESTANDCLIP(yMin, yMax, c, 1, 0, 3, 2); if (res == CRES_INVISIBLE) return; clipped = IS_CLIPPED(res); // // TESTANDCLIP(yMin, yMax, y2, x2, y1, x1, res); // res = TESTANDCLIP(yMin, yMax, c, 3, 2, 1, 0); if (res == CRES_INVISIBLE) return; lastClipped = IS_CLIPPED(res); clipped = clipped || lastClipped; if (hnd.clipMode == PH_MODE_DRAW_CLIP) { // // TESTANDCLIP(xMin, xMax, x1, y1, x2, y2, res); // res = TESTANDCLIP(xMin, xMax, c, 0, 1, 2, 3); if (res == CRES_INVISIBLE) return; clipped = clipped || IS_CLIPPED(res); // // TESTANDCLIP(xMin, xMax, x2, y2, x1, y1, res); // res = TESTANDCLIP(xMin, xMax, c, 2, 3, 0, 1); if (res == CRES_INVISIBLE) return; lastClipped = lastClipped || IS_CLIPPED(res); clipped = clipped || lastClipped; X1 = (int)(c[0]*MDP_MULT); Y1 = (int)(c[1]*MDP_MULT); X2 = (int)(c[2]*MDP_MULT); Y2 = (int)(c[3]*MDP_MULT); hnd.processFixedLine(X1, Y1, X2, Y2, pixelInfo, clipped, /* enable boundary checking in case of clipping to avoid entering out of bounds which could happens during rounding */ lastClipped /* Notify pProcessFixedLine that this is the end of the subpath (because of exiting out of boundaries) */ ); } else { /* Clamping starting from first vertex of the the processed * segment * * CLIPCLAMP(xMin, xMax, x1, y1, x2, y2, x3, y3, res); */ res = CLIPCLAMP(xMin, xMax, c, 0, 1, 2, 3, 4, 5); X1 = (int)(c[0]*MDP_MULT); Y1 = (int)(c[1]*MDP_MULT); /* Clamping only by left boundary */ if (res == CRES_MIN_CLIPPED) { X3 = (int)(c[4]*MDP_MULT); Y3 = (int)(c[5]*MDP_MULT); hnd.processFixedLine(X3, Y3, X1, Y1, pixelInfo, false, lastClipped); } else if (res == CRES_INVISIBLE) { return; } /* Clamping starting from last vertex of the the processed * segment * * CLIPCLAMP(xMin, xMax, x2, y2, x1, y1, x3, y3, res); */ res = CLIPCLAMP(xMin, xMax, c, 2, 3, 0, 1, 4, 5); /* Checking if there was a clip by right boundary */ lastClipped = lastClipped || (res == CRES_MAX_CLIPPED); X2 = (int)(c[2]*MDP_MULT); Y2 = (int)(c[3]*MDP_MULT); hnd.processFixedLine(X1, Y1, X2, Y2, pixelInfo, false, lastClipped); /* Clamping only by left boundary */ if (res == CRES_MIN_CLIPPED) { X3 = (int)(c[4]*MDP_MULT); Y3 = (int)(c[5]*MDP_MULT); hnd.processFixedLine(X2, Y2, X3, Y3, pixelInfo, false, lastClipped); } } } private static boolean doProcessPath(ProcessHandler hnd, Path2D.Float p2df, float transXf, float transYf) { float coords[] = new float[8]; float tCoords[] = new float[8]; float closeCoord[] = new float[] {0.0f, 0.0f}; float firstCoord[] = new float[2]; int pixelInfo[] = new int[5]; boolean subpathStarted = false; boolean skip = false; float lastX, lastY; pixelInfo[0] = 0; /* Adjusting boundaries to the capabilities of the * ProcessPath code */ hnd.dhnd.adjustBounds(LOWER_OUT_BND, LOWER_OUT_BND, UPPER_OUT_BND, UPPER_OUT_BND); /* Adding support of the KEY_STROKE_CONTROL rendering hint. * Now we are supporting two modes: "pixels at centers" and * "pixels at corners". * First one is disabled by default but could be enabled by setting * VALUE_STROKE_PURE to the rendering hint. It means that pixel at the * screen (x,y) has (x + 0.5, y + 0.5) float coordinates. * * Second one is enabled by default and means straightforward mapping * (x,y) --> (x,y) */ if (hnd.dhnd.strokeControl == SunHints.INTVAL_STROKE_PURE) { closeCoord[0] = -0.5f; closeCoord[1] = -0.5f; transXf -= 0.5; transYf -= 0.5; } PathIterator pi = p2df.getPathIterator(null); while (!pi.isDone()) { switch (pi.currentSegment(coords)) { case PathIterator.SEG_MOVETO: /* Performing closing of the unclosed segments */ if (subpathStarted && !skip) { if (hnd.clipMode == PH_MODE_FILL_CLIP) { if (tCoords[0] != closeCoord[0] || tCoords[1] != closeCoord[1]) { ProcessLine(hnd, tCoords[0], tCoords[1], closeCoord[0], closeCoord[1], pixelInfo); } } hnd.processEndSubPath(); } tCoords[0] = coords[0] + transXf; tCoords[1] = coords[1] + transYf; /* Checking SEG_MOVETO coordinates if they are out of the * [LOWER_BND, UPPER_BND] range. This check also handles * NaN and Infinity values. Skipping next path segment in * case of invalid data. */ if (tCoords[0] < UPPER_BND && tCoords[0] > LOWER_BND && tCoords[1] < UPPER_BND && tCoords[1] > LOWER_BND) { subpathStarted = true; skip = false; closeCoord[0] = tCoords[0]; closeCoord[1] = tCoords[1]; } else { skip = true; } pixelInfo[0] = 0; break; case PathIterator.SEG_LINETO: lastX = tCoords[2] = coords[0] + transXf; lastY = tCoords[3] = coords[1] + transYf; /* Checking SEG_LINETO coordinates if they are out of the * [LOWER_BND, UPPER_BND] range. This check also handles * NaN and Infinity values. Ignoring current path segment * in case of invalid data. If segment is skipped its * endpoint (if valid) is used to begin new subpath. */ if (lastX < UPPER_BND && lastX > LOWER_BND && lastY < UPPER_BND && lastY > LOWER_BND) { if (skip) { tCoords[0] = closeCoord[0] = lastX; tCoords[1] = closeCoord[1] = lastY; subpathStarted = true; skip = false; } else { ProcessLine(hnd, tCoords[0], tCoords[1], tCoords[2], tCoords[3], pixelInfo); tCoords[0] = lastX; tCoords[1] = lastY; } } break; case PathIterator.SEG_QUADTO: tCoords[2] = coords[0] + transXf; tCoords[3] = coords[1] + transYf; lastX = tCoords[4] = coords[2] + transXf; lastY = tCoords[5] = coords[3] + transYf; /* Checking SEG_QUADTO coordinates if they are out of the * [LOWER_BND, UPPER_BND] range. This check also handles * NaN and Infinity values. Ignoring current path segment * in case of invalid endpoints's data. Equivalent to * the SEG_LINETO if endpoint coordinates are valid but * there are invalid data among other coordinates */ if (lastX < UPPER_BND && lastX > LOWER_BND && lastY < UPPER_BND && lastY > LOWER_BND) { if (skip) { tCoords[0] = closeCoord[0] = lastX; tCoords[1] = closeCoord[1] = lastY; subpathStarted = true; skip = false; } else { if (tCoords[2] < UPPER_BND && tCoords[2] > LOWER_BND && tCoords[3] < UPPER_BND && tCoords[3] > LOWER_BND) { ProcessQuad(hnd, tCoords, pixelInfo); } else { ProcessLine(hnd, tCoords[0], tCoords[1], tCoords[4], tCoords[5], pixelInfo); } tCoords[0] = lastX; tCoords[1] = lastY; } } break; case PathIterator.SEG_CUBICTO: tCoords[2] = coords[0] + transXf; tCoords[3] = coords[1] + transYf; tCoords[4] = coords[2] + transXf; tCoords[5] = coords[3] + transYf; lastX = tCoords[6] = coords[4] + transXf; lastY = tCoords[7] = coords[5] + transYf; /* Checking SEG_CUBICTO coordinates if they are out of the * [LOWER_BND, UPPER_BND] range. This check also handles * NaN and Infinity values. Ignoring current path segment * in case of invalid endpoints's data. Equivalent to * the SEG_LINETO if endpoint coordinates are valid but * there are invalid data among other coordinates */ if (lastX < UPPER_BND && lastX > LOWER_BND && lastY < UPPER_BND && lastY > LOWER_BND) { if (skip) { tCoords[0] = closeCoord[0] = tCoords[6]; tCoords[1] = closeCoord[1] = tCoords[7]; subpathStarted = true; skip = false; } else { if (tCoords[2] < UPPER_BND && tCoords[2] > LOWER_BND && tCoords[3] < UPPER_BND && tCoords[3] > LOWER_BND && tCoords[4] < UPPER_BND && tCoords[4] > LOWER_BND && tCoords[5] < UPPER_BND && tCoords[5] > LOWER_BND) { ProcessCubic(hnd, tCoords, pixelInfo); } else { ProcessLine(hnd, tCoords[0], tCoords[1], tCoords[6], tCoords[7], pixelInfo); } tCoords[0] = lastX; tCoords[1] = lastY; } } break; case PathIterator.SEG_CLOSE: if (subpathStarted && !skip) { skip = false; if (tCoords[0] != closeCoord[0] || tCoords[1] != closeCoord[1]) { ProcessLine(hnd, tCoords[0], tCoords[1], closeCoord[0], closeCoord[1], pixelInfo); /* Storing last path's point for using in following * segments without initial moveTo */ tCoords[0] = closeCoord[0]; tCoords[1] = closeCoord[1]; } hnd.processEndSubPath(); } break; } pi.next(); } /* Performing closing of the unclosed segments */ if (subpathStarted & !skip) { if (hnd.clipMode == PH_MODE_FILL_CLIP) { if (tCoords[0] != closeCoord[0] || tCoords[1] != closeCoord[1]) { ProcessLine(hnd, tCoords[0], tCoords[1], closeCoord[0], closeCoord[1], pixelInfo); } } hnd.processEndSubPath(); } return true; } private static class Point { public int x; public int y; public boolean lastPoint; public Point prev; public Point next; public Point nextByY; public Edge edge; public Point(int x, int y, boolean lastPoint) { this.x = x; this.y = y; this.lastPoint = lastPoint; } }; private static class Edge { int x; int dx; Point p; int dir; Edge prev; Edge next; public Edge(Point p, int x, int dx, int dir) { this.p = p; this.x = x; this.dx = dx; this.dir = dir; } }; /* Size of the default buffer in the FillData structure. This buffer is * replaced with heap allocated in case of large paths. */ private static final int DF_MAX_POINT = 256; /* Following class accumulates points of the non-continuous flattened * general path during iteration through the origin path's segments . The * end of the each subpath is marked as lastPoint flag set at the last * point */ private static class FillData { List plgPnts; public int plgYMin; public int plgYMax; public FillData() { plgPnts = new Vector(DF_MAX_POINT); } public void addPoint(int x, int y, boolean lastPoint) { if (plgPnts.size() == 0) { plgYMin = plgYMax = y; } else { plgYMin = (plgYMin > y)?y:plgYMin; plgYMax = (plgYMax < y)?y:plgYMax; } plgPnts.add(new Point(x, y, lastPoint)); } public boolean isEmpty() { return plgPnts.size() == 0; } public boolean isEnded() { return plgPnts.get(plgPnts.size() - 1).lastPoint; } public boolean setEnded() { return plgPnts.get(plgPnts.size() - 1).lastPoint = true; } } private static class ActiveEdgeList { Edge head; public boolean isEmpty() { return (head == null); } public void insert(Point pnt, int cy) { Point np = pnt.next; int X1 = pnt.x, Y1 = pnt.y; int X2 = np.x, Y2 = np.y; Edge ne; if (Y1 == Y2) { /* Skipping horizontal segments */ return; } else { int dX = X2 - X1; int dY = Y2 - Y1; int stepx, x0, dy, dir; if (Y1 < Y2) { x0 = X1; dy = cy - Y1; dir = -1; } else { // (Y1 > Y2) x0 = X2; dy = cy - Y2; dir = 1; } /* We need to worry only about dX because dY is in denominator * and abs(dy) < MDP_MULT (cy is a first scanline of the scan * converted segment and we subtract y coordinate of the * nearest segment's end from it to obtain dy) */ if (dX > CALC_UBND || dX < CALC_LBND) { stepx = (int)((((double)dX)*MDP_MULT)/dY); x0 = x0 + (int)((((double)dX)*dy)/dY); } else { stepx = (dX<= q.x) { wasSwap = true; if (p == head) { temp = q.next; q.next = p; p.next = temp; head = q; r = q; } else { temp = q.next; q.next = p; p.next = temp; r.next = q; r = q; } } else { r = p; p = p.next; } q = p.next; if (q == s) s = p; } } // correction of the back links in the double linked edge list p = head; q = null; while (p != null) { p.prev = q; q = p; p = p.next; } } } private static void FillPolygon(FillProcessHandler hnd, int fillRule) { int k, y, n; boolean drawing; Edge active; int rightBnd = hnd.dhnd.xMax - 1; FillData fd = hnd.fd; int yMin = fd.plgYMin; int yMax = fd.plgYMax; int hashSize = ((yMax - yMin)>>MDP_PREC) + 4; /* Because of support of the KEY_STROKE_CONTROL hint we are performing * shift of the coordinates at the higher level */ int hashOffset = ((yMin - 1) & MDP_W_MASK); /* Winding counter */ int counter; /* Calculating mask to be applied to the winding counter */ int counterMask = (fillRule == PathIterator.WIND_NON_ZERO)? -1:1; int pntOffset; List pnts = fd.plgPnts; n = pnts.size(); if (n <=1) return; Point[] yHash = new Point[hashSize]; /* Creating double linked list (prev, next links) describing path order * and hash table with points which fall between scanlines. nextByY * link is used for the points which are between same scanlines. * Scanlines are passed through the centers of the pixels. */ Point curpt = pnts.get(0); curpt.prev = null; for (int i = 0; i < n - 1; i++) { curpt = pnts.get(i); Point nextpt = pnts.get(i + 1); int curHashInd = (curpt.y - hashOffset - 1) >> MDP_PREC; curpt.nextByY = yHash[curHashInd]; yHash[curHashInd] = curpt; curpt.next = nextpt; nextpt.prev = curpt; } Point ept = pnts.get(n - 1); int curHashInd = (ept.y - hashOffset - 1) >> MDP_PREC; ept.nextByY = yHash[curHashInd]; yHash[curHashInd] = ept; ActiveEdgeList activeList = new ActiveEdgeList(); for (y=hashOffset + MDP_MULT,k = 0; y<=yMax && k < hashSize; y += MDP_MULT, k++) { for(Point pt = yHash[k];pt != null; pt=pt.nextByY) { /* pt.y should be inside hashed interval * assert(y-MDP_MULT <= pt.y && pt.y < y); */ if (pt.prev != null && !pt.prev.lastPoint) { if (pt.prev.edge != null && pt.prev.y <= y) { activeList.delete(pt.prev.edge); pt.prev.edge = null; } else if (pt.prev.y > y) { activeList.insert(pt.prev, y); } } if (!pt.lastPoint && pt.next != null) { if (pt.edge != null && pt.next.y <= y) { activeList.delete(pt.edge); pt.edge = null; } else if (pt.next.y > y) { activeList.insert(pt, y); } } } if (activeList.isEmpty()) continue; activeList.sort(); counter = 0; drawing = false; int xl, xr; xl = xr = hnd.dhnd.xMin; Edge curEdge = activeList.head; while (curEdge != null) { counter += curEdge.dir; if ((counter & counterMask) != 0 && !drawing) { xl = (curEdge.x + MDP_MULT - 1)>>MDP_PREC; drawing = true; } if ((counter & counterMask) == 0 && drawing) { xr = (curEdge.x - 1) >> MDP_PREC; if (xl <= xr) { hnd.dhnd.drawScanline(xl, xr, y >> MDP_PREC); } drawing = false; } curEdge.x += curEdge.dx; curEdge = curEdge.next; } /* Performing drawing till the right boundary (for correct * rendering shapes clipped at the right side) */ if (drawing && xl <= rightBnd) { /* Support of the strokeHint was added into the * draw and fill methods of the sun.java2d.pipe.LoopPipe */ hnd.dhnd.drawScanline(xl, rightBnd, y >> MDP_PREC); } } } private static class FillProcessHandler extends ProcessHandler { FillData fd; /* Note: For more easy reading of the code below each java version of * the macros from the ProcessPath.c preceded by the commented * origin call containing verbose names of the parameters */ public void processFixedLine(int x1, int y1, int x2, int y2, int[] pixelInfo, boolean checkBounds, boolean endSubPath) { int outXMin, outXMax, outYMin, outYMax; int res; /* There is no need to round line coordinates to the forward * differencing precision anymore. Such a rounding was used for * preventing the curve go out the endpoint (this sometimes does * not help). The problem was fixed in the forward differencing * loops. */ if (checkBounds) { boolean lastClipped; /* This function is used only for filling shapes, so there is no * check for the type of clipping */ int c[] = new int[]{x1, y1, x2, y2, 0, 0}; outXMin = (int)(dhnd.xMinf * MDP_MULT); outXMax = (int)(dhnd.xMaxf * MDP_MULT); outYMin = (int)(dhnd.yMinf * MDP_MULT); outYMax = (int)(dhnd.yMaxf * MDP_MULT); /* * TESTANDCLIP(outYMin, outYMax, y1, x1, y2, x2, res); */ res = TESTANDCLIP(outYMin, outYMax, c, 1, 0, 3, 2); if (res == CRES_INVISIBLE) return; /* * TESTANDCLIP(outYMin, outYMax, y2, x2, y1, x1, res); */ res = TESTANDCLIP(outYMin, outYMax, c, 3, 2, 1, 0); if (res == CRES_INVISIBLE) return; lastClipped = IS_CLIPPED(res); /* Clamping starting from first vertex of the the processed * segment * * CLIPCLAMP(outXMin, outXMax, x1, y1, x2, y2, x3, y3, res); */ res = CLIPCLAMP(outXMin, outXMax, c, 0, 1, 2, 3, 4, 5); /* Clamping only by left boundary */ if (res == CRES_MIN_CLIPPED) { processFixedLine(c[4], c[5], c[0], c[1], pixelInfo, false, lastClipped); } else if (res == CRES_INVISIBLE) { return; } /* Clamping starting from last vertex of the the processed * segment * * CLIPCLAMP(outXMin, outXMax, x2, y2, x1, y1, x3, y3, res); */ res = CLIPCLAMP(outXMin, outXMax, c, 2, 3, 0, 1, 4, 5); /* Checking if there was a clip by right boundary */ lastClipped = lastClipped || (res == CRES_MAX_CLIPPED); processFixedLine(c[0], c[1], c[2], c[3], pixelInfo, false, lastClipped); /* Clamping only by left boundary */ if (res == CRES_MIN_CLIPPED) { processFixedLine(c[2], c[3], c[4], c[5], pixelInfo, false, lastClipped); } return; } /* Adding first point of the line only in case of empty or just * finished path */ if (fd.isEmpty() || fd.isEnded()) { fd.addPoint(x1, y1, false); } fd.addPoint(x2, y2, false); if (endSubPath) { fd.setEnded(); } } FillProcessHandler(DrawHandler dhnd) { super(dhnd, PH_MODE_FILL_CLIP); this.fd = new FillData(); } public void processEndSubPath() { if (!fd.isEmpty()) { fd.setEnded(); } } } }