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; import java.util.Arrays; import java.util.Iterator; public class Renderer implements LineSink { private static final class EdgeList { private float[] edges; private int last; private float edgeMinY; private float edgeMaxY; private static final int INIT_NUM_EDGES = 1000; public EdgeList() { edgeMinY = Float.POSITIVE_INFINITY; edgeMaxY = Float.NEGATIVE_INFINITY; last = 0; edges = new float[5 * INIT_NUM_EDGES]; } public int size() { return last / 5; } public float x0(int i) { return edges[5 * i]; } public float y0(int i) { return edges[5 * i + 1]; } public float x1(int i) { return edges[5 * i + 2]; } public float y1(int i) { return edges[5 * i + 3]; } public int or(int i) { return (int)edges[5 * i + 4]; } public void remove(int i) { int i5 = i * 5; edges[i5 + 4] = edges[--last]; edges[i5 + 3] = edges[--last]; edges[i5 + 2] = edges[--last]; edges[i5 + 1] = edges[--last]; edges[i5 ] = edges[--last]; } private void expand() { if (last + 5 > edges.length) { edges = Arrays.copyOf(edges, 2 * edges.length); } } public void add(float x0, float y0, float x1, float y1) { float or = (y0 < y1) ? 1f : -1f; // orientation: 1 = UP; -1 = DOWN if (or == -1f) { float tmp = y0; y0 = y1; y1 = tmp; tmp = x0; x0 = x1; x1 = tmp; } if (Math.ceil(y0) >= Math.ceil(y1)) { return; // skip edges that don't cross a scanline } if (y0 < edgeMinY) { edgeMinY = y0; } if (y1 > edgeMaxY) { edgeMaxY = y1; } expand(); edges[last++] = x0; edges[last++] = y0; edges[last++] = x1; edges[last++] = y1; edges[last++] = or; } public float getMinY() { return edgeMinY; } public float getMaxY() { return edgeMaxY; } } // Represents a scanline. Provides methods for extracting its crossings. private interface ScanLine { public int getY(); public int getCrossingAtidx(int i); public int getCrossingsLength(); public void sortCrossings(); } static class ScanLineIterator implements Iterator<ScanLine>, ScanLine { private static final int DEFAULT_CROSSINGS_SIZE = 32*1024; private final int[] crossings; private final int[] crossingsIndices; private final EdgeList edges; private final int maxXEntries; private final int stripHeight; // 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 minY, maxY; private int minX, maxX; private int nextY; private int stripMin; // start y value of the current strip // Respectively (nextY - 1 - stripMin) * maxXEntries and // crossingsIndices[nextY - 1 - StripMin]. They are the indeces into // the first and one past the last crossing of this scan line. private int startIdx, endIdx; ScanLineIterator(EdgeList edges, int flips, int bminx, int bminy, int bmaxx, int bmaxy) { this.edges = edges; // 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 stripHeightTMP = DEFAULT_CROSSINGS_SIZE/flips; stripHeightTMP = Math.min(stripHeightTMP, bmaxy - bminy + 1); stripHeightTMP = Math.max(stripHeightTMP, 1); stripHeight = stripHeightTMP; crossings = new int[stripHeight * flips]; crossingsIndices = new int[stripHeight]; setCrossings(crossingsIndices, flips); startIdx = endIdx = 0; maxXEntries = flips; // We don't care if we clip some of the line off with ceil, since // no scanline crossings will be eliminated (in fact, the ceil is // the y of the first scanline crossing). nextY = stripMin = minY = Math.max(bminy, (int)Math.ceil(edges.getMinY())); maxY = Math.min(bmaxy, (int)Math.ceil(edges.getMaxY())); // We accumulate X in this class because accumulating it in EdgeList // 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 edges would say 1000, // so we'd still have to accumulate the X bounds as we add crossings. minX = bminx; maxX = bmaxx; } public int getMinX() { return minX; } public int getMaxX() { return maxX; } public int getMinY() { return minY; } public int getMaxY() { return maxY; } // ScanLine interface methods public int getY() { return nextY - 1; } public int getCrossingAtidx(int i) { return crossings[startIdx + i]; } public int getCrossingsLength() { return endIdx - startIdx; } public void sortCrossings() { Arrays.sort(crossings, startIdx, endIdx); } // End ScanLine interface methods. private static void setCrossings(int[] crossingsIndices, int maxXEntries) { for (int i = 0; i < crossingsIndices.length; i++) { crossingsIndices[i] = i * maxXEntries; } } public boolean hasNext() { return nextY < maxY; } public void remove() { } // don't support removal public ScanLine next() { int idx = nextY - stripMin; // The first scanline should be treated as if we're entering a new strip. // We're on the first scanline if and only if idx == 0, because on every other // strip boundary idx==stripHeight. if (idx < stripHeight && idx != 0) { startIdx = idx * maxXEntries; endIdx = crossingsIndices[idx]; } else { // This is the first line in a strip we just entered if (idx != 0) { // If this is not the first strip then stripMin should be increased. stripMin += stripHeight; } setCrossings(crossingsIndices, maxXEntries); // just entered a new strip computeCrossingsInStrip(edges, nextY); startIdx = 0; endIdx = crossingsIndices[0]; } nextY++; return this; } private void computeCrossingsInStrip(EdgeList e, int sminy) { int smaxy = sminy + stripHeight; for (int i = 0; i < edges.size(); i++) { float y1 = e.y1(i); if (y1 <= sminy) { // must have processed it, so discard it. edges.remove(i); i--; // not ideal. This loop should be replaced by an iterator continue; } float y0 = e.y0(i); if (y0 > smaxy - 1) { // completely above current strip, we'll get to it later. continue; } float x0 = e.x0(i); float x1 = e.x1(i); int or = e.or(i); computeCrossingsForEdge(x0, y0, x1, y1, or, sminy, smaxy); } } private void computeCrossingsForEdge(float x0, float y0, float x1, float y1, int or, int sminy, int smaxy) { // Ignore the portions of the line that is outside of the strip // defined by [sminy, smaxy). Those portions have been processed in // a previous strip, will be processed in a future strip, or will // be ignored, because they lie entirely out of drawing bounds. float clipy0 = Math.max(y0, sminy); float clipy1 = Math.min(y1, smaxy); // compute the first and one past last lines crossed by this edge // within the given bounds. floor(clipy1) is the last crossing of // this line (in this strip) but we use ceil because we're using // half open intervals. 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 dxBydy = (x1 - x0) / (y1 - y0); // Compute first crossing point at y = minY int y = minY; float x = (y - y0)*dxBydy + x0; addCrossing(y, (int)x, or, sminy); // y is in subpixel coordinates, so scanlines are 1 apart. So, x // must increase by the slope. for (y++; y < maxY; y++) { x += dxBydy; addCrossing(y, (int)x, or, sminy); } } private void addCrossing(int y, int x, int or, int sminy) { if (x < minX) { minX = x; } if (x > maxX) { maxX = x; } int index = crossingsIndices[y - sminy]++; x <<= 1; crossings[index] = ((or == 1) ? (x | 0x1) : x); } } 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 sx0, sy0, pix_sx0, pix_sy0; // List of edges to be scanned. private EdgeList edges; // 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; 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 EdgeList(); lastOrientation = 0; flips = 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.sx0 = this.x0 = tosubpixx(pix_x0); this.sy0 = this.y0 = tosubpixy(pix_y0); this.lastOrientation = 0; this.firstOrientation = 0; } 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; } int orientation = (y0 < y1) ? 1 : -1; if (lastOrientation == 0) { firstOrientation = orientation; } else if (orientation != lastOrientation) { ++flips; } lastOrientation = orientation; edges.add(x0, y0, x1, y1); this.x0 = x1; this.y0 = y1; } public void 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(); } private void _endRendering() { if (flips == 0) { pix_bboxX0 = pix_bboxY0 = 0; pix_bboxX1 = pix_bboxY1 = -1; return; } // 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 2 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]; // 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; ScanLineIterator it = new ScanLineIterator(edges, flips, boundsMinX, boundsMinY, boundsMaxX, boundsMaxY); for ( ; it.hasNext(); ) { ScanLine line = it.next(); y = line.getY(); line.sortCrossings(); int clen = line.getCrossingsLength(); if (clen > 0) { int lowx = line.getCrossingAtidx(0) >> 1; int highx = line.getCrossingAtidx(clen - 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 < clen; i++) { int curxo = line.getCrossingAtidx(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; } else { // Start and end in different pixels alpha[pix_x++] += SUBPIXEL_POSITIONS_X - (x0 & SUBPIXEL_MASK_X); int pix_xmax = x1 >> SUBPIXEL_LG_POSITIONS_X; while (pix_x < pix_xmax) { alpha[pix_x++] += SUBPIXEL_POSITIONS_X; } // last pixel. Note: it could have 0 alpha (we // made the alpha array 1 larger than it needed to be // to avoid a conditional here). alpha[pix_x] += x1 & SUBPIXEL_MASK_X; } } } sum += crorientation; prev = curx; } if (((y & SUBPIXEL_MASK_Y) == SUBPIXEL_MASK_Y) || (y == boundsMaxY)) { emitRow(alpha, 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 = y + 1; j <= boundsMaxY; j++) { if (((j & SUBPIXEL_MASK_Y) == SUBPIXEL_MASK_Y) || (j == boundsMaxY)) { emitRow(alpha, j >> SUBPIXEL_LG_POSITIONS_Y, pix_minX, pix_maxX); pix_minX = Integer.MAX_VALUE; pix_maxX = Integer.MIN_VALUE; break; } } pix_bboxX0 = it.getMinX() >> SUBPIXEL_LG_POSITIONS_X; pix_bboxX1 = it.getMaxX() >> SUBPIXEL_LG_POSITIONS_X; pix_bboxY0 = it.getMinY() >> SUBPIXEL_LG_POSITIONS_Y; pix_bboxY1 = it.getMaxY() >> 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 runLen = 1; byte startVal = alphaRow[pix_from]; for (int i = pix_from + 1; i <= pix_to; i++) { byte nextVal = 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); } }