1 /*
   2  * Copyright (c) 2007, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.openpisces;
  27 
  28 import com.sun.javafx.geom.PathConsumer2D;
  29 import java.util.Arrays;
  30 
  31 /**
  32  */
  33 public final class Renderer implements PathConsumer2D {
  34 
  35     private final class ScanlineIterator {
  36 
  37         private int[] crossings;
  38         private int[] edgePtrs;
  39         private int edgeCount;
  40 
  41         // crossing bounds. The bounds are not necessarily tight (the scan line
  42         // at minY, for example, might have no crossings). The x bounds will
  43         // be accumulated as crossings are computed.
  44         private int nextY;
  45 
  46         private static final int INIT_CROSSINGS_SIZE = 10;
  47 
  48         private ScanlineIterator() {
  49             crossings = new int[INIT_CROSSINGS_SIZE];
  50             edgePtrs = new int[INIT_CROSSINGS_SIZE];
  51             reset();
  52         }
  53 
  54         public void reset() {
  55             // We don't care if we clip some of the line off with ceil, since
  56             // no scan line crossings will be eliminated (in fact, the ceil is
  57             // the y of the first scan line crossing).
  58             nextY = sampleRowMin;
  59             edgeCount = 0;
  60         }
  61 
  62         private int next() {
  63             // TODO: make function that convert from y value to bucket idx?
  64             // (RT-26922)
  65             int cury = nextY++;
  66             int bucket = cury - boundsMinY;
  67             int count = this.edgeCount;
  68             int ptrs[] = this.edgePtrs;
  69             float edges[] = Renderer.this.edges;
  70             int bucketcount = edgeBuckets[bucket*2 + 1];
  71             if ((bucketcount & 0x1) != 0) {
  72                 int newCount = 0;
  73                 for (int i = 0; i < count; i++) {
  74                     int ecur = ptrs[i];
  75                     if (edges[ecur+YMAX] > cury) {
  76                         ptrs[newCount++] = ecur;
  77                     }
  78                 }
  79                 count = newCount;
  80             }
  81             ptrs = Helpers.widenArray(ptrs, count, bucketcount >> 1);
  82             for (int ecur = edgeBuckets[bucket*2];
  83                  ecur != 0;
  84                  ecur = (int)edges[ecur+NEXT])
  85             {
  86                 ptrs[count++] = --ecur;
  87                 // REMIND: Adjust start Y if necessary
  88             }
  89             this.edgePtrs = ptrs;
  90             this.edgeCount = count;
  91 //            if ((count & 0x1) != 0) {
  92 //                System.out.println("ODD NUMBER OF EDGES!!!!");
  93 //            }
  94             int xings[] = this.crossings;
  95             if (xings.length < count) {
  96                 this.crossings = xings = new int[ptrs.length];
  97             }
  98             for (int i = 0; i < count; i++) {
  99                 int ecur = ptrs[i];
 100                 float curx = edges[ecur+CURX];
 101                 int cross = ((int) curx) << 1;
 102                 edges[ecur+CURX] = curx + edges[ecur+SLOPE];
 103                 if (edges[ecur+OR] > 0) {
 104                     cross |= 1;
 105                 }
 106                 int j = i;
 107                 while (--j >= 0) {
 108                     int jcross = xings[j];
 109                     if (jcross <= cross) {
 110                         break;
 111                     }
 112                     xings[j+1] = jcross;
 113                     ptrs[j+1] = ptrs[j];
 114                 }
 115                 xings[j+1] = cross;
 116                 ptrs[j+1] = ecur;
 117             }
 118             return count;
 119         }
 120 
 121         private boolean hasNext() {
 122             return nextY < sampleRowMax;
 123         }
 124 
 125         private int curY() {
 126             return nextY - 1;
 127         }
 128     }
 129 
 130 
 131 //////////////////////////////////////////////////////////////////////////////
 132 //  EDGE LIST
 133 //////////////////////////////////////////////////////////////////////////////
 134 // TODO(maybe): very tempting to use fixed point here. A lot of opportunities
 135 // for shifts and just removing certain operations altogether. (RT-26922)
 136 
 137     // common to all types of input path segments.
 138     private static final int YMAX = 0;
 139     private static final int CURX = 1;
 140     // NEXT and OR are meant to be indeces into "int" fields, but arrays must
 141     // be homogenous, so every field is a float. However floats can represent
 142     // exactly up to 26 bit ints, so we're ok.
 143     private static final int OR   = 2;
 144     private static final int SLOPE = 3;
 145     private static final int NEXT = 4;
 146     private static final int SIZEOF_EDGE = 5;
 147 
 148     private int sampleRowMin;
 149     private int sampleRowMax;
 150     private float edgeMinX;
 151     private float edgeMaxX;
 152 
 153     private float[] edges;
 154     private int[] edgeBuckets;
 155     private int numEdges;
 156 
 157     // RT-39439: Need to re-evaluate the BND constants set in the Picecs rasterizer for non AA shape
 158     //  private static final float DEC_BND = 20f;
 159     //  private static final float INC_BND = 8f;
 160     private static final float DEC_BND = 5f;
 161     private static final float INC_BND = 2f;
 162 
 163     // each bucket is a linked list. this method adds eptr to the
 164     // start "bucket"th linked list.
 165     private void addEdgeToBucket(final int eptr, final int bucket) {
 166         // we could implement this in terms of insertEdge, but this is a special
 167         // case, so we optimize a bit.
 168         edges[eptr+NEXT] = edgeBuckets[bucket*2];
 169         edgeBuckets[bucket*2] = eptr + 1;
 170         edgeBuckets[bucket*2 + 1] += 2;
 171     }
 172 
 173     // Flattens using adaptive forward differencing. This only carries out
 174     // one iteration of the AFD loop. All it does is update AFD variables (i.e.
 175     // X0, Y0, D*[X|Y], COUNT; not variables used for computing scanline crossings).
 176     private void quadBreakIntoLinesAndAdd(float x0, float y0,
 177                                           final Curve c,
 178                                           final float x2, final float y2)
 179     {
 180         final float QUAD_DEC_BND = 32;
 181         final int countlg = 4;
 182         int count = 1 << countlg;
 183         int countsq = count * count;
 184         float maxDD = Math.max(c.dbx / countsq, c.dby / countsq);
 185         while (maxDD > QUAD_DEC_BND) {
 186             maxDD /= 4;
 187             count <<= 1;
 188         }
 189 
 190         countsq = count * count;
 191         final float ddx = c.dbx / countsq;
 192         final float ddy = c.dby / countsq;
 193         float dx = c.bx / countsq + c.cx / count;
 194         float dy = c.by / countsq + c.cy / count;
 195 
 196         while (count-- > 1) {
 197             float x1 = x0 + dx;
 198             dx += ddx;
 199             float y1 = y0 + dy;
 200             dy += ddy;
 201             addLine(x0, y0, x1, y1);
 202             x0 = x1;
 203             y0 = y1;
 204         }
 205         addLine(x0, y0, x2, y2);
 206     }
 207 
 208     // x0, y0 and x3,y3 are the endpoints of the curve. We could compute these
 209     // using c.xat(0),c.yat(0) and c.xat(1),c.yat(1), but this might introduce
 210     // numerical errors, and our callers already have the exact values.
 211     // Another alternative would be to pass all the control points, and call c.set
 212     // here, but then too many numbers are passed around.
 213     private void curveBreakIntoLinesAndAdd(float x0, float y0,
 214                                            final Curve c,
 215                                            final float x3, final float y3)
 216     {
 217         final int countlg = 3;
 218         int count = 1 << countlg;
 219 
 220         // the dx and dy refer to forward differencing variables, not the last
 221         // coefficients of the "points" polynomial
 222         float dddx, dddy, ddx, ddy, dx, dy;
 223         dddx = 2f * c.dax / (1 << (3 * countlg));
 224         dddy = 2f * c.day / (1 << (3 * countlg));
 225 
 226         ddx = dddx + c.dbx / (1 << (2 * countlg));
 227         ddy = dddy + c.dby / (1 << (2 * countlg));
 228         dx = c.ax / (1 << (3 * countlg)) + c.bx / (1 << (2 * countlg)) + c.cx / (1 << countlg);
 229         dy = c.ay / (1 << (3 * countlg)) + c.by / (1 << (2 * countlg)) + c.cy / (1 << countlg);
 230 
 231         // we use x0, y0 to walk the line
 232         float x1 = x0, y1 = y0;
 233         while (count > 0) {
 234             while (Math.abs(ddx) > DEC_BND || Math.abs(ddy) > DEC_BND) {
 235                 dddx /= 8;
 236                 dddy /= 8;
 237                 ddx = ddx/4 - dddx;
 238                 ddy = ddy/4 - dddy;
 239                 dx = (dx - ddx) / 2;
 240                 dy = (dy - ddy) / 2;
 241                 count <<= 1;
 242             }
 243             // can only do this on even "count" values, because we must divide count by 2
 244             while (count % 2 == 0 && Math.abs(dx) <= INC_BND && Math.abs(dy) <= INC_BND) {
 245                 dx = 2 * dx + ddx;
 246                 dy = 2 * dy + ddy;
 247                 ddx = 4 * (ddx + dddx);
 248                 ddy = 4 * (ddy + dddy);
 249                 dddx = 8 * dddx;
 250                 dddy = 8 * dddy;
 251                 count >>= 1;
 252             }
 253             count--;
 254             if (count > 0) {
 255                 x1 += dx;
 256                 dx += ddx;
 257                 ddx += dddx;
 258                 y1 += dy;
 259                 dy += ddy;
 260                 ddy += dddy;
 261             } else {
 262                 x1 = x3;
 263                 y1 = y3;
 264             }
 265             addLine(x0, y0, x1, y1);
 266             x0 = x1;
 267             y0 = y1;
 268         }
 269     }
 270 
 271     private void addLine(float x1, float y1, float x2, float y2) {
 272         float or = 1; // orientation of the line. 1 if y increases, 0 otherwise.
 273         if (y2 < y1) {
 274             or = y2; // no need to declare a temp variable. We have or.
 275             y2 = y1;
 276             y1 = or;
 277             or = x2;
 278             x2 = x1;
 279             x1 = or;
 280             or = 0;
 281         }
 282         final int firstCrossing = Math.max((int)Math.ceil(y1), boundsMinY);
 283         final int lastCrossing = Math.min((int)Math.ceil(y2), boundsMaxY);
 284         if (firstCrossing >= lastCrossing) {
 285             return;
 286         }
 287         if (firstCrossing < sampleRowMin) { sampleRowMin = firstCrossing; }
 288         if (lastCrossing > sampleRowMax) { sampleRowMax = lastCrossing; }
 289 
 290         final float slope = (x2 - x1) / (y2 - y1);
 291 
 292         if (slope > 0) { // <==> x1 < x2
 293             if (x1 < edgeMinX) { edgeMinX = x1; }
 294             if (x2 > edgeMaxX) { edgeMaxX = x2; }
 295         } else {
 296             if (x2 < edgeMinX) { edgeMinX = x2; }
 297             if (x1 > edgeMaxX) { edgeMaxX = x1; }
 298         }
 299 
 300         final int ptr = numEdges * SIZEOF_EDGE;
 301         edges = Helpers.widenArray(edges, ptr, SIZEOF_EDGE);
 302         numEdges++;
 303         edges[ptr+OR] = or;
 304         edges[ptr+CURX] = x1 + (firstCrossing - y1) * slope;
 305         edges[ptr+SLOPE] = slope;
 306         edges[ptr+YMAX] = lastCrossing;
 307         final int bucketIdx = firstCrossing - boundsMinY;
 308         addEdgeToBucket(ptr, bucketIdx);
 309         edgeBuckets[(lastCrossing - boundsMinY)*2 + 1] |= 1;
 310     }
 311 
 312 // END EDGE LIST
 313 //////////////////////////////////////////////////////////////////////////////
 314 
 315 
 316     public static final int WIND_EVEN_ODD = 0;
 317     public static final int WIND_NON_ZERO = 1;
 318 
 319     // Antialiasing
 320     final private int SUBPIXEL_LG_POSITIONS_X;
 321     final private int SUBPIXEL_LG_POSITIONS_Y;
 322     final private int SUBPIXEL_POSITIONS_X;
 323     final private int SUBPIXEL_POSITIONS_Y;
 324     final private int SUBPIXEL_MASK_X;
 325     final private int SUBPIXEL_MASK_Y;
 326     final int MAX_AA_ALPHA;
 327 
 328     // Bounds of the drawing region, at subpixel precision.
 329     private int boundsMinX, boundsMinY, boundsMaxX, boundsMaxY;
 330 
 331     // Current winding rule
 332     private int windingRule;
 333 
 334     // Current drawing position, i.e., final point of last segment
 335     private float x0, y0;
 336 
 337     // Position of most recent 'moveTo' command
 338     private float pix_sx0, pix_sy0;
 339 
 340     public Renderer(int subpixelLgPositionsX, int subpixelLgPositionsY)
 341     {
 342         this.SUBPIXEL_LG_POSITIONS_X = subpixelLgPositionsX;
 343         this.SUBPIXEL_LG_POSITIONS_Y = subpixelLgPositionsY;
 344         this.SUBPIXEL_POSITIONS_X = 1 << (SUBPIXEL_LG_POSITIONS_X);
 345         this.SUBPIXEL_POSITIONS_Y = 1 << (SUBPIXEL_LG_POSITIONS_Y);
 346         this.SUBPIXEL_MASK_X = SUBPIXEL_POSITIONS_X - 1;
 347         this.SUBPIXEL_MASK_Y = SUBPIXEL_POSITIONS_Y - 1;
 348         this.MAX_AA_ALPHA = (SUBPIXEL_POSITIONS_X * SUBPIXEL_POSITIONS_Y);
 349     }
 350 
 351     public Renderer(int subpixelLgPositionsX, int subpixelLgPositionsY,
 352                     int pix_boundsX, int pix_boundsY,
 353                     int pix_boundsWidth, int pix_boundsHeight,
 354                     int windingRule)
 355     {
 356         this(subpixelLgPositionsX, subpixelLgPositionsY);
 357         reset(pix_boundsX, pix_boundsY, pix_boundsWidth, pix_boundsHeight,
 358               windingRule);
 359     }
 360 
 361     public void reset(int pix_boundsX, int pix_boundsY,
 362                       int pix_boundsWidth, int pix_boundsHeight,
 363                       int windingRule)
 364     {
 365         this.windingRule = windingRule;
 366 
 367         this.boundsMinX = pix_boundsX * SUBPIXEL_POSITIONS_X;
 368         this.boundsMinY = pix_boundsY * SUBPIXEL_POSITIONS_Y;
 369         this.boundsMaxX = (pix_boundsX + pix_boundsWidth) * SUBPIXEL_POSITIONS_X;
 370         this.boundsMaxY = (pix_boundsY + pix_boundsHeight) * SUBPIXEL_POSITIONS_Y;
 371 
 372         this.edgeMinX = Float.POSITIVE_INFINITY;
 373         this.edgeMaxX = Float.NEGATIVE_INFINITY;
 374         this.sampleRowMax = boundsMinY;
 375         this.sampleRowMin = boundsMaxY;
 376 
 377         int numBuckets = boundsMaxY - boundsMinY;
 378         if (edgeBuckets == null || edgeBuckets.length < numBuckets*2+2) {
 379             // The last 2 entries are ignored and only used to store unused
 380             // values for segments ending on the last line of the bounds
 381             // so we can avoid having to check the bounds on this array.
 382             edgeBuckets = new int[numBuckets*2 + 2];
 383         } else {
 384             // Only need to fill the first numBuckets*2 entries since the
 385             // last 2 entries are write-only for overflow avoidance only.
 386             Arrays.fill(edgeBuckets, 0, numBuckets*2, 0);
 387         }
 388         if (edges == null) {
 389             edges = new float[SIZEOF_EDGE * 32];
 390         }
 391         numEdges = 0;
 392         pix_sx0 = pix_sy0 = x0 = y0 = 0f;
 393     }
 394 
 395     private float tosubpixx(float pix_x) {
 396         return pix_x * SUBPIXEL_POSITIONS_X;
 397     }
 398     private float tosubpixy(float pix_y) {
 399         return pix_y * SUBPIXEL_POSITIONS_Y;
 400     }
 401 
 402     public void moveTo(float pix_x0, float pix_y0) {
 403         closePath();
 404         this.pix_sx0 = pix_x0;
 405         this.pix_sy0 = pix_y0;
 406         this.y0 = tosubpixy(pix_y0);
 407         this.x0 = tosubpixx(pix_x0);
 408     }
 409 
 410     public void lineTo(float pix_x1, float pix_y1) {
 411         float x1 = tosubpixx(pix_x1);
 412         float y1 = tosubpixy(pix_y1);
 413         addLine(x0, y0, x1, y1);
 414         x0 = x1;
 415         y0 = y1;
 416     }
 417 
 418     private Curve c = new Curve();
 419     @Override public void curveTo(float x1, float y1,
 420                                   float x2, float y2,
 421                                   float x3, float y3)
 422     {
 423         final float xe = tosubpixx(x3);
 424         final float ye = tosubpixy(y3);
 425         c.set(x0, y0, tosubpixx(x1), tosubpixy(y1), tosubpixx(x2), tosubpixy(y2), xe, ye);
 426         curveBreakIntoLinesAndAdd(x0, y0, c, xe, ye);
 427         x0 = xe;
 428         y0 = ye;
 429     }
 430 
 431     @Override public void quadTo(float x1, float y1, float x2, float y2) {
 432         final float xe = tosubpixx(x2);
 433         final float ye = tosubpixy(y2);
 434         c.set(x0, y0, tosubpixx(x1), tosubpixy(y1), xe, ye);
 435         quadBreakIntoLinesAndAdd(x0, y0, c, xe, ye);
 436         x0 = xe;
 437         y0 = ye;
 438     }
 439 
 440     public void closePath() {
 441         // lineTo expects its input in pixel coordinates.
 442         lineTo(pix_sx0, pix_sy0);
 443     }
 444 
 445     public void pathDone() {
 446         closePath();
 447     }
 448 
 449     private int savedAlpha[];
 450     private ScanlineIterator savedIterator;
 451     public void produceAlphas(AlphaConsumer ac) {
 452         ac.setMaxAlpha(MAX_AA_ALPHA);
 453 
 454         // Mask to determine the relevant bit of the crossing sum
 455         // 0x1 if EVEN_ODD, all bits if NON_ZERO
 456         int mask = (windingRule == WIND_EVEN_ODD) ? 0x1 : ~0x0;
 457 
 458         // add 2 to better deal with the last pixel in a pixel row.
 459         int width = ac.getWidth();
 460         int alpha[] = savedAlpha;
 461         if (alpha == null || alpha.length < width+2) {
 462             savedAlpha = alpha = new int[width+2];
 463         } else {
 464             Arrays.fill(alpha, 0, width+2, 0);
 465         }
 466 
 467         int bboxx0 = ac.getOriginX() << SUBPIXEL_LG_POSITIONS_X;
 468         int bboxx1 = bboxx0 + (width << SUBPIXEL_LG_POSITIONS_X);
 469 
 470         // Now we iterate through the scanlines. We must tell emitRow the coord
 471         // of the first non-transparent pixel, so we must keep accumulators for
 472         // the first and last pixels of the section of the current pixel row
 473         // that we will emit.
 474         // We also need to accumulate pix_bbox*, but the iterator does it
 475         // for us. We will just get the values from it once this loop is done
 476         int pix_maxX = bboxx1 >> SUBPIXEL_LG_POSITIONS_X;
 477         int pix_minX = bboxx0 >> SUBPIXEL_LG_POSITIONS_Y;
 478 
 479         int y = boundsMinY; // needs to be declared here so we emit the last row properly.
 480         ScanlineIterator it = savedIterator;
 481         if (it == null) {
 482             savedIterator = it = new ScanlineIterator();
 483         } else {
 484             it.reset();
 485         }
 486         for ( ; it.hasNext(); ) {
 487             int numCrossings = it.next();
 488             int[] crossings = it.crossings;
 489             y = it.curY();
 490 
 491             if (numCrossings > 0) {
 492                 int lowx = crossings[0] >> 1;
 493                 int highx = crossings[numCrossings - 1] >> 1;
 494                 int x0 = Math.max(lowx, bboxx0);
 495                 int x1 = Math.min(highx, bboxx1);
 496 
 497                 pix_minX = Math.min(pix_minX, x0 >> SUBPIXEL_LG_POSITIONS_X);
 498                 pix_maxX = Math.max(pix_maxX, x1 >> SUBPIXEL_LG_POSITIONS_X);
 499             }
 500 
 501             int sum = 0;
 502             int prev = bboxx0;
 503             for (int i = 0; i < numCrossings; i++) {
 504                 int curxo = crossings[i];
 505                 int curx = curxo >> 1;
 506                 int crorientation = ((curxo & 0x1) << 1) - 1;
 507                 if ((sum & mask) != 0) {
 508                     int x0 = Math.max(prev, bboxx0);
 509                     int x1 = Math.min(curx, bboxx1);
 510                     if (x0 < x1) {
 511                         x0 -= bboxx0; // turn x0, x1 from coords to indices
 512                         x1 -= bboxx0; // in the alpha array.
 513 
 514                         int pix_x = x0 >> SUBPIXEL_LG_POSITIONS_X;
 515                         int pix_xmaxm1 = (x1 - 1) >> SUBPIXEL_LG_POSITIONS_X;
 516 
 517                         if (pix_x == pix_xmaxm1) {
 518                             // Start and end in same pixel
 519                             alpha[pix_x] += (x1 - x0);
 520                             alpha[pix_x+1] -= (x1 - x0);
 521                         } else {
 522                             int pix_xmax = x1 >> SUBPIXEL_LG_POSITIONS_X;
 523                             alpha[pix_x] += SUBPIXEL_POSITIONS_X - (x0 & SUBPIXEL_MASK_X);
 524                             alpha[pix_x+1] += (x0 & SUBPIXEL_MASK_X);
 525                             alpha[pix_xmax] -= SUBPIXEL_POSITIONS_X - (x1 & SUBPIXEL_MASK_X);
 526                             alpha[pix_xmax+1] -= (x1 & SUBPIXEL_MASK_X);
 527                         }
 528                     }
 529                 }
 530                 sum += crorientation;
 531                 prev = curx;
 532             }
 533 
 534             // even if this last row had no crossings, alpha will be zeroed
 535             // from the last emitRow call. But this doesn't matter because
 536             // maxX < minX, so no row will be emitted to the cache.
 537             if ((y & SUBPIXEL_MASK_Y) == SUBPIXEL_MASK_Y) {
 538                 ac.setAndClearRelativeAlphas(alpha, y >> SUBPIXEL_LG_POSITIONS_Y,
 539                                              pix_minX, pix_maxX);
 540                 pix_maxX = bboxx1 >> SUBPIXEL_LG_POSITIONS_X;
 541                 pix_minX = bboxx0 >> SUBPIXEL_LG_POSITIONS_Y;
 542             }
 543         }
 544 
 545         // Emit final row.
 546         // Note, if y is on a MASK row then it was already sent above...
 547         if ((y & SUBPIXEL_MASK_Y) < SUBPIXEL_MASK_Y) {
 548             ac.setAndClearRelativeAlphas(alpha, y >> SUBPIXEL_LG_POSITIONS_Y,
 549                                          pix_minX, pix_maxX);
 550         }
 551     }
 552 
 553     public int getSubpixMinX() {
 554         int sampleColMin = (int) Math.ceil(edgeMinX);
 555         if (sampleColMin < boundsMinX) sampleColMin = boundsMinX;
 556         return sampleColMin;
 557     }
 558 
 559     public int getSubpixMaxX() {
 560         int sampleColMax = (int) Math.ceil(edgeMaxX);
 561         if (sampleColMax > boundsMaxX) sampleColMax = boundsMaxX;
 562         return sampleColMax;
 563     }
 564 
 565     public int getSubpixMinY() {
 566         return sampleRowMin;
 567     }
 568 
 569     public int getSubpixMaxY() {
 570         return sampleRowMax;
 571     }
 572 
 573     public int getOutpixMinX() {
 574         return (getSubpixMinX() >> SUBPIXEL_LG_POSITIONS_X);
 575     }
 576 
 577     public int getOutpixMaxX() {
 578         return (getSubpixMaxX() + SUBPIXEL_MASK_X) >> SUBPIXEL_LG_POSITIONS_X;
 579     }
 580 
 581     public int getOutpixMinY() {
 582         return (sampleRowMin >> SUBPIXEL_LG_POSITIONS_Y);
 583     }
 584 
 585     public int getOutpixMaxY() {
 586         return (sampleRowMax + SUBPIXEL_MASK_Y) >> SUBPIXEL_LG_POSITIONS_Y;
 587     }
 588 }