/* * @(#)BasicImageContext.java * * $Date: 2014-06-06 20:04:49 +0200 (P, 06 jún. 2014) $ * * Copyright (c) 2014 by Jeremy Wood. * All rights reserved. * * The copyright of this software is owned by Jeremy Wood. * You may not use, copy or modify this software, except in * accordance with the license agreement you entered into with * Jeremy Wood. For details see accompanying license terms. * * This software is probably, but not necessarily, discussed here: * https://javagraphics.java.net/ * * That site should also contain the most recent official version * of this software. (See the SVN repository for more details.) */ package com.bric.image; import java.awt.Graphics2D; import java.awt.Point; import java.awt.RenderingHints; import java.awt.geom.Line2D; import java.awt.geom.Point2D; import java.awt.image.BufferedImage; import java.awt.image.DataBufferInt; import java.util.Comparator; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.SortedMap; import java.util.SortedSet; import java.util.TreeMap; import java.util.TreeSet; import java.util.concurrent.ExecutorService; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.TimeUnit; import javax.media.jai.PerspectiveTransform; import com.bric.image.BasicImageContext.VariableWidthFunction.LineSegmentIntersectionException; import com.bric.math.MathG; /** This is a simple Java implementation of image transformations. * <p>This is the result of 12 different potential optimizations. * @see com.bric.image.BasicImageContextDemo */ public class BasicImageContext extends ImageContext { final int width, height; final int[] data; final int stride; final BufferedImage bi; boolean disposed = false; ExecutorService executor = null; /** Create a Graphics3D context that paints to a destination image using 6 threads. * * @param bi an RGB or ARGB image. */ public BasicImageContext(BufferedImage bi) { this(bi, 6); //TODO: in Java 1.8, use this: //executor = Executors.newWorkStealingPool(threads); } /** Create a Graphics3D context that paints to a destination image. * * @param bi an RGB or ARGB image. * @param numberOfThreads if positive then this is the number of threads used to * render tiles. If zero then calls to <code>drawImage</code> are not multithreaded. */ public BasicImageContext(BufferedImage bi,int numberOfThreads) { int type = bi.getType(); if(!(type==BufferedImage.TYPE_INT_ARGB || type==BufferedImage.TYPE_INT_RGB)) { throw new IllegalArgumentException("only TYPE_INT_RGB and TYPE_INT_ARGB are supported"); } this.bi = bi; width = bi.getWidth(); height = bi.getHeight(); stride = bi.getRaster().getWidth(); data = getPixels(bi); if(numberOfThreads>0) { executor = new ForkJoinPool(numberOfThreads); } } /** Return all the pixels in the argument in ARGB format. */ protected int[] getPixels(BufferedImage bi) { if ((bi.getType() != BufferedImage.TYPE_INT_ARGB && bi.getType() != BufferedImage.TYPE_INT_RGB) || !(bi.getRaster().getDataBuffer() instanceof DataBufferInt)) { BufferedImage tmp = bi; bi = new BufferedImage(tmp.getWidth(), tmp.getHeight(), BufferedImage.TYPE_INT_ARGB); Graphics2D g = bi.createGraphics(); g.drawImage(tmp, 0, 0, null); g.dispose(); } DataBufferInt buf = (DataBufferInt) bi.getRaster().getDataBuffer(); int[] p = buf.getData(); return p; } /** This calculates the left and right edges of a horizontal strip. */ static abstract class HorizontalStripFunction { /** Return the left and right x-coordinates (inclusive) that need to be processed * in a given row. * @param y the row to process. * @param dst a 2-pixel array storing the left and right x-coordinates (respectively) */ public abstract void getXEndpoints(int y,int[] dst); /** Create a similar new function that enforces a new min and max x-value. * This does not replace any previous min/max values: it further constrains them. */ public abstract HorizontalStripFunction derive(int newMinX,int newMaxX); } /** Without doing anything clever: this returns the same * left/right edges each time. (This effectively makes the * pixels we iterate over a rectangle.) */ static class FixedWidthFunction extends HorizontalStripFunction { final int minX, maxX; FixedWidthFunction(int minX,int maxX) { this.minX = minX; this.maxX = maxX; } public void getXEndpoints(int y,int[] dst) { dst[0] = minX; dst[1] = maxX; } public HorizontalStripFunction derive(int newMinX,int newMaxX) { return new FixedWidthFunction(Math.max(newMinX, minX), Math.min(newMaxX, maxX)); } } /** This chooses left/right edges that follow the contour of * a quadrilateral. (It rounds a little bit to make sure * all the relevant pixels will be covered.) */ static class VariableWidthFunction extends HorizontalStripFunction { final int minX, maxX; /** Sort Point2Ds in ascending y value. */ static Comparator<Point2D> yComparator = new Comparator<Point2D>() { @Override public int compare(Point2D o1, Point2D o2) { if(o1.getY()<o2.getY()) { return -1; } else if(o1.getY()>o2.getY()){ return 1; } else if(o1.getX()<o2.getX()) { return -1; } else if(o1.getX()>o2.getX()) { return 1; } else { return 0; } } }; /** Sort Point2Ds in ascending y value. */ static Comparator<Point2D> xComparator = new Comparator<Point2D>() { @Override public int compare(Point2D o1, Point2D o2) { if(o1.getX()<o2.getX()) { return -1; } else if(o1.getX()>o2.getX()){ return 1; } else if(o1.getY()<o2.getY()) { return -1; } else if(o1.getY()>o2.getY()) { return 1; } else { return 0; } } }; static class LineSegmentIntersectionException extends Exception { private static final long serialVersionUID = 1L; } /** The points that trace the left edge of the image, from top to bottom. */ final int[] leftX, leftY; /** The points that trace the right edge of the image, from top to bottom. */ final int[] rightX, rightY; private static boolean intersect(Point2D a1,Point2D a2,Point2D b1,Point2D b2) { return Line2D.linesIntersect(a1.getX(), a1.getY(), a2.getX(), a2.getY(), b1.getX(), b1.getY(), b2.getX(), b2.getY()); } VariableWidthFunction(int minX,int maxX,Point2D topLeft,Point2D topRight,Point2D bottomRight,Point2D bottomLeft) throws LineSegmentIntersectionException { this.minX = minX; this.maxX = maxX; if(intersect(topLeft, bottomLeft, topRight, bottomRight)) throw new LineSegmentIntersectionException(); if(intersect(topLeft, topRight, bottomLeft, bottomRight)) throw new LineSegmentIntersectionException(); SortedSet<Point2D> horizontalList = new TreeSet<Point2D>( xComparator ); horizontalList.add(topLeft); horizontalList.add(topRight); horizontalList.add(bottomLeft); horizontalList.add(bottomRight); double leftX = horizontalList.first().getX(); double rightX = horizontalList.last().getX(); SortedSet<Point> left = new TreeSet<Point>( yComparator ); SortedSet<Point> right = new TreeSet<Point>( yComparator ); Point2D[] path = new Point2D[] { topLeft, topRight, bottomRight, bottomLeft }; for(int a = 0; a<path.length; a++) { int prev = (a - 1 + path.length)%path.length; int next = (a + 1 + path.length)%path.length; boolean bottomMostVertex = path[prev].getY()<=path[a].getY() && path[next].getY()<=path[a].getY(); boolean topMostVertex = path[prev].getY()>=path[a].getY() && path[next].getY()>=path[a].getY(); if(path[a].getX()==leftX) { addPoint2D(left, path[a], false); if(bottomMostVertex || topMostVertex) { if(path[prev].getX()<=path[a].getX()) { addPoint2D(left, path[prev], false); } else if(path[next].getX()<=path[a].getX()) { addPoint2D(left, path[next], false); } } else { addPoint2D(left, path[prev], false); addPoint2D(left, path[next], false); } } if(path[a].getX()==rightX) { addPoint2D(right, path[a], true); if(bottomMostVertex || topMostVertex) { if(path[prev].getX()>=path[a].getX()) { addPoint2D(right, path[prev], true); } else if(path[next].getX()>=path[a].getX()) { addPoint2D(right, path[next], true); } } else { addPoint2D(right, path[prev], true); addPoint2D(right, path[next], true); } } } left = removeRedundantYs(left, false); right = removeRedundantYs(right, true); this.leftX = new int[left.size()]; this.leftY = new int[left.size()]; store(left, this.leftX, this.leftY); this.rightX = new int[right.size()]; this.rightY = new int[right.size()]; store(right, this.rightX, this.rightY); } VariableWidthFunction(int minX,int maxX,int[] leftX,int[] leftY,int[] rightX,int[] rightY) { this.minX = minX; this.maxX = maxX; this.leftX = leftX; this.leftY = leftY; this.rightX = rightX; this.rightY = rightY; } public HorizontalStripFunction derive(int newMinX,int newMaxX) { int finalMin = Math.max(newMinX, minX); int finalMax = Math.min(newMaxX, maxX); return new VariableWidthFunction(finalMin, finalMax, leftX, leftY, rightX, rightY); } private SortedSet<Point> removeRedundantYs(Set<Point> points,boolean useGreaterValue) { SortedMap<Integer, Integer> map = new TreeMap<Integer, Integer>(); Iterator<Point> iter = points.iterator(); while(iter.hasNext()) { Point p = iter.next(); Integer x = map.get(p.y); if(x==null) { map.put(p.y, p.x); } else if(useGreaterValue) { map.put(p.y, Math.max( p.x, x) ); } else { map.put(p.y, Math.min( p.x, x) ); } } SortedSet<Point> returnValue = new TreeSet<Point>( yComparator ); Iterator<Integer> yIter = map.keySet().iterator(); while(yIter.hasNext()) { Integer y = yIter.next(); Integer x = map.get(y); returnValue.add(new Point(x, y)); } return returnValue; } private void store(Set<Point> points,int[] xs,int[] ys) { Iterator<Point> pIter = points.iterator(); int ctr = 0; while(pIter.hasNext()) { Point p = pIter.next(); xs[ctr] = p.x; ys[ctr] = p.y; ctr++; } } private void addPoint2D(Set<Point> dest,Point2D p,boolean roundUp) { int y = (int)p.getY(); int x = roundUp ? MathG.ceilInt(p.getX()) : MathG.floorInt(p.getX()); if(y==p.getY()) { dest.add(new Point(x, y)); } else { dest.add(new Point(x, y)); dest.add(new Point(x, y+1)); } } public void getXEndpoints(int y,int[] dst) { /* This doesn't use a binary search, but the list is * only going to be about 6 units long. */ //calculate left edge (dst[0]): boolean leftEdge = false; for(int i = 0; i<leftX.length-1 && (!leftEdge); i++) { int ymin = leftY[i]; int ymax = leftY[i+1]; if(y>=ymin && y<=ymax) { int yrange = ymax - ymin; int xmin = leftX[i]; int xmax = leftX[i+1]; int xrange = xmax - xmin; int x = xmin + xrange*(y - ymin)/yrange; dst[0] = x; leftEdge = true; } } if( (!leftEdge) || dst[0]<minX) dst[0] = minX; //calculate right edge (dst[1]): boolean rightEdge = false; for(int i = 0; i<rightX.length-1 && (!rightEdge); i++) { int ymin = rightY[i]; int ymax = rightY[i+1]; if(y>=ymin && y<=ymax) { int yrange = ymax - ymin; int xmin = rightX[i]; int xmax = rightX[i+1]; int xrange = xmax - xmin; int x = xmin + xrange*(y - ymin)/yrange; dst[1] = x; rightEdge = true; } } if( (!rightEdge) || dst[1]>maxX) dst[1] = maxX; } } /** Draw an image to this Graphics3D. * <p>This respects the interpolation rendering hints. When the * interpolation hint is missing, this will also consult the antialiasing * hint or the render hint. The bilinear hint is used by default. * <p>This uses a source over composite. * * @param img the image to draw. * @param topLeft where the top-left corner of this image will be painted. * @param topRight where the top-right corner of this image will be painted. * @param bottomRight where the bottom-right corner of this image will be painted. * @param bottomLeft where the bottom-left corner of this image will be painted. */ public synchronized void drawImage(BufferedImage img,Point2D topLeft,Point2D topRight,Point2D bottomRight,Point2D bottomLeft) { if(disposed) throw new IllegalStateException("This Graphics3D context has been disposed."); Point2D srcTopLeft = new Point2D.Double(0,0); Point2D srcTopRight = new Point2D.Double(img.getWidth(),0); Point2D srcBottomLeft = new Point2D.Double(0,img.getHeight()); Point2D srcBottomRight = new Point2D.Double(img.getWidth(),img.getHeight()); double minX = Math.min( Math.min(topLeft.getX(), topRight.getX()), Math.min(bottomLeft.getX(), bottomRight.getX()) ); double maxX = Math.max( Math.max(topLeft.getX(), topRight.getX()), Math.max(bottomLeft.getX(), bottomRight.getX()) ); double minY = Math.min( Math.min(topLeft.getY(), topRight.getY()), Math.min(bottomLeft.getY(), bottomRight.getY()) ); double maxY = Math.max( Math.max(topLeft.getY(), topRight.getY()), Math.max(bottomLeft.getY(), bottomRight.getY()) ); int minXi = MathG.floorInt(minX)-1; int maxXi = MathG.ceilInt(maxX)+1; int minYi = MathG.floorInt(minY)-1; int maxYi = MathG.ceilInt(maxY)+1; //bound everything from [0,limit) minXi = Math.max(0, Math.min(width-1, minXi)); maxXi = Math.max(0, Math.min(width-1, maxXi)); minYi = Math.max(0, Math.min(height-1, minYi)); maxYi = Math.max(0, Math.min(height-1, maxYi)); PerspectiveTransform pt = PerspectiveTransform.getQuadToQuad( topLeft.getX(), topLeft.getY(), topRight.getX(), topRight.getY(), bottomLeft.getX(), bottomLeft.getY(), bottomRight.getX(), bottomRight.getY(), srcTopLeft.getX(), srcTopLeft.getY(), srcTopRight.getX(), srcTopRight.getY(), srcBottomLeft.getX(), srcBottomLeft.getY(), srcBottomRight.getX(), srcBottomRight.getY() ); int[] otherPixels = getPixels(img); int oStride = img.getRaster().getWidth(); int oWidth = img.getWidth(); int oHeight = img.getHeight(); boolean oHasAlpha = img.getColorModel().hasAlpha(); HorizontalStripFunction stripFunction; try { stripFunction = new VariableWidthFunction(minXi, maxXi, topLeft, topRight, bottomRight, bottomLeft); } catch(LineSegmentIntersectionException e) { stripFunction = new FixedWidthFunction(minXi, maxXi); } Object interpolationHint = getInterpolationRenderingHint(); if(executor!=null) { int y = minYi; while(y<=maxYi) { int cy = y / 100; int h = Math.min( (cy+1)*100, maxYi ) - y; int x = minXi; while(x<=maxXi) { int cx = x / 100; int w = Math.min( (cx+1)*100, maxXi ) - x; TileInstructions i = new TileInstructions(cx << 8 + cy, x, y, w - 1, h - 1, interpolationHint, otherPixels, pt, oHasAlpha, oWidth, oHeight, oStride, stripFunction); synchronized(pendingTileInstructions) { pendingTileInstructions.add(i); } executor.submit(new DrawTileRunnable()); x = (cx+1)*100; } y = (cy+1)*100; } } else { drawTile(minXi, minYi, maxXi, maxXi, interpolationHint, otherPixels, pt, oHasAlpha, oWidth, oHeight, oStride, stripFunction); } } class TileInstructions { int id, tileX, tileY, tileWidth, tileHeight, oWidth, oHeight, oStride; Object renderingHint; int[] otherPixels; PerspectiveTransform transform; boolean oHasAlpha; HorizontalStripFunction stripFunction; public TileInstructions(int id, int x, int y, int w, int h, Object rh, int[] otherPixels, PerspectiveTransform pt,boolean oHasAlpha,int oWidth, int oHeight,int oStride,HorizontalStripFunction stripFunction) { this.id = id; this.tileX = x; this.tileY = y; this.tileWidth = w; this.tileHeight = h; this.renderingHint = rh; this.otherPixels = otherPixels; this.transform = pt; this.oHasAlpha = oHasAlpha; this.oWidth = oWidth; this.oHeight = oHeight; this.oStride= oStride; this.stripFunction = stripFunction.derive(tileX, tileX+tileWidth); } @Override public String toString() { return "TileInstructions[ id="+id+", x="+tileX+", y="+tileY+", w="+tileWidth+", h="+tileHeight+", hint="+renderingHint+"]"; } } List<TileInstructions> pendingTileInstructions = new LinkedList<TileInstructions>(); Set<Integer> reservedTiles = new HashSet<Integer>(); class DrawTileRunnable implements Runnable { public void run() { TileInstructions c = null; while(c==null) { synchronized(pendingTileInstructions) { Iterator<TileInstructions> iter = pendingTileInstructions.iterator(); while(iter.hasNext() && c==null) { TileInstructions instr = iter.next(); synchronized(reservedTiles) { if(reservedTiles.add(instr.id)) { c = instr; iter.remove(); } } } } if(c==null) { synchronized(reservedTiles) { try { reservedTiles.wait(1000); } catch(Exception e) {} } } } try { drawTile( c.tileX, c.tileY, c.tileX+c.tileWidth, c.tileY+c.tileHeight, c.renderingHint, c.otherPixels, c.transform, c.oHasAlpha, c.oWidth, c.oHeight, c.oStride, c.stripFunction ); } finally { synchronized(pendingTileInstructions) { synchronized(reservedTiles) { reservedTiles.remove(c.id); reservedTiles.notifyAll(); } } } } } protected void drawTile(int minXi,int minYi,int maxXi,int maxYi,Object interpolationHint,int[] otherPixels,PerspectiveTransform pt,boolean oHasAlpha,int oWidth,int oHeight,int oStride, HorizontalStripFunction stripFunction) { double transformedX, transformedY; double m00, m01, m02, m10, m11, m12, m20, m21, m22, w; { double[][] matrix = new double[3][3]; pt.getMatrix(matrix); m00 = matrix[0][0]; m01 = matrix[0][1]; m02 = matrix[0][2]; m10 = matrix[1][0]; m11 = matrix[1][1]; m12 = matrix[1][2]; m20 = matrix[2][0]; m21 = matrix[2][1]; m22 = matrix[2][2]; } int[] xEndpoints = new int[2]; if(RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR.equals(interpolationHint)) { if (oHasAlpha) { for(int y = minYi; y<=maxYi; y++) { int yw = y * stride; double yd = y; stripFunction.getXEndpoints(y, xEndpoints); for(int x = xEndpoints[0]; x<=xEndpoints[1]; x++) { double xd = x; //transform (x,y) to (transformedX, transformedY): w = m20 * xd + m21 * yd + m22; transformedX = (m00 * xd + m01 * yd + m02) / w; transformedY = (m10 * xd + m11 * yd + m12) / w; int newX = (int)(transformedX+.5); int newY = (int)(transformedY+.5); if(newY>=0 && newY<oHeight && newX>=0 && newX<oWidth) { int src = otherPixels[newY * oStride + newX]; int srcA = src >>> 24; if(srcA==255) { data[yw+x] = src; } else if(srcA>0) { int r = (src >> 16) & 0xff; int g = (src >> 8) & 0xff; int b = src & 0xff; int dst = data[yw + x]; int dstA = (dst >> 24) & 0xff; int dstAX = (dstA) * (255 - srcA); int dstR = (dst >> 16) & 0xff; int dstG = (dst >> 8) & 0xff; int dstB = dst & 0xff; int srcAX = srcA * 255; int resA = srcAX + dstAX; if(resA!=0) { r = (r*srcAX + dstR * dstAX)/resA; g = (g*srcAX + dstG * dstAX)/resA; b = (b*srcAX + dstB * dstAX)/resA; data[yw+x] = (resA / 255 << 24) | ((r>255) ? 0xff0000 : r << 16) | ((g>255) ? 0xff00 : g << 8) | ((b>255) ? 0xff : b); } } } } } } else { for(int y = minYi; y<=maxYi; y++) { int yw = y * stride; double yd = y; stripFunction.getXEndpoints(y, xEndpoints); for(int x = xEndpoints[0]; x<=xEndpoints[1]; x++) { double xd = x; //transform (x,y) to (transformedX, transformedY): w = m20 * xd + m21 * yd + m22; transformedX = (m00 * xd + m01 * yd + m02) / w; transformedY = (m10 * xd + m11 * yd + m12) / w; int newX = (int)(transformedX+.5); int newY = (int)(transformedY+.5); if(newY>=0 && newY<oHeight && newX>=0 && newX<oWidth) { data[yw+x] = otherPixels[newY*oStride+newX]; } } } } } else { int windowLength; if(RenderingHints.VALUE_INTERPOLATION_BICUBIC.equals(interpolationHint)) { windowLength = 4; } else { windowLength = 2; } double windowLengthD = windowLength; double incr = 1.0 / (windowLengthD-1.0); int windowArea = windowLength*windowLength; if (oHasAlpha) { for(int y = minYi; y<=maxYi; y++) { int yw = y * stride; double yd = y; stripFunction.getXEndpoints(y, xEndpoints); for(int x = xEndpoints[0]; x<=xEndpoints[1]; x++) { double xd = x; int samples = windowArea; int srcA = 0; int r = 0; int g = 0; int b = 0; for(double dx = 0; dx<windowLengthD; dx++) { for(double dy = 0; dy<windowLengthD; dy++) { double x2 = xd+dx*incr; double y2 = yd+dy*incr; //transform (x,y) to (transformedX, transformedY): w = m20 * x2 + m21 * y2 + m22; transformedX = (m00 * x2 + m01 * y2 + m02) / w; transformedY = (m10 * x2 + m11 * y2 + m12) / w; int newX = (int)(transformedX-.00001); int newY = (int)(transformedY-.00001); if(newY>=0 && newY<oHeight && newX>=0 && newX<oWidth) { int opix = otherPixels[newY * oStride + newX]; srcA += (opix >> 24) & 0xff; r += opix & 0xff0000; g += opix & 0xff00; b += opix & 0xff; } else { samples--; } } } if(samples>0) { srcA = srcA/samples; r = (r >>> 16)/samples; g = (g >>> 8)/samples; b = b/samples; if(srcA==255) { data[yw+x] = 0xff000000 | (r << 16) | (g << 8) | b; } else if(srcA>0) { int dst = data[yw+x]; int dstAX = (dst >>> 24) * (255 - srcA); int dstR = (dst >> 16) & 0xff; int dstG = (dst >> 8) & 0xff; int dstB = dst & 0xff; int srcAX = srcA * 255; int resA = (srcAX + dstAX); if(resA!=0) { r = (r * srcAX + dstR * dstAX) / resA; g = (g * srcAX + dstG * dstAX) / resA; b = (b * srcAX + dstB * dstAX) / resA; data[yw+x] = (resA / 255 << 24) | ((r>255) ? 0xff0000 : r << 16) | ((g>255) ? 0xff00 : g << 8) | ((b>255) ? 0xff : b); } } } } } } else { for(int y = minYi; y<=maxYi; y++) { int yw = y * stride; double yd = y; stripFunction.getXEndpoints(y, xEndpoints); for(int x = xEndpoints[0]; x<=xEndpoints[1]; x++) { double xd = x; int samples = windowArea; int srcA = 0; int r = 0; int g = 0; int b = 0; for(double dx = 0; dx<windowLengthD; dx++) { for(double dy = 0; dy<windowLengthD; dy++) { double x2 = xd+dx*incr; double y2 = yd+dy*incr; //transform (x,y) to (transformedX, transformedY): w = m20 * x2 + m21 * y2 + m22; transformedX = (m00 * x2 + m01 * y2 + m02) / w; transformedY = (m10 * x2 + m11 * y2 + m12) / w; int newX = (int)(transformedX-.00001); int newY = (int)(transformedY-.00001); if(newY>=0 && newY<oHeight && newX>=0 && newX<oWidth) { int opix = otherPixels[newY * oStride + newX]; srcA += 255; r += opix & 0xff0000; g += opix & 0xff00; b += opix & 0xff; } else { samples--; } } } if(samples>0) { srcA = srcA/samples; r = (r >>> 16)/samples; g = (g >>> 8)/samples; b = b/samples; if(srcA==255) { data[yw+x] = 0xff000000 | (r << 16) | (g << 8) | b; } else if(srcA>0) { int dst = data[yw+x]; int dstAX = (dst >>> 24) * (255 - srcA); int dstR = (dst >> 16) & 0xff; int dstG = (dst >> 8) & 0xff; int dstB = dst & 0xff; int srcAX = srcA * 255; int resA = (srcAX + dstAX); if(resA!=0) { r = (r * srcAX + dstR * dstAX) / resA; g = (g * srcAX + dstG * dstAX) / resA; b = (b * srcAX + dstB * dstAX) / resA; data[yw+x] = (resA / 255 << 24) | ((r>255) ? 0xff0000 : r << 16) | ((g>255) ? 0xff00 : g << 8) | ((b>255) ? 0xff : b); } } } } } } } } /** Commit all changes back to the BufferedImage this context paints to. */ public synchronized void dispose() { if (executor != null) { executor.shutdown(); try { executor.awaitTermination(60, TimeUnit.MINUTES); } catch (InterruptedException e) { e.printStackTrace(); } } disposed = true; } }