/* -*- 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
*/
@Override
protected void blendModeImpl() {
if (blendMode == BLEND) {
g2.setComposite(defaultComposite);
} else {
g2.setComposite((ColorModel srcColorModel, ColorModel dstColorModel, RenderingHints hints1) -> 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 final int mode;
private BlendingContext(int mode) {
this.mode = mode;
}
@Override
public void dispose() { }
@Override
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;
switch (mode) {
case OPEN:
fillMode = Arc2D.OPEN;
//strokeMode = Arc2D.OPEN;
break;
case PIE:
//fillMode = Arc2D.PIE;
strokeMode = Arc2D.PIE;
break;
case CHORD:
fillMode = Arc2D.CHORD;
strokeMode = Arc2D.CHORD;
break;
default:
break;
}
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++) {
switch (source.format) {
case 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);
} break;
case 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);
} break;
case 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;
} break;
default:
break;
}
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 d
* @param c
*/
@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);
// }
}