jdk/src/share/classes/sun/java2d/pisces/Renderer.java
author dlila
Tue, 10 Aug 2010 13:19:44 -0400
changeset 6284 695b3c6241c8
parent 5506 202f599c92aa
child 6997 3642614e2282
permissions -rw-r--r--
6967436: lines longer than 2^15 can fill window. 6967433: dashed lines broken when using scaling transforms. Summary: converted pisces to floating point. Also, using better AA algorithm Reviewed-by: flar

/*
 * 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.Arrays;

public class Renderer implements LineSink {

///////////////////////////////////////////////////////////////////////////////
// Scan line iterator and edge crossing data.
//////////////////////////////////////////////////////////////////////////////

    private int[] crossings;

    // This is an array of indices into the edge array. It is initialized to
    // [i * SIZEOF_STRUCT_EDGE for i in range(0, edgesSize/SIZEOF_STRUCT_EDGE)]
    // (where range(i, j) is i,i+1,...,j-1 -- just like in python).
    // The reason for keeping this is because we need the edges array sorted
    // by y0, but we don't want to move all that data around, so instead we
    // sort the indices into the edge array, and use edgeIndices to access
    // the edges array. This is meant to simulate a pointer array (hence the name)
    private int[] edgePtrs;

    // 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 int minY, maxY;
    private int minX, maxX;
    private int nextY;

    // indices into the edge pointer list. They indicate the "active" sublist in
    // the edge list (the portion of the list that contains all the edges that
    // cross the next scan line).
    private int lo, hi;

    private static final int INIT_CROSSINGS_SIZE = 50;
    private void ScanLineItInitialize() {
        crossings = new int[INIT_CROSSINGS_SIZE];
        edgePtrs = new int[edgesSize / SIZEOF_STRUCT_EDGE];
        for (int i = 0; i < edgePtrs.length; i++) {
            edgePtrs[i] = i * SIZEOF_STRUCT_EDGE;
        }

        qsort(0, edgePtrs.length - 1);

        // 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).
        nextY = minY = Math.max(boundsMinY, (int)Math.ceil(edgeMinY));
        maxY = Math.min(boundsMaxY, (int)Math.ceil(edgeMaxY));

        for (lo = 0; lo < edgePtrs.length && edges[edgePtrs[lo]+Y1] <= nextY; lo++)
            ;
        for (hi = lo; hi < edgePtrs.length && edges[edgePtrs[hi]+CURY] <= nextY; hi++)
            ; // the active list is *edgePtrs[lo] (inclusive) *edgePtrs[hi] (exclusive)
        for (int i = lo; i < hi; i++) {
            setCurY(edgePtrs[i], nextY);
        }

        // We accumulate X in the iterator because accumulating it in addEdge
        // like we do with Y does not do much good: if there's an edge
        // (0,0)->(1000,10000), and if y gets clipped to 1000, then the x
        // bound should be 100, but the accumulator from addEdge would say 1000,
        // so we'd still have to accumulate the X bounds as we add crossings.
        minX = boundsMinX;
        maxX = boundsMaxX;
    }

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

    private int ScanLineItGoToNextYAndComputeCrossings() {
        // we go through the active list and remove the ones that don't cross
        // the nextY scanline.
        int crossingIdx = 0;
        for (int i = lo; i < hi; i++) {
            if (edges[edgePtrs[i]+Y1] <= nextY) {
                edgePtrs[i] = edgePtrs[lo++];
            }
        }
        if (hi - lo > crossings.length) {
            int newSize = Math.max(hi - lo, crossings.length * 2);
            crossings = Arrays.copyOf(crossings, newSize);
        }
        // Now every edge between lo and hi crosses nextY. Compute it's
        // crossing and put it in the crossings array.
        for (int i = lo; i < hi; i++) {
            addCrossing(nextY, getCurCrossing(edgePtrs[i]), (int)edges[edgePtrs[i]+OR], crossingIdx);
            gotoNextY(edgePtrs[i]);
            crossingIdx++;
        }

        nextY++;
        // Expand active list to include new edges.
        for (; hi < edgePtrs.length && edges[edgePtrs[hi]+CURY] <= nextY; hi++) {
            setCurY(edgePtrs[hi], nextY);
        }

        Arrays.sort(crossings, 0, crossingIdx);
        return crossingIdx;
    }

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

    private void addCrossing(int y, int x, int or, int idx) {
        if (x < minX) {
            minX = x;
        }
        if (x > maxX) {
            maxX = x;
        }
        x <<= 1;
        crossings[idx] = ((or == 1) ? (x | 0x1) : x);
    }


    // quicksort implementation for sorting the edge indices ("pointers")
    // by increasing y0. first, last are indices into the "pointer" array
    // It sorts the pointer array from first (inclusive) to last (inclusive)
    private void qsort(int first, int last) {
        if (last > first) {
            int p = partition(first, last);
            if (first < p - 1) {
                qsort(first, p - 1);
            }
            if (p < last) {
                qsort(p, last);
            }
        }
    }

    // i, j are indices into edgePtrs.
    private int partition(int i, int j) {
        int pivotVal = edgePtrs[i];
        while (i <= j) {
            // edges[edgePtrs[i]+1] is equivalent to (*(edgePtrs[i])).y0 in C
            while (edges[edgePtrs[i]+CURY] < edges[pivotVal+CURY]) { i++; }
            while (edges[edgePtrs[j]+CURY] > edges[pivotVal+CURY]) { j--; }
            if (i <= j) {
                int tmp = edgePtrs[i];
                edgePtrs[i] = edgePtrs[j];
                edgePtrs[j] = tmp;
                i++;
                j--;
            }
        }
        return i;
    }

//============================================================================


//////////////////////////////////////////////////////////////////////////////
//  EDGE LIST
//////////////////////////////////////////////////////////////////////////////

    private static final int INIT_NUM_EDGES = 1000;
    private static final int SIZEOF_STRUCT_EDGE = 5;

    // The following array is a poor man's struct array:
    // it simulates a struct array by having
    // edges[SIZEOF_STRUCT_EDGE * i + j] be the jth field in the ith element
    // of an array of edge structs.
    private float[] edges;
    private int edgesSize; // size of the edge list.
    private static final int Y1    = 0;
    private static final int SLOPE = 1;
    private static final int OR    = 2; // the orientation. This can be -1 or 1.
                                     // -1 means up, 1 means down.
    private static final int CURY  = 3; // j = 5 corresponds to the "current Y".
                             // Each edge keeps track of the last scanline
                             // crossing it computed, and this is the y coord of
                             // that scanline.
    private static final int CURX = 4; //the x coord of the current crossing.

    // Note that while the array is declared as a float[] not all of it's
    // elements should be floats. currentY and Orientation should be ints (or int and
    // byte respectively), but they all need to be the same type. This isn't
    // really a problem because floats can represent exactly all 23 bit integers,
    // which should be more than enough.
    // Note, also, that we only need x1 for slope computation, so we don't need
    // to store it. x0, y0 don't need to be stored either. They can be put into
    // curx, cury, and it's ok if they're lost when curx and cury are changed.
    // We take this undeniably ugly and error prone approach (instead of simply
    // making an Edge class) for performance reasons. Also, it would probably be nicer
    // to have one array for each field, but that would defeat the purpose because
    // it would make poor use of the processor cache, since we tend to access
    // all the fields for one edge at a time.

    private float edgeMinY;
    private float edgeMaxY;


    private void addEdge(float x0, float y0, float x1, float y1) {
        float or = (y0 < y1) ? 1f : -1f; // orientation: 1 = UP; -1 = DOWN
        if (or == -1) {
            float tmp = y0;
            y0 = y1;
            y1 = tmp;
            tmp = x0;
            x0 = x1;
            x1 = tmp;
        }
        // skip edges that don't cross a scanline
        if (Math.ceil(y0) >= Math.ceil(y1)) {
            return;
        }

        int newSize = edgesSize + SIZEOF_STRUCT_EDGE;
        if (edges.length < newSize) {
            edges = Arrays.copyOf(edges, newSize * 2);
        }
        edges[edgesSize+CURX] = x0;
        edges[edgesSize+CURY] = y0;
        edges[edgesSize+Y1] = y1;
        edges[edgesSize+SLOPE] = (x1 - x0) / (y1 - y0);
        edges[edgesSize+OR] = or;
        // the crossing values can't be initialized meaningfully yet. This
        // will have to wait until setCurY is called
        edgesSize += SIZEOF_STRUCT_EDGE;

        // Accumulate edgeMinY and edgeMaxY
        if (y0 < edgeMinY) { edgeMinY = y0; }
        if (y1 > edgeMaxY) { edgeMaxY = y1; }
    }

    // As far as the following methods care, this edges extends to infinity.
    // They can compute the x intersect of any horizontal line.
    // precondition: idx is the index to the start of the desired edge.
    // So, if the ith edge is wanted, idx should be SIZEOF_STRUCT_EDGE * i
    private void setCurY(int idx, int y) {
        // compute the x crossing of edge at idx and horizontal line y
        // currentXCrossing = (y - y0)*slope + x0
        edges[idx + CURX] = (y - edges[idx + CURY]) * edges[idx + SLOPE] + edges[idx+CURX];
        edges[idx + CURY] = (float)y;
    }

    private void gotoNextY(int idx) {
        edges[idx + CURY] += 1f; // i.e. curY += 1
        edges[idx + CURX] += edges[idx + SLOPE]; // i.e. curXCrossing += slope
    }

    private int getCurCrossing(int idx) {
        return (int)edges[idx + CURX];
    }
//====================================================================================

    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
    final PiscesCache cache;

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

    // Pixel bounding box for current primitive
    private int pix_bboxX0, pix_bboxY0, pix_bboxX1, pix_bboxY1;

    // Current winding rule
    final private 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,
                    PiscesCache cache) {
        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.edges = new float[SIZEOF_STRUCT_EDGE * INIT_NUM_EDGES];
        edgeMinY = Float.POSITIVE_INFINITY;
        edgeMaxY = Float.NEGATIVE_INFINITY;
        edgesSize = 0;

        this.windingRule = windingRule;
        this.cache = cache;

        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;

        this.pix_bboxX0 = pix_boundsX;
        this.pix_bboxY0 = pix_boundsY;
        this.pix_bboxX1 = pix_boundsX + pix_boundsWidth;
        this.pix_bboxY1 = pix_boundsY + pix_boundsHeight;
    }

    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) {
        close();
        this.pix_sx0 = pix_x0;
        this.pix_sy0 = pix_y0;
        this.y0 = tosubpixy(pix_y0);
        this.x0 = tosubpixx(pix_x0);
    }

    public void lineJoin() { /* do nothing */ }

    public void lineTo(float pix_x1, float pix_y1) {
        float x1 = tosubpixx(pix_x1);
        float y1 = tosubpixy(pix_y1);

        // Ignore horizontal lines
        if (y0 == y1) {
            this.x0 = x1;
            return;
        }

        addEdge(x0, y0, x1, y1);

        this.x0 = x1;
        this.y0 = y1;
    }

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

    public void end() {
        close();
    }

    private void _endRendering() {
        // 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 = ((boundsMaxX - boundsMinX) >> SUBPIXEL_LG_POSITIONS_X) + 1;
        byte[] alpha = new byte[width+1];

        // 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.
        ScanLineItInitialize();
        for ( ; ScanLineItHasNext(); ) {
            int numCrossings = ScanLineItGoToNextYAndComputeCrossings();
            y = ScanLineItCurrentY();

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

                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 = boundsMinX;
            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, boundsMinX);
                    int x1 = Math.min(curx, boundsMaxX);
                    if (x0 < x1) {
                        x0 -= boundsMinX; // turn x0, x1 from coords to indeces
                        x1 -= boundsMinX; // 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;
            }

            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);
        }
        pix_bboxX0 = minX >> SUBPIXEL_LG_POSITIONS_X;
        pix_bboxX1 = maxX >> SUBPIXEL_LG_POSITIONS_X;
        pix_bboxY0 = minY >> SUBPIXEL_LG_POSITIONS_Y;
        pix_bboxY1 = maxY >> SUBPIXEL_LG_POSITIONS_Y;
    }


    public void endRendering() {
        // Set up the cache to accumulate the bounding box
        if (cache != null) {
            cache.bboxX0 = Integer.MAX_VALUE;
            cache.bboxY0 = Integer.MAX_VALUE;
            cache.bboxX1 = Integer.MIN_VALUE;
            cache.bboxY1 = Integer.MIN_VALUE;
        }

        _endRendering();
    }

    public void getBoundingBox(int[] pix_bbox) {
        pix_bbox[0] = pix_bboxX0;
        pix_bbox[1] = pix_bboxY0;
        pix_bbox[2] = pix_bboxX1 - pix_bboxX0;
        pix_bbox[3] = pix_bboxY1 - pix_bboxY0;
    }

    private void emitRow(byte[] 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, pix_to);

                // Perform run-length encoding and store results in the cache
                int from = pix_from - (boundsMinX >> SUBPIXEL_LG_POSITIONS_X);
                int to = pix_to - (boundsMinX >> SUBPIXEL_LG_POSITIONS_X);

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