/* JAI-Ext - OpenSource Java Advanced Image Extensions Library * http://www.geo-solutions.it/ * Copyright 2014 GeoSolutions * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package it.geosolutions.jaiext.affine; import it.geosolutions.jaiext.interpolators.InterpolationNearest; import it.geosolutions.jaiext.iterators.RandomIterFactory; import it.geosolutions.jaiext.range.Range; import java.awt.Point; import java.awt.Rectangle; import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import java.awt.image.Raster; import java.awt.image.RenderedImage; import java.awt.image.WritableRaster; import java.awt.image.renderable.ParameterBlock; import java.util.Map; import javax.media.jai.BorderExtender; import javax.media.jai.GeometricOpImage; import javax.media.jai.ImageLayout; import javax.media.jai.Interpolation; import javax.media.jai.JAI; import javax.media.jai.PlanarImage; import javax.media.jai.ROI; import javax.media.jai.RenderedOp; import javax.media.jai.iterator.RandomIter; import javax.media.jai.util.ImagingException; import javax.media.jai.util.ImagingListener; import com.sun.media.jai.util.ImageUtil; /** * An OpImage class to perform (possibly filtered) affine mapping between a source and destination image. * * The geometric relationship between source and destination pixels is defined as the following (<code>x</code> and <code>y</code> denote the source * pixel coordinates; <code>x'</code> and <code>y'</code> denote the destination pixel coordinates; <code>m</code> denotes the 3x2 transform matrix): * <ul> * <code> * x' = m[0][0] * x + m[0][1] * y + m[0][2] * <br> * y' = m[1][0] * x + m[1][1] * y + m[1][2] * </code> * </ul> * */ abstract class AffineOpImage extends GeometricOpImage { protected static final double HALF_PIXEL = 0.5d; /** ROI extender */ final static BorderExtender roiExtender = BorderExtender .createInstance(BorderExtender.BORDER_ZERO); /** * Unsigned short Max Value */ protected static final int USHORT_MAX_VALUE = Short.MAX_VALUE - Short.MIN_VALUE; /** * The forward AffineTransform describing the image transformation. */ protected AffineTransform f_transform; /** * The inverse AffineTransform describing the image transformation. */ protected AffineTransform i_transform; /** The Interpolation object. */ protected Interpolation interp; /** Store source & padded rectangle info */ protected Rectangle srcimg; protected Rectangle padimg; /** The BorderExtender */ protected BorderExtender extender; /** The true writable area */ protected Rectangle theDest; /** Cache the ImagingListener. */ protected ImagingListener listener; /** Destination value for No Data byte */ protected byte[] destinationNoDataByte; /** Destination value for No Data ushort */ protected short[] destinationNoDataUShort; /** Destination value for No Data short */ protected short[] destinationNoDataShort; /** Destination value for No Data int */ protected int[] destinationNoDataInt; /** Destination value for No Data float */ protected float[] destinationNoDataFloat; /** Destination value for No Data double */ protected double[] destinationNoDataDouble; /** Boolean for checking if the interpolator is Nearest */ protected boolean isNearestNew = false; /** Boolean for checking if the interpolator is Bilinear */ protected boolean isBilinearNew = false; /** Boolean for checking if the interpolator is Bicubic */ protected boolean isBicubicNew = false; /** Value indicating if roi RasterAccessor should be used on computations */ protected boolean useROIAccessor; /** * Scanline walking : variables & constants */ /** The fixed-point denominator of the fractional offsets. */ protected static final int GEOM_FRAC_MAX = 0x100000; double m00, m10, flr_m00, flr_m10; double fracdx, fracdx1, fracdy, fracdy1; int incx, incx1, incy, incy1; int ifracdx, ifracdx1, ifracdy, ifracdy1; /** * Padding values for interpolation */ protected int lpad, rpad, tpad, bpad; /** Source ROI */ protected final ROI srcROI; /** ROI image */ protected final PlanarImage srcROIImage; /** Rectangle containing ROI bounds */ protected final Rectangle roiBounds; /** Boolean indicating if a ROI object is used */ protected final boolean hasROI; /** Boolean for checking if no data range is present */ protected boolean hasNoData = false; /** No Data Range */ protected Range noData; /** Boolean indicating if No Data and ROI are not used */ protected boolean caseA; /** Boolean indicating if only the ROI is used */ protected boolean caseB; /** Boolean indicating if only the No Data are used */ protected boolean caseC; /** Extended source image*/ protected RenderedOp extendedIMG; /** Extended ROI image*/ protected RenderedOp srcROIImgExt; /** * The extended bounds used by the roi iterator */ protected Rectangle roiRect; /** * Computes floor(num/denom) using integer arithmetic. denom must not be equal to 0. */ protected static int floorRatio(long num, long denom) { if (denom < 0) { denom = -denom; num = -num; } if (num >= 0) { return (int) (num / denom); } else { return (int) ((num - denom + 1) / denom); } } /** * Computes ceil(num/denom) using integer arithmetic. denom must not be equal to 0. */ protected static int ceilRatio(long num, long denom) { if (denom < 0) { denom = -denom; num = -num; } if (num >= 0) { return (int) ((num + denom - 1) / denom); } else { return (int) (num / denom); } } private static ImageLayout layoutHelper(ImageLayout layout, RenderedImage source, AffineTransform forward_tr) { ImageLayout newLayout; if (layout != null) { newLayout = (ImageLayout) layout.clone(); } else { newLayout = new ImageLayout(); } // // Get sx0,sy0 coordinates and width & height of the source // float sx0 = (float) source.getMinX(); float sy0 = (float) source.getMinY(); float sw = (float) source.getWidth(); float sh = (float) source.getHeight(); // // The 4 points (clockwise order) are // (sx0, sy0), (sx0+sw, sy0) // (sx0, sy0+sh), (sx0+sw, sy0+sh) // Point2D[] pts = new Point2D[4]; pts[0] = new Point2D.Float(sx0, sy0); pts[1] = new Point2D.Float((sx0 + sw), sy0); pts[2] = new Point2D.Float((sx0 + sw), (sy0 + sh)); pts[3] = new Point2D.Float(sx0, (sy0 + sh)); // Forward map forward_tr.transform(pts, 0, pts, 0, 4); float dx0 = Float.MAX_VALUE; float dy0 = Float.MAX_VALUE; float dx1 = -Float.MAX_VALUE; float dy1 = -Float.MAX_VALUE; for (int i = 0; i < 4; i++) { float px = (float) pts[i].getX(); float py = (float) pts[i].getY(); dx0 = Math.min(dx0, px); dy0 = Math.min(dy0, py); dx1 = Math.max(dx1, px); dy1 = Math.max(dy1, py); } // // Get the width & height of the resulting bounding box. // This is set on the layout // int lw = (int) (dx1 - dx0); int lh = (int) (dy1 - dy0); // // Set the starting integral coordinate // with the following criterion. // If it's greater than 0.5, set it to the next integral value (ceil) // else set it to the integral value (floor). // int lx0, ly0; int i_dx0 = (int) Math.floor(dx0); if (Math.abs(dx0 - i_dx0) <= 0.5) { lx0 = i_dx0; } else { lx0 = (int) Math.ceil(dx0); } int i_dy0 = (int) Math.floor(dy0); if (Math.abs(dy0 - i_dy0) <= 0.5) { ly0 = i_dy0; } else { ly0 = (int) Math.ceil(dy0); } // // Create the layout // newLayout.setMinX(lx0); newLayout.setMinY(ly0); newLayout.setWidth(lw); newLayout.setHeight(lh); return newLayout; } /** * Constructs an AffineOpImage from a RenderedImage source, AffineTransform, and Interpolation object. The image dimensions are determined by * forward-mapping the source bounds. The tile grid layout, SampleModel, and ColorModel are specified by the image source, possibly overridden by * values from the ImageLayout parameter. * * @param source a RenderedImage. * @param extender a BorderExtender, or null. * @param layout an ImageLayout optionally containing the tile grid layout, SampleModel, and ColorModel, or null. * @param transform the desired AffineTransform. * @param interp an Interpolation object. */ public AffineOpImage(RenderedImage source, BorderExtender extender, Map config, ImageLayout layout, AffineTransform transform, Interpolation interp, double[] backgroundValues) { super(vectorize(source), layoutHelper(layout, source, transform), config, true, extender, interp, backgroundValues); listener = ImageUtil.getImagingListener((java.awt.RenderingHints) config); // store the interp and extender objects this.interp = interp; // the extender this.extender = extender; // Store the padding values lpad = interp.getLeftPadding(); rpad = interp.getRightPadding(); tpad = interp.getTopPadding(); bpad = interp.getBottomPadding(); // // Store source bounds rectangle // and the padded rectangle (for extension cases) // srcimg = new Rectangle(getSourceImage(0).getMinX(), getSourceImage(0).getMinY(), getSourceImage(0).getWidth(), getSourceImage(0).getHeight()); padimg = new Rectangle(srcimg.x - lpad, srcimg.y - tpad, srcimg.width + lpad + rpad, srcimg.height + tpad + bpad); if (extender == null) { // // Source has to be shrunk as per interpolation // as a result the destination produced could // be different from the layout // // // Get sx0,sy0 coordinates and width & height of the source // float sx0 = (float) srcimg.x; float sy0 = (float) srcimg.y; float sw = (float) srcimg.width; float sh = (float) srcimg.height; // // get padding amounts as per interpolation // float f_lpad = (float) lpad; float f_rpad = (float) rpad; float f_tpad = (float) tpad; float f_bpad = (float) bpad; // // As per pixel defined to be at (0.5, 0.5) // if (!(interp instanceof InterpolationNearest)) { f_lpad += 0.5; f_tpad += 0.5; f_rpad += 0.5; f_bpad += 0.5; } // // Shrink the source by padding amount prior to forward map // This is the maxmimum available source than can be mapped // sx0 += f_lpad; sy0 += f_tpad; sw -= (f_lpad + f_rpad); sh -= (f_tpad + f_bpad); // // The 4 points are (x0, y0), (x0+w, y0) // (x0+w, y0+h), (x0, y0+h) // Point2D[] pts = new Point2D[4]; pts[0] = new Point2D.Float(sx0, sy0); pts[1] = new Point2D.Float((sx0 + sw), sy0); pts[2] = new Point2D.Float((sx0 + sw), (sy0 + sh)); pts[3] = new Point2D.Float(sx0, (sy0 + sh)); // Forward map transform.transform(pts, 0, pts, 0, 4); float dx0 = Float.MAX_VALUE; float dy0 = Float.MAX_VALUE; float dx1 = -Float.MAX_VALUE; float dy1 = -Float.MAX_VALUE; for (int i = 0; i < 4; i++) { float px = (float) pts[i].getX(); float py = (float) pts[i].getY(); dx0 = Math.min(dx0, px); dy0 = Math.min(dy0, py); dx1 = Math.max(dx1, px); dy1 = Math.max(dy1, py); } // // The layout is the wholly contained integer area of the // corresponding floating point bounding box. // We cannot round the corners of the floating rect because it // would increase the size of the rect, so we need to ceil the // upper corner and floor the lower corner. // int lx0 = (int) Math.ceil(dx0); int ly0 = (int) Math.ceil(dy0); int lx1 = (int) Math.floor(dx1); int ly1 = (int) Math.floor(dy1); theDest = new Rectangle(lx0, ly0, lx1 - lx0, ly1 - ly0); } else { theDest = getBounds(); // Padding of the input image in order to avoid the call of the getExtendedData() method // Extend the Source image ParameterBlock pb = new ParameterBlock(); pb.setSource(source, 0); pb.set(lpad, 0); pb.set(rpad, 1); pb.set(tpad, 2); pb.set(bpad, 3); pb.set(extender, 4); // Call of the Border operation extendedIMG = JAI.create("border", pb); } // Store the inverse and forward transforms. try { this.i_transform = transform.createInverse(); } catch (Exception e) { String message = JaiI18N.getString("AffineOpImage0"); listener.errorOccurred(message, new ImagingException(message, e), this, false); // throw new RuntimeException(JaiI18N.getString("AffineOpImage0")); } this.f_transform = (AffineTransform) transform.clone(); // // Store the incremental values used in scanline walking. // m00 = i_transform.getScaleX(); // get m00 flr_m00 = Math.floor(m00); fracdx = m00 - flr_m00; fracdx1 = 1.0F - fracdx; incx = (int) flr_m00; // Movement incx1 = incx + 1; // along x ifracdx = (int) Math.round(fracdx * GEOM_FRAC_MAX); ifracdx1 = GEOM_FRAC_MAX - ifracdx; m10 = i_transform.getShearY(); // get m10 flr_m10 = Math.floor(m10); fracdy = m10 - flr_m10; fracdy1 = 1.0F - fracdy; incy = (int) flr_m10; // Movement incy1 = incy + 1; // along y ifracdy = (int) Math.round(fracdy * GEOM_FRAC_MAX); ifracdy1 = GEOM_FRAC_MAX - ifracdy; // SG Retrieve the rendered source image and its ROI. Object property = source.getProperty("ROI"); if (property instanceof ROI) { srcROI = (ROI) property; srcROIImage = srcROI.getAsImage(); // FIXME can we use smaller bounds here? roiRect = new Rectangle(srcROIImage.getMinX() - lpad, srcROIImage.getMinY() - tpad, srcROIImage.getWidth() + lpad + rpad, srcROIImage.getHeight() + tpad + bpad); // Padding of the input ROI image in order to avoid the call of the getExtendedData() method // Calculate the padding between the ROI and the source image padded roiBounds = srcROIImage.getBounds(); Rectangle srcRect = padimg; int deltaX0 = (roiBounds.x - srcRect.x); int leftP = deltaX0 > 0 ? deltaX0 : 0; int deltaY0 = (roiBounds.y - srcRect.y); int topP = deltaY0 > 0 ? deltaY0 : 0; int deltaX1 = (srcRect.x + srcRect.width - roiBounds.x - roiBounds.width); int rightP = deltaX1 > 0 ? deltaX1 : 0; int deltaY1 = (srcRect.y + srcRect.height - roiBounds.y - roiBounds.height); int bottomP = deltaY1 > 0 ? deltaY1 : 0; // Extend the ROI image ParameterBlock pb = new ParameterBlock(); pb.setSource(srcROIImage, 0); pb.set(leftP, 0); pb.set(rightP, 1); pb.set(topP, 2); pb.set(bottomP, 3); pb.set(roiExtender, 4); srcROIImgExt = JAI.create("border", pb); hasROI = true; } else { srcROI = null; srcROIImage = null; roiBounds = null; hasROI = false; } } /** * Computes the source point corresponding to the supplied point. * * @param destPt the position in destination image coordinates to map to source image coordinates. * * @return a <code>Point2D</code> of the same class as <code>destPt</code>. * * @throws IllegalArgumentException if <code>destPt</code> is <code>null</code>. * * @since JAI 1.1.2 */ public Point2D mapDestPoint(Point2D destPt) { if (destPt == null) { throw new IllegalArgumentException(JaiI18N.getString("Generic0")); } Point2D dpt = (Point2D) destPt.clone(); dpt.setLocation(dpt.getX() + 0.5, dpt.getY() + 0.5); Point2D spt = i_transform.transform(dpt, null); spt.setLocation(spt.getX() - 0.5, spt.getY() - 0.5); return spt; } /** * Computes the destination point corresponding to the supplied point. * * @param sourcePt the position in source image coordinates to map to destination image coordinates. * * @return a <code>Point2D</code> of the same class as <code>sourcePt</code>. * * @throws IllegalArgumentException if <code>destPt</code> is <code>null</code>. * * @since JAI 1.1.2 */ public Point2D mapSourcePoint(Point2D sourcePt) { if (sourcePt == null) { throw new IllegalArgumentException(JaiI18N.getString("Generic0")); } Point2D spt = (Point2D) sourcePt.clone(); spt.setLocation(spt.getX() + 0.5, spt.getY() + 0.5); Point2D dpt = f_transform.transform(spt, null); dpt.setLocation(dpt.getX() - 0.5, dpt.getY() - 0.5); return dpt; } /** * Forward map the source Rectangle. */ protected Rectangle forwardMapRect(Rectangle sourceRect, int sourceIndex) { return f_transform.createTransformedShape(sourceRect).getBounds(); } /** * Backward map the destination Rectangle. */ protected Rectangle backwardMapRect(Rectangle destRect, int sourceIndex) { // // Backward map the destination to get the corresponding // source Rectangle // float dx0 = (float) destRect.x; float dy0 = (float) destRect.y; float dw = (float) (destRect.width); float dh = (float) (destRect.height); Point2D[] pts = new Point2D[4]; pts[0] = new Point2D.Float(dx0, dy0); pts[1] = new Point2D.Float((dx0 + dw), dy0); pts[2] = new Point2D.Float((dx0 + dw), (dy0 + dh)); pts[3] = new Point2D.Float(dx0, (dy0 + dh)); i_transform.transform(pts, 0, pts, 0, 4); float f_sx0 = Float.MAX_VALUE; float f_sy0 = Float.MAX_VALUE; float f_sx1 = -Float.MAX_VALUE; float f_sy1 = -Float.MAX_VALUE; for (int i = 0; i < 4; i++) { float px = (float) pts[i].getX(); float py = (float) pts[i].getY(); f_sx0 = Math.min(f_sx0, px); f_sy0 = Math.min(f_sy0, py); f_sx1 = Math.max(f_sx1, px); f_sy1 = Math.max(f_sy1, py); } int s_x0 = 0, s_y0 = 0, s_x1 = 0, s_y1 = 0; // Find the bounding box of the source rectangle if (interp instanceof InterpolationNearest) { s_x0 = (int) Math.floor(f_sx0); s_y0 = (int) Math.floor(f_sy0); // Fix for bug 4485920 was to add " + 0.05" to the following // two lines. It should be noted that the fix was made based // on empirical evidence and tested thoroughly, but it is not // known whether this is the root cause. s_x1 = (int) Math.ceil(f_sx1 + 0.5); s_y1 = (int) Math.ceil(f_sy1 + 0.5); } else { s_x0 = (int) Math.floor(f_sx0 - 0.5); s_y0 = (int) Math.floor(f_sy0 - 0.5); s_x1 = (int) Math.ceil(f_sx1); s_y1 = (int) Math.ceil(f_sy1); } // // Return the new rectangle // return new Rectangle(s_x0, s_y0, s_x1 - s_x0, s_y1 - s_y0); } /** * Backward map a destination coordinate (using inverse_transform) to get the corresponding source coordinate. We need not worry about * interpolation here. * * @param destPt the destination point to backward map * @return source point result of the backward map */ public void mapDestPoint(Point2D destPoint, Point2D srcPoint) { i_transform.transform(destPoint, srcPoint); } public Raster computeTile(int tileX, int tileY) { // // Create a new WritableRaster to represent this tile. // Point org = new Point(tileXToX(tileX), tileYToY(tileY)); WritableRaster dest = createWritableRaster(sampleModel, org); // // Clip output rectangle to image bounds. // Rectangle rect = new Rectangle(org.x, org.y, tileWidth, tileHeight); // // Clip destination tile against the writable destination // area. This is either the layout or a smaller area if // no extension is specified. // Rectangle destRect = rect.intersection(theDest); Rectangle destRect1 = rect.intersection(getBounds()); if ((destRect.width <= 0) || (destRect.height <= 0)) { // No area to write if (setBackground) ImageUtil.fillBackground(dest, destRect1, backgroundValues); return dest; } // // determine the source rectangle needed to compute the destRect // Rectangle srcRect = mapDestRect(destRect, 0); if (extender == null) { srcRect = srcRect.intersection(srcimg); } else { srcRect = srcRect.intersection(padimg); } if (!(srcRect.width > 0 && srcRect.height > 0)) { if (setBackground) ImageUtil.fillBackground(dest, destRect1, backgroundValues); return dest; } if (!destRect1.equals(destRect)) { // beware that destRect1 contains destRect ImageUtil.fillBordersWithBackgroundValues(destRect1, destRect, dest, backgroundValues); } Raster[] sources = new Raster[1]; // SourceImage PlanarImage srcIMG = getSourceImage(0); // Get the source and ROI data // Note that the getExtendedData() method is not called because the input images are padded. // For each image there is a check if the rectangle is contained inside the source image; // if this not happen, the data is taken from the padded image. if (extender == null) { sources[0] = srcIMG.getData(srcRect); } else { if(srcIMG.getBounds().contains(srcRect)){ sources[0] = srcIMG.getData(srcRect); } else { sources[0] = extendedIMG.getData(srcRect); } } // Compute the destination tile. computeRect(sources, dest, destRect); // Recycle the source tile if (getSourceImage(0).overlapsMultipleTiles(srcRect)) { recycleTile(sources[0]); } return dest; } protected abstract void computeRect(Raster[] sources, WritableRaster dest, Rectangle destRect); @Override public synchronized void dispose() { if (srcROIImage != null) { srcROIImage.dispose(); } if(extendedIMG != null) { extendedIMG.dispose(); } super.dispose(); } // Scanline clipping stuff /** * Sets clipMinX, clipMaxX based on s_ix, s_iy, ifracx, ifracy, dst_min_x, and dst_min_y. Padding factors are added and subtracted from the source * bounds as given by src_rect_{x,y}{1,2}. For example, for nearest-neighbor interpo the padding factors should be set to (0, 0, 0, 0); for * bilinear, (0, 1, 0, 1); and for bicubic, (1, 2, 1, 2). * * <p> * The returned Range object will be for the Integer class and will contain extrema equivalent to clipMinX and clipMaxX. */ protected javax.media.jai.util.Range performScanlineClipping(float src_rect_x1, float src_rect_y1, float src_rect_x2, float src_rect_y2, int s_ix, int s_iy, int ifracx, int ifracy, int dst_min_x, int dst_max_x, int lpad, int rpad, int tpad, int bpad) { int clipMinX = dst_min_x; int clipMaxX = dst_max_x; long xdenom = incx * GEOM_FRAC_MAX + ifracdx; if (xdenom != 0) { long clipx1 = (long) src_rect_x1 + lpad; long clipx2 = (long) src_rect_x2 - rpad; long x1 = ((clipx1 - s_ix) * GEOM_FRAC_MAX - ifracx) + dst_min_x * xdenom; long x2 = ((clipx2 - s_ix) * GEOM_FRAC_MAX - ifracx) + dst_min_x * xdenom; // Moving backwards, switch roles of left and right edges if (xdenom < 0) { long tmp = x1; x1 = x2; x2 = tmp; } int dx1 = ceilRatio(x1, xdenom); clipMinX = Math.max(clipMinX, dx1); int dx2 = floorRatio(x2, xdenom) + 1; clipMaxX = Math.min(clipMaxX, dx2); } else { // xdenom == 0, all points have same x coordinate as the first if (s_ix < src_rect_x1 || s_ix >= src_rect_x2) { clipMinX = clipMaxX = dst_min_x; return new javax.media.jai.util.Range(Integer.class, new Integer(clipMinX), new Integer(clipMaxX)); } } long ydenom = incy * GEOM_FRAC_MAX + ifracdy; if (ydenom != 0) { long clipy1 = (long) src_rect_y1 + tpad; long clipy2 = (long) src_rect_y2 - bpad; long y1 = ((clipy1 - s_iy) * GEOM_FRAC_MAX - ifracy) + dst_min_x * ydenom; long y2 = ((clipy2 - s_iy) * GEOM_FRAC_MAX - ifracy) + dst_min_x * ydenom; // Moving backwards, switch roles of top and bottom edges if (ydenom < 0) { long tmp = y1; y1 = y2; y2 = tmp; } int dx1 = ceilRatio(y1, ydenom); clipMinX = Math.max(clipMinX, dx1); int dx2 = floorRatio(y2, ydenom) + 1; clipMaxX = Math.min(clipMaxX, dx2); } else { // ydenom == 0, all points have same y coordinate as the first if (s_iy < src_rect_y1 || s_iy >= src_rect_y2) { clipMinX = clipMaxX = dst_min_x; } } if (clipMinX > dst_max_x) clipMinX = dst_max_x; if (clipMaxX < dst_min_x) clipMaxX = dst_min_x; return new javax.media.jai.util.Range(Integer.class, new Integer(clipMinX), new Integer( clipMaxX)); } /** * Sets s_ix, s_iy, ifracx, ifracy to their values at x == clipMinX from their initial values at x == dst_min_x. * * <p> * The return Point array will contain the updated values of s_ix and s_iy in the first element and those of ifracx and ifracy in the second * element. */ protected Point[] advanceToStartOfScanline(int dst_min_x, int clipMinX, int s_ix, int s_iy, int ifracx, int ifracy) { // Skip output up to clipMinX long skip = clipMinX - dst_min_x; long dx = ((long) ifracx + skip * ifracdx) / GEOM_FRAC_MAX; long dy = ((long) ifracy + skip * ifracdy) / GEOM_FRAC_MAX; s_ix += skip * incx + (int) dx; s_iy += skip * incy + (int) dy; long lfracx = ifracx + skip * ifracdx; if (lfracx >= 0) { ifracx = (int) (lfracx % GEOM_FRAC_MAX); } else { ifracx = (int) (-(-lfracx % GEOM_FRAC_MAX)); } long lfracy = ifracy + skip * ifracdy; if (lfracy >= 0) { ifracy = (int) (lfracy % GEOM_FRAC_MAX); } else { ifracy = (int) (-(-lfracy % GEOM_FRAC_MAX)); } return new Point[] { new Point(s_ix, s_iy), new Point(ifracx, ifracy) }; } }