package org.myrobotlab.openni; /* -*- mode: jde; c-basic-offset: 2; indent-tabs-mode: nil -*- */ /* PImage - storage class for pixel data Part of the Processing project - http://processing.org Copyright (c) 2001-05 Ben Fry, Massachusetts Institute of Technology and Casey Reas, Interaction Design Institute Ivrea Additional code contributions from toxi 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 */ import java.awt.image.BufferedImage; import java.awt.image.PixelGrabber; import java.awt.image.WritableRaster; import java.io.BufferedOutputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; /** * image class developed by toxi and fry. * * <PRE> * [fry 0407XX] * - get() on RGB images sets the high bits to opaque * - modification of naming for functions * - inclusion of Object.clone() * - make get(), copy(), blend() honor imageMode * - lots of moving things around for new megabucket api * * [toxi 030722] * advanced copying/blitting code * * [fry 030918] * integrated and modified to fit p5 spec * * [toxi 030930] * - target pixel buffer doesn't loose alpha channel anymore * with every blitting operation alpha values are increased now * - resizing by large factors (>250%) doesn't yield any rounding errors * anymore, changed to 16bit precision (=65536% max or 0.000015% min) * - replicate() is now only using REPLACE mode to avoid semantic problems * - added blend() methods to use replicate()'s functionality, * but with blend modes * * [toxi 031006] * blit_resize() is now clipping input coordinates to avoid array * exceptions target dimension can be larger than destination image * object, outside pixels will be skipped * * [toxi 031017] * versions of replicate() and blend() methods which use cross-image * blitting are now called in the destination image and expect a source * image object as parameter. this is to provide an easy syntax for cases * where the main pixel buffer is the destination. as those methods are * overloaded in BApplet, users can call those functions directly without * explicitly giving a reference to PGraphics. * </PRE> */ public class PImage implements PConstants, Cloneable { /** * Format for this image, one of RGB, ARGB or ALPHA. note that RGB images * still require 0xff in the high byte because of how they'll be manipulated * by other functions */ public int format; public int pixels[]; public int width, height; // would scan line be useful? maybe for pow of 2 gl textures // note! inherited by PGraphics public int imageMode = CORNER; public boolean smooth = false; /** native storage for java 1.3 image object */ // public Object image; /** for subclasses that need to store info about the image */ public Object cache; /** modified portion of the image */ public boolean modified; public int mx1, my1, mx2, my2; // private fields private int fracU, ifU, fracV, ifV, u1, u2, v1, v2, sX, sY, iw, iw1, ih1; private int ul, ll, ur, lr, cUL, cLL, cUR, cLR; private int srcXOffset, srcYOffset; private int r, g, b, a; private int[] srcBuffer; // fixed point precision is limited to 15 bits!! static final int PRECISIONB = 15; static final int PRECISIONF = 1 << PRECISIONB; static final int PREC_MAXVAL = PRECISIONF - 1; /** * Note that when using imageMode(CORNERS), the x2 and y2 positions are * non-inclusive. */ /* * public int[] loadPixels(int x1, int y1, int x2, int y2) { if (modified) { * // have to set the modified region to include the min/max // of the * coordinates coming in. // also, mustn't get the pixels for the section * that's // already been marked as modified. gah. // too complicated, just * throw an error String msg = * "getPixels(x, y, w, h) cannot be used multiple times. " + * "Use getPixels() once to get the entire image instead."; throw new * RuntimeException(msg); } * * if (imageMode == CORNER) { // x2, y2 are w/h x2 += x1; y2 += y1; } * * if (pixels == null) { // this is a java 1.3 buffered image if (image == * null) { // this is just an error throw new RuntimeException( * "PImage not properly setup for getPixels()"); } else { pixels = new * int[width*height]; } } * * if (image == null) { // this happens when using just the 1.1 library // no * need to do anything, since the pixels have already been grabbed * * } else { // copy the contents of the buffered image to pixels[] * //((BufferedImage) image).getRGB(x, y, w, h, output.pixels, 0, width); try * { //System.out.println("running getrgb..."); Class bufferedImageClass = * Class.forName("java.awt.image.BufferedImage"); // getRGB(int startX, int * startY, int w, int h, int[] rgbArray, int offset, int scansize) Method * getRgbMethod = bufferedImageClass.getMethod("getRGB", new Class[] { * Integer.TYPE, Integer.TYPE, Integer.TYPE, Integer.TYPE, int[].class, * Integer.TYPE, Integer.TYPE }); getRgbMethod.invoke(image, new Object[] { * new Integer(x1), new Integer(y1), new Integer(x2 - x1 + 1), new Integer(y2 * - y1 + 1), pixels, new Integer(0), new Integer(width) }); * * } catch (Exception e) { e.printStackTrace(); } } return pixels; // just to * be nice } */ static final int PREC_ALPHA_SHIFT = 24 - PRECISIONB; static final int PREC_RED_SHIFT = 16 - PRECISIONB; // internal kernel stuff for the gaussian blur filter int blurRadius; int blurKernelSize; int[] blurKernel; int[][] blurMult; // //////////////////////////////////////////////////////////// static byte tiff_header[] = { 77, 77, 0, 42, 0, 0, 0, 8, 0, 9, 0, -2, 0, 4, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0, 3, 0, 0, 0, 1, 0, 0, 0, 0, 1, 2, 0, 3, 0, 0, 0, 3, 0, 0, 0, 122, 1, 6, 0, 3, 0, 0, 0, 1, 0, 2, 0, 0, 1, 17, 0, 4, 0, 0, 0, 1, 0, 0, 3, 0, 1, 21, 0, 3, 0, 0, 0, 1, 0, 3, 0, 0, 1, 22, 0, 3, 0, 0, 0, 1, 0, 0, 0, 0, 1, 23, 0, 4, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 8, 0, 8 }; /** * Blend a two colors based on a particular mode. */ static public int blend(int c1, int c2, int mode) { switch (mode) { case BLEND: return blend_multiply(c1, c2); case ADD: return blend_add_pin(c1, c2); case SUBTRACT: return blend_sub_pin(c1, c2); case LIGHTEST: return blend_lightest(c1, c2); case DARKEST: return blend_darkest(c1, c2); case REPLACE: return c2; } return 0; } /** * additive blend with clipping */ private static int blend_add_pin(int a, int b) { int f = (b & ALPHA_MASK) >>> 24; return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | low(((a & RED_MASK) + ((b & RED_MASK) >> 8) * f), RED_MASK) & RED_MASK | low(((a & GREEN_MASK) + ((b & GREEN_MASK) >> 8) * f), GREEN_MASK) & GREEN_MASK | low((a & BLUE_MASK) + (((b & BLUE_MASK) * f) >> 8), BLUE_MASK)); } /** * only returns the blended darkest colour */ private static int blend_darkest(int a, int b) { int f = (b & ALPHA_MASK) >>> 24; return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | mix(a & RED_MASK, low(a & RED_MASK, ((b & RED_MASK) >> 8) * f), f) & RED_MASK | mix(a & GREEN_MASK, low(a & GREEN_MASK, ((b & GREEN_MASK) >> 8) * f), f) & GREEN_MASK | mix(a & BLUE_MASK, low(a & BLUE_MASK, ((b & BLUE_MASK) * f) >> 8), f)); } /** * only returns the blended lightest colour */ private static int blend_lightest(int a, int b) { int f = (b & ALPHA_MASK) >>> 24; return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | high(a & RED_MASK, ((b & RED_MASK) >> 8) * f) & RED_MASK | high(a & GREEN_MASK, ((b & GREEN_MASK) >> 8) * f) & GREEN_MASK | high(a & BLUE_MASK, ((b & BLUE_MASK) * f) >> 8)); } // //////////////////////////////////////////////////////////// private static int blend_multiply(int a, int b) { int f = (b & ALPHA_MASK) >>> 24; return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | mix(a & RED_MASK, b & RED_MASK, f) & RED_MASK | mix(a & GREEN_MASK, b & GREEN_MASK, f) & GREEN_MASK | mix(a & BLUE_MASK, b & BLUE_MASK, f)); } /** * subtractive blend with clipping */ private static int blend_sub_pin(int a, int b) { int f = (b & ALPHA_MASK) >>> 24; return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 | high(((a & RED_MASK) - ((b & RED_MASK) >> 8) * f), GREEN_MASK) & RED_MASK | high(((a & GREEN_MASK) - ((b & GREEN_MASK) >> 8) * f), BLUE_MASK) & GREEN_MASK | high((a & BLUE_MASK) - (((b & BLUE_MASK) * f) >> 8), 0)); } // never used locally // private static float frac(float x) { // return (x - (int) x); // } // //////////////////////////////////////////////////////////// // MARKING IMAGE AS MODIFIED / FOR USE w/ GET/SET /* * public int[] loadPixels() { return getPixels(0, 0, width, height); } */ private static int high(int a, int b) { return (a > b) ? a : b; } private static int low(int a, int b) { return (a < b) ? a : b; } /** * generic linear interpolation */ private static int mix(int a, int b, int f) { return a + (((b - a) * f) >> 8); } // public void pixelsUpdated() { // mx1 = Integer.MAX_VALUE; // my1 = Integer.MAX_VALUE; // mx2 = -Integer.MAX_VALUE; // my2 = -Integer.MAX_VALUE; // modified = false; // } // //////////////////////////////////////////////////////////// // GET/SET PIXELS /** * [toxi 030902] Creates a Targa32 formatted byte sequence of specified pixel * buffer * * [fry 030917] Modified to write directly to OutputStream, because of memory * issues with first making an array of the data. * * tga spec: http://organicbit.com/closecombat/formats/tga.html */ static public boolean saveHeaderTGA(OutputStream output, int width, int height) { try { byte header[] = new byte[18]; // set header info header[2] = 0x02; header[12] = (byte) (width & 0xff); header[13] = (byte) (width >> 8); header[14] = (byte) (height & 0xff); header[15] = (byte) (height >> 8); header[16] = 32; // bits per pixel header[17] = 8; // bits per colour component output.write(header); return true; } catch (IOException e) { e.printStackTrace(); } return false; } static public boolean saveHeaderTIFF(OutputStream output, int width, int height) { try { byte tiff[] = new byte[768]; System.arraycopy(tiff_header, 0, tiff, 0, tiff_header.length); tiff[30] = (byte) ((width >> 8) & 0xff); tiff[31] = (byte) ((width) & 0xff); tiff[42] = tiff[102] = (byte) ((height >> 8) & 0xff); tiff[43] = tiff[103] = (byte) ((height) & 0xff); int count = width * height * 3; tiff[114] = (byte) ((count >> 24) & 0xff); tiff[115] = (byte) ((count >> 16) & 0xff); tiff[116] = (byte) ((count >> 8) & 0xff); tiff[117] = (byte) ((count) & 0xff); output.write(tiff); return true; } catch (IOException e) { e.printStackTrace(); } return false; } static public boolean saveTGA(OutputStream output, int pixels[], int width, int height) { try { if (!saveHeaderTGA(output, width, height)) { return false; } int index = (height - 1) * width; for (int y = height - 1; y >= 0; y--) { for (int x = 0; x < width; x++) { int col = pixels[index + x]; output.write(col & 0xff); output.write(col >> 8 & 0xff); output.write(col >> 16 & 0xff); output.write(col >>> 24 & 0xff); } index -= width; } output.flush(); return true; } catch (IOException e) { e.printStackTrace(); } return false; } static public boolean saveTIFF(OutputStream output, int pixels[], int width, int height) { try { if (!saveHeaderTIFF(output, width, height)) { return false; } for (int i = 0; i < pixels.length; i++) { output.write((pixels[i] >> 16) & 0xff); output.write((pixels[i] >> 8) & 0xff); output.write(pixels[i] & 0xff); } output.flush(); return true; } catch (IOException e) { e.printStackTrace(); } return false; } /* * // properly debugged version from copy() // in case the below one doesn't * work * * public void set(int dx, int dy, PImage src) { // source int sx = 0; int sy * = 0; int sw = src.width; int sh = src.height; * * // target int tx = dx; // < 0 ? 0 : x; int ty = dy; // < 0 ? 0 : y; int tw * = width; int th = height; * * if (tx < 0) { // say if target x were -3 sx -= tx; // source x -(-3) (or * add 3) sw += tx; // source width -3 tw += tx; // target width -3 tx = 0; // * target x is zero (upper corner) } if (ty < 0) { sy -= ty; sh += ty; th += * ty; ty = 0; } if (tx + tw > width) { int extra = (tx + tw) - width; sw -= * extra; tw -= extra; } if (ty + th > height) { int extra = (ty + th) - * height; sh -= extra; sw -= extra; } * * for (int row = sy; row < sy + sh; row++) { System.arraycopy(src.pixels, * row*src.width + sx, pixels, (dy+row)*width + tx, sw); } } */ /** * Create an empty image object, set its format to RGB. The pixel array is not * allocated. */ public PImage() { format = RGB; // makes sure that this guy is useful cache = null; } // //////////////////////////////////////////////////////////// // ALPHA CHANNEL /** * Create a new RGB (alpha ignored) image of a specific size. All pixels are * set to zero, meaning black, but since the alpha is zero, it will be * transparent. */ public PImage(int width, int height) { init(width, height, RGB); // this(new int[width * height], width, height, ARGB); // toxi: is it maybe better to init the image with max alpha enabled? // for(int i=0; i<pixels.length; i++) pixels[i]=0xffffffff; // fry: i'm opting for the full transparent image, which is how // photoshop works, and our audience oughta be familiar with. // also, i want to avoid having to set all those pixels since // in java it's super slow, and most using this fxn will be // setting all the pixels anyway. // toxi: agreed and same reasons why i left it out ;) } public PImage(int pixels[], int width, int height, int format) { this.pixels = pixels; this.width = width; this.height = height; this.format = format; this.cache = null; } /** * Construct a new PImage from a java.awt.Image * * this constructor assumes that you've done the work of making sure a * MediaTracker has been used to fully download the data and that the img is * valid. */ public PImage(java.awt.Image img) { width = img.getWidth(null); height = img.getHeight(null); pixels = new int[width * height]; PixelGrabber pg = new PixelGrabber(img, 0, 0, width, height, pixels, 0, width); try { pg.grabPixels(); } catch (InterruptedException e) { } format = RGB; cache = null; } /** * Copies and blends 1 pixel with MODE to pixel in this image. */ public void blend(int sx, int sy, int dx, int dy, int mode) { if ((dx >= 0) && (dx < width) && (sx >= 0) && (sx < width) && (dy >= 0) && (dy < height) && (sy >= 0) && (sy < height)) { pixels[dy * width + dx] = blend(pixels[dy * width + dx], pixels[sy * width + sx], mode); } } /** * Blends one area of this image to another area */ public void blend(int sx1, int sy1, int sx2, int sy2, int dx1, int dy1, int dx2, int dy2, int mode) { blend(this, sx1, sy1, sx2, sy2, dx1, dy1, dx2, dy2, mode); } // //////////////////////////////////////////////////////////// // REPLICATING & BLENDING (AREAS) OF PIXELS /** * Copies and blends 1 pixel with MODE to pixel in another image */ public void blend(PImage src, int sx, int sy, int dx, int dy, int mode) { if ((dx >= 0) && (dx < width) && (sx >= 0) && (sx < src.width) && (dy >= 0) && (dy < height) && (sy >= 0) && (sy < src.height)) { pixels[dy * width + dx] = blend(pixels[dy * width + dx], src.pixels[sy * src.width + sx], mode); } } /** * Copies area of one image into another PImage object */ public void blend(PImage src, int sx1, int sy1, int sx2, int sy2, int dx1, int dy1, int dx2, int dy2, int mode) { if (imageMode == CORNER) { // if CORNERS, do nothing sx2 += sx1; sy2 += sy1; dx2 += dx1; dy2 += dy1; // } else if (imageMode == CENTER) { // sx2 /= 2f; sy2 /= 2f; // dx2 /= 2f; dy2 /= 2f; } if ((src == this) && intersect(sx1, sy1, sx2, sy2, dx1, dy1, dx2, dy2)) { blit_resize(get(sx1, sy1, sx2 - sx1, sy2 - sy1), 0, 0, sx2 - sx1 - 1, sy2 - sy1 - 1, pixels, width, height, dx1, dy1, dx2, dy2, mode); } else { blit_resize(src, sx1, sy1, sx2, sy2, pixels, width, height, dx1, dy1, dx2, dy2, mode); } } /** * Internal blitter/resizer/copier from toxi. Uses bilinear filtering if * smooth() has been enabled 'mode' determines the blending mode used in the * process. */ private void blit_resize(PImage img, int srcX1, int srcY1, int srcX2, int srcY2, int[] destPixels, int screenW, int screenH, int destX1, int destY1, int destX2, int destY2, int mode) { if (srcX1 < 0) srcX1 = 0; if (srcY1 < 0) srcY1 = 0; if (srcX2 >= img.width) srcX2 = img.width - 1; if (srcY2 >= img.width) srcY2 = img.height - 1; int srcW = srcX2 - srcX1; int srcH = srcY2 - srcY1; int destW = destX2 - destX1; int destH = destY2 - destY1; if (!smooth) { srcW++; srcH++; } if (destW <= 0 || destH <= 0 || srcW <= 0 || srcH <= 0 || destX1 >= screenW || destY1 >= screenH || srcX1 >= img.width || srcY1 >= img.height) { return; } int dx = (int) (srcW / (float) destW * PRECISIONF); int dy = (int) (srcH / (float) destH * PRECISIONF); srcXOffset = destX1 < 0 ? -destX1 * dx : srcX1 * PRECISIONF; srcYOffset = destY1 < 0 ? -destY1 * dy : srcY1 * PRECISIONF; if (destX1 < 0) { destW += destX1; destX1 = 0; } if (destY1 < 0) { destH += destY1; destY1 = 0; } destW = low(destW, screenW - destX1); destH = low(destH, screenH - destY1); int destOffset = destY1 * screenW + destX1; srcBuffer = img.pixels; if (smooth) { // use bilinear filtering iw = img.width; iw1 = img.width - 1; ih1 = img.height - 1; switch (mode) { case BLEND: for (int y = 0; y < destH; y++) { filter_new_scanline(); for (int x = 0; x < destW; x++) { destPixels[destOffset + x] = blend_multiply(destPixels[destOffset + x], filter_bilinear()); sX += dx; } destOffset += screenW; srcYOffset += dy; } break; case ADD: for (int y = 0; y < destH; y++) { filter_new_scanline(); for (int x = 0; x < destW; x++) { destPixels[destOffset + x] = blend_add_pin(destPixels[destOffset + x], filter_bilinear()); sX += dx; } destOffset += screenW; srcYOffset += dy; } break; case SUBTRACT: for (int y = 0; y < destH; y++) { filter_new_scanline(); for (int x = 0; x < destW; x++) { destPixels[destOffset + x] = blend_sub_pin(destPixels[destOffset + x], filter_bilinear()); sX += dx; } destOffset += screenW; srcYOffset += dy; } break; case LIGHTEST: for (int y = 0; y < destH; y++) { filter_new_scanline(); for (int x = 0; x < destW; x++) { destPixels[destOffset + x] = blend_lightest(destPixels[destOffset + x], filter_bilinear()); sX += dx; } destOffset += screenW; srcYOffset += dy; } break; case DARKEST: for (int y = 0; y < destH; y++) { filter_new_scanline(); for (int x = 0; x < destW; x++) { destPixels[destOffset + x] = blend_darkest(destPixels[destOffset + x], filter_bilinear()); sX += dx; } destOffset += screenW; srcYOffset += dy; } break; case REPLACE: for (int y = 0; y < destH; y++) { filter_new_scanline(); for (int x = 0; x < destW; x++) { destPixels[destOffset + x] = filter_bilinear(); sX += dx; } destOffset += screenW; srcYOffset += dy; } break; } } else { // nearest neighbour scaling (++fast!) switch (mode) { case BLEND: for (int y = 0; y < destH; y++) { sX = srcXOffset; sY = (srcYOffset >> PRECISIONB) * img.width; for (int x = 0; x < destW; x++) { destPixels[destOffset + x] = blend_multiply(destPixels[destOffset + x], srcBuffer[sY + (sX >> PRECISIONB)]); sX += dx; } destOffset += screenW; srcYOffset += dy; } break; case ADD: for (int y = 0; y < destH; y++) { sX = srcXOffset; sY = (srcYOffset >> PRECISIONB) * img.width; for (int x = 0; x < destW; x++) { destPixels[destOffset + x] = blend_add_pin(destPixels[destOffset + x], srcBuffer[sY + (sX >> PRECISIONB)]); sX += dx; } destOffset += screenW; srcYOffset += dy; } break; case SUBTRACT: for (int y = 0; y < destH; y++) { sX = srcXOffset; sY = (srcYOffset >> PRECISIONB) * img.width; for (int x = 0; x < destW; x++) { destPixels[destOffset + x] = blend_sub_pin(destPixels[destOffset + x], srcBuffer[sY + (sX >> PRECISIONB)]); sX += dx; } destOffset += screenW; srcYOffset += dy; } break; case LIGHTEST: for (int y = 0; y < destH; y++) { sX = srcXOffset; sY = (srcYOffset >> PRECISIONB) * img.width; for (int x = 0; x < destW; x++) { destPixels[destOffset + x] = blend_lightest(destPixels[destOffset + x], srcBuffer[sY + (sX >> PRECISIONB)]); sX += dx; } destOffset += screenW; srcYOffset += dy; } break; case DARKEST: for (int y = 0; y < destH; y++) { sX = srcXOffset; sY = (srcYOffset >> PRECISIONB) * img.width; for (int x = 0; x < destW; x++) { destPixels[destOffset + x] = blend_darkest(destPixels[destOffset + x], srcBuffer[sY + (sX >> PRECISIONB)]); sX += dx; } destOffset += screenW; srcYOffset += dy; } break; case REPLACE: for (int y = 0; y < destH; y++) { sX = srcXOffset; sY = (srcYOffset >> PRECISIONB) * img.width; for (int x = 0; x < destW; x++) { destPixels[destOffset + x] = srcBuffer[sY + (sX >> PRECISIONB)]; sX += dx; } destOffset += screenW; srcYOffset += dy; } break; } } } protected void blur(float r) { // adjustment to make this algorithm // similar to photoshop's gaussian blur settings int radius = (int) (r * 3.5f); radius = (radius < 1) ? 1 : ((radius < 248) ? radius : 248); // radius = min(Math.max(1, radius), 248); if (blurRadius != radius) { // it's actually a little silly to cache this stuff // when all the cost is gonna come from allocating 2x the // image size in r1[] and r2[] et al. blurRadius = radius; blurKernelSize = 1 + radius * 2; blurKernel = new int[blurKernelSize]; // 1 + radius*2]; blurMult = new int[blurKernelSize][256]; // new // int[1+radius*2][256]; // TODO: this sum isn't used anywhere ?! why accumulate if you're not // using? // int sum = 0; for (int i = 1; i < radius; i++) { int radiusi = radius - i; blurKernel[radius + i] = blurKernel[radiusi] = radiusi * radiusi; // sum += blurKernel[radiusi] + blurKernel[radiusi]; for (int j = 0; j < 256; j++) { blurMult[radius + i][j] = blurMult[radiusi][j] = blurKernel[radiusi] * j; } } blurKernel[radius] = radius * radius; // sum += blurKernel[radius]; for (int j = 0; j < 256; j++) { blurMult[radius][j] = blurKernel[radius] * j; } } // void blur(BImage img,int x, int y,int w,int h){ int sum, cr, cg, cb; int read, ri, xl, yl, ym, riw; // int[] pix=img.pixels; // int iw=img.width; int wh = width * height; int r1[] = new int[wh]; int g1[] = new int[wh]; int b1[] = new int[wh]; for (int i = 0; i < wh; i++) { ri = pixels[i]; r1[i] = (ri & 0xff0000) >> 16; g1[i] = (ri & 0x00ff00) >> 8; b1[i] = (ri & 0x0000ff); } int r2[] = new int[wh]; int g2[] = new int[wh]; int b2[] = new int[wh]; int x = 0; // Math.max(0, x); int y = 0; // Math.max(0, y); int w = width; // x + w - Math.max(0, (x+w)-width); int h = height; // y + h - Math.max(0, (y+h)-height); int yi = y * width; for (yl = y; yl < h; yl++) { for (xl = x; xl < w; xl++) { cb = cg = cr = sum = 0; ri = xl - blurRadius; for (int i = 0; i < blurKernelSize; i++) { read = ri + i; if ((read >= x) && (read < w)) { read += yi; cr += blurMult[i][r1[read]]; cg += blurMult[i][g1[read]]; cb += blurMult[i][b1[read]]; sum += blurKernel[i]; } } ri = yi + xl; r2[ri] = cr / sum; g2[ri] = cg / sum; b2[ri] = cb / sum; } yi += width; } yi = y * width; for (yl = y; yl < h; yl++) { ym = yl - blurRadius; riw = ym * width; for (xl = x; xl < w; xl++) { cb = cg = cr = sum = 0; ri = ym; read = xl + riw; for (int i = 0; i < blurKernelSize; i++) { if ((ri < h) && (ri >= y)) { cr += blurMult[i][r2[read]]; cg += blurMult[i][g2[read]]; cb += blurMult[i][b2[read]]; sum += blurKernel[i]; } ri++; read += width; } pixels[xl + yi] = 0xff000000 | (cr / sum) << 16 | (cg / sum) << 8 | (cb / sum); } yi += width; } } /** * Duplicate an image, returns new PImage object. The pixels[] array for the * new object will be unique and recopied from the source image. */ @Override public Object clone() throws CloneNotSupportedException { // ignore PImage c = (PImage) super.clone(); // super.clone() will only copy the reference to the pixels // array, so this will do a proper duplication of it instead. c.pixels = new int[width * height]; System.arraycopy(pixels, 0, c.pixels, 0, pixels.length); // return the goods return c; } /** * Copy things from one area of this image to another area in the same image. */ public void copy(int sx1, int sy1, int sx2, int sy2, int dx1, int dy1, int dx2, int dy2) { copy(this, sx1, sy1, sx2, sy2, dx1, dy1, dx2, dy2); } /** * Copies area of one image into another PImage object. */ public void copy(PImage src, int sx1, int sy1, int sx2, int sy2, int dx1, int dy1, int dx2, int dy2) { if (imageMode == CORNER) { // if CORNERS, do nothing sx2 += sx1; sy2 += sy1; dx2 += dx1; dy2 += dy1; // } else if (imageMode == CENTER) { // sx2 /= 2f; sy2 /= 2f; // dx2 /= 2f; dy2 /= 2f; } if ((src == this) && intersect(sx1, sy1, sx2, sy2, dx1, dy1, dx2, dy2)) { // if src is me, and things intersect, make a copy of the data blit_resize(get(sx1, sy1, sx2 - sx1, sy2 - sy1), 0, 0, sx2 - sx1 - 1, sy2 - sy1 - 1, pixels, width, height, dx1, dy1, dx2, dy2, REPLACE); } else { blit_resize(src, sx1, sy1, sx2, sy2, pixels, width, height, dx1, dy1, dx2, dy2, REPLACE); } } /** * Method to apply a variety of basic filters to this image. * <P> * <UL> * <LI>filter(BLUR) provides a basic blur. * <LI>filter(GRAY) converts the image to grayscale based on luminance. * <LI>filter(INVERT) will invert the color components in the image. * <LI>filter(OPAQUE) set all the high bits in the image to opaque * <LI>filter(THRESHOLD) converts the image to black and white. * </UL> */ public void filter(int kind) { switch (kind) { case BLUR: // TODO write basic low-pass filter blur here // what does photoshop do on the edges with this guy? // better yet.. why bother? just use gaussian with radius 1 filter(BLUR, 1); break; case GRAY: // Converts RGB image data into grayscale using // weighted RGB components, and keeps alpha channel intact. // [toxi 040115] for (int i = 0; i < pixels.length; i++) { int col = pixels[i]; // luminance = 0.3*red + 0.59*green + 0.11*blue // 0.30 * 256 = 77 // 0.59 * 256 = 151 // 0.11 * 256 = 28 int lum = (77 * (col >> 16 & 0xff) + 151 * (col >> 8 & 0xff) + 28 * (col & 0xff)) >> 8; pixels[i] = (col & ALPHA_MASK) | lum << 16 | lum << 8 | lum; } break; case INVERT: for (int i = 0; i < pixels.length; i++) { // pixels[i] = 0xff000000 | pixels[i] ^= 0xffffff; } break; case POSTERIZE: throw new RuntimeException("Use filter(POSTERIZE, int levels) " + "instead of filter(POSTERIZE)"); case RGB: for (int i = 0; i < pixels.length; i++) { pixels[i] |= 0xff000000; } format = RGB; break; case THRESHOLD: filter(THRESHOLD, 0.5f); break; } updatePixels(); // mark as modified } // //////////////////////////////////////////////////////////// // COPYING IMAGE DATA /** * Method to apply a variety of basic filters to this image. These filters all * take a parameter. * <P> * <UL> * <LI>filter(BLUR, int radius) performs a gaussian blur of the specified * radius. * <LI>filter(POSTERIZE, int levels) will posterize the image to between 2 and * 255 levels. * <LI>filter(THRESHOLD, float center) allows you to set the center point for * the threshold. It takes a value from 0 to 1.0. * </UL> */ public void filter(int kind, float param) { switch (kind) { case BLUR: blur(param); break; case GRAY: throw new RuntimeException("Use filter(GRAY) instead of " + "filter(GRAY, param)"); case INVERT: throw new RuntimeException("Use filter(INVERT) instead of " + "filter(INVERT, param)"); case OPAQUE: throw new RuntimeException("Use filter(OPAQUE) instead of " + "filter(OPAQUE, param)"); case POSTERIZE: int levels = (int) param; if ((levels < 2) || (levels > 255)) { throw new RuntimeException("Levels must be between 2 and 255 for " + "filter(POSTERIZE, levels)"); } // TODO not optimized int levels256 = 256 / levels; int levels1 = levels - 1; for (int i = 0; i < pixels.length; i++) { int rlevel = ((pixels[i] >> 16) & 0xff) / levels256; int glevel = ((pixels[i] >> 8) & 0xff) / levels256; int blevel = (pixels[i] & 0xff) / levels256; rlevel = (rlevel * 255 / levels1) & 0xff; glevel = (glevel * 255 / levels1) & 0xff; blevel = (blevel * 255 / levels1) & 0xff; pixels[i] = ((0xff000000 & pixels[i]) | (rlevel << 16) | (glevel << 8) | blevel); } break; case THRESHOLD: // greater than or equal to the threshold int thresh = (int) (param * 255); for (int i = 0; i < pixels.length; i++) { int max = Math.max((pixels[i] & RED_MASK) >> 16, Math.max((pixels[i] & GREEN_MASK) >> 8, (pixels[i] & BLUE_MASK))); pixels[i] = (pixels[i] & ALPHA_MASK) | ((max < thresh) ? 0x000000 : 0xffffff); } break; } updatePixels(); // mark as modified } // //////////////////////////////////////////////////////////// private int filter_bilinear() { fracU = sX & PREC_MAXVAL; ifU = PREC_MAXVAL - fracU; ul = (ifU * ifV) >> PRECISIONB; ll = (ifU * fracV) >> PRECISIONB; ur = (fracU * ifV) >> PRECISIONB; lr = (fracU * fracV) >> PRECISIONB; u1 = (sX >> PRECISIONB); u2 = low(u1 + 1, iw1); // get color values of the 4 neighbouring texels cUL = srcBuffer[v1 + u1]; cUR = srcBuffer[v1 + u2]; cLL = srcBuffer[v2 + u1]; cLR = srcBuffer[v2 + u2]; r = ((ul * ((cUL & RED_MASK) >> 16) + ll * ((cLL & RED_MASK) >> 16) + ur * ((cUR & RED_MASK) >> 16) + lr * ((cLR & RED_MASK) >> 16)) << PREC_RED_SHIFT) & RED_MASK; g = ((ul * (cUL & GREEN_MASK) + ll * (cLL & GREEN_MASK) + ur * (cUR & GREEN_MASK) + lr * (cLR & GREEN_MASK)) >>> PRECISIONB) & GREEN_MASK; b = (ul * (cUL & BLUE_MASK) + ll * (cLL & BLUE_MASK) + ur * (cUR & BLUE_MASK) + lr * (cLR & BLUE_MASK)) >>> PRECISIONB; a = ((ul * ((cUL & ALPHA_MASK) >>> 24) + ll * ((cLL & ALPHA_MASK) >>> 24) + ur * ((cUR & ALPHA_MASK) >>> 24) + lr * ((cLR & ALPHA_MASK) >>> 24)) << PREC_ALPHA_SHIFT) & ALPHA_MASK; return a | r | g | b; } private void filter_new_scanline() { sX = srcXOffset; fracV = srcYOffset & PREC_MAXVAL; ifV = PREC_MAXVAL - fracV; v1 = (srcYOffset >> PRECISIONB) * iw; v2 = low((srcYOffset >> PRECISIONB) + 1, ih1) * iw; } /** * Returns a copy of this PImage. Equivalent to get(0, 0, width, height). */ public PImage get() { try { return (PImage) clone(); } catch (CloneNotSupportedException e) { return null; } } // //////////////////////////////////////////////////////////// // internal blending methods /** * Returns an ARGB "color" type (a packed 32 bit int with the color. If the * coordinate is outside the image, zero is returned (black, but completely * transparent). * <P> * If the image is in RGB format (i.e. on a PVideo object), the value will get * its high bits set, just to avoid cases where they haven't been set already. * <P> * If the image is in ALPHA format, this returns a white color that has its * alpha value set. * <P> * This function is included primarily for beginners. It is quite slow because * it has to check to see if the x, y that was provided is inside the bounds, * and then has to check to see what image type it is. If you want things to * be more efficient, access the pixels[] array directly. */ public int get(int x, int y) { if ((x < 0) || (y < 0) || (x >= width) || (y >= height)) return 0; switch (format) { case RGB: return pixels[y * width + x] | 0xff000000; case ARGB: return pixels[y * width + x]; case ALPHA: return (pixels[y * width + x] << 24) | 0xffffff; } return 0; } /** * Grab a subsection of a PImage, and copy it into a fresh PImage. This honors * imageMode() for the coordinates. */ public PImage get(int x, int y, int w, int h) { if (imageMode == CORNERS) { // if CORNER, do nothing // x2 += x1; y2 += y1; // 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 newbie = new PImage(new int[w * h], w, h, format); int index = y * width + x; int index2 = 0; for (int row = y; row < y + h; row++) { System.arraycopy(pixels, index, newbie.pixels, index2, w); index += width; index2 += w; } return newbie; } public BufferedImage getImage() { loadPixels(); int type = (format == RGB) ? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB; BufferedImage image = new BufferedImage(width, height, type); WritableRaster wr = image.getRaster(); wr.setDataElements(0, 0, width, height, pixels); return image; } /** * mode is one of CORNERS or CORNER, because the others are just too weird for * the other functions */ public void imageMode(int mode) { if ((mode == CORNER) || (mode == CORNERS)) { imageMode = mode; } else { throw new RuntimeException("imageMode() only works with CORNER or CORNERS"); } } // /////////////////////////////////////////////////////////// // BLEND MODE IMPLEMENTIONS /** * Function to be used by subclasses to setup their own bidness. */ public void init(int width, int height, int format) { // ignore this.width = width; this.height = height; this.pixels = new int[width * height]; this.format = format; this.cache = null; } /** * Check to see if two rectangles intersect one another */ protected boolean intersect(int sx1, int sy1, int sx2, int sy2, int dx1, int dy1, int dx2, int dy2) { int sw = sx2 - sx1 + 1; int sh = sy2 - sy1 + 1; int dw = dx2 - dx1 + 1; int dh = dy2 - dy1 + 1; if (dx1 < sx1) { dw += dx1 - sx1; if (dw > sw) { dw = sw; } } else { int w = sw + sx1 - dx1; if (dw > w) { dw = w; } } if (dy1 < sy1) { dh += dy1 - sy1; if (dh > sh) { dh = sh; } } else { int h = sh + sy1 - dy1; if (dh > h) { dh = h; } } return !(dw <= 0 || dh <= 0); } /** * For subclasses where the pixels[] buffer isn't set by default, this should * copy all data into the pixels[] array */ public void loadPixels() { // ignore } /** * Set alpha channel for an image. Black colors in the source image will make * the destination image completely transparent, and white will make things * fully opaque. Gray values will be in-between steps. * <P> * Strictly speaking the "blue" value from the source image is used as the * alpha color. For a fully grayscale image, this is correct, but for a color * image it's not 100% accurate. For a more accurate conversion, first use * filter(GRAY) which will make the image into a "correct" grayscake by * performing a proper luminance-based conversion. */ public void mask(int alpha[]) { // don't execute if mask image is different size if (alpha.length != pixels.length) { throw new RuntimeException("The PImage used with mask() must be " + "the same size as the applet."); } for (int i = 0; i < pixels.length; i++) { pixels[i] = ((alpha[i] & 0xff) << 24) | (pixels[i] & 0xffffff); } format = ARGB; } /** * Set alpha channel for an image using another image as the source. */ public void mask(PImage alpha) { mask(alpha.pixels); } // //////////////////////////////////////////////////////////// // FILE I/O /** * Disable smoothing. See smooth(). */ public void noSmooth() { smooth = false; } public void save(String filename) { // ignore try { OutputStream os = null; if (filename.toLowerCase().endsWith(".tga")) { os = new BufferedOutputStream(new FileOutputStream(filename), 32768); saveTGA(os, pixels, width, height); } else { if (!filename.toLowerCase().endsWith(".tif") && !filename.toLowerCase().endsWith(".tiff")) { // if no .tif extension, add it.. filename += ".tif"; } os = new BufferedOutputStream(new FileOutputStream(filename), 32768); saveTIFF(os, pixels, width, height); } os.flush(); os.close(); } catch (IOException e) { e.printStackTrace(); } } /** * Silently ignores if the coordinate is outside the image. */ public void set(int x, int y, int c) { if ((x < 0) || (y < 0) || (x >= width) || (y >= height)) return; pixels[y * width + x] = c; } public void set(int x1, int y1, PImage image) { int x2 = x1 + image.width; int y2 = y1 + image.height; // off to the top and/or left if ((x2 < 0) || (y2 < 0)) return; int ix1 = 0; int iy1 = 0; // int ix2 = image.width; // int iy2 = image.height; if (x1 < 0) { // off left edge ix1 += -x1; x1 = 0; } if (y1 < 0) { // off top edge iy1 += -y1; y1 = 0; } if (x2 >= width) { // off right edge // ix2 -= x2 - width; x2 = width; } if (y2 >= height) { // off bottom edge // iy2 -= y2 - height; y2 = height; } int src = iy1 * image.width + ix1; int dest = y1 * width + x1; int len = x2 - x1; for (int y = y1; y < y2; y++) { // for (int x = x1; x < x2; x++) { System.arraycopy(image.pixels, src, pixels, dest, len); src += len; dest += len; } } /** * If true in PImage, use bilinear interpolation for copy() operations. When * inherited by PGraphics, also controls shapes. */ public void smooth() { smooth = true; } /** * Mark all pixels as needing update. */ public void updatePixels() { // ignore updatePixels(0, 0, width, height); } /** * Note that when using imageMode(CORNERS), the x2 and y2 positions are * non-inclusive. */ public void updatePixels(int x1, int y1, int x2, int y2) { // ignore // if (!modified) { // could just set directly, but.. // } if (imageMode == CORNER) { // x2, y2 are w/h x2 += x1; y2 += y1; } if (!modified) { mx1 = x1; mx2 = x2; my1 = y1; my2 = y2; modified = true; } else { if (x1 < mx1) mx1 = x1; if (x1 > mx2) mx2 = x1; if (y1 < my1) my1 = y1; if (y1 > my2) my2 = y1; if (x2 < mx1) mx1 = x2; if (x2 > mx2) mx2 = x2; if (y2 < my1) my1 = y2; if (y2 > my2) my2 = y2; } } }