Hello again. This attachmet is a "can of worms" implementation without all the fancy (and slow) iteration. It also includes all of the other suggestions you sent in your first review of Dasher and Renderer last week (most importantly, the firstOrientation issue, horizontal lines filtering, and adding prefixes to variable names to make it clear whether they refer to pixels, or subpixels).
Regards, Denis. ----- "Denis Lila" <dl...@redhat.com> wrote: > Hello Jim. > > I implemented your "can of worms" idea. It works, and it got rid of > the biasing. > I wasn't able to send a webrev, but there are many changes and a side > by side > comparison would probably be useless, so I just attached the file. I > hope this is > ok. > > I also implemented a "better" iterating structure for the lines and > the > strips and crossings. I think it is better in every way, except > performance. > The new file is more than 200 lines smaller than the old one. The only > members of Renderer are now the AA variables and the position > variables > (sx*, sy*, x*, y*). > What I've done is I added an EdgeList class, which encapsulates all > the edge > related variables in the old Renderer. At first, I had an Edge class > in addition > to the EdgeList class, and while this was much nicer, it turned out to > be too > expensive (see last paragraph). > I've also added a ScanLineIterator, so instead of _endRendering > iterating > through strips, and then calling renderStrip() which iterates through > the > scanlines in that strip, and then through the crossings in that > scanline, > what happens now is that _endRendering uses the Iterator<ScanLine> to > iterate through each scanline, get get its crossings and iterate > through them > to accumulate the alpha. By the way, a ScanLine is a type defined by > an > interface which exports methods for getting the y coord of the line, > the > number of crossings in it, the ith crossing, and a method for sorting > its crossings. > The class that implements ScanLine is ScanLineIterator itself. I made > a > ScanLine class, but I was afraid performance would suffer because of > all the > object creations (this turned out not to be an issue, after I switched > to the > current way, and remeasured things). I did not switch back because > this is > only slightly worse. > > As for performance: I wrote a simple program that tries to draw a > dashed path > that consists of about 160 dashed lines of width 1 and length 30000, > going > from the centre of the frame to some point. On my machine, this takes > about 4.9 > seconds in openjdk6, and 26 seconds using the attached file. Back when > I was using > the Edge class it took about 39 seconds. Everything without hundres of > thousands of > edges is not much slower > I have not changed any of the algorithms. ScanLineIterator still goes > through > strips of the same size and computes crossings in every strip using > the same > method as before, so I don't know why it's so slow. It can't be > because of anything > happening in _endRendering, because there are only about 9000 > scanlines and for each > of them I've just added a few calls to one line getters (which used to > be direct > accesses into arrays). > > Thanks, > Denis. > > ----- "Jim Graham" <james.gra...@oracle.com> wrote: > > > Denis Lila wrote: > > > Hello Jim. > > > > > > Thank you very much for taking the time to read through this. > > > > > >> 169 - if origLen reaches the end of the dash exactly (the "==" > > case) > > > > > > You're right, I should. I can't just replace <= with == > though, > > > because the results will be the same: in the equal case origLen > > will > > > become 0, and on the next iteration, the (origLen < > dash[idx]-phase) > > will > > > be true, and we will do a goTo(x1,y1), which is what we just did > in > > the > > > previous iteration (unless dash[idx] is 0, in which case the > results > > > > > will be even worse). The best solution to this is to just do a > > nested > > > check for the == case. > > > > Ah, right - because there is no "break" when origLen becomes zero. > > Sounds like you're on it. > > > > >> 171 - Aren't x0,y0 stored as subpix values? You would then be > > comparing > > >> a subpix value to a non-subpix value. Perhaps if the subpix > calls > > are > > >> moved to the top of the function I think this should work OK? > > > > > > That's true, they are. This is very puzzling. If a horizontal > > line is > > > added, when the crossings for it are being computed, dxBydy should > > be NaN, and > > > wouldn't an error be thrown when we try to cast to an int in the > > call to addCrossing? > > > > I'm not sure - I didn't trace it through very far - I just noted > that > > > > the values were likely in different "resolutions". > > > > >> 194,197 - Shouldn't these be constants, or based on the > > SUB_POS_XY? > > > > > > I suppose I should make a biasing constant. I don't think they > > should be based > > > on SUB_POS_XY though, because the biasing is done to subpixel > > coordinates so > > > there is no danger that if our supersampling is fine enough the > > biasing will > > > make the coordinates jump over some scan line. > > > > I'm guessing you punted on my "can of worms" suggestion then. ;-) > > > > >> 216 - if you save the sx0,sy0 before subpix'ing them then you > don't > > have > > >> to "unsubpix" them here. (Though you still need the subpix sy0 > for > > line > > >> 209 - or you could just call subpix on it for that line and at > > least > > >> you'd save 1 call to sub/unsub). > > > > > > Ok. I'll just save subpixed and unsubpixed versions of sx0, > sy0. > > That should > > > eliminate all sx0,sy0 related calls to tosubpix and topix except > in > > moveTo. > > > > and lineTo. You may only need 3 of those values, though, if I > > remember > > my code reading well enough. > > > > >> 256,264 - casting to int is problematic. It truncates towards 0 > > which > > >> means negatives are ceil'd and positives are floor'd. It would > be > > best > > >> to use floor here instead. On the other hand, if negative > numbers > > are > > >> always "off the left side of the drawable" then this is moot. > > > > > > That's why I left it at int casting. Do you still think I > should > > change it > > > to floor? > > > > If you mean floor, I think it best to use floor. Unless you can > prove > > > > that negatives aren't really an issue and that the strange > truncation > > on > > them won't be numerically a problem - but I don't think it is worth > it > > > > for this code. > > > > > Speaking of which, is there a good way to edit and build openJDK > > from eclipse? > > > Then this sort of embarrassing error could be avoided (including > the > > printStats() call). > > > > I don't use Eclipse, sorry. :-( > > > > > As for Arrays.newSize()... I can't find it here: > > > > > > http://download.oracle.com/docs/cd/E17409_01/javase/6/docs/api/java/util/Arrays.html > > > Is this a new function added in 7? > > > > Sorry, make that Arrays.copyOf(..., newSize). I tried to type the > > name > > from memory and got it wrong. > > > > >> 721 - Arrays.sort() > > > > > > I thought about using this, but I did some measurements, and > it > > turns out that > > > Arrays.sort() is a bit slower if the portion of the array being > > sorted has fewer > > > than about 70 elements. > > > > I wonder what the typical number of elements is. Is this sorting > > crossings per line? Then simple primitives like circles should only > > have 2 per line, right? Is it worth testing for really small > numbers > > of > > elements (much lower than 70) and doing a manual sort? Or am I > > misunderstanding what is being sorted there? > > > > >> How comfortable do you feel with that conversion? > > > > > > I'll try to do it and include it in a new patch along with, > > hopefully, a better way > > > to iterate through strips, and especially crossings. Right now all > > the iteration > > > state is saved in global variables. This is... not good. I spent > far > > too much time last > > > week on bugs caused by this sort of thing. Ideally, any members > that > > can't be put at > > > the top of a class (like our edge and crossing data) should be put > > in classes of their own. > > > > That sounds good, but also consider doing it in separate stages to > > reduce churn in the code reviewing (and you then have revs to go > back > > > > and test which stage caused a probem if we find a bug later): > > > > - first get all on floats > > - then change strip management > > - then change to open coordinate intervals > > - (or vice versa) > > > > > Do you have any ideas about how to iterate through edges in a > strip > > without going through > > > every edge? I was thinking of maybe using some sort of tree to > split > > the drawing surface, > > > but I haven't given it much thought. > > > > If you look for something like the native code for > > sun/java2d/pipe/ShapeSpanIterator.c you will see the way I typically > > like to do edge setup and enumeration. > > > > That code uses the "half open interval" approach for both X and Y > > intervals. > > > > I then sort the edge list by "leading Y" and then move through the > > edges > > using the following manner (kind of like an inch worm eating the > > edges, > > maintaining a pointer to the beginning and end of an "active list" > of > > > > edges that are in play at any given time by virtue of having their Y > > range intersect the current sampling Y). Note that I use an array > of > > > > edges and then a parallel array of pointers into the edges so that I > > can > > sort just the pointers and avoid moving tons of edge data around. > > Also, > > later when I eliminate edges from the active list I just move their > > pointers into and out of view rather than having to copy the edge > > data. > > It is harder to do an array of pointers in Java, though - perhaps > an > > > > array of indices? Here is some basic pseudocode: > > > > start with lo and hi pointing at index 0 in edge list. > > until edge list is exhausted { > > process edges between lo and hi (empty on first pass) > > scan from hi to lo and toss any edges that are exhausted > > (compacting remaining pointers towards hi) > > keep incrementing hi to accept any new edges coming into play > > process edges between lo and hi to increment to the next Y > > (note that new edges from previous step may not > > need any processing in this stage if their starting > > Y equals the next Y to be sampled) > > } > > > > Gradually lo and hi make their way through the list. The edges > above > > hi > > are always sorted in order of when they may come into play as we > move > > > > downward in Y. The edges between lo and hi are also usually kept > > sorted > > by their current X so that the stage that processes them into spans > > can > > just run through them. The edges below lo are usually random > garbage > > > > because no care was taken during the "pruning" step to worry about > > what > > happens to the pointer table down there as lo is incremented (the > > algorithm only ever looks up the array of pointers). > > > > I hope that helps... > > > > ...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; public class Renderer implements 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_POSITIONS_X; private int SUBPIXEL_POSITIONS_Y; private int SUBPIXEL_MASK_X; private int SUBPIXEL_MASK_Y; 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 int boundsMinX, boundsMinY, boundsMaxX, boundsMaxY; // Bounds of the strip being rendered (see _endRendering), at subsample // precision private int rasterMinX, rasterMaxX, rasterMinY, rasterMaxY; // Pixel bounding box for current primitive private int pix_bboxX0, pix_bboxY0, pix_bboxX1, pix_bboxY1; // Current winding rule 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 sx0, sy0, pix_sx0, pix_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(int subpixelLgPositionsX, int subpixelLgPositionsY, int pix_boundsX, int pix_boundsY, int pix_boundsWidth, int pix_boundsHeight, int windingRule, PiscesCache pc) { 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); lastOrientation = 0; flips = 0; resetEdges(); 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; this.windingRule = windingRule; this.cache = pc; } 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) { // System.out.println("Renderer: moveTo " + x0/65536.0 + " " + y/65536.0); close(); this.pix_sx0 = pix_x0; this.pix_sy0 = pix_y0; this.sx0 = this.x0 = tosubpixx(pix_x0); this.sy0 = this.y0 = tosubpixy(pix_y0); this.lastOrientation = 0; this.firstOrientation = 0; } public void lineJoin() { // System.out.println("Renderer: 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 // 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; addEdge(x0, y0, x1, y1); this.x0 = x1; this.y0 = y1; } public void close() { // System.out.println("Renderer: close"); if (firstOrientation != 0) { int orientation = lastOrientation; if (y0 != sy0) { orientation = (y0 < sy0) ? 1 : -1; } if (orientation != firstOrientation) { ++flips; } // lineTo expects its input in pixel coordinates. lineTo(pix_sx0, pix_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) { float y0 = edges[index + 1]; float y1 = edges[index + 3]; // Clip to valid Y range float clipy0 = (y0 > boundsMinY) ? y0 : boundsMinY; float clipy1 = (y1 < boundsMaxY) ? y1 : boundsMaxY; // compute the first and last scanlines crossed by this edge within the // given bounds. int minY = (int)Math.ceil(clipy0); int maxY = (int)Math.ceil(clipy1); // Exit if no scanlines are crossed if (minY >= maxY) { return; } // Scan convert line using a DDA approach float x0 = edges[index]; float x1 = edges[index + 2]; float dxBydy = (x1 - x0) / (y1 - y0); // Compute first crossing point at y = minY int orientation = (int)edges[index + 4]; int y = minY; float x = (y - y0)*dxBydy + x0; addCrossing(y, (int)x, orientation); // y is in subpixel coordinates, so scalines are 1 apart. So, x // must increase by the slope. for (y++; y < maxY; y++) { x += dxBydy; addCrossing(y, (int)x, 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) { rasterMinX = boundsMinX; } if (rasterMinY < boundsMinY) { rasterMinY = boundsMinY; } if (rasterMaxX > boundsMaxX) { rasterMaxX = boundsMaxX; } if (rasterMaxY > boundsMaxY) { rasterMaxY = boundsMaxY; } } private void _endRendering() { if (flips == 0) { pix_bboxX0 = pix_bboxY0 = 0; pix_bboxX1 = pix_bboxY1 = -1; return; } float minY = (edgeMinY > boundsMinY) ? edgeMinY : boundsMinY; float maxY = (edgeMaxY < boundsMaxY) ? edgeMaxY : boundsMaxY; // Check for empty intersection of primitive with the drawing area if (minY > maxY) { pix_bboxX0 = pix_bboxY0 = 0; pix_bboxX1 = pix_bboxY1 = -1; return; } // Compute Y extent in subpixel coordinates int iminY = (int)Math.ceil(minY); int imaxY = (int)Math.ceil(maxY); int yextent = imaxY - iminY; // Initialize X bounds, will be refined for each strip pix_bboxX0 = Integer.MAX_VALUE; pix_bboxX1 = Integer.MIN_VALUE; // Set Y bounds pix_bboxY0 = iminY >> SUBPIXEL_LG_POSITIONS_Y; pix_bboxY1 = imaxY >> SUBPIXEL_LG_POSITIONS_Y; // Compute number of pixel rows that can be processed 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, imaxY); setCrossingsExtents(i, last, flips); // Process edges from the edge list int maxIdx = edgeIdx; for (int index = 0; index < maxIdx; index += 5) { // If edge lies entirely above current strip, discard it // because either it is in some previously processed strip, or // it is completely out of bounds. In either case, we no longer // need it. if (edges[index + 3] <= i) { // 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; } // If edge lies entirely below current strip, skip it for now if (edges[index + 1] >= last) { continue; } computeCrossingsForEdge(index, i, last); } computeBounds(); if (rasterMaxX <= rasterMinX) { continue; } pix_bboxX0 = Math.min(pix_bboxX0, rasterMinX >> SUBPIXEL_LG_POSITIONS_X); pix_bboxX1 = Math.max(pix_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[] 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 renderStrip() { // Grow rowAA according to the raster width int width = (rasterMaxX - rasterMinX) >> 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 pix_minX = Integer.MAX_VALUE; int pix_maxX = Integer.MIN_VALUE; iterateCrossings(); while (hasMoreCrossingRows()) { y = crossingY; 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; 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 = 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 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 rowAA[pix_x] += x1 - x0; } else { // Start and end in different pixels rowAA[pix_x++] += SUBPIXEL_POSITIONS_X - (x0 & SUBPIXEL_MASK_X); int pix_xmax = x1 >> SUBPIXEL_LG_POSITIONS_X; while (pix_x < pix_xmax) { rowAA[pix_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[pix_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, pix_minX, pix_maxX); pix_minX = Integer.MAX_VALUE; pix_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, pix_minX, pix_maxX); pix_minX = Integer.MAX_VALUE; pix_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 pix_y, int pix_minX, int pix_maxX) { // Copy rowAA data into the cache if one is present if (cache != null) { if (pix_maxX >= pix_minX) { int pix_x0 = pix_minX + (rasterMinX >> SUBPIXEL_LG_POSITIONS_X); int pix_x1 = pix_maxX + (rasterMinX >> SUBPIXEL_LG_POSITIONS_X); cache.startRow(pix_y, pix_x0, pix_x1); int srcIdx = pix_minX; // Perform run-length encoding // and store results in the cache byte startVal = rowAA[srcIdx++]; int runLen = 1; while (srcIdx <= pix_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, pix_minX, pix_maxX); } // Edge list data private float[] edges = new float[5*INITIAL_EDGES]; private int edgeIdx = 0; private float edgeMinY = Float.POSITIVE_INFINITY; private float edgeMaxY = Float.NEGATIVE_INFINITY; private void addEdge(float x0, float y0, float x1, float y1) { int newLen = edgeIdx + 5; if (edges.length < newLen) { edges = java.util.Arrays.copyOf(edges, 2 * newLen); } int orientation = 1; if (y0 > y1) { float tmp = y0; y0 = y1; y1 = tmp; orientation = -1; } // Skip edges that don't cross a subsampled scanline if (Math.ceil(y0) >= Math.ceil(y1)) { return; } if (orientation == -1) { float 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 = Float.POSITIVE_INFINITY; this.edgeMaxY = Float.NEGATIVE_INFINITY; } // Crossing list data // crossings stores the x-values of the scanline crossings of each edge // in the strip being rendered (see _endRendering() and renderStrip()). // It stores them such that after all the crossings are computed, // the crossings for the ith scanline are // crossings[crossingMaxXEntries * i], ..., crossings[crossingIndices[i] - 1] private int[] crossings; private int[] crossingIndices; private int crossingMinY; private int crossingMaxY; private int crossingMinX = Integer.MAX_VALUE; private int crossingMaxX = Integer.MIN_VALUE; private int crossingMaxXEntries; 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; 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]; } } // NOTE: it is the caller's responsibility to ensure that off+len <= x.length. // If it isn't, this will throw an exception. private static void insertionSort(int[] x, final int off, final int len) { for (int i = off + 1; i < off + len; i++) { int xi = x[i]; int j = i - 1; for (; j >= off && x[j] > xi; j--) { x[j + 1] = x[j]; } x[j + 1] = xi; // compensate for the last j-- } } private void sortCrossings() { int start = 0; for (int i = 0; i <= crossingMaxY - crossingMinY; i++) { insertionSort(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; } 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; } } }