| /* |
| * 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; |
| |
| public class Renderer extends LineSink { |
| public static final int WIND_EVEN_ODD = 0; |
| public static final int WIND_NON_ZERO = 1; |
| |
| // Initial edge list size |
| // IMPL_NOTE - restore size after growth |
| public static final int INITIAL_EDGES = 1000; |
| |
| // Recommended maximum scratchpad sizes. The arrays will grow |
| // larger if needed, but when finished() is called they will be released |
| // if they have grown larger than these sizes. |
| public static final int DEFAULT_INDICES_SIZE = 8192; |
| public static final int DEFAULT_CROSSINGS_SIZE = 32*1024; |
| |
| // Antialiasing |
| private int SUBPIXEL_LG_POSITIONS_X; |
| private int SUBPIXEL_LG_POSITIONS_Y; |
| private int SUBPIXEL_MASK_X; |
| private int SUBPIXEL_MASK_Y; |
| private int SUBPIXEL_POSITIONS_X; |
| private int SUBPIXEL_POSITIONS_Y; |
| int MAX_AA_ALPHA; |
| private int MAX_AA_ALPHA_DENOM; |
| private int HALF_MAX_AA_ALPHA_DENOM; |
| private int XSHIFT; |
| private int YSHIFT; |
| private int YSTEP; |
| private int HYSTEP; |
| private int YMASK; |
| |
| private static final int MIN_QUAD_OPT_WIDTH = 100 << 16; |
| |
| // Cache to store RLE-encoded coverage mask of the current primitive |
| PiscesCache cache; |
| |
| // Bounds of the drawing region, at S15.16 precsion |
| private int boundsMinX, boundsMinY, boundsMaxX, boundsMaxY; |
| |
| // Bounds of the current primitive, at subsample precision |
| private int rasterMinX, rasterMaxX, rasterMinY, rasterMaxY; |
| |
| // Pixel bounding box for current primitive |
| private int bboxX0, bboxY0, bboxX1, bboxY1; |
| |
| // Current winding rule |
| private int windingRule; |
| |
| // Current drawing position, i.e., final point of last segment |
| private int x0, y0; |
| |
| // Position of most recent 'moveTo' command |
| private int sx0, sy0; |
| |
| // Buffer to be filled with one row's worth of alpha values |
| private byte[] rowAA; // needs to be short if 16x16 subsampling |
| |
| // Track the number of vertical extrema of the incoming edge list |
| // in order to determine the maximum number of crossings of a |
| // scanline |
| private int firstOrientation; |
| private int lastOrientation; |
| private int flips; |
| |
| // Parameters for emitRow |
| private int alphaWidth; |
| |
| public Renderer() { |
| } |
| |
| public void setAntialiasing(int subpixelLgPositionsX, |
| int subpixelLgPositionsY) { |
| 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.MAX_AA_ALPHA_DENOM = 255*MAX_AA_ALPHA; |
| this.HALF_MAX_AA_ALPHA_DENOM = MAX_AA_ALPHA_DENOM/2; |
| this.XSHIFT = 16 - SUBPIXEL_LG_POSITIONS_X; |
| this.YSHIFT = 16 - SUBPIXEL_LG_POSITIONS_Y; |
| this.YSTEP = 1 << YSHIFT; |
| this.HYSTEP = 1 << (YSHIFT - 1); |
| this.YMASK = ~(YSTEP - 1); |
| } |
| |
| public int getSubpixelLgPositionsX() { |
| return SUBPIXEL_LG_POSITIONS_X; |
| } |
| |
| public int getSubpixelLgPositionsY() { |
| return SUBPIXEL_LG_POSITIONS_Y; |
| } |
| |
| public void setWindingRule(int windingRule) { |
| this.windingRule = windingRule; |
| } |
| |
| public int getWindingRule() { |
| return windingRule; |
| } |
| |
| public void beginRendering(int boundsX, int boundsY, |
| int boundsWidth, int boundsHeight) { |
| lastOrientation = 0; |
| flips = 0; |
| |
| resetEdges(); |
| |
| this.boundsMinX = boundsX << 16; |
| this.boundsMinY = boundsY << 16; |
| this.boundsMaxX = (boundsX + boundsWidth) << 16; |
| this.boundsMaxY = (boundsY + boundsHeight) << 16; |
| |
| this.bboxX0 = boundsX; |
| this.bboxY0 = boundsY; |
| this.bboxX1 = boundsX + boundsWidth; |
| this.bboxY1 = boundsY + boundsHeight; |
| } |
| |
| public void moveTo(int x0, int y0) { |
| // System.out.println("Renderer: moveTo " + x0/65536.0 + " " + y0/65536.0); |
| close(); |
| this.sx0 = this.x0 = x0; |
| this.sy0 = this.y0 = y0; |
| this.lastOrientation = 0; |
| } |
| |
| public void lineJoin() { |
| // System.out.println("Renderer: lineJoin"); |
| // do nothing |
| } |
| |
| public void lineTo(int x1, int y1) { |
| // System.out.println("Renderer: lineTo " + x1/65536.0 + " " + y1/65536.0); |
| |
| // Ignore horizontal lines |
| // Next line will count flip |
| if (y0 == y1) { |
| this.x0 = x1; |
| return; |
| } |
| |
| int orientation = (y0 < y1) ? 1 : -1; |
| if (lastOrientation == 0) { |
| firstOrientation = orientation; |
| } else if (orientation != lastOrientation) { |
| ++flips; |
| } |
| lastOrientation = orientation; |
| |
| // Bias Y by 1 ULP so endpoints never lie on a scanline |
| addEdge(x0, y0 | 0x1, x1, y1 | 0x1); |
| |
| this.x0 = x1; |
| this.y0 = y1; |
| } |
| |
| public void close() { |
| // System.out.println("Renderer: close"); |
| |
| int orientation = lastOrientation; |
| if (y0 != sy0) { |
| orientation = (y0 < sy0) ? 1 : -1; |
| } |
| if (orientation != firstOrientation) { |
| ++flips; |
| } |
| lineTo(sx0, sy0); |
| } |
| |
| public void end() { |
| close(); |
| // System.out.println("Renderer: end"); |
| // do nothing |
| } |
| |
| // Scan convert a single edge |
| private void computeCrossingsForEdge(int index, |
| int boundsMinY, int boundsMaxY) { |
| int iy0 = edges[index + 1]; |
| int iy1 = edges[index + 3]; |
| |
| // Clip to valid Y range |
| int clipy0 = (iy0 > boundsMinY) ? iy0 : boundsMinY; |
| int clipy1 = (iy1 < boundsMaxY) ? iy1 : boundsMaxY; |
| |
| int minY = ((clipy0 + HYSTEP) & YMASK) + HYSTEP; |
| int maxY = ((clipy1 - HYSTEP) & YMASK) + HYSTEP; |
| |
| // IMPL_NOTE - If line falls outside the valid X range, could |
| // draw a vertical line instead |
| |
| // Exit if no scanlines are crossed |
| if (minY > maxY) { |
| return; |
| } |
| |
| // Scan convert line using a DDA approach |
| |
| int ix0 = edges[index]; |
| int ix1 = edges[index + 2]; |
| long dx = ((long) ix1) - ix0; |
| long dy = ((long) iy1) - iy0; |
| |
| // Compute first crossing point at y = minY |
| int orientation = edges[index + 4]; |
| int y = minY; |
| long lx = (((long) y) - iy0)*dx/dy + ix0; |
| addCrossing(y >> YSHIFT, (int)(lx >> XSHIFT), orientation); |
| |
| // Advance y to next scanline, exit if past endpoint |
| y += YSTEP; |
| if (y > maxY) { |
| return; |
| } |
| |
| // Compute xstep only if additional scanlines are crossed |
| // For each scanline, add xstep to lx and YSTEP to y and |
| // emit the new crossing |
| long xstep = ((long)YSTEP*dx)/dy; |
| for (; y <= maxY; y += YSTEP) { |
| lx += xstep; |
| addCrossing(y >> YSHIFT, (int)(lx >> XSHIFT), orientation); |
| } |
| } |
| |
| private void computeBounds() { |
| rasterMinX = crossingMinX & ~SUBPIXEL_MASK_X; |
| rasterMaxX = crossingMaxX | SUBPIXEL_MASK_X; |
| rasterMinY = crossingMinY & ~SUBPIXEL_MASK_Y; |
| rasterMaxY = crossingMaxY | SUBPIXEL_MASK_Y; |
| |
| // If nothing was drawn, we have: |
| // minX = Integer.MAX_VALUE and maxX = Integer.MIN_VALUE |
| // so nothing to render |
| if (rasterMinX > rasterMaxX || rasterMinY > rasterMaxY) { |
| rasterMinX = 0; |
| rasterMaxX = -1; |
| rasterMinY = 0; |
| rasterMaxY = -1; |
| return; |
| } |
| |
| if (rasterMinX < boundsMinX >> XSHIFT) { |
| rasterMinX = boundsMinX >> XSHIFT; |
| } |
| if (rasterMinY < boundsMinY >> YSHIFT) { |
| rasterMinY = boundsMinY >> YSHIFT; |
| } |
| if (rasterMaxX > boundsMaxX >> XSHIFT) { |
| rasterMaxX = boundsMaxX >> XSHIFT; |
| } |
| if (rasterMaxY > boundsMaxY >> YSHIFT) { |
| rasterMaxY = boundsMaxY >> YSHIFT; |
| } |
| } |
| |
| private int clamp(int x, int min, int max) { |
| if (x < min) { |
| return min; |
| } else if (x > max) { |
| return max; |
| } |
| return x; |
| } |
| |
| private void _endRendering() { |
| if (flips == 0) { |
| bboxX0 = bboxY0 = 0; |
| bboxX1 = bboxY1 = -1; |
| return; |
| } |
| |
| // Special case for filling a single rect with a flat, opaque color |
| // REMIND: This special case was not originally written to fill a |
| // cache object and called directly to a Blit - it needs some code |
| // to fill the cache instead to be useful for this usage... |
| if (false /* Does not work with cache (yet?) */ && |
| edgeIdx == 10 && |
| edges[0] == edges[2] && |
| edges[1] == edges[6] && |
| edges[3] == edges[8] && |
| edges[5] == edges[7] && |
| Math.abs(edges[0] - edges[5]) > MIN_QUAD_OPT_WIDTH) |
| { |
| |
| int x0 = edges[0] >> XSHIFT; |
| int y0 = edges[1] >> YSHIFT; |
| int x1 = edges[5] >> XSHIFT; |
| int y1 = edges[3] >> YSHIFT; |
| |
| if (x0 > x1) { |
| int tmp = x0; |
| x0 = x1; |
| x1 = tmp; |
| } |
| if (y0 > y1) { |
| int tmp = y0; |
| y0 = y1; |
| y1 = tmp; |
| } |
| |
| int bMinX = this.boundsMinX >> XSHIFT; |
| int bMinY = this.boundsMinY >> YSHIFT; |
| int bMaxX = this.boundsMaxX >> XSHIFT; |
| int bMaxY = this.boundsMaxY >> YSHIFT; |
| |
| // Clip to image bounds in supersampled coordinates |
| x0 = clamp(x0, bMinX, bMaxX); |
| x1 = clamp(x1, bMinX, bMaxX); |
| y0 = clamp(y0, bMinY, bMaxY); |
| y1 = clamp(y1, bMinY, bMaxY); |
| |
| /* |
| * REMIND: Need to fill the cache here instead... |
| Blit.fillRectSrcOver(this, |
| imageData, imageType, |
| imageOffset, |
| imageScanlineStride, imagePixelStride, |
| width, height, |
| x0, y0, x1, y1, |
| cred, cgreen, cblue); |
| */ |
| |
| bboxX0 = x0 >> SUBPIXEL_LG_POSITIONS_X; |
| bboxY0 = y0 >> SUBPIXEL_LG_POSITIONS_Y; |
| bboxX1 = (x1 + SUBPIXEL_POSITIONS_X - 1) |
| >> SUBPIXEL_LG_POSITIONS_X; |
| bboxY1 = (y1 + SUBPIXEL_POSITIONS_Y - 1) |
| >> SUBPIXEL_LG_POSITIONS_Y; |
| |
| return; |
| } |
| |
| int minY = (edgeMinY > boundsMinY) ? edgeMinY : boundsMinY; |
| int maxY = (edgeMaxY < boundsMaxY) ? edgeMaxY : boundsMaxY; |
| |
| // Check for empty intersection of primitive with the drawing area |
| if (minY > maxY) { |
| bboxX0 = bboxY0 = 0; |
| bboxX1 = bboxY1 = -1; |
| return; |
| } |
| |
| // Compute Y extent in subpixel coordinates |
| int iminY = (minY >> YSHIFT) & ~SUBPIXEL_MASK_Y; |
| int imaxY = (maxY >> YSHIFT) | SUBPIXEL_MASK_Y; |
| int yextent = (imaxY - iminY) + 1; |
| |
| // Maximum number of crossings |
| int size = flips*yextent; |
| |
| int bmax = (boundsMaxY >> YSHIFT) - 1; |
| if (imaxY > bmax) { |
| imaxY = bmax; |
| } |
| |
| // Initialize X bounds, will be refined for each strip |
| bboxX0 = Integer.MAX_VALUE; |
| bboxX1 = Integer.MIN_VALUE; |
| |
| // Set Y bounds |
| bboxY0 = iminY >> SUBPIXEL_LG_POSITIONS_Y; |
| bboxY1 = (imaxY + SUBPIXEL_POSITIONS_Y - 1) >> SUBPIXEL_LG_POSITIONS_Y; |
| |
| // Compute number of rows that can be processing using |
| // a crossings table no larger than DEFAULT_CROSSINGS_SIZE. |
| // However, we must process at least one row, so we grow the table |
| // temporarily if needed. This would require an object with a |
| // huge number of flips. |
| int rows = DEFAULT_CROSSINGS_SIZE/(flips*SUBPIXEL_POSITIONS_Y); |
| rows = Math.min(rows, yextent); |
| rows = Math.max(rows, 1); |
| for (int i = iminY; i <= imaxY; i += rows*SUBPIXEL_POSITIONS_Y) { |
| // Compute index of last scanline to be processed in this pass |
| int last = Math.min(i + rows*SUBPIXEL_POSITIONS_Y - 1, imaxY); |
| setCrossingsExtents(i, last, flips); |
| |
| int bminY = i << YSHIFT; |
| int bmaxY = (last << YSHIFT) | ~YMASK; |
| |
| // Process edges from the edge list |
| int maxIdx = edgeIdx; |
| for (int index = 0; index < maxIdx; index += 5) { |
| // Test y1 < min: |
| // |
| // If edge lies entirely above current strip, |
| // discard it |
| if (edges[index + 3] < bminY) { |
| // Overwrite the edge with the last edge |
| edgeIdx -= 5; |
| int fidx = edgeIdx; |
| int tidx = index; |
| edges[tidx++] = edges[fidx++]; |
| edges[tidx++] = edges[fidx++]; |
| edges[tidx++] = edges[fidx++]; |
| edges[tidx++] = edges[fidx++]; |
| edges[tidx ] = edges[fidx ]; |
| |
| maxIdx -= 5; |
| index -= 5; |
| continue; |
| } |
| |
| // Test y0 > max: |
| // |
| // If edge lies entirely below current strip, |
| // skip it for now |
| if (edges[index + 1] > bmaxY) { |
| continue; |
| } |
| |
| computeCrossingsForEdge(index, bminY, bmaxY); |
| } |
| |
| computeBounds(); |
| if (rasterMaxX < rasterMinX) { |
| continue; |
| } |
| |
| bboxX0 = Math.min(bboxX0, |
| rasterMinX >> SUBPIXEL_LG_POSITIONS_X); |
| bboxX1 = Math.max(bboxX1, |
| (rasterMaxX + SUBPIXEL_POSITIONS_X - 1) |
| >> SUBPIXEL_LG_POSITIONS_X); |
| renderStrip(); |
| } |
| |
| // Free up any unusually large scratchpad memory used by the |
| // preceding primitive |
| crossingListFinished(); |
| } |
| |
| 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[] bbox) { |
| bbox[0] = bboxX0; |
| bbox[1] = bboxY0; |
| bbox[2] = bboxX1 - bboxX0; |
| bbox[3] = bboxY1 - bboxY0; |
| } |
| |
| private void renderStrip() { |
| // Grow rowAA according to the raster width |
| int width = (rasterMaxX - rasterMinX + 1) >> SUBPIXEL_LG_POSITIONS_X; |
| alphaWidth = width; |
| |
| // Allocate one extra entry in rowAA to avoid a conditional in |
| // the rendering loop |
| int bufLen = width + 1; |
| if (this.rowAA == null || this.rowAA.length < bufLen) { |
| this.rowAA = new byte[bufLen]; |
| } |
| |
| // 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; |
| |
| int y = 0; |
| int prevY = rasterMinY - 1; |
| |
| int minX = Integer.MAX_VALUE; |
| int maxX = Integer.MIN_VALUE; |
| |
| iterateCrossings(); |
| while (hasMoreCrossingRows()) { |
| y = crossingY; |
| |
| // Emit any skipped rows |
| for (int j = prevY + 1; j < y; j++) { |
| if (((j & SUBPIXEL_MASK_Y) == SUBPIXEL_MASK_Y) || |
| (j == rasterMaxY)) { |
| emitRow(j >> SUBPIXEL_LG_POSITIONS_Y, 0, -1); |
| } |
| } |
| prevY = y; |
| |
| if (crossingRowIndex < crossingRowCount) { |
| int lx = crossings[crossingRowOffset + crossingRowIndex]; |
| lx >>= 1; |
| int hx = crossings[crossingRowOffset + crossingRowCount - 1]; |
| hx >>= 1; |
| int x0 = lx > rasterMinX ? lx : rasterMinX; |
| int x1 = hx < rasterMaxX ? hx : rasterMaxX; |
| x0 -= rasterMinX; |
| x1 -= rasterMinX; |
| |
| minX = Math.min(minX, x0 >> SUBPIXEL_LG_POSITIONS_X); |
| maxX = Math.max(maxX, x1 >> SUBPIXEL_LG_POSITIONS_X); |
| } |
| |
| int sum = 0; |
| int prev = rasterMinX; |
| while (crossingRowIndex < crossingRowCount) { |
| int crxo = crossings[crossingRowOffset + crossingRowIndex]; |
| crossingRowIndex++; |
| |
| int crx = crxo >> 1; |
| int crorientation = ((crxo & 0x1) == 0x1) ? 1 : -1; |
| |
| if ((sum & mask) != 0) { |
| // Clip to active X range, if x1 < x0 loop will |
| // have no effect |
| int x0 = prev > rasterMinX ? prev : rasterMinX; |
| int x1 = crx < rasterMaxX ? crx : rasterMaxX; |
| |
| // Empty spans |
| if (x1 > x0) { |
| x0 -= rasterMinX; |
| x1 -= rasterMinX; |
| |
| // Accumulate alpha, equivalent to: |
| // for (int x = x0; x < x1; x++) { |
| // ++rowAA[x >> SUBPIXEL_LG_POSITIONS_X]; |
| // } |
| // |
| // In the middle of the span, we can update a full |
| // pixel at a time (i.e., SUBPIXEL_POSITIONS_X |
| // subpixels) |
| |
| int x = x0 >> SUBPIXEL_LG_POSITIONS_X; |
| int xmaxm1 = (x1 - 1) >> SUBPIXEL_LG_POSITIONS_X; |
| if (x == xmaxm1) { |
| // Start and end in same pixel |
| rowAA[x] += x1 - x0; |
| } else { |
| // Start and end in different pixels |
| rowAA[x++] += SUBPIXEL_POSITIONS_X - |
| (x0 & SUBPIXEL_MASK_X); |
| int xmax = x1 >> SUBPIXEL_LG_POSITIONS_X; |
| while (x < xmax) { |
| rowAA[x++] += SUBPIXEL_POSITIONS_X; |
| } |
| // Note - at this point it is possible that |
| // x == width, which implies that |
| // x1 & SUBPIXEL_MASK_X == 0. We allocate |
| // one extra entry in rowAA so this |
| // assignment will be harmless. The alternative |
| // is an extra conditional here, or some other |
| // scheme to deal with the last pixel better. |
| rowAA[x] += x1 & SUBPIXEL_MASK_X; |
| } |
| } |
| } |
| sum += crorientation; |
| prev = crx; |
| } |
| |
| // Every SUBPIXEL_POSITIONS rows, output an antialiased row |
| if (((y & SUBPIXEL_MASK_Y) == SUBPIXEL_MASK_Y) || |
| (y == rasterMaxY)) { |
| emitRow(y >> SUBPIXEL_LG_POSITIONS_Y, minX, maxX); |
| minX = Integer.MAX_VALUE; |
| maxX = Integer.MIN_VALUE; |
| } |
| } |
| |
| // Emit final row |
| for (int j = prevY + 1; j <= rasterMaxY; j++) { |
| if (((j & SUBPIXEL_MASK_Y) == SUBPIXEL_MASK_Y) || |
| (j == rasterMaxY)) { |
| emitRow(j >> SUBPIXEL_LG_POSITIONS_Y, minX, maxX); |
| minX = Integer.MAX_VALUE; |
| maxX = Integer.MIN_VALUE; |
| } |
| } |
| } |
| |
| private void clearAlpha(byte[] alpha, |
| int width, |
| int minX, int maxX) { |
| if (maxX >= minX) { |
| int w = maxX - minX + 1; |
| if (w + minX > width) { |
| w = width - minX; |
| } |
| |
| int aidx = minX; |
| for (int i = 0; i < w; i++, aidx++) { |
| alpha[aidx] = (byte)0; |
| } |
| } |
| } |
| |
| private void emitRow(int y, int minX, int maxX) { |
| // Copy rowAA data into the cache if one is present |
| if (cache != null) { |
| if (maxX >= minX) { |
| int x0 = minX + (rasterMinX >> SUBPIXEL_LG_POSITIONS_X); |
| int x1 = maxX + (rasterMinX >> SUBPIXEL_LG_POSITIONS_X); |
| |
| cache.startRow(y, x0, x1); |
| int srcIdx = minX; |
| |
| // Perform run-length encoding |
| // and store results in the cache |
| byte startVal = rowAA[srcIdx++]; |
| int runLen = 1; |
| while (srcIdx <= maxX) { |
| byte nextVal = rowAA[srcIdx++]; |
| if (nextVal == startVal && runLen < 255) { |
| ++runLen; |
| } else { |
| cache.addRLERun(startVal, runLen); |
| |
| runLen = 1; |
| startVal = nextVal; |
| } |
| } |
| cache.addRLERun(startVal, runLen); |
| cache.addRLERun((byte)0, 0); |
| } |
| } |
| |
| clearAlpha(rowAA, |
| alphaWidth, |
| minX, maxX); |
| } |
| |
| public void setCache(PiscesCache cache) { |
| this.cache = cache; |
| } |
| |
| // Edge list data |
| |
| private int[] edges = new int[5*INITIAL_EDGES]; |
| private int edgeIdx = 0; |
| private int edgeMinY = Integer.MAX_VALUE; |
| private int edgeMaxY = Integer.MIN_VALUE; |
| |
| private void addEdge(int x0, int y0, int x1, int y1) { |
| int newLen = edgeIdx + 5; |
| if (edges.length < newLen) { |
| int[] tmp = new int[Math.max(11*edges.length/10, newLen)]; |
| System.arraycopy(edges, 0, tmp, 0, edgeIdx); |
| this.edges = tmp; |
| } |
| |
| int orientation = 1; |
| if (y0 > y1) { |
| int tmp = y0; |
| y0 = y1; |
| y1 = tmp; |
| |
| orientation = -1; |
| } |
| |
| // Skip edges that don't cross a subsampled scanline |
| int eminY = ((y0 + HYSTEP) & YMASK); |
| int emaxY = ((y1 - HYSTEP) & YMASK); |
| if (eminY > emaxY) { |
| return; |
| } |
| |
| if (orientation == -1) { |
| int tmp = x0; |
| x0 = x1; |
| x1 = tmp; |
| } |
| |
| edges[edgeIdx++] = x0; |
| edges[edgeIdx++] = y0; |
| edges[edgeIdx++] = x1; |
| edges[edgeIdx++] = y1; |
| edges[edgeIdx++] = orientation; |
| |
| // Update Y bounds of primitive |
| if (y0 < edgeMinY) { |
| edgeMinY = y0; |
| } |
| if (y1 > edgeMaxY) { |
| edgeMaxY = y1; |
| } |
| } |
| |
| private void resetEdges() { |
| this.edgeIdx = 0; |
| this.edgeMinY = Integer.MAX_VALUE; |
| this.edgeMaxY = Integer.MIN_VALUE; |
| } |
| |
| // Crossing list data |
| |
| private int[] crossingIndices; |
| private int[] crossings; |
| private int crossingMinY; |
| private int crossingMaxY; |
| private int crossingMinX = Integer.MAX_VALUE; |
| private int crossingMaxX = Integer.MIN_VALUE; |
| private int crossingMaxXEntries; |
| private int numCrossings = 0; |
| private boolean crossingsSorted = false; |
| |
| private int crossingY; |
| private int crossingRowCount; |
| private int crossingRowOffset; |
| private int crossingRowIndex; |
| |
| private void setCrossingsExtents(int minY, int maxY, int maxXEntries) { |
| int yextent = maxY - minY + 1; |
| |
| // Grow indices array as needed |
| if (crossingIndices == null || crossingIndices.length < yextent) { |
| this.crossingIndices = |
| new int[Math.max(yextent, DEFAULT_INDICES_SIZE)]; |
| } |
| // Grow crossings array as needed |
| if (crossings == null || crossings.length < yextent*maxXEntries) { |
| this.crossings = new int[Math.max(yextent*maxXEntries, |
| DEFAULT_CROSSINGS_SIZE)]; |
| } |
| this.crossingMinY = minY; |
| this.crossingMaxY = maxY; |
| this.crossingMaxXEntries = maxXEntries; |
| resetCrossings(); |
| } |
| |
| private void resetCrossings() { |
| int yextent = crossingMaxY - crossingMinY + 1; |
| int start = 0; |
| for (int i = 0; i < yextent; i++) { |
| crossingIndices[i] = start; |
| start += crossingMaxXEntries; |
| } |
| crossingMinX = Integer.MAX_VALUE; |
| crossingMaxX = Integer.MIN_VALUE; |
| numCrossings = 0; |
| crossingsSorted = false; |
| } |
| |
| // Free sorting arrays if larger than maximum size |
| private void crossingListFinished() { |
| if (crossings != null && crossings.length > DEFAULT_CROSSINGS_SIZE) { |
| crossings = new int[DEFAULT_CROSSINGS_SIZE]; |
| } |
| if (crossingIndices != null && |
| crossingIndices.length > DEFAULT_INDICES_SIZE) |
| { |
| crossingIndices = new int[DEFAULT_INDICES_SIZE]; |
| } |
| } |
| |
| private void sortCrossings(int[] x, int off, int len) { |
| for (int i = off + 1; i < off + len; i++) { |
| int j = i; |
| int xj = x[j]; |
| int xjm1; |
| |
| while (j > off && (xjm1 = x[j - 1]) > xj) { |
| x[j] = xjm1; |
| x[j - 1] = xj; |
| j--; |
| } |
| } |
| } |
| |
| private void sortCrossings() { |
| int start = 0; |
| for (int i = 0; i <= crossingMaxY - crossingMinY; i++) { |
| sortCrossings(crossings, start, crossingIndices[i] - start); |
| start += crossingMaxXEntries; |
| } |
| } |
| |
| private void addCrossing(int y, int x, int orientation) { |
| if (x < crossingMinX) { |
| crossingMinX = x; |
| } |
| if (x > crossingMaxX) { |
| crossingMaxX = x; |
| } |
| |
| int index = crossingIndices[y - crossingMinY]++; |
| x <<= 1; |
| crossings[index] = (orientation == 1) ? (x | 0x1) : x; |
| |
| ++numCrossings; |
| } |
| |
| private void iterateCrossings() { |
| if (!crossingsSorted) { |
| sortCrossings(); |
| crossingsSorted = true; |
| } |
| crossingY = crossingMinY - 1; |
| crossingRowOffset = -crossingMaxXEntries; |
| } |
| |
| private boolean hasMoreCrossingRows() { |
| if (++crossingY <= crossingMaxY) { |
| crossingRowOffset += crossingMaxXEntries; |
| int y = crossingY - crossingMinY; |
| crossingRowCount = crossingIndices[y] - y*crossingMaxXEntries; |
| crossingRowIndex = 0; |
| return true; |
| } else { |
| return false; |
| } |
| } |
| } |