/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ /* Part of the Processing project - http://processing.org Copyright (c) 2013-15 The Processing Foundation Copyright (c) 2005-13 Ben Fry and Casey Reas This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package processing.awt; import java.awt.*; import java.awt.font.TextAttribute; import java.awt.geom.*; import java.awt.image.*; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import processing.core.*; /** * Subclass for PGraphics that implements the graphics API using Java2D. *
* To get access to the Java 2D "Graphics2D" object for the default * renderer, use: *
* Graphics2D g2 = (Graphics2D) g.getNative(); ** This will let you do Graphics2D calls directly, but is not supported * in any way shape or form. Which just means "have fun, but don't complain * if it breaks." *
* Advanced debugging notes for Java2D. */ public class PGraphicsJava2D extends PGraphics { //// BufferStrategy strategy; //// BufferedImage bimage; //// VolatileImage vimage; // Canvas canvas; //// boolean useCanvas = true; // boolean useCanvas = false; //// boolean useRetina = true; //// boolean useOffscreen = true; // ~40fps // boolean useOffscreen = false; /** * */ public Graphics2D g2; // protected BufferedImage offscreen; Composite defaultComposite; GeneralPath gpath; // path for contours so gpath can be closed GeneralPath auxPath; boolean openContour; /// break the shape at the next vertex (next vertex() call is a moveto()) boolean breakShape; /// coordinates for internal curve calculation float[] curveCoordX; float[] curveCoordY; float[] curveDrawX; float[] curveDrawY; int transformCount; AffineTransform transformStack[] = new AffineTransform[MATRIX_STACK_DEPTH]; double[] transform = new double[6]; Line2D.Float line = new Line2D.Float(); Ellipse2D.Float ellipse = new Ellipse2D.Float(); Rectangle2D.Float rect = new Rectangle2D.Float(); Arc2D.Float arc = new Arc2D.Float(); /** * */ protected Color tintColorObject; /** * */ protected Color fillColorObject; /** * */ public boolean fillGradient; /** * */ public Paint fillGradientObject; /** * */ protected Stroke strokeObject; /** * */ protected Color strokeColorObject; /** * */ public boolean strokeGradient; /** * */ public Paint strokeGradientObject; Font fontObject; ////////////////////////////////////////////////////////////// // INTERNAL /** * */ public PGraphicsJava2D() { } //public void setParent(PApplet parent) //public void setPrimary(boolean primary) //public void setPath(String path) // /** // * Called in response to a resize event, handles setting the // * new width and height internally, as well as re-allocating // * the pixel buffer for the new size. // * // * Note that this will nuke any cameraMode() settings. // */ // @Override // public void setSize(int iwidth, int iheight) { // ignore // width = iwidth; // height = iheight; // // allocate(); // reapplySettings(); // } // @Override // protected void allocate() { // //surface.initImage(this, width, height); // surface.initImage(this); // } /* @Override protected void allocate() { // Tried this with RGB instead of ARGB for the primarySurface version, // but didn't see any performance difference (OS X 10.6, Java 6u24). // For 0196, also attempted RGB instead of ARGB, but that causes // strange things to happen with blending. // image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); if (primarySurface) { if (useCanvas) { if (canvas != null) { parent.removeListeners(canvas); parent.remove(canvas); } canvas = new Canvas(); canvas.setIgnoreRepaint(true); // parent.setLayout(new BorderLayout()); // parent.add(canvas, BorderLayout.CENTER); parent.add(canvas); // canvas.validate(); // parent.doLayout(); if (canvas.getWidth() != width || canvas.getHeight() != height) { PApplet.debug("PGraphicsJava2D comp size being set to " + width + "x" + height); canvas.setSize(width, height); } else { PApplet.debug("PGraphicsJava2D comp size already " + width + "x" + height); } parent.addListeners(canvas); // canvas.createBufferStrategy(1); // g2 = (Graphics2D) canvas.getGraphics(); } else { parent.updateListeners(parent); // in case they're already there // using a compatible image here doesn't seem to provide any performance boost if (useOffscreen) { // Needs to be RGB otherwise there's a major performance hit [0204] // http://code.google.com/p/processing/issues/detail?id=729 image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); // GraphicsConfiguration gc = parent.getGraphicsConfiguration(); // image = gc.createCompatibleImage(width, height); offscreen = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); // offscreen = gc.createCompatibleImage(width, height); g2 = (Graphics2D) offscreen.getGraphics(); } else { // System.out.println("hopefully faster " + width + " " + height); // new Exception().printStackTrace(System.out); GraphicsConfiguration gc = canvas.getGraphicsConfiguration(); // If not realized (off-screen, i.e the Color Selector Tool), // gc will be null. if (gc == null) { GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); gc = ge.getDefaultScreenDevice().getDefaultConfiguration(); } image = gc.createCompatibleImage(width, height); g2 = (Graphics2D) image.getGraphics(); } } } else { // not the primary surface // Since this buffer's offscreen anyway, no need for the extra offscreen // buffer. However, unlike the primary surface, this feller needs to be // ARGB so that blending ("alpha" compositing) will work properly. image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); g2 = (Graphics2D) image.getGraphics(); } */ /* if (primarySurface) { Canvas canvas = ((PSurfaceAWT) surface).canvas; GraphicsConfiguration gc = canvas.getGraphicsConfiguration(); // If not realized (off-screen, i.e the Color Selector Tool), // gc will be null. if (gc == null) { GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); gc = ge.getDefaultScreenDevice().getDefaultConfiguration(); } image = gc.createCompatibleImage(width, height); g2 = (Graphics2D) image.getGraphics(); } else { } g2 = (Graphics2D) image.getGraphics(); } */ //public void dispose() @Override public PSurface createSurface() { return surface = new PSurfaceAWT(this); } /** * Still need a means to get the java.awt.Image object, since getNative() * is going to return the {@link Graphics2D} object. * @return */ @Override public Image getImage() { return image; } /** Returns the java.awt.Graphics2D object used by this renderer. * @return */ @Override public Object getNative() { return g2; } ////////////////////////////////////////////////////////////// // FRAME // @Override // public boolean canDraw() { // return true; // } // @Override // public void requestDraw() { //// EventQueue.invokeLater(new Runnable() { //// public void run() { // parent.handleDraw(); //// } //// }); // } // Graphics2D g2old; /** * * @return */ public Graphics2D checkImage() { if (image == null || ((BufferedImage) image).getWidth() != width*pixelDensity || ((BufferedImage) image).getHeight() != height*pixelDensity) { // ((VolatileImage) image).getWidth() != width || // ((VolatileImage) image).getHeight() != height) { // image = new BufferedImage(width * pixelFactor, height * pixelFactor // format == RGB ? BufferedImage.TYPE_INT_ARGB); GraphicsConfiguration gc = null; if (surface != null) { Component comp = null; //surface.getComponent(); if (comp == null) { // System.out.println("component null, but parent.frame is " + parent.frame); comp = parent.frame; } if (comp != null) { gc = comp.getGraphicsConfiguration(); } } // If not realized (off-screen, i.e the Color Selector Tool), gc will be null. if (gc == null) { //System.err.println("GraphicsConfiguration null in initImage()"); GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); gc = ge.getDefaultScreenDevice().getDefaultConfiguration(); } // Formerly this was broken into separate versions based on offscreen or // not, but we may as well create a compatible image; it won't hurt, right? int wide = width * pixelDensity; int high = height * pixelDensity; // System.out.println("re-creating image"); image = gc.createCompatibleImage(wide, high, Transparency.TRANSLUCENT); // image = gc.createCompatibleVolatileImage(wide, high); //image = surface.getComponent().createImage(width, height); } return (Graphics2D) image.getGraphics(); } @Override public void beginDraw() { g2 = checkImage(); // Calling getGraphics() seems to nuke several settings. // It seems to be re-creating a new Graphics2D object each time. // https://github.com/processing/processing/issues/3331 if (strokeObject != null) { g2.setStroke(strokeObject); } // https://github.com/processing/processing/issues/2617 if (fontObject != null) { g2.setFont(fontObject); } // https://github.com/processing/processing/issues/4019 if (blendMode != 0) { blendMode(blendMode); } handleSmooth(); /* // NOTE: Calling image.getGraphics() will create a new Graphics context, // even if it's for the same image that's already had a context created. // Seems like a speed/memory issue, and also requires that all smoothing, // stroke, font and other props be reset. Can't find a good answer about // whether getGraphics() and dispose() on each frame is 1) better practice // and 2) minimal overhead, however. Instinct suggests #1 may be true, // but #2 seems a problem. if (primarySurface && !useOffscreen) { GraphicsConfiguration gc = canvas.getGraphicsConfiguration(); if (false) { if (image == null || ((VolatileImage) image).validate(gc) == VolatileImage.IMAGE_INCOMPATIBLE) { image = gc.createCompatibleVolatileImage(width, height); g2 = (Graphics2D) image.getGraphics(); reapplySettings = true; } } else { if (image == null) { image = gc.createCompatibleImage(width, height); PApplet.debug("created new image, type is " + image); g2 = (Graphics2D) image.getGraphics(); reapplySettings = true; } } } if (useCanvas && primarySurface) { if (parent.frameCount == 0) { canvas.createBufferStrategy(2); strategy = canvas.getBufferStrategy(); PApplet.debug("PGraphicsJava2D.beginDraw() strategy is " + strategy); BufferCapabilities caps = strategy.getCapabilities(); caps = strategy.getCapabilities(); PApplet.debug("PGraphicsJava2D.beginDraw() caps are " + " flipping: " + caps.isPageFlipping() + " front/back accel: " + caps.getFrontBufferCapabilities().isAccelerated() + " " + "/" + caps.getBackBufferCapabilities().isAccelerated()); } GraphicsConfiguration gc = canvas.getGraphicsConfiguration(); if (bimage == null || bimage.getWidth() != width || bimage.getHeight() != height) { PApplet.debug("PGraphicsJava2D creating new image"); bimage = gc.createCompatibleImage(width, height); // image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); g2 = bimage.createGraphics(); defaultComposite = g2.getComposite(); reapplySettings = true; } } */ checkSettings(); resetMatrix(); // reset model matrix vertexCount = 0; } /** * Smoothing for Java2D is 2 for bilinear, and 3 for bicubic (the default). * Internally, smooth(1) is the default, smooth(0) is noSmooth(). */ protected void handleSmooth() { if (smooth == 0) { g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_OFF); } else { g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); if (smooth == 1 || smooth == 3) { // default is bicubic g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); } else if (smooth == 2) { g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); } // http://docs.oracle.com/javase/tutorial/2d/text/renderinghints.html // Oracle Java text anti-aliasing on OS X looks like s*t compared to the // text rendering with Apple's old Java 6. Below, several attempts to fix: g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); // Turns out this is the one that actually makes things work. // Kerning is still screwed up, however. g2.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON); // g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, // RenderingHints.VALUE_TEXT_ANTIALIAS_GASP); // g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, // RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB); // g2.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, // RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY); } } @Override public void endDraw() { // hm, mark pixels as changed, because this will instantly do a full // copy of all the pixels to the surface.. so that's kind of a mess. //updatePixels(); if (primaryGraphics) { /* //if (canvas != null) { if (useCanvas) { //System.out.println(canvas); // alternate version //canvas.repaint(); // ?? what to do for swapping buffers // System.out.println("endDraw() frameCount is " + parent.frameCount); // if (parent.frameCount != 0) { redraw(); // } } else if (useOffscreen) { // don't copy the pixels/data elements of the buffered image directly, // since it'll disable the nice speedy pipeline stuff, sending all drawing // into a world of suck that's rough 6 trillion times slower. synchronized (image) { //System.out.println("inside j2d sync"); image.getGraphics().drawImage(offscreen, 0, 0, null); } } else { // changed to not dispose and get on each frame, // otherwise a new Graphics context is used on each frame // g2.dispose(); // System.out.println("not doing anything special in endDraw()"); } */ } else { // TODO this is probably overkill for most tasks... loadPixels(); } // // Marking as modified, and then calling updatePixels() in // // the super class, which just sets the mx1, my1, mx2, my2 // // coordinates of the modified area. This avoids doing the // // full copy of the pixels to the surface in this.updatePixels(). // setModified(); // super.updatePixels(); // Marks pixels as modified so that the pixels will be updated. // Also sets mx1/y1/x2/y2 so that OpenGL will pick it up. setModified(); g2.dispose(); } /* private void redraw() { // only need this check if the validate() call will use redraw() // if (strategy == null) return; do { PApplet.debug("PGraphicsJava2D.redraw() top of outer do { } block"); do { PApplet.debug("PGraphicsJava2D.redraw() top of inner do { } block"); PApplet.debug("strategy is " + strategy); Graphics bsg = strategy.getDrawGraphics(); // if (vimage != null) { // bsg.drawImage(vimage, 0, 0, null); // } else { bsg.drawImage(bimage, 0, 0, null); // if (parent.frameCount == 0) { // try { // ImageIO.write(image, "jpg", new java.io.File("/Users/fry/Desktop/buff.jpg")); // } catch (IOException e) { // e.printStackTrace(); // } // } // } bsg.dispose(); // the strategy version // g2.dispose(); // if (!strategy.contentsLost()) { // if (parent.frameCount != 0) { // Toolkit.getDefaultToolkit().sync(); // } // } else { // System.out.println("XXXXX strategy contents lost"); // } // } // } } while (strategy.contentsRestored()); PApplet.debug("PGraphicsJava2D.redraw() showing strategy"); strategy.show(); } while (strategy.contentsLost()); PApplet.debug("PGraphicsJava2D.redraw() out of do { } block"); } */ ////////////////////////////////////////////////////////////// // SETTINGS //protected void checkSettings() @Override protected void defaultSettings() { // if (!useCanvas) { // // Papered over another threading issue... // // See if this comes back now that the other issue is fixed. //// while (g2 == null) { //// try { //// System.out.println("sleeping until g2 is available"); //// Thread.sleep(5); //// } catch (InterruptedException e) { } //// } defaultComposite = g2.getComposite(); // } super.defaultSettings(); } //protected void reapplySettings() ////////////////////////////////////////////////////////////// // HINT @Override public void hint(int which) { // take care of setting the hint super.hint(which); // Avoid badness when drawing shorter strokes. // http://code.google.com/p/processing/issues/detail?id=1068 // Unfortunately cannot always be enabled, because it makes the // stroke in many standard Processing examples really gross. if (which == ENABLE_STROKE_PURE) { g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE); } else if (which == DISABLE_STROKE_PURE) { g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_DEFAULT); } } ////////////////////////////////////////////////////////////// // SHAPE CREATION @Override protected PShape createShapeFamily(int type) { return new PShape(this, type); } @Override protected PShape createShapePrimitive(int kind, float... p) { return new PShape(this, kind, p); } // @Override // public PShape createShape(PShape source) { // return PShapeOpenGL.createShape2D(this, source); // } /* protected PShape createShapeImpl(PGraphicsJava2D pg, int type) { PShape shape = null; if (type == PConstants.GROUP) { shape = new PShape(pg, PConstants.GROUP); } else if (type == PShape.PATH) { shape = new PShape(pg, PShape.PATH); } else if (type == PShape.GEOMETRY) { shape = new PShape(pg, PShape.GEOMETRY); } // defaults to false, don't assign it and make complexity for overrides //shape.set3D(false); return shape; } */ /* static protected PShape createShapeImpl(PGraphicsJava2D pg, int kind, float... p) { PShape shape = null; int len = p.length; if (kind == POINT) { if (len != 2) { showWarning("Wrong number of parameters"); return null; } shape = new PShape(pg, PShape.PRIMITIVE); shape.setKind(POINT); } else if (kind == LINE) { if (len != 4) { showWarning("Wrong number of parameters"); return null; } shape = new PShape(pg, PShape.PRIMITIVE); shape.setKind(LINE); } else if (kind == TRIANGLE) { if (len != 6) { showWarning("Wrong number of parameters"); return null; } shape = new PShape(pg, PShape.PRIMITIVE); shape.setKind(TRIANGLE); } else if (kind == QUAD) { if (len != 8) { showWarning("Wrong number of parameters"); return null; } shape = new PShape(pg, PShape.PRIMITIVE); shape.setKind(QUAD); } else if (kind == RECT) { if (len != 4 && len != 5 && len != 8 && len != 9) { showWarning("Wrong number of parameters"); return null; } shape = new PShape(pg, PShape.PRIMITIVE); shape.setKind(RECT); } else if (kind == ELLIPSE) { if (len != 4 && len != 5) { showWarning("Wrong number of parameters"); return null; } shape = new PShape(pg, PShape.PRIMITIVE); shape.setKind(ELLIPSE); } else if (kind == ARC) { if (len != 6 && len != 7) { showWarning("Wrong number of parameters"); return null; } shape = new PShape(pg, PShape.PRIMITIVE); shape.setKind(ARC); } else if (kind == BOX) { showWarning("Primitive not supported in 2D"); } else if (kind == SPHERE) { showWarning("Primitive not supported in 2D"); } else { showWarning("Unrecognized primitive type"); } if (shape != null) { shape.setParams(p); } // defaults to false, don't assign it and make complexity for overrides //shape.set3D(false); return shape; } */ ////////////////////////////////////////////////////////////// // SHAPES @Override public void beginShape(int kind) { //super.beginShape(kind); shape = kind; vertexCount = 0; curveVertexCount = 0; // set gpath to null, because when mixing curves and straight // lines, vertexCount will be set back to zero, so vertexCount == 1 // is no longer a good indicator of whether the shape is new. // this way, just check to see if gpath is null, and if it isn't // then just use it to continue the shape. gpath = null; auxPath = null; } //public boolean edge(boolean e) //public void normal(float nx, float ny, float nz) { //public void textureMode(int mode) @Override public void texture(PImage image) { showMethodWarning("texture"); } @Override public void vertex(float x, float y) { curveVertexCount = 0; //float vertex[]; if (vertexCount == vertices.length) { float temp[][] = new float[vertexCount<<1][VERTEX_FIELD_COUNT]; System.arraycopy(vertices, 0, temp, 0, vertexCount); vertices = temp; //message(CHATTER, "allocating more vertices " + vertices.length); } // not everyone needs this, but just easier to store rather // than adding another moving part to the code... vertices[vertexCount][X] = x; vertices[vertexCount][Y] = y; vertexCount++; switch (shape) { case POINTS: point(x, y); break; case LINES: if ((vertexCount % 2) == 0) { line(vertices[vertexCount-2][X], vertices[vertexCount-2][Y], x, y); } break; case TRIANGLES: if ((vertexCount % 3) == 0) { triangle(vertices[vertexCount - 3][X], vertices[vertexCount - 3][Y], vertices[vertexCount - 2][X], vertices[vertexCount - 2][Y], x, y); } break; case TRIANGLE_STRIP: if (vertexCount >= 3) { triangle(vertices[vertexCount - 2][X], vertices[vertexCount - 2][Y], vertices[vertexCount - 1][X], vertices[vertexCount - 1][Y], vertices[vertexCount - 3][X], vertices[vertexCount - 3][Y]); } break; case TRIANGLE_FAN: if (vertexCount >= 3) { // This is an unfortunate implementation because the stroke for an // adjacent triangle will be repeated. However, if the stroke is not // redrawn, it will replace the adjacent line (when it lines up // perfectly) or show a faint line (when off by a small amount). // The alternative would be to wait, then draw the shape as a // polygon fill, followed by a series of vertices. But that's a // poor method when used with PDF, DXF, or other recording objects, // since discrete triangles would likely be preferred. triangle(vertices[0][X], vertices[0][Y], vertices[vertexCount - 2][X], vertices[vertexCount - 2][Y], x, y); } break; case QUAD: case QUADS: if ((vertexCount % 4) == 0) { quad(vertices[vertexCount - 4][X], vertices[vertexCount - 4][Y], vertices[vertexCount - 3][X], vertices[vertexCount - 3][Y], vertices[vertexCount - 2][X], vertices[vertexCount - 2][Y], x, y); } break; case QUAD_STRIP: // 0---2---4 // | | | // 1---3---5 if ((vertexCount >= 4) && ((vertexCount % 2) == 0)) { quad(vertices[vertexCount - 4][X], vertices[vertexCount - 4][Y], vertices[vertexCount - 2][X], vertices[vertexCount - 2][Y], x, y, vertices[vertexCount - 3][X], vertices[vertexCount - 3][Y]); } break; case POLYGON: if (gpath == null) { gpath = new GeneralPath(); gpath.moveTo(x, y); } else if (breakShape) { gpath.moveTo(x, y); breakShape = false; } else { gpath.lineTo(x, y); } break; } } @Override public void vertex(float x, float y, float z) { showDepthWarningXYZ("vertex"); } @Override public void vertex(float[] v) { vertex(v[X], v[Y]); } @Override public void vertex(float x, float y, float u, float v) { showVariationWarning("vertex(x, y, u, v)"); } @Override public void vertex(float x, float y, float z, float u, float v) { showDepthWarningXYZ("vertex"); } @Override public void beginContour() { if (openContour) { PGraphics.showWarning("Already called beginContour()"); return; } // draw contours to auxiliary path so main path can be closed later GeneralPath contourPath = auxPath; auxPath = gpath; gpath = contourPath; if (contourPath != null) { // first contour does not break breakShape = true; } openContour = true; } @Override public void endContour() { if (!openContour) { PGraphics.showWarning("Need to call beginContour() first"); return; } // close this contour if (gpath != null) gpath.closePath(); // switch back to main path GeneralPath contourPath = gpath; gpath = auxPath; auxPath = contourPath; openContour = false; } @Override public void endShape(int mode) { if (openContour) { // correct automagically, notify user endContour(); PGraphics.showWarning("Missing endContour() before endShape()"); } if (gpath != null) { // make sure something has been drawn if (shape == POLYGON) { if (mode == CLOSE) { gpath.closePath(); } if (auxPath != null) { gpath.append(auxPath, false); } drawShape(gpath); } } shape = 0; } ////////////////////////////////////////////////////////////// // CLIPPING @Override protected void clipImpl(float x1, float y1, float x2, float y2) { g2.setClip(new Rectangle2D.Float(x1, y1, x2 - x1, y2 - y1)); } @Override public void noClip() { g2.setClip(null); } ////////////////////////////////////////////////////////////// // BLEND /** * ( begin auto-generated from blendMode.xml ) * * This is a new reference entry for Processing 2.0. It will be updated shortly. * * ( end auto-generated ) * * @webref Rendering * @param mode the blending mode to use */ @Override protected void blendModeImpl() { if (blendMode == BLEND) { g2.setComposite(defaultComposite); } else { g2.setComposite(new Composite() { @Override public CompositeContext createContext(ColorModel srcColorModel, ColorModel dstColorModel, RenderingHints hints) { return new BlendingContext(blendMode); } }); } } // Blending implementation cribbed from portions of Romain Guy's // demo and terrific writeup on blending modes in Java 2D. // http://www.curious-creature.org/2006/09/20/new-blendings-modes-for-java2d/ private static final class BlendingContext implements CompositeContext { private int mode; private BlendingContext(int mode) { this.mode = mode; } public void dispose() { } public void compose(Raster src, Raster dstIn, WritableRaster dstOut) { // not sure if this is really necessary, since we control our buffers if (src.getSampleModel().getDataType() != DataBuffer.TYPE_INT || dstIn.getSampleModel().getDataType() != DataBuffer.TYPE_INT || dstOut.getSampleModel().getDataType() != DataBuffer.TYPE_INT) { throw new IllegalStateException("Source and destination must store pixels as INT."); } int width = Math.min(src.getWidth(), dstIn.getWidth()); int height = Math.min(src.getHeight(), dstIn.getHeight()); int[] srcPixels = new int[width]; int[] dstPixels = new int[width]; for (int y = 0; y < height; y++) { src.getDataElements(0, y, width, 1, srcPixels); dstIn.getDataElements(0, y, width, 1, dstPixels); for (int x = 0; x < width; x++) { dstPixels[x] = blendColor(dstPixels[x], srcPixels[x], mode); } dstOut.setDataElements(0, y, width, 1, dstPixels); } } } ////////////////////////////////////////////////////////////// // BEZIER VERTICES @Override public void bezierVertex(float x1, float y1, float x2, float y2, float x3, float y3) { bezierVertexCheck(); gpath.curveTo(x1, y1, x2, y2, x3, y3); } @Override public void bezierVertex(float x2, float y2, float z2, float x3, float y3, float z3, float x4, float y4, float z4) { showDepthWarningXYZ("bezierVertex"); } ////////////////////////////////////////////////////////////// // QUADRATIC BEZIER VERTICES @Override public void quadraticVertex(float ctrlX, float ctrlY, float endX, float endY) { bezierVertexCheck(); Point2D cur = gpath.getCurrentPoint(); float x1 = (float) cur.getX(); float y1 = (float) cur.getY(); bezierVertex(x1 + ((ctrlX-x1)*2/3.0f), y1 + ((ctrlY-y1)*2/3.0f), endX + ((ctrlX-endX)*2/3.0f), endY + ((ctrlY-endY)*2/3.0f), endX, endY); } @Override public void quadraticVertex(float x2, float y2, float z2, float x4, float y4, float z4) { showDepthWarningXYZ("quadVertex"); } ////////////////////////////////////////////////////////////// // CURVE VERTICES @Override protected void curveVertexCheck() { super.curveVertexCheck(); if (curveCoordX == null) { curveCoordX = new float[4]; curveCoordY = new float[4]; curveDrawX = new float[4]; curveDrawY = new float[4]; } } @Override protected void curveVertexSegment(float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4) { curveCoordX[0] = x1; curveCoordY[0] = y1; curveCoordX[1] = x2; curveCoordY[1] = y2; curveCoordX[2] = x3; curveCoordY[2] = y3; curveCoordX[3] = x4; curveCoordY[3] = y4; curveToBezierMatrix.mult(curveCoordX, curveDrawX); curveToBezierMatrix.mult(curveCoordY, curveDrawY); // since the paths are continuous, // only the first point needs the actual moveto if (gpath == null) { gpath = new GeneralPath(); gpath.moveTo(curveDrawX[0], curveDrawY[0]); } gpath.curveTo(curveDrawX[1], curveDrawY[1], curveDrawX[2], curveDrawY[2], curveDrawX[3], curveDrawY[3]); } @Override public void curveVertex(float x, float y, float z) { showDepthWarningXYZ("curveVertex"); } ////////////////////////////////////////////////////////////// // RENDERER //public void flush() ////////////////////////////////////////////////////////////// // POINT, LINE, TRIANGLE, QUAD @Override public void point(float x, float y) { if (stroke) { // if (strokeWeight > 1) { line(x, y, x + EPSILON, y + EPSILON); // } else { // set((int) screenX(x, y), (int) screenY(x, y), strokeColor); // } } } @Override public void line(float x1, float y1, float x2, float y2) { line.setLine(x1, y1, x2, y2); strokeShape(line); } @Override public void triangle(float x1, float y1, float x2, float y2, float x3, float y3) { gpath = new GeneralPath(); gpath.moveTo(x1, y1); gpath.lineTo(x2, y2); gpath.lineTo(x3, y3); gpath.closePath(); drawShape(gpath); } @Override public void quad(float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4) { GeneralPath gp = new GeneralPath(); gp.moveTo(x1, y1); gp.lineTo(x2, y2); gp.lineTo(x3, y3); gp.lineTo(x4, y4); gp.closePath(); drawShape(gp); } ////////////////////////////////////////////////////////////// // RECT //public void rectMode(int mode) //public void rect(float a, float b, float c, float d) @Override protected void rectImpl(float x1, float y1, float x2, float y2) { rect.setFrame(x1, y1, x2-x1, y2-y1); drawShape(rect); } ////////////////////////////////////////////////////////////// // ELLIPSE //public void ellipseMode(int mode) //public void ellipse(float a, float b, float c, float d) @Override protected void ellipseImpl(float x, float y, float w, float h) { ellipse.setFrame(x, y, w, h); drawShape(ellipse); } ////////////////////////////////////////////////////////////// // ARC //public void arc(float a, float b, float c, float d, // float start, float stop) @Override protected void arcImpl(float x, float y, float w, float h, float start, float stop, int mode) { // 0 to 90 in java would be 0 to -90 for p5 renderer // but that won't work, so -90 to 0? start = -start * RAD_TO_DEG; stop = -stop * RAD_TO_DEG; // ok to do this because already checked for NaN // while (start < 0) { // start += 360; // stop += 360; // } // if (start > stop) { // float temp = start; // start = stop; // stop = temp; // } float sweep = stop - start; // The defaults, before 2.0b7, were to stroke as Arc2D.OPEN, and then fill // using Arc2D.PIE. That's a little wonky, but it's here for compatability. int fillMode = Arc2D.PIE; int strokeMode = Arc2D.OPEN; if (mode == OPEN) { fillMode = Arc2D.OPEN; //strokeMode = Arc2D.OPEN; } else if (mode == PIE) { //fillMode = Arc2D.PIE; strokeMode = Arc2D.PIE; } else if (mode == CHORD) { fillMode = Arc2D.CHORD; strokeMode = Arc2D.CHORD; } if (fill) { //System.out.println("filla"); arc.setArc(x, y, w, h, start, sweep, fillMode); fillShape(arc); } if (stroke) { //System.out.println("strokey"); arc.setArc(x, y, w, h, start, sweep, strokeMode); strokeShape(arc); } } ////////////////////////////////////////////////////////////// // JAVA2D SHAPE/PATH HANDLING /** * * @param s */ protected void fillShape(Shape s) { if (fillGradient) { g2.setPaint(fillGradientObject); g2.fill(s); } else if (fill) { g2.setColor(fillColorObject); g2.fill(s); } } /** * * @param s */ protected void strokeShape(Shape s) { if (strokeGradient) { g2.setPaint(strokeGradientObject); g2.draw(s); } else if (stroke) { g2.setColor(strokeColorObject); g2.draw(s); } } /** * * @param s */ protected void drawShape(Shape s) { if (fillGradient) { g2.setPaint(fillGradientObject); g2.fill(s); } else if (fill) { g2.setColor(fillColorObject); g2.fill(s); } if (strokeGradient) { g2.setPaint(strokeGradientObject); g2.draw(s); } else if (stroke) { g2.setColor(strokeColorObject); g2.draw(s); } } ////////////////////////////////////////////////////////////// // BOX //public void box(float size) @Override public void box(float w, float h, float d) { showMethodWarning("box"); } ////////////////////////////////////////////////////////////// // SPHERE //public void sphereDetail(int res) //public void sphereDetail(int ures, int vres) @Override public void sphere(float r) { showMethodWarning("sphere"); } ////////////////////////////////////////////////////////////// // BEZIER //public float bezierPoint(float a, float b, float c, float d, float t) //public float bezierTangent(float a, float b, float c, float d, float t) //protected void bezierInitCheck() //protected void bezierInit() /** Ignored (not needed) in Java 2D. */ @Override public void bezierDetail(int detail) { } //public void bezier(float x1, float y1, // float x2, float y2, // float x3, float y3, // float x4, float y4) //public void bezier(float x1, float y1, float z1, // float x2, float y2, float z2, // float x3, float y3, float z3, // float x4, float y4, float z4) ////////////////////////////////////////////////////////////// // CURVE //public float curvePoint(float a, float b, float c, float d, float t) //public float curveTangent(float a, float b, float c, float d, float t) /** Ignored (not needed) in Java 2D. */ @Override public void curveDetail(int detail) { } //public void curveTightness(float tightness) //protected void curveInitCheck() //protected void curveInit() //public void curve(float x1, float y1, // float x2, float y2, // float x3, float y3, // float x4, float y4) //public void curve(float x1, float y1, float z1, // float x2, float y2, float z2, // float x3, float y3, float z3, // float x4, float y4, float z4) // ////////////////////////////////////////////////////////////// // // // SMOOTH // // // @Override // public void smooth() { // smooth = true; // // if (quality == 0) { // quality = 4; // change back to bicubic // } // // g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, // RenderingHints.VALUE_ANTIALIAS_ON); // // g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, // quality == 4 ? // RenderingHints.VALUE_INTERPOLATION_BICUBIC : // RenderingHints.VALUE_INTERPOLATION_BILINEAR); // // // http://docs.oracle.com/javase/tutorial/2d/text/renderinghints.html // // Oracle Java text anti-aliasing on OS X looks like s*t compared to the // // text rendering with Apple's old Java 6. Below, several attempts to fix: // g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, // RenderingHints.VALUE_TEXT_ANTIALIAS_ON); // // Turns out this is the one that actually makes things work. // // Kerning is still screwed up, however. // g2.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, // RenderingHints.VALUE_FRACTIONALMETRICS_ON); //// g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, //// RenderingHints.VALUE_TEXT_ANTIALIAS_GASP); //// g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, //// RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB); // //// g2.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, //// RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY); // // } // // // @Override // public void smooth(int quality) { // this.quality = quality; // if (quality == 0) { // noSmooth(); // } else { // smooth(); // } // } // // // @Override // public void noSmooth() { // smooth = false; // quality = 0; // https://github.com/processing/processing/issues/3113 // g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, // RenderingHints.VALUE_ANTIALIAS_OFF); // g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, // RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); // g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, // RenderingHints.VALUE_TEXT_ANTIALIAS_OFF); // } ////////////////////////////////////////////////////////////// // IMAGE //public void imageMode(int mode) //public void image(PImage image, float x, float y) //public void image(PImage image, float x, float y, float c, float d) //public void image(PImage image, // float a, float b, float c, float d, // int u1, int v1, int u2, int v2) /** * Handle renderer-specific image drawing. * @param who */ @Override protected void imageImpl(PImage who, float x1, float y1, float x2, float y2, int u1, int v1, int u2, int v2) { // Image not ready yet, or an error if (who.width <= 0 || who.height <= 0) return; ImageCache cash = (ImageCache) getCache(who); // Nuke the cache if the image was resized if (cash != null) { if (who.pixelWidth != cash.image.getWidth() || who.pixelHeight != cash.image.getHeight()) { cash = null; } } if (cash == null) { //System.out.println("making new image cache"); cash = new ImageCache(); //who); setCache(who, cash); who.updatePixels(); // mark the whole thing for update who.setModified(); } // If image previously was tinted, or the color changed // or the image was tinted, and tint is now disabled if ((tint && !cash.tinted) || (tint && (cash.tintedColor != tintColor)) || (!tint && cash.tinted)) { // For tint change, mark all pixels as needing update. who.updatePixels(); } if (who.isModified()) { if (who.pixels == null) { // This might be a PGraphics that hasn't been drawn to yet. // Can't just bail because the cache has been created above. // https://github.com/processing/processing/issues/2208 who.pixels = new int[who.pixelWidth * who.pixelHeight]; } cash.update(who, tint, tintColor); who.setModified(false); } u1 *= who.pixelDensity; v1 *= who.pixelDensity; u2 *= who.pixelDensity; v2 *= who.pixelDensity; g2.drawImage(((ImageCache) getCache(who)).image, (int) x1, (int) y1, (int) x2, (int) y2, u1, v1, u2, v2, null); // Every few years I think "nah, Java2D couldn't possibly be that f*king // slow, why are we doing this by hand?" then comes the affirmation: // Composite oldComp = null; // if (false && tint) { // oldComp = g2.getComposite(); // int alpha = (tintColor >> 24) & 0xff; // System.out.println("using alpha composite"); // Composite alphaComp = // AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha / 255f); // g2.setComposite(alphaComp); // } // // long t = System.currentTimeMillis(); // g2.drawImage(who.getImage(), // (int) x1, (int) y1, (int) x2, (int) y2, // u1, v1, u2, v2, null); // System.out.println(System.currentTimeMillis() - t); // // if (oldComp != null) { // g2.setComposite(oldComp); // } } static class ImageCache { boolean tinted; int tintedColor; int[] tintedTemp; // one row of tinted pixels BufferedImage image; // BufferedImage compat; // public ImageCache(PImage source) { //// this.source = source; // // even if RGB, set the image type to ARGB, because the // // image may have an alpha value for its tint(). //// int type = BufferedImage.TYPE_INT_ARGB; // //System.out.println("making new buffered image"); //// image = new BufferedImage(source.width, source.height, type); // } /** * Update the pixels of the cache image. Already determined that the tint * has changed, or the pixels have changed, so should just go through * with the update without further checks. */ public void update(PImage source, boolean tint, int tintColor) { //int bufferType = BufferedImage.TYPE_INT_ARGB; int targetType = ARGB; boolean opaque = (tintColor & 0xFF000000) == 0xFF000000; if (source.format == RGB) { if (!tint || (tint && opaque)) { //bufferType = BufferedImage.TYPE_INT_RGB; targetType = RGB; } } // boolean wrongType = (image != null) && (image.getType() != bufferType); // if ((image == null) || wrongType) { // image = new BufferedImage(source.width, source.height, bufferType); // } // Must always use an ARGB image, otherwise will write zeros // in the alpha channel when drawn to the screen. // https://github.com/processing/processing/issues/2030 if (image == null) { image = new BufferedImage(source.pixelWidth, source.pixelHeight, BufferedImage.TYPE_INT_ARGB); } WritableRaster wr = image.getRaster(); if (tint) { if (tintedTemp == null || tintedTemp.length != source.pixelWidth) { tintedTemp = new int[source.pixelWidth]; } int a2 = (tintColor >> 24) & 0xff; // System.out.println("tint color is " + a2); // System.out.println("source.pixels[0] alpha is " + (source.pixels[0] >>> 24)); int r2 = (tintColor >> 16) & 0xff; int g2 = (tintColor >> 8) & 0xff; int b2 = (tintColor) & 0xff; //if (bufferType == BufferedImage.TYPE_INT_RGB) { if (targetType == RGB) { // The target image is opaque, meaning that the source image has no // alpha (is not ARGB), and the tint has no alpha. int index = 0; for (int y = 0; y < source.pixelHeight; y++) { for (int x = 0; x < source.pixelWidth; x++) { int argb1 = source.pixels[index++]; int r1 = (argb1 >> 16) & 0xff; int g1 = (argb1 >> 8) & 0xff; int b1 = (argb1) & 0xff; // Prior to 2.1, the alpha channel was commented out here, // but can't remember why (just thought unnecessary b/c of RGB?) // https://github.com/processing/processing/issues/2030 tintedTemp[x] = 0xFF000000 | (((r2 * r1) & 0xff00) << 8) | ((g2 * g1) & 0xff00) | (((b2 * b1) & 0xff00) >> 8); } wr.setDataElements(0, y, source.pixelWidth, 1, tintedTemp); } // could this be any slower? // float[] scales = { tintR, tintG, tintB }; // float[] offsets = new float[3]; // RescaleOp op = new RescaleOp(scales, offsets, null); // op.filter(image, image); //} else if (bufferType == BufferedImage.TYPE_INT_ARGB) { } else if (targetType == ARGB) { if (source.format == RGB && (tintColor & 0xffffff) == 0xffffff) { int hi = tintColor & 0xff000000; int index = 0; for (int y = 0; y < source.pixelHeight; y++) { for (int x = 0; x < source.pixelWidth; x++) { tintedTemp[x] = hi | (source.pixels[index++] & 0xFFFFFF); } wr.setDataElements(0, y, source.pixelWidth, 1, tintedTemp); } } else { int index = 0; for (int y = 0; y < source.pixelHeight; y++) { if (source.format == RGB) { int alpha = tintColor & 0xFF000000; for (int x = 0; x < source.pixelWidth; x++) { int argb1 = source.pixels[index++]; int r1 = (argb1 >> 16) & 0xff; int g1 = (argb1 >> 8) & 0xff; int b1 = (argb1) & 0xff; tintedTemp[x] = alpha | (((r2 * r1) & 0xff00) << 8) | ((g2 * g1) & 0xff00) | (((b2 * b1) & 0xff00) >> 8); } } else if (source.format == ARGB) { for (int x = 0; x < source.pixelWidth; x++) { int argb1 = source.pixels[index++]; int a1 = (argb1 >> 24) & 0xff; int r1 = (argb1 >> 16) & 0xff; int g1 = (argb1 >> 8) & 0xff; int b1 = (argb1) & 0xff; tintedTemp[x] = (((a2 * a1) & 0xff00) << 16) | (((r2 * r1) & 0xff00) << 8) | ((g2 * g1) & 0xff00) | (((b2 * b1) & 0xff00) >> 8); } } else if (source.format == ALPHA) { int lower = tintColor & 0xFFFFFF; for (int x = 0; x < source.pixelWidth; x++) { int a1 = source.pixels[index++]; tintedTemp[x] = (((a2 * a1) & 0xff00) << 16) | lower; } } wr.setDataElements(0, y, source.pixelWidth, 1, tintedTemp); } } // Not sure why ARGB images take the scales in this order... // float[] scales = { tintR, tintG, tintB, tintA }; // float[] offsets = new float[4]; // RescaleOp op = new RescaleOp(scales, offsets, null); // op.filter(image, image); } } else { // !tint if (targetType == RGB && (source.pixels[0] >> 24 == 0)) { // If it's an RGB image and the high bits aren't set, need to set // the high bits to opaque because we're drawing ARGB images. source.filter(OPAQUE); // Opting to just manipulate the image here, since it shouldn't // affect anything else (and alpha(get(x, y)) should return 0xff). // Wel also make no guarantees about the values of the pixels array // in a PImage and how the high bits will be set. } // If no tint, just shove the pixels on in there verbatim wr.setDataElements(0, 0, source.pixelWidth, source.pixelHeight, source.pixels); } this.tinted = tint; this.tintedColor = tintColor; // GraphicsConfiguration gc = parent.getGraphicsConfiguration(); // compat = gc.createCompatibleImage(image.getWidth(), // image.getHeight(), // Transparency.TRANSLUCENT); // // Graphics2D g = compat.createGraphics(); // g.drawImage(image, 0, 0, null); // g.dispose(); } } ////////////////////////////////////////////////////////////// // SHAPE //public void shapeMode(int mode) //public void shape(PShape shape) //public void shape(PShape shape, float x, float y) //public void shape(PShape shape, float x, float y, float c, float d) ////////////////////////////////////////////////////////////// // SHAPE I/O //public PShape loadShape(String filename) @Override public PShape loadShape(String filename, String options) { String extension = PApplet.getExtension(filename); if (extension.equals("svg") || extension.equals("svgz")) { return new PShapeJava2D(parent.loadXML(filename)); } PGraphics.showWarning("Unsupported format: " + filename); return null; } ////////////////////////////////////////////////////////////// // TEXT ATTRIBTUES //public void textAlign(int align) //public void textAlign(int alignX, int alignY) @Override public float textAscent() { if (textFont == null) { defaultFontOrDeath("textAscent"); } Font font = (Font) textFont.getNative(); if (font != null) { //return getFontMetrics(font).getAscent(); return g2.getFontMetrics(font).getAscent(); } return super.textAscent(); } @Override public float textDescent() { if (textFont == null) { defaultFontOrDeath("textDescent"); } Font font = (Font) textFont.getNative(); if (font != null) { //return getFontMetrics(font).getDescent(); return g2.getFontMetrics(font).getDescent(); } return super.textDescent(); } //public void textFont(PFont which) //public void textFont(PFont which, float size) //public void textLeading(float leading) //public void textMode(int mode) @Override protected boolean textModeCheck(int mode) { return mode == MODEL; } /** * Same as parent, but override for native version of the font. *
* Called from textFontImpl and textSizeImpl, so the metrics * will get recorded properly. */ @Override protected void handleTextSize(float size) { // if a native version available, derive this font Font font = (Font) textFont.getNative(); // don't derive again if the font size has not changed if (font != null) { if (font.getSize2D() != size) { Map// * Unlike in PImage, where updatePixels() only requests that the // * update happens, in PGraphicsJava2D, this will happen immediately. // */ // @Override // public void updatePixels() { // //updatePixels(0, 0, width, height); //// WritableRaster raster = ((BufferedImage) (useOffscreen && primarySurface ? offscreen : image)).getRaster(); //// WritableRaster raster = image.getRaster(); // updatePixels(0, 0, width, height); // } /** * Update the pixels[] buffer to the PGraphics image. *
* Unlike in PImage, where updatePixels() only requests that the * update happens, in PGraphicsJava2D, this will happen immediately. * @param c * @param d */ @Override public void updatePixels(int x, int y, int c, int d) { //if ((x == 0) && (y == 0) && (c == width) && (d == height)) { // System.err.format("%d %d %d %d .. w/h = %d %d .. pw/ph = %d %d %n", x, y, c, d, width, height, pixelWidth, pixelHeight); if ((x != 0) || (y != 0) || (c != pixelWidth) || (d != pixelHeight)) { // Show a warning message, but continue anyway. showVariationWarning("updatePixels(x, y, w, h)"); // new Exception().printStackTrace(System.out); } // updatePixels(); if (pixels != null) { getRaster().setDataElements(0, 0, pixelWidth, pixelHeight, pixels); } modified = true; } // @Override // protected void updatePixelsImpl(int x, int y, int w, int h) { // super.updatePixelsImpl(x, y, w, h); // // if ((x != 0) || (y != 0) || (w != width) || (h != height)) { // // Show a warning message, but continue anyway. // showVariationWarning("updatePixels(x, y, w, h)"); // } // getRaster().setDataElements(0, 0, width, height, pixels); // } ////////////////////////////////////////////////////////////// // GET/SET static int getset[] = new int[1]; @Override public int get(int x, int y) { if ((x < 0) || (y < 0) || (x >= width) || (y >= height)) return 0; //return ((BufferedImage) image).getRGB(x, y); // WritableRaster raster = ((BufferedImage) (useOffscreen && primarySurface ? offscreen : image)).getRaster(); WritableRaster raster = getRaster(); raster.getDataElements(x, y, getset); if (raster.getNumBands() == 3) { // https://github.com/processing/processing/issues/2030 return getset[0] | 0xff000000; } return getset[0]; } //public PImage get(int x, int y, int w, int h) @Override protected void getImpl(int sourceX, int sourceY, int sourceWidth, int sourceHeight, PImage target, int targetX, int targetY) { // last parameter to getRGB() is the scan size of the *target* buffer //((BufferedImage) image).getRGB(x, y, w, h, output.pixels, 0, w); // WritableRaster raster = // ((BufferedImage) (useOffscreen && primarySurface ? offscreen : image)).getRaster(); WritableRaster raster = getRaster(); if (sourceWidth == target.pixelWidth && sourceHeight == target.pixelHeight) { raster.getDataElements(sourceX, sourceY, sourceWidth, sourceHeight, target.pixels); // https://github.com/processing/processing/issues/2030 if (raster.getNumBands() == 3) { target.filter(OPAQUE); } } else { // TODO optimize, incredibly inefficient to reallocate this much memory int[] temp = new int[sourceWidth * sourceHeight]; raster.getDataElements(sourceX, sourceY, sourceWidth, sourceHeight, temp); // Copy the temporary output pixels over to the outgoing image int sourceOffset = 0; int targetOffset = targetY*target.pixelWidth + targetX; for (int y = 0; y < sourceHeight; y++) { if (raster.getNumBands() == 3) { for (int i = 0; i < sourceWidth; i++) { // Need to set the high bits for this feller // https://github.com/processing/processing/issues/2030 target.pixels[targetOffset + i] = 0xFF000000 | temp[sourceOffset + i]; } } else { System.arraycopy(temp, sourceOffset, target.pixels, targetOffset, sourceWidth); } sourceOffset += sourceWidth; targetOffset += target.pixelWidth; } } } @Override public void set(int x, int y, int argb) { if ((x < 0) || (y < 0) || (x >= pixelWidth) || (y >= pixelHeight)) return; // ((BufferedImage) image).setRGB(x, y, argb); getset[0] = argb; // WritableRaster raster = ((BufferedImage) (useOffscreen && primarySurface ? offscreen : image)).getRaster(); // WritableRaster raster = image.getRaster(); getRaster().setDataElements(x, y, getset); } //public void set(int x, int y, PImage img) @Override protected void setImpl(PImage sourceImage, int sourceX, int sourceY, int sourceWidth, int sourceHeight, int targetX, int targetY) { WritableRaster raster = getRaster(); // ((BufferedImage) (useOffscreen && primarySurface ? offscreen : image)).getRaster(); if ((sourceX == 0) && (sourceY == 0) && (sourceWidth == sourceImage.pixelWidth) && (sourceHeight == sourceImage.pixelHeight)) { // System.out.format("%d %d %dx%d %d%n", targetX, targetY, // sourceImage.width, sourceImage.height, // sourceImage.pixels.length); raster.setDataElements(targetX, targetY, sourceImage.pixelWidth, sourceImage.pixelHeight, sourceImage.pixels); } else { // TODO optimize, incredibly inefficient to reallocate this much memory PImage temp = sourceImage.get(sourceX, sourceY, sourceWidth, sourceHeight); raster.setDataElements(targetX, targetY, temp.pixelWidth, temp.pixelHeight, temp.pixels); } } ////////////////////////////////////////////////////////////// // MASK static final String MASK_WARNING = "mask() cannot be used on the main drawing surface"; @Override public void mask(int[] alpha) { if (primaryGraphics) { showWarning(MASK_WARNING); } else { super.mask(alpha); } } @Override public void mask(PImage alpha) { if (primaryGraphics) { showWarning(MASK_WARNING); } else { super.mask(alpha); } } ////////////////////////////////////////////////////////////// // FILTER // Because the PImage versions call loadPixels() and // updatePixels(), no need to override anything here. //public void filter(int kind) //public void filter(int kind, float param) ////////////////////////////////////////////////////////////// // COPY @Override public void copy(int sx, int sy, int sw, int sh, int dx, int dy, int dw, int dh) { if ((sw != dw) || (sh != dh)) { g2.drawImage(image, dx, dy, dx + dw, dy + dh, sx, sy, sx + sw, sy + sh, null); } else { dx = dx - sx; // java2d's "dx" is the delta, not dest dy = dy - sy; g2.copyArea(sx, sy, sw, sh, dx, dy); } } @Override public void copy(PImage src, int sx, int sy, int sw, int sh, int dx, int dy, int dw, int dh) { g2.drawImage((Image) src.getNative(), dx, dy, dx + dw, dy + dh, sx, sy, sx + sw, sy + sh, null); } ////////////////////////////////////////////////////////////// // BLEND // static public int blendColor(int c1, int c2, int mode) // public void blend(int sx, int sy, int sw, int sh, // int dx, int dy, int dw, int dh, int mode) // public void blend(PImage src, // int sx, int sy, int sw, int sh, // int dx, int dy, int dw, int dh, int mode) ////////////////////////////////////////////////////////////// // SAVE // public void save(String filename) { // loadPixels(); // super.save(filename); // } }