Hi Jim.

I implemented your bucket sort idea. I'm not just using the buckets
to remove the y-sort. I use them in the iteration through the scanlines
too. What happens is that on any iteration, the active list is the
doubly linked list buckets[nextY-boundsMinY]. I did this because I thought
less memory would need to be moved around compared to when we just kept
the active list "pointers" in an array. For example, with doubly linked
lists we can implement insertion sort with O(1) writes. With arrays we
have to use O(n) writes. This also allows us to get rid of the the edgePtrs
array.

I ran some benchmarks, and unfortunately I was wrong, somehwere. All the tests
are at least 10% slower than the insertion sort version of what we have now.
I can't immediately see why this is. The only thing I can think of is that
there are a lot of float -> int casts, but then again, I don't know how
slow this operation is. It would be good if it's because of the casts because
it would no longer be an issue when we convert to native.

I alo made an unrelated change: I now find the orientation and update x0,y0
much earlier than we used to. The way I was doing it before was silly.

Regards,
Denis.

----- "Jim Graham" <james.gra...@oracle.com> wrote:

> Hi Denis,
> 
> Good news!
> 
> On 10/28/2010 3:27 PM, Denis Lila wrote:
> >> If we moved to a Curve class or some other way to
> >> consolidate the 3 lists (may be easier in native code), this might
> win
> >> in more cases...
> >
> > Does that mean you no longer think we should flatten every curve as
> soon
> > as we get it?
> 
> No, I was just discussion the feasibility of that one suggestion in
> the 
> context of all of the possibilities of whether or not we took the
> other 
> choices.  If you think that flattening will pay off then we don't have
> 
> to worry about the 3 lists.  It was just that when I hacked it in, I 
> still had your 3 lists and so the pros and cons of horizontal edge 
> sorting were relative to that version of the renderer...
> 
>                       ...jim
/*
 * Copyright (c) 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.
 */

package sun.java2d.pisces;

import java.util.Iterator;

import sun.awt.geom.PathConsumer2D;

public class Renderer implements PathConsumer2D {

    private class ScanlineIterator {

        private int[] crossings;

        // crossing bounds. The bounds are not necessarily tight (the scan line
        // at minY, for example, might have no crossings). The x bounds will
        // be accumulated as crossings are computed.
        private final int maxY;
        private int nextY;

        private static final int INIT_CROSSINGS_SIZE = 10;

        private ScanlineIterator() {
            crossings = new int[INIT_CROSSINGS_SIZE];

            // We don't care if we clip some of the line off with ceil, since
            // no scan line crossings will be eliminated (in fact, the ceil is
            // the y of the first scan line crossing).
            final int minY = getFirstScanLineCrossing();
            nextY = minY;
            maxY = Math.min(boundsMaxY, (int)Math.ceil(edgeMaxY));
        }

        private int next() {
            // TODO: make function that convert from y value to bucket idx?
            int bucket = nextY - boundsMinY;
            final int numLeft = removeEdgesBelowY(bucket, edgeBuckets[bucket], nextY);

            crossings = Helpers.widenArray(crossings, 0, numLeft);

            for (int ecur = edgeBuckets[bucket]; ecur != NULL; ecur = (int)edges[ecur+NEXT]) {
                makeSortedByCurX(bucket, ecur);
            }

            int crossingIdx = 0;
            // Now every edge between lo and hi crosses nextY. Compute it's
            // crossing and put it in the crossings array.
            int prev = NULL;
            for (int ecur = edgeBuckets[bucket]; ecur != NULL; ecur = (int)edges[ecur+NEXT]) {
                // insertion sorts both edges and crossings.
                int cross = ((int) edges[ecur+CURX]) << 1;
                if (edges[ecur+OR] > 0) {
                    cross |= 1;
                }
                edges[ecur+CURY] += 1;
                edges[ecur+CURX] += edges[ecur+SLOPE];
                crossings[crossingIdx++] = cross;
                prev = ecur;
            }

            nextY++;
            if (prev != NULL && nextY < maxY) {
                // if this is executed when nextY == maxY
                // there would be an array index out of bounds exception.
                spliceListIntoList(bucket, prev, bucket+1);
            }
            return crossingIdx;
        }

        private boolean hasNext() {
            return nextY < maxY;
        }

        private int curY() {
            return nextY - 1;
        }
    }


//////////////////////////////////////////////////////////////////////////////
//  EDGE LIST
//////////////////////////////////////////////////////////////////////////////
// TODO(maybe): very tempting to use fixed point here. A lot of opportunities
// for shifts and just removing certain operations altogether.

    // common to all types of input path segments.
    private static final int YMIN = 0;
    private static final int YMAX = 1;
    private static final int CURX = 2;
    // this and OR are meant to be indeces into "int" fields, but arrays must
    // be homogenous, so every field is a float. However floats can represent
    // exactly up to 26 bit ints, so we're ok.
    private static final int CURY = 3;
    private static final int OR   = 4;
    private static final int SLOPE = 5;
    private static final int NEXT = 6;
    private static final int PREV = 7;

    private float edgeMinY = Float.POSITIVE_INFINITY;
    private float edgeMaxY = Float.NEGATIVE_INFINITY;
    private float edgeMinX = Float.POSITIVE_INFINITY;
    private float edgeMaxX = Float.NEGATIVE_INFINITY;

    private static final int SIZEOF_EDGE = 8;
    private static final int NULL = -SIZEOF_EDGE-1;
    private float[] edges = null;
    private int[] edgeBuckets = null;
    private int numEdges;

    private static final float DEC_BND = 20f;
    private static final float INC_BND = 8f;

    // splice the list starting at node beg and ending at node end (inclusive)
    // to the start of the list in bucket bucket.
    // preconditions: end must be the last node in the fromBucket linked list.
    // It must not be null.
    private void spliceListIntoList(int fromBucket, int end, int toBucket) {
        assert end != NULL && edges[end+NEXT] == NULL;
        int beg = edgeBuckets[fromBucket];

        int first = edgeBuckets[toBucket];
        edgeBuckets[toBucket] = beg;
        edges[end+NEXT] = first;

        if (first != NULL) {
            edges[first+PREV] = end;
        }
    }

    // assuming the buck list is sorted from the first node up to ptr.prev,
    // rearrange it so that it is sorted including ptr.
    // TODO: make sorting parameter customizable ?
    private void makeSortedByCurX(int bucket, final int ptr) {
        assert ptr != NULL;
        if (edges[ptr+PREV] == NULL) { // ptr is the first edge in the bucket
            assert edgeBuckets[bucket] == ptr;
            // all 1 element lists are already sorted
            return;
        }
        float x = edges[ptr+CURX];

        int prev = (int)edges[ptr+PREV];
        while (prev != NULL && x < edges[prev+CURX]) {
            prev = (int)edges[prev+PREV];
        }
        final int cur = (int)(prev == NULL ? edgeBuckets[bucket] : edges[prev+NEXT]);
        if (cur != ptr) {
//            removeEdge(bucket, ptr);
//            insertEdge(bucket, prev, ptr);

            // equivalent to the above 2 lines, but inlined and optimized
            final int ptrPredecessor = (int)edges[ptr+PREV];
            final int next = (int)edges[ptr+NEXT];
            edges[ptrPredecessor+NEXT] = next;
            if (next != NULL) {
                edges[next+PREV] = ptrPredecessor;
            }

            if (prev == NULL) {
                edgeBuckets[bucket] = ptr;
            } else {
                edges[prev+NEXT] = ptr;
            }
            edges[ptr+NEXT] = cur;
            edges[ptr+PREV] = prev;
            edges[cur+PREV] = ptr;
        }
    }

    // insert edge cur right after prev.
    @SuppressWarnings("unused")
    private void insertEdge(int bucket, int prev, int cur) {
        if (prev == NULL) {
            final int first = edgeBuckets[bucket];
            edgeBuckets[bucket] = cur;
            edges[cur+NEXT] = first;
            edges[cur+PREV] = NULL;
            if (first != NULL) {
                edges[first+PREV] = cur;
            }
        } else {
            int next = (int)edges[prev+NEXT];
            edges[prev+NEXT] = cur;
            edges[cur+NEXT] = next;
            edges[cur+PREV] = prev;
            if (next != NULL) {
                edges[next+PREV] = cur;
            }
        }
    }

    // Removes all edges with YMAX<y from the linked list in edgeBuckets[bucket]
    // starting at node ptr.
    private int removeEdgesBelowY(int bucket, int ptr, float y) {
        assert ptr != NULL;
        int numLeft = 0;
        while(ptr != NULL) {
            if (edges[ptr+YMAX] <= y) {
                ptr = removeEdge(bucket, ptr);
            } else {
                ptr = (int)edges[ptr+NEXT];
                numLeft++;
            }
        }
        return numLeft;
    }

    private int removeEdge(int bucket, int toRemove) {
        assert toRemove != NULL;

        final int prev = (int)edges[toRemove+PREV];
        final int next = (int)edges[toRemove+NEXT];

        if (prev == NULL) {
            edgeBuckets[bucket] = next;
        } else {
            edges[prev+NEXT] = next;
        }
        if (next != NULL) {
            edges[next+PREV] = prev;
        }
        return next;
    }

    // each bucket is a linked list. this method adds eptr to the
    // start "bucket"th linked list.
    private void addEdgeToBucket(final int eptr, final int bucket) {
        // we could implement this in terms of insertEdge, but this is a special
        // case, so we optimize a bit.
        final int firstEdge = edgeBuckets[bucket];
        edgeBuckets[bucket] = eptr;
        edges[eptr+NEXT] = firstEdge;
        if (firstEdge != NULL) {
            edges[firstEdge+PREV] = eptr;
        }
        edges[eptr+PREV] = NULL;
    }

    // Flattens using adaptive forward differencing. This only carries out
    // one iteration of the AFD loop. All it does is update AFD variables (i.e.
    // X0, Y0, D*[X|Y], COUNT; not variables used for computing scanline crossings).
    private void quadBreakIntoLinesAndAdd(float[] pts, final int or) {
        final int countlg = 3;
        int count = 1 << countlg;

        // the dx and dy refer to forward differencing variables, not the last
        // coefficients of the "points" polynomial
        float ddx, ddy, dx, dy;
        c.set(pts, 6);

        ddx = c.dbx / (1 << (2 * countlg));
        ddy = c.dby / (1 << (2 * countlg));
        dx = c.bx / (1 << (2 * countlg)) + c.cx / (1 << countlg);
        dy = c.by / (1 << (2 * countlg)) + c.cy / (1 << countlg);

        float x0, y0, x1, y1;
        x0 = x1 = pts[0];
        y0 = y1 = pts[1];
        while (count > 0) {
            while (Math.abs(ddx) > DEC_BND || Math.abs(ddy) > DEC_BND) {
                ddx = ddx / 4;
                ddy = ddy / 4;
                dx = (dx - ddx) / 2;
                dy = (dy - ddy) / 2;
                count <<= 1;
            }
            // can only do this on even "count" values, because we must divide count by 2
            while (count % 2 == 0 && Math.abs(dx) <= INC_BND && Math.abs(dy) <= INC_BND) {
                dx = 2 * dx + ddx;
                dy = 2 * dy + ddy;
                ddx = 4 * ddx;
                ddy = 4 * ddy;
                count >>= 1;
            }
            count--;
            if (count > 0) {
                x1 += dx;
                dx += ddx;
                y1 += dy;
                dy += ddy;
            } else {
                x1 = pts[4];
                y1 = pts[5];
            }
            addLine(x0, y0, x1, y1, or);
            x0 = x1;
            y0 = y1;
        }
    }

    private void curveBreakIntoLinesAndAdd(float[] pts, final int or) {
        final int countlg = 3;
        int count = 1 << countlg;

        // the dx and dy refer to forward differencing variables, not the last
        // coefficients of the "points" polynomial
        float dddx, dddy, ddx, ddy, dx, dy;
        c.set(pts, 8);
        dddx = 2f * c.dax / (1 << (3 * countlg));
        dddy = 2f * c.day / (1 << (3 * countlg));

        ddx = dddx + c.dbx / (1 << (2 * countlg));
        ddy = dddy + c.dby / (1 << (2 * countlg));
        dx = c.ax / (1 << (3 * countlg)) + c.bx / (1 << (2 * countlg)) + c.cx / (1 << countlg);
        dy = c.ay / (1 << (3 * countlg)) + c.by / (1 << (2 * countlg)) + c.cy / (1 << countlg);

        float x0, y0, x1, y1;
        x0 = x1 = pts[0];
        y0 = y1 = pts[1];
        while (count > 0) {
            while (Math.abs(ddx) > DEC_BND || Math.abs(ddy) > DEC_BND) {
                dddx /= 8;
                dddy /= 8;
                ddx = ddx/4 - dddx;
                ddy = ddy/4 - dddy;
                dx = (dx - ddx) / 2;
                dy = (dy - ddy) / 2;
                count <<= 1;
            }
            // can only do this on even "count" values, because we must divide count by 2
            while (count % 2 == 0 && Math.abs(dx) <= INC_BND && Math.abs(dy) <= INC_BND) {
                dx = 2 * dx + ddx;
                dy = 2 * dy + ddy;
                ddx = 4 * (ddx + dddx);
                ddy = 4 * (ddy + dddy);
                dddx = 8 * dddx;
                dddy = 8 * dddy;
                count >>= 1;
            }
            count--;
            if (count > 0) {
                x1 += dx;
                dx += ddx;
                ddx += dddx;
                y1 += dy;
                dy += ddy;
                ddy += dddy;
            } else {
                x1 = pts[6];
                y1 = pts[7];
            }
            addLine(x0, y0, x1, y1, or);
            x0 = x1;
            y0 = y1;
        }
    }

    // Preconditions: y2 > y1 and the curve must cross some scanline
    // i.e.: y1 <= y < y2 for some y such that boundsMinY <= y < boundsMaxY
    private void addLine(float x1, float y1, float x2, float y2, int or) {
        final int firstCrossing = (int)Math.max(Math.ceil(y1), boundsMinY);
        if (firstCrossing >= boundsMaxY) {
            return;
        }
        final int ptr = numEdges * SIZEOF_EDGE;
        edges = Helpers.widenArray(edges, ptr, SIZEOF_EDGE);
        numEdges++;
        edges[ptr+YMIN] = y1;
        edges[ptr+YMAX] = y2;
        edges[ptr+OR] = or;
        edges[ptr+CURY] = firstCrossing;
        edges[ptr+SLOPE] = (x2 - x1) / (y2 - y1);
        edges[ptr+CURX] = x1 + (edges[ptr+CURY] - y1) * edges[ptr+SLOPE];
        final int bucketIdx = firstCrossing - boundsMinY;
        addEdgeToBucket(ptr, bucketIdx);
    }

    // preconditions: should not be called before the last line has been added
    // to the edge list (even though it will return a correct answer at that
    // point in time, it's not meant to be used that way).
    private int getFirstScanLineCrossing() {
        return Math.max(boundsMinY, (int)Math.ceil(edgeMinY));
    }

    // precondition: the curve in pts must be monotonic and increasing in y.
    private void somethingTo(float[] pts, final int type, final int or) {
        final float xs = pts[0], ys = pts[1], xf = pts[type-2], yf = pts[type-1];
        double firstScanline = Math.ceil(ys);
        if (firstScanline >= Math.ceil(yf) ||
            firstScanline >= boundsMaxY || yf <= boundsMinY)
        {
            return;
        }

        if (ys < edgeMinY) { edgeMinY = ys; }
        if (yf > edgeMaxY) { edgeMaxY = yf; }

        int minXidx = (pts[0] < pts[type-2] ? 0 : type - 2);
        float minX = pts[minXidx];
        float maxX = pts[type - 2 - minXidx];
        if (minX < edgeMinX) { edgeMinX = minX; }
        if (maxX > edgeMaxX) { edgeMaxX = maxX; }

        switch (type) {
        case 4:
            addLine(xs, ys, xf, yf, or);
            break;
        case 6:
            quadBreakIntoLinesAndAdd(pts, or);
            break;
        case 8:
            curveBreakIntoLinesAndAdd(pts, or);
            break;
        default:
            throw new InternalError();
        }
    }

// END EDGE LIST
//////////////////////////////////////////////////////////////////////////////


    public static final int WIND_EVEN_ODD = 0;
    public static final int WIND_NON_ZERO = 1;

    // Antialiasing
    final private int SUBPIXEL_LG_POSITIONS_X;
    final private int SUBPIXEL_LG_POSITIONS_Y;
    final private int SUBPIXEL_POSITIONS_X;
    final private int SUBPIXEL_POSITIONS_Y;
    final private int SUBPIXEL_MASK_X;
    final private int SUBPIXEL_MASK_Y;
    final int MAX_AA_ALPHA;

    // Cache to store RLE-encoded coverage mask of the current primitive
    PiscesCache cache;

    // Bounds of the drawing region, at subpixel precision.
    private final int boundsMinX, boundsMinY, boundsMaxX, boundsMaxY;

    // Current winding rule
    private final int windingRule;

    // Current drawing position, i.e., final point of last segment
    private float x0, y0;

    // Position of most recent 'moveTo' command
    private float pix_sx0, pix_sy0;

    public Renderer(int subpixelLgPositionsX, int subpixelLgPositionsY,
                    int pix_boundsX, int pix_boundsY,
                    int pix_boundsWidth, int pix_boundsHeight,
                    int windingRule)
    {
        this.SUBPIXEL_LG_POSITIONS_X = subpixelLgPositionsX;
        this.SUBPIXEL_LG_POSITIONS_Y = subpixelLgPositionsY;
        this.SUBPIXEL_MASK_X = (1 << (SUBPIXEL_LG_POSITIONS_X)) - 1;
        this.SUBPIXEL_MASK_Y = (1 << (SUBPIXEL_LG_POSITIONS_Y)) - 1;
        this.SUBPIXEL_POSITIONS_X = 1 << (SUBPIXEL_LG_POSITIONS_X);
        this.SUBPIXEL_POSITIONS_Y = 1 << (SUBPIXEL_LG_POSITIONS_Y);
        this.MAX_AA_ALPHA = (SUBPIXEL_POSITIONS_X * SUBPIXEL_POSITIONS_Y);

        this.windingRule = windingRule;

        this.boundsMinX = pix_boundsX * SUBPIXEL_POSITIONS_X;
        this.boundsMinY = pix_boundsY * SUBPIXEL_POSITIONS_Y;
        this.boundsMaxX = (pix_boundsX + pix_boundsWidth) * SUBPIXEL_POSITIONS_X;
        this.boundsMaxY = (pix_boundsY + pix_boundsHeight) * SUBPIXEL_POSITIONS_Y;

        edgeBuckets = new int[boundsMaxY - boundsMinY];
        java.util.Arrays.fill(edgeBuckets, NULL);
    }

    private float tosubpixx(float pix_x) {
        return pix_x * SUBPIXEL_POSITIONS_X;
    }
    private float tosubpixy(float pix_y) {
        return pix_y * SUBPIXEL_POSITIONS_Y;
    }

    public void moveTo(float pix_x0, float pix_y0) {
        closePath();
        this.pix_sx0 = pix_x0;
        this.pix_sy0 = pix_y0;
        this.y0 = tosubpixy(pix_y0);
        this.x0 = tosubpixx(pix_x0);
    }

    private final float[][] pts = new float[2][8];
    private final float[] ts = new float[4];

    private static void invertPolyPoints(float[] pts, int off, int type) {
        for (int i = off, j = off + type - 2; i < j; i += 2, j -= 2) {
            float tmp = pts[i];
            pts[i] = pts[j];
            pts[j] = tmp;
            tmp = pts[i+1];
            pts[i+1] = pts[j+1];
            pts[j+1] = tmp;
        }
    }

    // return orientation before making the curve upright.
    private static int makeMonotonicCurveUpright(float[] pts, int off, int type) {
        float y0 = pts[off + 1];
        float y1 = pts[off + type - 1];
        if (y0 > y1) {
            invertPolyPoints(pts, off, type);
            return -1;
        } else if (y0 < y1) {
            return 1;
        }
        return 0;
    }

    public void lineTo(float pix_x1, float pix_y1) {
        pts[0][0] = x0; pts[0][1] = y0;
        x0 = tosubpixx(pix_x1);
        y0 = tosubpixy(pix_y1);
        pts[0][2] = x0; pts[0][3] = y0;
        int or = makeMonotonicCurveUpright(pts[0], 0, 4);
        somethingTo(pts[0], 4, or);
    }

    Curve c = new Curve();
    private void curveOrQuadTo(int type) {
        c.set(pts[0], type);
        int numTs = c.dxRoots(ts, 0);
        numTs += c.dyRoots(ts, numTs);
        numTs = Helpers.filterOutNotInAB(ts, 0, numTs, 0, 1);
        Helpers.isort(ts, 0, numTs);

        Iterator<float[]> it = Curve.breakPtsAtTs(pts, type, ts, numTs);
        while(it.hasNext()) {
            float[] curCurve = it.next();
            int or = makeMonotonicCurveUpright(curCurve, 0, type);
            somethingTo(curCurve, type, or);
        }
    }

    @Override public void curveTo(float x1, float y1,
                                  float x2, float y2,
                                  float x3, float y3)
    {
        pts[0][0] = x0; pts[0][1] = y0;
        pts[0][2] = tosubpixx(x1); pts[0][3] = tosubpixy(y1);
        pts[0][4] = tosubpixx(x2); pts[0][5] = tosubpixy(y2);
        x0 = tosubpixx(x3);
        y0 = tosubpixy(y3);
        pts[0][6] = x0; pts[0][7] = y0;
        curveOrQuadTo(8);
    }

    @Override public void quadTo(float x1, float y1, float x2, float y2) {
        pts[0][0] = x0; pts[0][1] = y0;
        pts[0][2] = tosubpixx(x1); pts[0][3] = tosubpixy(y1);
        x0 = tosubpixx(x2);
        y0 = tosubpixy(y2);
        pts[0][4] = x0; pts[0][5] = y0;
        curveOrQuadTo(6);
    }

    public void closePath() {
        // lineTo expects its input in pixel coordinates.
        lineTo(pix_sx0, pix_sy0);
    }

    public void pathDone() {
        closePath();
    }


    @Override
    public long getNativeConsumer() {
        throw new InternalError("Renderer does not use a native consumer.");
    }

    private void _endRendering(final int pix_bboxx0, final int pix_bboxy0,
                               final int pix_bboxx1, final int pix_bboxy1)
    {
        // Mask to determine the relevant bit of the crossing sum
        // 0x1 if EVEN_ODD, all bits if NON_ZERO
        int mask = (windingRule == WIND_EVEN_ODD) ? 0x1 : ~0x0;

        // add 1 to better deal with the last pixel in a pixel row.
        int width = pix_bboxx1 - pix_bboxx0 + 1;
        int[] alpha = new int[width+1];

        int bboxx0 = pix_bboxx0 << SUBPIXEL_LG_POSITIONS_X;
        int bboxx1 = pix_bboxx1 << SUBPIXEL_LG_POSITIONS_X;

        // Now we iterate through the scanlines. We must tell emitRow the coord
        // of the first non-transparent pixel, so we must keep accumulators for
        // the first and last pixels of the section of the current pixel row
        // that we will emit.
        // We also need to accumulate pix_bbox*, but the iterator does it
        // for us. We will just get the values from it once this loop is done
        int pix_maxX = Integer.MIN_VALUE;
        int pix_minX = Integer.MAX_VALUE;

        int y = boundsMinY; // needs to be declared here so we emit the last row properly.
        ScanlineIterator it = this.new ScanlineIterator();
        for ( ; it.hasNext(); ) {
            int numCrossings = it.next();
            int[] crossings = it.crossings;
            y = it.curY();

            if (numCrossings > 0) {
                int lowx = crossings[0] >> 1;
                int highx = crossings[numCrossings - 1] >> 1;
                int x0 = Math.max(lowx, bboxx0);
                int x1 = Math.min(highx, bboxx1);

                pix_minX = Math.min(pix_minX, x0 >> SUBPIXEL_LG_POSITIONS_X);
                pix_maxX = Math.max(pix_maxX, x1 >> SUBPIXEL_LG_POSITIONS_X);
            }

            int sum = 0;
            int prev = bboxx0;
            for (int i = 0; i < numCrossings; i++) {
                int curxo = crossings[i];
                int curx = curxo >> 1;
                int crorientation = ((curxo & 0x1) == 0x1) ? 1 : -1;
                if ((sum & mask) != 0) {
                    int x0 = Math.max(prev, bboxx0);
                    int x1 = Math.min(curx, bboxx1);
                    if (x0 < x1) {
                        x0 -= bboxx0; // turn x0, x1 from coords to indeces
                        x1 -= bboxx0; // in the alpha array.

                        int pix_x = x0 >> SUBPIXEL_LG_POSITIONS_X;
                        int pix_xmaxm1 = (x1 - 1) >> SUBPIXEL_LG_POSITIONS_X;

                        if (pix_x == pix_xmaxm1) {
                            // Start and end in same pixel
                            alpha[pix_x] += (x1 - x0);
                            alpha[pix_x+1] -= (x1 - x0);
                        } else {
                            int pix_xmax = x1 >> SUBPIXEL_LG_POSITIONS_X;
                            alpha[pix_x] += SUBPIXEL_POSITIONS_X - (x0 & SUBPIXEL_MASK_X);
                            alpha[pix_x+1] += (x0 & SUBPIXEL_MASK_X);
                            alpha[pix_xmax] -= SUBPIXEL_POSITIONS_X - (x1 & SUBPIXEL_MASK_X);
                            alpha[pix_xmax+1] -= (x1 & SUBPIXEL_MASK_X);
                        }
                    }
                }
                sum += crorientation;
                prev = curx;
            }

            // even if this last row had no crossings, alpha will be zeroed
            // from the last emitRow call. But this doesn't matter because
            // maxX < minX, so no row will be emitted to the cache.
            if ((y & SUBPIXEL_MASK_Y) == SUBPIXEL_MASK_Y) {
                emitRow(alpha, y >> SUBPIXEL_LG_POSITIONS_Y, pix_minX, pix_maxX);
                pix_minX = Integer.MAX_VALUE;
                pix_maxX = Integer.MIN_VALUE;
            }
        }

        // Emit final row
        if (pix_maxX >= pix_minX) {
            emitRow(alpha, y >> SUBPIXEL_LG_POSITIONS_Y, pix_minX, pix_maxX);
        }
    }

    public void endRendering() {
        final int bminx = boundsMinX >> SUBPIXEL_LG_POSITIONS_X;
        final int bmaxx = boundsMaxX >> SUBPIXEL_LG_POSITIONS_X;
        final int bminy = boundsMinY >> SUBPIXEL_LG_POSITIONS_Y;
        final int bmaxy = boundsMaxY >> SUBPIXEL_LG_POSITIONS_Y;
        final int eminx = ((int)Math.floor(edgeMinX)) >> SUBPIXEL_LG_POSITIONS_X;
        final int emaxx = ((int)Math.ceil(edgeMaxX)) >> SUBPIXEL_LG_POSITIONS_X;
        final int eminy = ((int)Math.floor(edgeMinY)) >> SUBPIXEL_LG_POSITIONS_Y;
        final int emaxy = ((int)Math.ceil(edgeMaxY)) >> SUBPIXEL_LG_POSITIONS_Y;

        final int minX = Math.max(bminx, eminx);
        final int maxX = Math.min(bmaxx, emaxx);
        final int minY = Math.max(bminy, eminy);
        final int maxY = Math.min(bmaxy, emaxy);
        if (minX > maxX || minY > maxY) {
            this.cache = new PiscesCache(bminx, bminy, bmaxx, bmaxy);
            return;
        }

        this.cache = new PiscesCache(minX, minY, maxX, maxY);
        _endRendering(minX, minY, maxX, maxY);
    }

    public PiscesCache getCache() {
        if (cache == null) {
            throw new InternalError("cache not yet initialized");
        }
        return cache;
    }

    private void emitRow(int[] alphaRow, int pix_y, int pix_from, int pix_to) {
        // Copy rowAA data into the cache if one is present
        if (cache != null) {
            if (pix_to >= pix_from) {
                cache.startRow(pix_y, pix_from);

                // Perform run-length encoding and store results in the cache
                int from = pix_from - cache.bboxX0;
                int to = pix_to - cache.bboxX0;

                int runLen = 1;
                int startVal = alphaRow[from];
                for (int i = from + 1; i <= to; i++) {
                    int nextVal = startVal + alphaRow[i];
                    if (nextVal == startVal) {
                        runLen++;
                    } else {
                        cache.addRLERun(startVal, runLen);
                        runLen = 1;
                        startVal = nextVal;
                    }
                }
                cache.addRLERun(startVal, runLen);
            }
        }
        java.util.Arrays.fill(alphaRow, 0);
    }
}

Reply via email to