/* -*- mode: jde; c-basic-offset: 2; indent-tabs-mode: nil -*- */ /* Part of the Processing project - http://processing.org Copyright (c) 2005 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.core; import java.awt.*; import java.awt.geom.*; import java.awt.image.*; /** * Subclass for PGraphics that implements the graphics API * in Java 1.3+ using Java 2D. */ public class PGraphics2 extends PGraphics { public Graphics2D g2; GeneralPath gpath; 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; protected Color strokeColorObject; ////////////////////////////////////////////////////////////// // INTERNAL /** * Constructor for the PGraphics2 object. * This prototype only exists because of annoying * java compilers, and should not be used. */ public PGraphics2() { } /** * Constructor for the PGraphics object. Use this to ensure that * the defaults get set properly. In a subclass, use this(w, h) * as the first line of a subclass' constructor to properly set * the internal fields and defaults. * * @param iwidth viewport width * @param iheight viewport height */ public PGraphics2(int iwidth, int iheight, PApplet parent) { super(iwidth, iheight, parent); //resize(iwidth, iheight); } /** * Called in repsonse 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. */ public void resize(int iwidth, int iheight) { // ignore //System.out.println("resize " + iwidth + " " + iheight); width = iwidth; height = iheight; width1 = width - 1; height1 = height - 1; allocate(); // clear the screen with the old background color background(backgroundColor); } // broken out because of subclassing for opengl protected void allocate() { image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); g2 = (Graphics2D) image.getGraphics(); } ////////////////////////////////////////////////////////////// // FRAME // turn off mis.newPixels public void endFrame() { // moving this back here (post-68) because of macosx thread problem //mis.newPixels(pixels, cm, 0, width); } ////////////////////////////////////////////////////////////// // SHAPES public void vertex(float x, float y) { splineVertexCount = 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][MX] = x; vertices[vertexCount][MY] = y; vertexCount++; switch (shape) { case POINTS: point(x, y); break; case LINES: if ((vertexCount % 2) == 0) { line(vertices[vertexCount-2][MX], vertices[vertexCount-2][MY], x, y); } break; case LINE_STRIP: case LINE_LOOP: if (gpath == null) { gpath = new GeneralPath(); gpath.moveTo(x, y); } else { gpath.lineTo(x, y); } break; case TRIANGLES: if ((vertexCount % 3) == 0) { triangle(vertices[vertexCount - 3][MX], vertices[vertexCount - 3][MY], vertices[vertexCount - 2][MX], vertices[vertexCount - 2][MY], x, y); } break; case TRIANGLE_STRIP: if (vertexCount >= 3) { triangle(vertices[vertexCount - 2][MX], vertices[vertexCount - 2][MY], vertices[vertexCount - 1][MX], vertices[vertexCount - 1][MY], vertices[vertexCount - 3][MX], vertices[vertexCount - 3][MY]); } break; case TRIANGLE_FAN: if (vertexCount == 3) { triangle(vertices[0][MX], vertices[0][MY], vertices[1][MX], vertices[1][MY], x, y); } else if (vertexCount > 3) { gpath = new GeneralPath(); // when vertexCount > 3, draw an un-closed triangle // for indices 0 (center), previous, current gpath.moveTo(vertices[0][MX], vertices[0][MY]); gpath.lineTo(vertices[vertexCount - 2][MX], vertices[vertexCount - 2][MY]); gpath.lineTo(x, y); draw_shape(gpath); } break; case QUADS: if ((vertexCount % 4) == 0) { quad(vertices[vertexCount - 4][MX], vertices[vertexCount - 4][MY], vertices[vertexCount - 3][MX], vertices[vertexCount - 3][MY], vertices[vertexCount - 2][MX], vertices[vertexCount - 2][MY], x, y); } break; case QUAD_STRIP: // 0---2---4 // | | | // 1---3---5 if ((vertexCount >= 4) && ((vertexCount % 2) == 0)) { quad(vertices[vertexCount - 4][MX], vertices[vertexCount - 4][MY], vertices[vertexCount - 2][MX], vertices[vertexCount - 2][MY], x, y, vertices[vertexCount - 3][MX], vertices[vertexCount - 3][MY]); } break; case POLYGON: if (gpath == null) { gpath = new GeneralPath(); gpath.moveTo(x, y); } else { gpath.lineTo(x, y); } break; } } public void bezierVertex(float x1, float y1, float x2, float y2, float x3, float y3) { if (gpath == null) { throw new RuntimeException("Must call vertex() at least once " + "before using bezierVertex()"); } switch (shape) { case LINE_LOOP: case LINE_STRIP: case POLYGON: gpath.curveTo(x1, y1, x2, y2, x3, y3); break; default: throw new RuntimeException("bezierVertex() can only be used with " + "LINE_STRIP, LINE_LOOP, or POLYGON"); } } float curveX[] = new float[4]; float curveY[] = new float[4]; public void curveVertex(float x, float y) { if ((shape != LINE_LOOP) && (shape != LINE_STRIP) && (shape != POLYGON)) { throw new RuntimeException("curveVertex() can only be used with " + "LINE_LOOP, LINE_STRIP, and POLYGON shapes"); } if (!curve_inited) curve_init(); vertexCount = 0; if (splineVertices == null) { splineVertices = new float[DEFAULT_SPLINE_VERTICES][VERTEX_FIELD_COUNT]; } // if more than 128 points, shift everything back to the beginning if (splineVertexCount == DEFAULT_SPLINE_VERTICES) { System.arraycopy(splineVertices[DEFAULT_SPLINE_VERTICES - 3], 0, splineVertices[0], 0, VERTEX_FIELD_COUNT); System.arraycopy(splineVertices[DEFAULT_SPLINE_VERTICES - 2], 0, splineVertices[1], 0, VERTEX_FIELD_COUNT); System.arraycopy(splineVertices[DEFAULT_SPLINE_VERTICES - 1], 0, splineVertices[2], 0, VERTEX_FIELD_COUNT); splineVertexCount = 3; } // this new guy will be the fourth point (or higher), // which means it's time to draw segments of the curve if (splineVertexCount >= 3) { curveX[0] = splineVertices[splineVertexCount-3][MX]; curveY[0] = splineVertices[splineVertexCount-3][MY]; curveX[1] = splineVertices[splineVertexCount-2][MX]; curveY[1] = splineVertices[splineVertexCount-2][MY]; curveX[2] = splineVertices[splineVertexCount-1][MX]; curveY[2] = splineVertices[splineVertexCount-1][MY]; curveX[3] = x; curveY[3] = y; curveToBezierMatrix.mult(curveX, curveX); curveToBezierMatrix.mult(curveY, curveY); // since the paths are continuous, // only the first point needs the actual moveto if (gpath == null) { gpath = new GeneralPath(); gpath.moveTo(curveX[0], curveY[0]); } gpath.curveTo(curveX[1], curveY[1], curveX[2], curveY[2], curveX[3], curveY[3]); } // add the current point to the list splineVertices[splineVertexCount][MX] = x; splineVertices[splineVertexCount][MY] = y; splineVertexCount++; } public void beginShape(int kind) { super.beginShape(kind); // 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; } public void endShape() { if (gpath != null) { // make sure something has been drawn switch (shape) { case LINE_STRIP: stroke_shape(gpath); break; case LINE_LOOP: gpath.closePath(); stroke_shape(gpath); break; case POLYGON: gpath.closePath(); draw_shape(gpath); break; } } shape = 0; } ////////////////////////////////////////////////////////////// protected void fill_shape(Shape s) { if (fill) { g2.setColor(fillColorObject); g2.fill(s); } } protected void stroke_shape(Shape s) { if (stroke) { g2.setColor(strokeColorObject); g2.draw(s); } } protected void draw_shape(Shape s) { if (fill) { g2.setColor(fillColorObject); g2.fill(s); } if (stroke) { g2.setColor(strokeColorObject); g2.draw(s); } } ////////////////////////////////////////////////////////////// public void point(float x, float y) { line(x, y, x, y); } public void line(float x1, float y1, float x2, float y2) { //graphics.setColor(strokeColorObject); //graphics.drawLine(x1, y1, x2, y2); line.setLine(x1, y1, x2, y2); stroke_shape(line); } 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(); draw_shape(gpath); } 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(); draw_shape(gp); } ////////////////////////////////////////////////////////////// protected void rectImpl(float x1, float y1, float x2, float y2) { rect.setFrame(x1, y1, x2-x1, y2-y1); draw_shape(rect); } protected void ellipseImpl(float x, float y, float w, float h) { ellipse.setFrame(x, y, w, h); draw_shape(ellipse); } protected void arcImpl(float x, float y, float w, float h, float start, float stop) { // 0 to 90 in java would be 0 to -90 for p5 renderer // but that won't work, so -90 to 0? if (stop - start >= TWO_PI) { start = 0; stop = 360; } else { 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 span = stop - start; // stroke as Arc2D.OPEN, fill as Arc2D.PIE if (fill) { //System.out.println("filla"); arc.setArc(x, y, w, h, start, span, Arc2D.PIE); fill_shape(arc); } if (stroke) { //System.out.println("strokey"); arc.setArc(x, y, w, h, start, span, Arc2D.OPEN); stroke_shape(arc); } } ////////////////////////////////////////////////////////////// /** Ignored (not needed) in Java 2D. */ public void bezierDetail(int detail) { } /** Ignored (not needed) in Java 2D. */ public void curveDetail(int detail) { } ////////////////////////////////////////////////////////////// protected void imageImpl(PImage who, float x1, float y1, float x2, float y2, //float x, float y, float w, float h, int u1, int v1, int u2, int v2) { if (who.cache == null) { who.cache = new ImageCache(who); who.updatePixels(); // mark the whole thing for update } ImageCache cash = (ImageCache) who.cache; // 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.modified) { cash.update(); who.modified = false; //System.out.println("image modified"); } g2.drawImage(((ImageCache) who.cache).image, (int) x1, (int) y1, (int) x2, (int) y2, u1, v1, u2, v2, null); } class ImageCache { PImage source; boolean tinted; int tintedColor; int tintedPixels[]; BufferedImage image; 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; image = new BufferedImage(source.width, source.height, type); } public void update() { //boolean t, int argb) { if ((source.format == ARGB) || (source.format == RGB)) { if (tint) { // create tintedPixels[] if necessary if (tintedPixels == null) { tintedPixels = new int[source.width * source.height]; } int argb2 = tintColor; int a2 = (tintColor >> 24) & 0xff; int r2 = (tintColor >> 16) & 0xff; int g2 = (tintColor >> 8) & 0xff; int b2 = (tintColor) & 0xff; //System.out.println("a2 is " + a2); // multiply each of the color components into tintedPixels // if straight RGB image, don't bother multiplying // (also avoids problems if high bits not set) if (source.format == RGB) { int alpha = a2 << 24; for (int i = 0; i < tintedPixels.length; i++) { int argb1 = source.pixels[i]; int r1 = (argb1 >> 16) & 0xff; int g1 = (argb1 >> 8) & 0xff; int b1 = (argb1) & 0xff; tintedPixels[i] = alpha | (((r2 * r1) & 0xff00) << 8) | ((g2 * g1) & 0xff00) | (((b2 * b1) & 0xff00) >> 8); } } else { for (int i = 0; i < tintedPixels.length; i++) { int argb1 = source.pixels[i]; int a1 = (argb1 >> 24) & 0xff; int r1 = (argb1 >> 16) & 0xff; int g1 = (argb1 >> 8) & 0xff; int b1 = (argb1) & 0xff; tintedPixels[i] = (((a2 * a1) & 0xff00) << 16) | (((r2 * r1) & 0xff00) << 8) | ((g2 * g1) & 0xff00) | (((b2 * b1) & 0xff00) >> 8); } } tinted = true; tintedColor = tintColor; // finally, do a setRGB based on tintedPixels image.setRGB(0, 0, source.width, source.height, tintedPixels, 0, source.width); } else { // no tint // just do a setRGB like before // (and we'll just hope that the high bits are set) image.setRGB(0, 0, source.width, source.height, source.pixels, 0, source.width); } } else if (source.format == ALPHA) { if (tintedPixels == null) { tintedPixels = new int[source.width * source.height]; } int lowbits = tintColor & 0x00ffffff; if (((tintColor >> 24) & 0xff) >= 254) { // no actual alpha to the tint, set the image's alpha // as the high 8 bits, and use the color as the low 24 bits for (int i = 0; i < tintedPixels.length; i++) { // don't bother with the math if value is zero tintedPixels[i] = (source.pixels[i] == 0) ? 0 : (source.pixels[i] << 24) | lowbits; } } else { // multiply each image alpha by the tint alpha int alphabits = (tintColor >> 24) & 0xff; for (int i = 0; i < tintedPixels.length; i++) { tintedPixels[i] = (source.pixels[i] == 0) ? 0 : (((alphabits * source.pixels[i]) & 0xFF00) << 16) | lowbits; } } // mark the pixels for next time tinted = true; tintedColor = tintColor; // finally, do a setRGB based on tintedPixels image.setRGB(0, 0, source.width, source.height, tintedPixels, 0, source.width); } } } ////////////////////////////////////////////////////////////// public void translate(float tx, float ty) { g2.translate(tx, ty); } public void rotate(float angle) { g2.rotate(angle); } public void scale(float s) { g2.scale(s, s); } public void scale(float sx, float sy) { g2.scale(sx, sy); } ////////////////////////////////////////////////////////////// public void pushMatrix() { if (transformCount == transformStack.length) { throw new RuntimeException("pushMatrix() cannot use push more than " + transformStack.length + " times"); } transformStack[transformCount] = g2.getTransform(); transformCount++; } public void popMatrix() { if (transformCount == 0) { throw new RuntimeException("missing a popMatrix() " + "to go with that pushMatrix()"); } transformCount--; g2.setTransform(transformStack[transformCount]); } public void resetMatrix() { g2.setTransform(new AffineTransform()); } public void applyMatrix(float n00, float n01, float n02, float n10, float n11, float n12) { g2.transform(new AffineTransform(n00, n10, n01, n11, n02, n12)); } public void loadMatrix() { g2.getTransform().getMatrix(transform); m00 = (float) transform[0]; m01 = (float) transform[2]; m02 = (float) transform[4]; m10 = (float) transform[1]; m11 = (float) transform[3]; m12 = (float) transform[5]; } public float screenX(float x, float y) { g2.getTransform().getMatrix(transform); //return m00*x + m01*y + m02; return (float)transform[0]*x + (float)transform[2]*y + (float)transform[4]; } public float screenY(float x, float y) { g2.getTransform().getMatrix(transform); return (float)transform[1]*x + (float)transform[3]*y + (float)transform[5]; } ////////////////////////////////////////////////////////////// protected void tintFromCalc() { super.tintFromCalc(); // TODO actually implement tinted images tintColorObject = new Color(tintColor, true); } protected void fillFromCalc() { super.fillFromCalc(); fillColorObject = new Color(fillColor, true); } protected void strokeFromCalc() { super.strokeFromCalc(); strokeColorObject = new Color(strokeColor, true); } ////////////////////////////////////////////////////////////// public void strokeWeight(float weight) { super.strokeWeight(weight); set_stroke(); } public void strokeJoin(int join) { super.strokeJoin(join); set_stroke(); } public void strokeCap(int cap) { super.strokeCap(cap); set_stroke(); } protected void set_stroke() { int cap = BasicStroke.CAP_BUTT; if (strokeCap == ROUND) { cap = BasicStroke.CAP_ROUND; } else if (strokeCap == PROJECT) { cap = BasicStroke.CAP_SQUARE; } int join = BasicStroke.JOIN_BEVEL; if (strokeJoin == MITER) { join = BasicStroke.JOIN_MITER; } else if (strokeJoin == ROUND) { join = BasicStroke.JOIN_ROUND; } g2.setStroke(new BasicStroke(strokeWeight, cap, join)); } ////////////////////////////////////////////////////////////// public void background(PImage image) { if ((image.width != width) || (image.height != height)) { throw new RuntimeException("background image must be " + "the same size as your application"); } if ((image.format != RGB) && (image.format != ARGB)) { throw new RuntimeException("background images should be RGB or ARGB"); } // draw the image to screen without any transformations set(0, 0, image); } public void clear() { g2.setColor(new Color(backgroundColor)); g2.fillRect(0, 0, width, height); } ////////////////////////////////////////////////////////////// // FROM PIMAGE public void smooth() { g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); } public void noSmooth() { g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); } ////////////////////////////////////////////////////////////// public void loadPixels() { if ((pixels == null) || (pixels.length != width * height)) { pixels = new int[width * height]; } ((BufferedImage) image).getRGB(0, 0, width, height, pixels, 0, width); } /** * Update the pixels[] buffer to the PGraphics image. *

* Unlike in PImage, where updatePixels() only asks that the * update happens, in PGraphics2, this will happen immediately. */ public void updatePixels() { updatePixels(0, 0, width, height); } /** * Update the pixels[] buffer to the PGraphics image. *

* Unlike in PImage, where updatePixels() only asks that the * update happens, in PGraphics2, this will happen immediately. */ public void updatePixels(int x, int y, int c, int d) { ((BufferedImage) image).setRGB(x, y, (imageMode == CORNER) ? c : (c - x), (imageMode == CORNER) ? d : (d - y), pixels, 0, width); } ////////////////////////////////////////////////////////////// public int get(int x, int y) { if ((x < 0) || (y < 0) || (x >= width) || (y >= height)) return 0; return ((BufferedImage) image).getRGB(x, y); } public PImage get(int x, int y, int w, int h) { if (imageMode == CORNERS) { // if CORNER, do nothing // w/h are x2/y2 in this case, bring em down to size w = (w - x); h = (h - x); } if (x < 0) { w += x; // clip off the left edge x = 0; } if (y < 0) { h += y; // clip off some of the height y = 0; } if (x + w > width) w = width - x; if (y + h > height) h = height - y; PImage output = new PImage(w, h); // oops, the last parameter is the scan size of the *target* buffer ((BufferedImage) image).getRGB(x, y, w, h, output.pixels, 0, w); return output; } /** * Grab a copy of the current pixel buffer. */ public PImage get() { PImage outgoing = new PImage(width, height); ((BufferedImage) image).getRGB(0, 0, width, height, outgoing.pixels, 0, width); return outgoing; } public void set(int x, int y, int argb) { if ((x < 0) || (y < 0) || (x >= width) || (y >= height)) return; ((BufferedImage) image).setRGB(x, y, argb); } // fully debugged /* public void set(int dx, int dy, PImage src) { int sx = 0; int sy = 0; int sw = src.width; int sh = src.height; if (dx < 0) { // off left edge sx -= dx; sw += dx; dx = 0; } if (dy < 0) { // off top edge sy -= dy; sh += dy; dy = 0; } if (dx + sw > width) { // off right edge sw = width - dx; } if (dy + sh > height) { // off bottom edge sh = height - dy; } //System.out.println(dx + " " + dy + " " + // sx + " " + sy + " " + sw + " " + sh + " " + // src.pixels + " " + 0 + " " + src.width); BufferedImage bi = (BufferedImage) image; bi.setRGB(dx, dy, sw, sh, src.pixels, sy*src.width + sx, src.width); } */ protected void setImpl(int dx, int dy, int sx, int sy, int sw, int sh, PImage src) { BufferedImage bi = (BufferedImage) image; bi.setRGB(dx, dy, sw, sh, src.pixels, sy*src.width + sx, src.width); } ////////////////////////////////////////////////////////////// public void filter(int kind) { loadPixels(); super.filter(kind); updatePixels(); } public void filter(int kind, float param) { loadPixels(); super.filter(kind, param); updatePixels(); } ////////////////////////////////////////////////////////////// public void copy(PImage src, int sx1, int sy1, int sx2, int sy2, int dx1, int dy1, int dx2, int dy2) { loadPixels(); super.copy(src, sx1, sy1, sx2, sy2, dx1, dy1, dx2, dy2); updatePixels(); } public void blend(PImage src, int sx, int sy, int dx, int dy, int mode) { loadPixels(); super.blend(src, sx, sy, dx, dy, mode); updatePixels(); } public void blend(int sx, int sy, int dx, int dy, int mode) { loadPixels(); super.blend(sx, sy, dx, dy, mode); updatePixels(); } public void blend(int sx1, int sy1, int sx2, int sy2, int dx1, int dy1, int dx2, int dy2, int mode) { loadPixels(); super.blend(sx1, sy1, sx2, sy2, dx1, dy1, dx2, dy2, mode); updatePixels(); } public void blend(PImage src, int sx1, int sy1, int sx2, int sy2, int dx1, int dy1, int dx2, int dy2, int mode) { loadPixels(); super.blend(src, sx1, sy1, sx2, sy2, dx1, dy1, dx2, dy2, mode); updatePixels(); } public void save(String filename) { loadPixels(); super.save(filename); //boolean ImageIO.write(RenderedImage im, String formatName, File output) } }