/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */
/*
Part of the Processing project - http://processing.org
Copyright (c) 2004-08 Ben Fry and Casey Reas
Copyright (c) 2001-04 Massachusetts Institute of Technology
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.image.*;
import java.io.*;
import java.util.HashMap;
import javax.imageio.ImageIO;
/**
* Storage class for pixel data. This is the base class for most image and
* pixel information, such as PGraphics and the video library classes.
* <P>
* Code for copying, resizing, scaling, and blending contributed
* by <A HREF="http://www.toxi.co.uk">toxi</A>.
* <P>
*/
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;
/**
* Path to parent object that will be used with save().
* This prevents users from needing savePath() to use PImage.save().
*/
public PApplet parent;
// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
/** for subclasses that need to store info about the image */
protected HashMap<Object,Object> cacheMap;
/** modified portion of the image */
protected boolean modified;
protected 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;
static final int PREC_ALPHA_SHIFT = 24-PRECISIONB;
static final int PREC_RED_SHIFT = 16-PRECISIONB;
// internal kernel stuff for the gaussian blur filter
private int blurRadius;
private int blurKernelSize;
private int[] blurKernel;
private int[][] blurMult;
//////////////////////////////////////////////////////////////
/**
* Create an empty image object, set its format to RGB.
* The pixel array is not allocated.
*/
public PImage() {
format = ARGB; // default to ARGB images for release 0116
// cache = null;
}
/**
* 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);
// 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 width, int height, int format) {
init(width, height, format);
}
/**
* Function to be used by subclasses of PImage to init later than
* at the constructor, or re-init later when things changes.
* Used by Capture and Movie classes (and perhaps others),
* because the width/height will not be known when super() is called.
* (Leave this public so that other libraries can do the same.)
*/
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 the alpha on an image, using a really primitive loop.
*/
protected void checkAlpha() {
if (pixels == null) return;
for (int i = 0; i < pixels.length; i++) {
// since transparency is often at corners, hopefully this
// will find a non-transparent pixel quickly and exit
if ((pixels[i] & 0xff000000) != 0xff000000) {
format = ARGB;
break;
}
}
}
//////////////////////////////////////////////////////////////
/**
* 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) {
if (img instanceof BufferedImage) {
BufferedImage bi = (BufferedImage) img;
width = bi.getWidth();
height = bi.getHeight();
pixels = new int[width * height];
WritableRaster raster = bi.getRaster();
raster.getDataElements(0, 0, width, height, pixels);
} else { // go the old school java 1.0 route
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;
}
/**
* Returns a BufferedImage from this PImage.
*/
public java.awt.Image 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;
}
//////////////////////////////////////////////////////////////
/**
* Store data of some kind for a renderer that requires extra metadata of
* some kind. Usually this is a renderer-specific representation of the
* image data, for instance a BufferedImage with tint() settings applied for
* PGraphicsJava2D, or resized image data and OpenGL texture indices for
* PGraphicsOpenGL.
*/
public void setCache(Object parent, Object storage) {
if (cacheMap == null) cacheMap = new HashMap<Object, Object>();
cacheMap.put(parent, storage);
}
/**
* Get cache storage data for the specified renderer. Because each renderer
* will cache data in different formats, it's necessary to store cache data
* keyed by the renderer object. Otherwise, attempting to draw the same
* image to both a PGraphicsJava2D and a PGraphicsOpenGL will cause errors.
* @param parent The PGraphics object (or any object, really) associated
* @return data stored for the specified parent
*/
public Object getCache(Object parent) {
if (cacheMap == null) return null;
return cacheMap.get(parent);
}
/**
* Remove information associated with this renderer from the cache, if any.
* @param parent The PGraphics object whose cache data should be removed
*/
public void removeCache(Object parent) {
if (cacheMap != null) {
cacheMap.remove(parent);
}
}
//////////////////////////////////////////////////////////////
// MARKING IMAGE AS MODIFIED / FOR USE w/ GET/SET
public boolean isModified() { // ignore
return modified;
}
public void setModified() { // ignore
modified = true;
}
public void setModified(boolean m) { // ignore
modified = m;
}
/**
* Call this when you want to mess with the pixels[] array.
* <p/>
* For subclasses where the pixels[] buffer isn't set by default,
* this should copy all data into the pixels[] array
*/
public void loadPixels() { // ignore
}
/**
* Call this when finished messing with the pixels[] array.
* <p/>
* Mark all pixels as needing update.
*/
public void updatePixels() { // ignore
updatePixelsImpl(0, 0, width, height);
}
/**
* Mark the pixels in this region as needing an update.
* <P>
* This is not currently used by any of the renderers, however the api
* is structured this way in the hope of being able to use this to
* speed things up in the future.
*/
public void updatePixels(int x, int y, int w, int h) { // ignore
// if (imageMode == CORNER) { // x2, y2 are w/h
// x2 += x1;
// y2 += y1;
//
// } else if (imageMode == CENTER) {
// x1 -= x2 / 2;
// y1 -= y2 / 2;
// x2 += x1;
// y2 += y1;
// }
updatePixelsImpl(x, y, w, h);
}
protected void updatePixelsImpl(int x, int y, int w, int h) {
int x2 = x + w;
int y2 = y + h;
if (!modified) {
mx1 = x;
mx2 = x2;
my1 = y;
my2 = y2;
modified = true;
} else {
if (x < mx1) mx1 = x;
if (x > mx2) mx2 = x;
if (y < my1) my1 = y;
if (y > my2) my2 = y;
if (x2 < mx1) mx1 = x2;
if (x2 > mx2) mx2 = x2;
if (y2 < my1) my1 = y2;
if (y2 > my2) my2 = y2;
}
}
//////////////////////////////////////////////////////////////
// COPYING IMAGE DATA
/**
* Duplicate an image, returns new PImage object.
* The pixels[] array for the new object will be unique
* and recopied from the source image. This is implemented as an
* override of Object.clone(). We recommend using get() instead,
* because it prevents you from needing to catch the
* CloneNotSupportedException, and from doing a cast from the result.
*/
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;
}
/**
* Resize this image to a new width and height.
* Use 0 for wide or high to make that dimension scale proportionally.
*/
public void resize(int wide, int high) { // ignore
// Make sure that the pixels[] array is valid
loadPixels();
if (wide <= 0 && high <= 0) {
width = 0; // Gimme a break, don't waste my time
height = 0;
pixels = new int[0];
} else {
if (wide == 0) { // Use height to determine relative size
float diff = (float) high / (float) height;
wide = (int) (width * diff);
} else if (high == 0) { // Use the width to determine relative size
float diff = (float) wide / (float) width;
high = (int) (height * diff);
}
PImage temp = new PImage(wide, high, this.format);
temp.copy(this, 0, 0, width, height, 0, 0, wide, high);
this.width = wide;
this.height = high;
this.pixels = temp.pixels;
}
// Mark the pixels array as altered
updatePixels();
}
//////////////////////////////////////////////////////////////
// GET/SET PIXELS
/**
* 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 with 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.
* As of release 0149, no longer 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 - y);
} else if (imageMode == CENTER) {
x -= w/2;
y -= h/2;
}
*/
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;
return getImpl(x, y, w, h);
}
/**
* Internal function to actually handle getting a block of pixels that
* has already been properly cropped to a valid region. That is, x/y/w/h
* are guaranteed to be inside the image space, so the implementation can
* use the fastest possible pixel copying method.
*/
protected PImage getImpl(int x, int y, int w, int h) {
PImage newbie = new PImage(w, h, format);
newbie.parent = parent;
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;
}
/**
* Returns a copy of this PImage. Equivalent to get(0, 0, width, height).
*/
public PImage get() {
try {
PImage clone = (PImage) clone();
// don't want to pass this down to the others
// http://dev.processing.org/bugs/show_bug.cgi?id=1245
clone.cacheMap = null;
return clone;
} catch (CloneNotSupportedException e) {
return null;
}
}
/**
* Set a single pixel to the specified color.
*/
public void set(int x, int y, int c) {
if ((x < 0) || (y < 0) || (x >= width) || (y >= height)) return;
pixels[y*width + x] = c;
updatePixelsImpl(x, y, x+1, y+1); // slow?
}
/**
* Efficient method of drawing an image's pixels directly to this surface.
* No variations are employed, meaning that any scale, tint, or imageMode
* settings will be ignored.
*/
public void set(int x, int y, PImage src) {
int sx = 0;
int sy = 0;
int sw = src.width;
int sh = src.height;
// if (imageMode == CENTER) {
// x -= src.width/2;
// y -= src.height/2;
// }
if (x < 0) { // off left edge
sx -= x;
sw += x;
x = 0;
}
if (y < 0) { // off top edge
sy -= y;
sh += y;
y = 0;
}
if (x + sw > width) { // off right edge
sw = width - x;
}
if (y + sh > height) { // off bottom edge
sh = height - y;
}
// this could be nonexistant
if ((sw <= 0) || (sh <= 0)) return;
setImpl(x, y, sx, sy, sw, sh, src);
}
/**
* Internal function to actually handle setting a block of pixels that
* has already been properly cropped from the image to a valid region.
*/
protected void setImpl(int dx, int dy, int sx, int sy, int sw, int sh,
PImage src) {
int srcOffset = sy * src.width + sx;
int dstOffset = dy * width + dx;
for (int y = sy; y < sy + sh; y++) {
System.arraycopy(src.pixels, srcOffset, pixels, dstOffset, sw);
srcOffset += src.width;
dstOffset += width;
}
updatePixelsImpl(sx, sy, sx+sw, sy+sh);
}
//////////////////////////////////////////////////////////////
// ALPHA CHANNEL
/**
* 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[]) {
loadPixels();
// 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;
updatePixels();
}
/**
* Set alpha channel for an image using another image as the source.
*/
public void mask(PImage alpha) {
mask(alpha.pixels);
}
//////////////////////////////////////////////////////////////
// IMAGE FILTERS
/**
* 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.
* <LI>filter(DILATE) grow white/light areas
* <LI>filter(ERODE) shrink white/light areas
* </UL>
* Luminance conversion code contributed by
* <A HREF="http://www.toxi.co.uk">toxi</A>
* <P/>
* Gaussian blur code contributed by
* <A HREF="http://incubator.quasimondo.com">Mario Klingemann</A>
*/
public void filter(int kind) {
loadPixels();
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:
if (format == ALPHA) {
// for an alpha image, convert it to an opaque grayscale
for (int i = 0; i < pixels.length; i++) {
int col = 255 - pixels[i];
pixels[i] = 0xff000000 | (col << 16) | (col << 8) | col;
}
format = RGB;
} else {
// 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;
// [toxi20050728] added new filters
case ERODE:
dilate(true);
break;
case DILATE:
dilate(false);
break;
}
updatePixels(); // mark as modified
}
/**
* 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>
* Gaussian blur code contributed by
* <A HREF="http://incubator.quasimondo.com">Mario Klingemann</A>
* and later updated by toxi for better speed.
*/
public void filter(int kind, float param) {
loadPixels();
switch (kind) {
case BLUR:
if (format == ALPHA)
blurAlpha(param);
else if (format == ARGB)
blurARGB(param);
else
blurRGB(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)");
}
int levels1 = levels - 1;
for (int i = 0; i < pixels.length; i++) {
int rlevel = (pixels[i] >> 16) & 0xff;
int glevel = (pixels[i] >> 8) & 0xff;
int blevel = pixels[i] & 0xff;
rlevel = (((rlevel * levels) >> 8) * 255) / levels1;
glevel = (((glevel * levels) >> 8) * 255) / levels1;
blevel = (((blevel * levels) >> 8) * 255) / levels1;
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;
// [toxi20050728] added new filters
case ERODE:
throw new RuntimeException("Use filter(ERODE) instead of " +
"filter(ERODE, param)");
case DILATE:
throw new RuntimeException("Use filter(DILATE) instead of " +
"filter(DILATE, param)");
}
updatePixels(); // mark as modified
}
/**
* Optimized code for building the blur kernel.
* further optimized blur code (approx. 15% for radius=20)
* bigger speed gains for larger radii (~30%)
* added support for various image types (ALPHA, RGB, ARGB)
* [toxi 050728]
*/
protected void buildBlurKernel(float r) {
int radius = (int) (r * 3.5f);
radius = (radius < 1) ? 1 : ((radius < 248) ? radius : 248);
if (blurRadius != radius) {
blurRadius = radius;
blurKernelSize = 1 + blurRadius<<1;
blurKernel = new int[blurKernelSize];
blurMult = new int[blurKernelSize][256];
int bk,bki;
int[] bm,bmi;
for (int i = 1, radiusi = radius - 1; i < radius; i++) {
blurKernel[radius+i] = blurKernel[radiusi] = bki = radiusi * radiusi;
bm=blurMult[radius+i];
bmi=blurMult[radiusi--];
for (int j = 0; j < 256; j++)
bm[j] = bmi[j] = bki*j;
}
bk = blurKernel[radius] = radius * radius;
bm = blurMult[radius];
for (int j = 0; j < 256; j++)
bm[j] = bk*j;
}
}
protected void blurAlpha(float r) {
int sum, cb;
int read, ri, ym, ymi, bk0;
int b2[] = new int[pixels.length];
int yi = 0;
buildBlurKernel(r);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
//cb = cg = cr = sum = 0;
cb = sum = 0;
read = x - blurRadius;
if (read<0) {
bk0=-read;
read=0;
} else {
if (read >= width)
break;
bk0=0;
}
for (int i = bk0; i < blurKernelSize; i++) {
if (read >= width)
break;
int c = pixels[read + yi];
int[] bm=blurMult[i];
cb += bm[c & BLUE_MASK];
sum += blurKernel[i];
read++;
}
ri = yi + x;
b2[ri] = cb / sum;
}
yi += width;
}
yi = 0;
ym=-blurRadius;
ymi=ym*width;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
//cb = cg = cr = sum = 0;
cb = sum = 0;
if (ym<0) {
bk0 = ri = -ym;
read = x;
} else {
if (ym >= height)
break;
bk0 = 0;
ri = ym;
read = x + ymi;
}
for (int i = bk0; i < blurKernelSize; i++) {
if (ri >= height)
break;
int[] bm=blurMult[i];
cb += bm[b2[read]];
sum += blurKernel[i];
ri++;
read += width;
}
pixels[x+yi] = (cb/sum);
}
yi += width;
ymi += width;
ym++;
}
}
protected void blurRGB(float r) {
int sum, cr, cg, cb; //, k;
int /*pixel,*/ read, ri, /*roff,*/ ym, ymi, /*riw,*/ bk0;
int r2[] = new int[pixels.length];
int g2[] = new int[pixels.length];
int b2[] = new int[pixels.length];
int yi = 0;
buildBlurKernel(r);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
cb = cg = cr = sum = 0;
read = x - blurRadius;
if (read<0) {
bk0=-read;
read=0;
} else {
if (read >= width)
break;
bk0=0;
}
for (int i = bk0; i < blurKernelSize; i++) {
if (read >= width)
break;
int c = pixels[read + yi];
int[] bm=blurMult[i];
cr += bm[(c & RED_MASK) >> 16];
cg += bm[(c & GREEN_MASK) >> 8];
cb += bm[c & BLUE_MASK];
sum += blurKernel[i];
read++;
}
ri = yi + x;
r2[ri] = cr / sum;
g2[ri] = cg / sum;
b2[ri] = cb / sum;
}
yi += width;
}
yi = 0;
ym=-blurRadius;
ymi=ym*width;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
cb = cg = cr = sum = 0;
if (ym<0) {
bk0 = ri = -ym;
read = x;
} else {
if (ym >= height)
break;
bk0 = 0;
ri = ym;
read = x + ymi;
}
for (int i = bk0; i < blurKernelSize; i++) {
if (ri >= height)
break;
int[] bm=blurMult[i];
cr += bm[r2[read]];
cg += bm[g2[read]];
cb += bm[b2[read]];
sum += blurKernel[i];
ri++;
read += width;
}
pixels[x+yi] = 0xff000000 | (cr/sum)<<16 | (cg/sum)<<8 | (cb/sum);
}
yi += width;
ymi += width;
ym++;
}
}
protected void blurARGB(float r) {
int sum, cr, cg, cb, ca;
int /*pixel,*/ read, ri, /*roff,*/ ym, ymi, /*riw,*/ bk0;
int wh = pixels.length;
int r2[] = new int[wh];
int g2[] = new int[wh];
int b2[] = new int[wh];
int a2[] = new int[wh];
int yi = 0;
buildBlurKernel(r);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
cb = cg = cr = ca = sum = 0;
read = x - blurRadius;
if (read<0) {
bk0=-read;
read=0;
} else {
if (read >= width)
break;
bk0=0;
}
for (int i = bk0; i < blurKernelSize; i++) {
if (read >= width)
break;
int c = pixels[read + yi];
int[] bm=blurMult[i];
ca += bm[(c & ALPHA_MASK) >>> 24];
cr += bm[(c & RED_MASK) >> 16];
cg += bm[(c & GREEN_MASK) >> 8];
cb += bm[c & BLUE_MASK];
sum += blurKernel[i];
read++;
}
ri = yi + x;
a2[ri] = ca / sum;
r2[ri] = cr / sum;
g2[ri] = cg / sum;
b2[ri] = cb / sum;
}
yi += width;
}
yi = 0;
ym=-blurRadius;
ymi=ym*width;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
cb = cg = cr = ca = sum = 0;
if (ym<0) {
bk0 = ri = -ym;
read = x;
} else {
if (ym >= height)
break;
bk0 = 0;
ri = ym;
read = x + ymi;
}
for (int i = bk0; i < blurKernelSize; i++) {
if (ri >= height)
break;
int[] bm=blurMult[i];
ca += bm[a2[read]];
cr += bm[r2[read]];
cg += bm[g2[read]];
cb += bm[b2[read]];
sum += blurKernel[i];
ri++;
read += width;
}
pixels[x+yi] = (ca/sum)<<24 | (cr/sum)<<16 | (cg/sum)<<8 | (cb/sum);
}
yi += width;
ymi += width;
ym++;
}
}
/**
* Generic dilate/erode filter using luminance values
* as decision factor. [toxi 050728]
*/
protected void dilate(boolean isInverted) {
int currIdx=0;
int maxIdx=pixels.length;
int[] out=new int[maxIdx];
if (!isInverted) {
// erosion (grow light areas)
while (currIdx<maxIdx) {
int currRowIdx=currIdx;
int maxRowIdx=currIdx+width;
while (currIdx<maxRowIdx) {
int colOrig,colOut;
colOrig=colOut=pixels[currIdx];
int idxLeft=currIdx-1;
int idxRight=currIdx+1;
int idxUp=currIdx-width;
int idxDown=currIdx+width;
if (idxLeft<currRowIdx)
idxLeft=currIdx;
if (idxRight>=maxRowIdx)
idxRight=currIdx;
if (idxUp<0)
idxUp=0;
if (idxDown>=maxIdx)
idxDown=currIdx;
int colUp=pixels[idxUp];
int colLeft=pixels[idxLeft];
int colDown=pixels[idxDown];
int colRight=pixels[idxRight];
// compute luminance
int currLum =
77*(colOrig>>16&0xff) + 151*(colOrig>>8&0xff) + 28*(colOrig&0xff);
int lumLeft =
77*(colLeft>>16&0xff) + 151*(colLeft>>8&0xff) + 28*(colLeft&0xff);
int lumRight =
77*(colRight>>16&0xff) + 151*(colRight>>8&0xff) + 28*(colRight&0xff);
int lumUp =
77*(colUp>>16&0xff) + 151*(colUp>>8&0xff) + 28*(colUp&0xff);
int lumDown =
77*(colDown>>16&0xff) + 151*(colDown>>8&0xff) + 28*(colDown&0xff);
if (lumLeft>currLum) {
colOut=colLeft;
currLum=lumLeft;
}
if (lumRight>currLum) {
colOut=colRight;
currLum=lumRight;
}
if (lumUp>currLum) {
colOut=colUp;
currLum=lumUp;
}
if (lumDown>currLum) {
colOut=colDown;
currLum=lumDown;
}
out[currIdx++]=colOut;
}
}
} else {
// dilate (grow dark areas)
while (currIdx<maxIdx) {
int currRowIdx=currIdx;
int maxRowIdx=currIdx+width;
while (currIdx<maxRowIdx) {
int colOrig,colOut;
colOrig=colOut=pixels[currIdx];
int idxLeft=currIdx-1;
int idxRight=currIdx+1;
int idxUp=currIdx-width;
int idxDown=currIdx+width;
if (idxLeft<currRowIdx)
idxLeft=currIdx;
if (idxRight>=maxRowIdx)
idxRight=currIdx;
if (idxUp<0)
idxUp=0;
if (idxDown>=maxIdx)
idxDown=currIdx;
int colUp=pixels[idxUp];
int colLeft=pixels[idxLeft];
int colDown=pixels[idxDown];
int colRight=pixels[idxRight];
// compute luminance
int currLum =
77*(colOrig>>16&0xff) + 151*(colOrig>>8&0xff) + 28*(colOrig&0xff);
int lumLeft =
77*(colLeft>>16&0xff) + 151*(colLeft>>8&0xff) + 28*(colLeft&0xff);
int lumRight =
77*(colRight>>16&0xff) + 151*(colRight>>8&0xff) + 28*(colRight&0xff);
int lumUp =
77*(colUp>>16&0xff) + 151*(colUp>>8&0xff) + 28*(colUp&0xff);
int lumDown =
77*(colDown>>16&0xff) + 151*(colDown>>8&0xff) + 28*(colDown&0xff);
if (lumLeft<currLum) {
colOut=colLeft;
currLum=lumLeft;
}
if (lumRight<currLum) {
colOut=colRight;
currLum=lumRight;
}
if (lumUp<currLum) {
colOut=colUp;
currLum=lumUp;
}
if (lumDown<currLum) {
colOut=colDown;
currLum=lumDown;
}
out[currIdx++]=colOut;
}
}
}
System.arraycopy(out,0,pixels,0,maxIdx);
}
//////////////////////////////////////////////////////////////
// COPY
/**
* Copy things from one area of this image
* to another area in the same image.
*/
public void copy(int sx, int sy, int sw, int sh,
int dx, int dy, int dw, int dh) {
blend(this, sx, sy, sw, sh, dx, dy, dw, dh, REPLACE);
}
/**
* Copies area of one image into another PImage object.
*/
public void copy(PImage src,
int sx, int sy, int sw, int sh,
int dx, int dy, int dw, int dh) {
blend(src, sx, sy, sw, sh, dx, dy, dw, dh, REPLACE);
}
//////////////////////////////////////////////////////////////
// BLEND
/**
* Blend two colors based on a particular mode.
* <UL>
* <LI>REPLACE - destination colour equals colour of source pixel: C = A.
* Sometimes called "Normal" or "Copy" in other software.
*
* <LI>BLEND - linear interpolation of colours:
* <TT>C = A*factor + B</TT>
*
* <LI>ADD - additive blending with white clip:
* <TT>C = min(A*factor + B, 255)</TT>.
* Clipped to 0..255, Photoshop calls this "Linear Burn",
* and Director calls it "Add Pin".
*
* <LI>SUBTRACT - substractive blend with black clip:
* <TT>C = max(B - A*factor, 0)</TT>.
* Clipped to 0..255, Photoshop calls this "Linear Dodge",
* and Director calls it "Subtract Pin".
*
* <LI>DARKEST - only the darkest colour succeeds:
* <TT>C = min(A*factor, B)</TT>.
* Illustrator calls this "Darken".
*
* <LI>LIGHTEST - only the lightest colour succeeds:
* <TT>C = max(A*factor, B)</TT>.
* Illustrator calls this "Lighten".
*
* <LI>DIFFERENCE - subtract colors from underlying image.
*
* <LI>EXCLUSION - similar to DIFFERENCE, but less extreme.
*
* <LI>MULTIPLY - Multiply the colors, result will always be darker.
*
* <LI>SCREEN - Opposite multiply, uses inverse values of the colors.
*
* <LI>OVERLAY - A mix of MULTIPLY and SCREEN. Multiplies dark values,
* and screens light values.
*
* <LI>HARD_LIGHT - SCREEN when greater than 50% gray, MULTIPLY when lower.
*
* <LI>SOFT_LIGHT - Mix of DARKEST and LIGHTEST.
* Works like OVERLAY, but not as harsh.
*
* <LI>DODGE - Lightens light tones and increases contrast, ignores darks.
* Called "Color Dodge" in Illustrator and Photoshop.
*
* <LI>BURN - Darker areas are applied, increasing contrast, ignores lights.
* Called "Color Burn" in Illustrator and Photoshop.
* </UL>
* <P>A useful reference for blending modes and their algorithms can be
* found in the <A HREF="http://www.w3.org/TR/SVG12/rendering.html">SVG</A>
* specification.</P>
* <P>It is important to note that Processing uses "fast" code, not
* necessarily "correct" code. No biggie, most software does. A nitpicker
* can find numerous "off by 1 division" problems in the blend code where
* <TT>>>8</TT> or <TT>>>7</TT> is used when strictly speaking
* <TT>/255.0</T> or <TT>/127.0</TT> should have been used.</P>
* <P>For instance, exclusion (not intended for real-time use) reads
* <TT>r1 + r2 - ((2 * r1 * r2) / 255)</TT> because <TT>255 == 1.0</TT>
* not <TT>256 == 1.0</TT>. In other words, <TT>(255*255)>>8</TT> is not
* the same as <TT>(255*255)/255</TT>. But for real-time use the shifts
* are preferrable, and the difference is insignificant for applications
* built with Processing.</P>
*/
static public int blendColor(int c1, int c2, int mode) {
switch (mode) {
case REPLACE: return c2;
case BLEND: return blend_blend(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 DIFFERENCE: return blend_difference(c1, c2);
case EXCLUSION: return blend_exclusion(c1, c2);
case MULTIPLY: return blend_multiply(c1, c2);
case SCREEN: return blend_screen(c1, c2);
case HARD_LIGHT: return blend_hard_light(c1, c2);
case SOFT_LIGHT: return blend_soft_light(c1, c2);
case OVERLAY: return blend_overlay(c1, c2);
case DODGE: return blend_dodge(c1, c2);
case BURN: return blend_burn(c1, c2);
}
return 0;
}
/**
* Blends one area of this image to another area.
* @see processing.core.PImage#blendColor(int,int,int)
*/
public void blend(int sx, int sy, int sw, int sh,
int dx, int dy, int dw, int dh, int mode) {
blend(this, sx, sy, sw, sh, dx, dy, dw, dh, mode);
}
/**
* Copies area of one image into another PImage object.
* @see processing.core.PImage#blendColor(int,int,int)
*/
public void blend(PImage src,
int sx, int sy, int sw, int sh,
int dx, int dy, int dw, int dh, int mode) {
/*
if (imageMode == CORNER) { // if CORNERS, do nothing
sx2 += sx1;
sy2 += sy1;
dx2 += dx1;
dy2 += dy1;
} else if (imageMode == CENTER) {
sx1 -= sx2 / 2f;
sy1 -= sy2 / 2f;
sx2 += sx1;
sy2 += sy1;
dx1 -= dx2 / 2f;
dy1 -= dy2 / 2f;
dx2 += dx1;
dy2 += dy1;
}
*/
int sx2 = sx + sw;
int sy2 = sy + sh;
int dx2 = dx + dw;
int dy2 = dy + dh;
loadPixels();
if (src == this) {
if (intersect(sx, sy, sx2, sy2, dx, dy, dx2, dy2)) {
blit_resize(get(sx, sy, sx2 - sx, sy2 - sy),
0, 0, sx2 - sx - 1, sy2 - sy - 1,
pixels, width, height, dx, dy, dx2, dy2, mode);
} else {
// same as below, except skip the loadPixels() because it'd be redundant
blit_resize(src, sx, sy, sx2, sy2,
pixels, width, height, dx, dy, dx2, dy2, mode);
}
} else {
src.loadPixels();
blit_resize(src, sx, sy, sx2, sy2,
pixels, width, height, dx, dy, dx2, dy2, mode);
//src.updatePixels();
}
updatePixels();
}
/**
* Check to see if two rectangles intersect one another
*/
private 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);
}
//////////////////////////////////////////////////////////////
/**
* 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.height) srcY2 = img.height - 1;
int srcW = srcX2 - srcX1;
int srcH = srcY2 - srcY1;
int destW = destX2 - destX1;
int destH = destY2 - destY1;
boolean smooth = true; // may as well go with the smoothing these days
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 = (int) (destX1 < 0 ? -destX1 * dx : srcX1 * PRECISIONF);
srcYOffset = (int) (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++) {
// davbol - renamed old blend_multiply to blend_blend
destPixels[destOffset + x] =
blend_blend(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;
case DIFFERENCE:
for (int y = 0; y < destH; y++) {
filter_new_scanline();
for (int x = 0; x < destW; x++) {
destPixels[destOffset + x] =
blend_difference(destPixels[destOffset + x], filter_bilinear());
sX += dx;
}
destOffset += screenW;
srcYOffset += dy;
}
break;
case EXCLUSION:
for (int y = 0; y < destH; y++) {
filter_new_scanline();
for (int x = 0; x < destW; x++) {
destPixels[destOffset + x] =
blend_exclusion(destPixels[destOffset + x], filter_bilinear());
sX += dx;
}
destOffset += screenW;
srcYOffset += dy;
}
break;
case MULTIPLY:
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 SCREEN:
for (int y = 0; y < destH; y++) {
filter_new_scanline();
for (int x = 0; x < destW; x++) {
destPixels[destOffset + x] =
blend_screen(destPixels[destOffset + x], filter_bilinear());
sX += dx;
}
destOffset += screenW;
srcYOffset += dy;
}
break;
case OVERLAY:
for (int y = 0; y < destH; y++) {
filter_new_scanline();
for (int x = 0; x < destW; x++) {
destPixels[destOffset + x] =
blend_overlay(destPixels[destOffset + x], filter_bilinear());
sX += dx;
}
destOffset += screenW;
srcYOffset += dy;
}
break;
case HARD_LIGHT:
for (int y = 0; y < destH; y++) {
filter_new_scanline();
for (int x = 0; x < destW; x++) {
destPixels[destOffset + x] =
blend_hard_light(destPixels[destOffset + x], filter_bilinear());
sX += dx;
}
destOffset += screenW;
srcYOffset += dy;
}
break;
case SOFT_LIGHT:
for (int y = 0; y < destH; y++) {
filter_new_scanline();
for (int x = 0; x < destW; x++) {
destPixels[destOffset + x] =
blend_soft_light(destPixels[destOffset + x], filter_bilinear());
sX += dx;
}
destOffset += screenW;
srcYOffset += dy;
}
break;
// davbol - proposed 2007-01-09
case DODGE:
for (int y = 0; y < destH; y++) {
filter_new_scanline();
for (int x = 0; x < destW; x++) {
destPixels[destOffset + x] =
blend_dodge(destPixels[destOffset + x], filter_bilinear());
sX += dx;
}
destOffset += screenW;
srcYOffset += dy;
}
break;
case BURN:
for (int y = 0; y < destH; y++) {
filter_new_scanline();
for (int x = 0; x < destW; x++) {
destPixels[destOffset + x] =
blend_burn(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++) {
// davbol - renamed old blend_multiply to blend_blend
destPixels[destOffset + x] =
blend_blend(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;
case DIFFERENCE:
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_difference(destPixels[destOffset + x],
srcBuffer[sY + (sX >> PRECISIONB)]);
sX += dx;
}
destOffset += screenW;
srcYOffset += dy;
}
break;
case EXCLUSION:
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_exclusion(destPixels[destOffset + x],
srcBuffer[sY + (sX >> PRECISIONB)]);
sX += dx;
}
destOffset += screenW;
srcYOffset += dy;
}
break;
case MULTIPLY:
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 SCREEN:
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_screen(destPixels[destOffset + x],
srcBuffer[sY + (sX >> PRECISIONB)]);
sX += dx;
}
destOffset += screenW;
srcYOffset += dy;
}
break;
case OVERLAY:
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_overlay(destPixels[destOffset + x],
srcBuffer[sY + (sX >> PRECISIONB)]);
sX += dx;
}
destOffset += screenW;
srcYOffset += dy;
}
break;
case HARD_LIGHT:
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_hard_light(destPixels[destOffset + x],
srcBuffer[sY + (sX >> PRECISIONB)]);
sX += dx;
}
destOffset += screenW;
srcYOffset += dy;
}
break;
case SOFT_LIGHT:
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_soft_light(destPixels[destOffset + x],
srcBuffer[sY + (sX >> PRECISIONB)]);
sX += dx;
}
destOffset += screenW;
srcYOffset += dy;
}
break;
// davbol - proposed 2007-01-09
case DODGE:
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_dodge(destPixels[destOffset + x],
srcBuffer[sY + (sX >> PRECISIONB)]);
sX += dx;
}
destOffset += screenW;
srcYOffset += dy;
}
break;
case BURN:
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_burn(destPixels[destOffset + x],
srcBuffer[sY + (sX >> PRECISIONB)]);
sX += dx;
}
destOffset += screenW;
srcYOffset += dy;
}
break;
}
}
}
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;
}
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;
}
//////////////////////////////////////////////////////////////
// internal blending methods
private static int low(int a, int b) {
return (a < b) ? a : b;
}
private static int high(int a, int b) {
return (a > b) ? a : b;
}
// davbol - added peg helper, equiv to constrain(n,0,255)
private static int peg(int n) {
return (n < 0) ? 0 : ((n > 255) ? 255 : n);
}
private static int mix(int a, int b, int f) {
return a + (((b - a) * f) >> 8);
}
/////////////////////////////////////////////////////////////
// BLEND MODE IMPLEMENTIONS
private static int blend_blend(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));
}
/**
* 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));
}
/**
* 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));
}
/**
* 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));
}
/**
* 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));
}
/**
* returns the absolute value of the difference of the input colors
* C = |A - B|
*/
private static int blend_difference(int a, int b) {
// setup (this portion will always be the same)
int f = (b & ALPHA_MASK) >>> 24;
int ar = (a & RED_MASK) >> 16;
int ag = (a & GREEN_MASK) >> 8;
int ab = (a & BLUE_MASK);
int br = (b & RED_MASK) >> 16;
int bg = (b & GREEN_MASK) >> 8;
int bb = (b & BLUE_MASK);
// formula:
int cr = (ar > br) ? (ar-br) : (br-ar);
int cg = (ag > bg) ? (ag-bg) : (bg-ag);
int cb = (ab > bb) ? (ab-bb) : (bb-ab);
// alpha blend (this portion will always be the same)
return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 |
(peg(ar + (((cr - ar) * f) >> 8)) << 16) |
(peg(ag + (((cg - ag) * f) >> 8)) << 8) |
(peg(ab + (((cb - ab) * f) >> 8)) ) );
}
/**
* Cousin of difference, algorithm used here is based on a Lingo version
* found here: http://www.mediamacros.com/item/item-1006687616/
* (Not yet verified to be correct).
*/
private static int blend_exclusion(int a, int b) {
// setup (this portion will always be the same)
int f = (b & ALPHA_MASK) >>> 24;
int ar = (a & RED_MASK) >> 16;
int ag = (a & GREEN_MASK) >> 8;
int ab = (a & BLUE_MASK);
int br = (b & RED_MASK) >> 16;
int bg = (b & GREEN_MASK) >> 8;
int bb = (b & BLUE_MASK);
// formula:
int cr = ar + br - ((ar * br) >> 7);
int cg = ag + bg - ((ag * bg) >> 7);
int cb = ab + bb - ((ab * bb) >> 7);
// alpha blend (this portion will always be the same)
return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 |
(peg(ar + (((cr - ar) * f) >> 8)) << 16) |
(peg(ag + (((cg - ag) * f) >> 8)) << 8) |
(peg(ab + (((cb - ab) * f) >> 8)) ) );
}
/**
* returns the product of the input colors
* C = A * B
*/
private static int blend_multiply(int a, int b) {
// setup (this portion will always be the same)
int f = (b & ALPHA_MASK) >>> 24;
int ar = (a & RED_MASK) >> 16;
int ag = (a & GREEN_MASK) >> 8;
int ab = (a & BLUE_MASK);
int br = (b & RED_MASK) >> 16;
int bg = (b & GREEN_MASK) >> 8;
int bb = (b & BLUE_MASK);
// formula:
int cr = (ar * br) >> 8;
int cg = (ag * bg) >> 8;
int cb = (ab * bb) >> 8;
// alpha blend (this portion will always be the same)
return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 |
(peg(ar + (((cr - ar) * f) >> 8)) << 16) |
(peg(ag + (((cg - ag) * f) >> 8)) << 8) |
(peg(ab + (((cb - ab) * f) >> 8)) ) );
}
/**
* returns the inverse of the product of the inverses of the input colors
* (the inverse of multiply). C = 1 - (1-A) * (1-B)
*/
private static int blend_screen(int a, int b) {
// setup (this portion will always be the same)
int f = (b & ALPHA_MASK) >>> 24;
int ar = (a & RED_MASK) >> 16;
int ag = (a & GREEN_MASK) >> 8;
int ab = (a & BLUE_MASK);
int br = (b & RED_MASK) >> 16;
int bg = (b & GREEN_MASK) >> 8;
int bb = (b & BLUE_MASK);
// formula:
int cr = 255 - (((255 - ar) * (255 - br)) >> 8);
int cg = 255 - (((255 - ag) * (255 - bg)) >> 8);
int cb = 255 - (((255 - ab) * (255 - bb)) >> 8);
// alpha blend (this portion will always be the same)
return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 |
(peg(ar + (((cr - ar) * f) >> 8)) << 16) |
(peg(ag + (((cg - ag) * f) >> 8)) << 8) |
(peg(ab + (((cb - ab) * f) >> 8)) ) );
}
/**
* returns either multiply or screen for darker or lighter values of A
* (the inverse of hard light)
* C =
* A < 0.5 : 2 * A * B
* A >=0.5 : 1 - (2 * (255-A) * (255-B))
*/
private static int blend_overlay(int a, int b) {
// setup (this portion will always be the same)
int f = (b & ALPHA_MASK) >>> 24;
int ar = (a & RED_MASK) >> 16;
int ag = (a & GREEN_MASK) >> 8;
int ab = (a & BLUE_MASK);
int br = (b & RED_MASK) >> 16;
int bg = (b & GREEN_MASK) >> 8;
int bb = (b & BLUE_MASK);
// formula:
int cr = (ar < 128) ? ((ar*br)>>7) : (255-(((255-ar)*(255-br))>>7));
int cg = (ag < 128) ? ((ag*bg)>>7) : (255-(((255-ag)*(255-bg))>>7));
int cb = (ab < 128) ? ((ab*bb)>>7) : (255-(((255-ab)*(255-bb))>>7));
// alpha blend (this portion will always be the same)
return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 |
(peg(ar + (((cr - ar) * f) >> 8)) << 16) |
(peg(ag + (((cg - ag) * f) >> 8)) << 8) |
(peg(ab + (((cb - ab) * f) >> 8)) ) );
}
/**
* returns either multiply or screen for darker or lighter values of B
* (the inverse of overlay)
* C =
* B < 0.5 : 2 * A * B
* B >=0.5 : 1 - (2 * (255-A) * (255-B))
*/
private static int blend_hard_light(int a, int b) {
// setup (this portion will always be the same)
int f = (b & ALPHA_MASK) >>> 24;
int ar = (a & RED_MASK) >> 16;
int ag = (a & GREEN_MASK) >> 8;
int ab = (a & BLUE_MASK);
int br = (b & RED_MASK) >> 16;
int bg = (b & GREEN_MASK) >> 8;
int bb = (b & BLUE_MASK);
// formula:
int cr = (br < 128) ? ((ar*br)>>7) : (255-(((255-ar)*(255-br))>>7));
int cg = (bg < 128) ? ((ag*bg)>>7) : (255-(((255-ag)*(255-bg))>>7));
int cb = (bb < 128) ? ((ab*bb)>>7) : (255-(((255-ab)*(255-bb))>>7));
// alpha blend (this portion will always be the same)
return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 |
(peg(ar + (((cr - ar) * f) >> 8)) << 16) |
(peg(ag + (((cg - ag) * f) >> 8)) << 8) |
(peg(ab + (((cb - ab) * f) >> 8)) ) );
}
/**
* returns the inverse multiply plus screen, which simplifies to
* C = 2AB + A^2 - 2A^2B
*/
private static int blend_soft_light(int a, int b) {
// setup (this portion will always be the same)
int f = (b & ALPHA_MASK) >>> 24;
int ar = (a & RED_MASK) >> 16;
int ag = (a & GREEN_MASK) >> 8;
int ab = (a & BLUE_MASK);
int br = (b & RED_MASK) >> 16;
int bg = (b & GREEN_MASK) >> 8;
int bb = (b & BLUE_MASK);
// formula:
int cr = ((ar*br)>>7) + ((ar*ar)>>8) - ((ar*ar*br)>>15);
int cg = ((ag*bg)>>7) + ((ag*ag)>>8) - ((ag*ag*bg)>>15);
int cb = ((ab*bb)>>7) + ((ab*ab)>>8) - ((ab*ab*bb)>>15);
// alpha blend (this portion will always be the same)
return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 |
(peg(ar + (((cr - ar) * f) >> 8)) << 16) |
(peg(ag + (((cg - ag) * f) >> 8)) << 8) |
(peg(ab + (((cb - ab) * f) >> 8)) ) );
}
/**
* Returns the first (underlay) color divided by the inverse of
* the second (overlay) color. C = A / (255-B)
*/
private static int blend_dodge(int a, int b) {
// setup (this portion will always be the same)
int f = (b & ALPHA_MASK) >>> 24;
int ar = (a & RED_MASK) >> 16;
int ag = (a & GREEN_MASK) >> 8;
int ab = (a & BLUE_MASK);
int br = (b & RED_MASK) >> 16;
int bg = (b & GREEN_MASK) >> 8;
int bb = (b & BLUE_MASK);
// formula:
int cr = (br==255) ? 255 : peg((ar << 8) / (255 - br)); // division requires pre-peg()-ing
int cg = (bg==255) ? 255 : peg((ag << 8) / (255 - bg)); // "
int cb = (bb==255) ? 255 : peg((ab << 8) / (255 - bb)); // "
// alpha blend (this portion will always be the same)
return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 |
(peg(ar + (((cr - ar) * f) >> 8)) << 16) |
(peg(ag + (((cg - ag) * f) >> 8)) << 8) |
(peg(ab + (((cb - ab) * f) >> 8)) ) );
}
/**
* returns the inverse of the inverse of the first (underlay) color
* divided by the second (overlay) color. C = 255 - (255-A) / B
*/
private static int blend_burn(int a, int b) {
// setup (this portion will always be the same)
int f = (b & ALPHA_MASK) >>> 24;
int ar = (a & RED_MASK) >> 16;
int ag = (a & GREEN_MASK) >> 8;
int ab = (a & BLUE_MASK);
int br = (b & RED_MASK) >> 16;
int bg = (b & GREEN_MASK) >> 8;
int bb = (b & BLUE_MASK);
// formula:
int cr = (br==0) ? 0 : 255 - peg(((255 - ar) << 8) / br); // division requires pre-peg()-ing
int cg = (bg==0) ? 0 : 255 - peg(((255 - ag) << 8) / bg); // "
int cb = (bb==0) ? 0 : 255 - peg(((255 - ab) << 8) / bb); // "
// alpha blend (this portion will always be the same)
return (low(((a & ALPHA_MASK) >>> 24) + f, 0xff) << 24 |
(peg(ar + (((cr - ar) * f) >> 8)) << 16) |
(peg(ag + (((cg - ag) * f) >> 8)) << 8) |
(peg(ab + (((cb - ab) * f) >> 8)) ) );
}
//////////////////////////////////////////////////////////////
// FILE I/O
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
};
static final String TIFF_ERROR =
"Error: Processing can only read its own TIFF files.";
static protected PImage loadTIFF(byte tiff[]) {
if ((tiff[42] != tiff[102]) || // width/height in both places
(tiff[43] != tiff[103])) {
System.err.println(TIFF_ERROR);
return null;
}
int width =
((tiff[30] & 0xff) << 8) | (tiff[31] & 0xff);
int height =
((tiff[42] & 0xff) << 8) | (tiff[43] & 0xff);
int count =
((tiff[114] & 0xff) << 24) |
((tiff[115] & 0xff) << 16) |
((tiff[116] & 0xff) << 8) |
(tiff[117] & 0xff);
if (count != width * height * 3) {
System.err.println(TIFF_ERROR + " (" + width + ", " + height +")");
return null;
}
// check the rest of the header
for (int i = 0; i < TIFF_HEADER.length; i++) {
if ((i == 30) || (i == 31) || (i == 42) || (i == 43) ||
(i == 102) || (i == 103) ||
(i == 114) || (i == 115) || (i == 116) || (i == 117)) continue;
if (tiff[i] != TIFF_HEADER[i]) {
System.err.println(TIFF_ERROR + " (" + i + ")");
return null;
}
}
PImage outgoing = new PImage(width, height, RGB);
int index = 768;
count /= 3;
for (int i = 0; i < count; i++) {
outgoing.pixels[i] =
0xFF000000 |
(tiff[index++] & 0xff) << 16 |
(tiff[index++] & 0xff) << 8 |
(tiff[index++] & 0xff);
}
return outgoing;
}
protected boolean saveTIFF(OutputStream output) {
// shutting off the warning, people can figure this out themselves
/*
if (format != RGB) {
System.err.println("Warning: only RGB information is saved with " +
".tif files. Use .tga or .png for ARGB images and others.");
}
*/
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);
// spew the header to the disk
output.write(tiff);
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;
}
/**
* Creates a Targa32 formatted byte sequence of specified
* pixel buffer using RLE compression.
* </p>
* Also figured out how to avoid parsing the image upside-down
* (there's a header flag to set the image origin to top-left)
* </p>
* Starting with revision 0092, the format setting is taken into account:
* <UL>
* <LI><TT>ALPHA</TT> images written as 8bit grayscale (uses lowest byte)
* <LI><TT>RGB</TT> → 24 bits
* <LI><TT>ARGB</TT> → 32 bits
* </UL>
* All versions are RLE compressed.
* </p>
* Contributed by toxi 8-10 May 2005, based on this RLE
* <A HREF="http://www.wotsit.org/download.asp?f=tga">specification</A>
*/
protected boolean saveTGA(OutputStream output) {
byte header[] = new byte[18];
if (format == ALPHA) { // save ALPHA images as 8bit grayscale
header[2] = 0x0B;
header[16] = 0x08;
header[17] = 0x28;
} else if (format == RGB) {
header[2] = 0x0A;
header[16] = 24;
header[17] = 0x20;
} else if (format == ARGB) {
header[2] = 0x0A;
header[16] = 32;
header[17] = 0x28;
} else {
throw new RuntimeException("Image format not recognized inside save()");
}
// set image dimensions lo-hi byte order
header[12] = (byte) (width & 0xff);
header[13] = (byte) (width >> 8);
header[14] = (byte) (height & 0xff);
header[15] = (byte) (height >> 8);
try {
output.write(header);
int maxLen = height * width;
int index = 0;
int col; //, prevCol;
int[] currChunk = new int[128];
// 8bit image exporter is in separate loop
// to avoid excessive conditionals...
if (format == ALPHA) {
while (index < maxLen) {
boolean isRLE = false;
int rle = 1;
currChunk[0] = col = pixels[index] & 0xff;
while (index + rle < maxLen) {
if (col != (pixels[index + rle]&0xff) || rle == 128) {
isRLE = (rle > 1);
break;
}
rle++;
}
if (isRLE) {
output.write(0x80 | (rle - 1));
output.write(col);
} else {
rle = 1;
while (index + rle < maxLen) {
int cscan = pixels[index + rle] & 0xff;
if ((col != cscan && rle < 128) || rle < 3) {
currChunk[rle] = col = cscan;
} else {
if (col == cscan) rle -= 2;
break;
}
rle++;
}
output.write(rle - 1);
for (int i = 0; i < rle; i++) output.write(currChunk[i]);
}
index += rle;
}
} else { // export 24/32 bit TARGA
while (index < maxLen) {
boolean isRLE = false;
currChunk[0] = col = pixels[index];
int rle = 1;
// try to find repeating bytes (min. len = 2 pixels)
// maximum chunk size is 128 pixels
while (index + rle < maxLen) {
if (col != pixels[index + rle] || rle == 128) {
isRLE = (rle > 1); // set flag for RLE chunk
break;
}
rle++;
}
if (isRLE) {
output.write(128 | (rle - 1));
output.write(col & 0xff);
output.write(col >> 8 & 0xff);
output.write(col >> 16 & 0xff);
if (format == ARGB) output.write(col >>> 24 & 0xff);
} else { // not RLE
rle = 1;
while (index + rle < maxLen) {
if ((col != pixels[index + rle] && rle < 128) || rle < 3) {
currChunk[rle] = col = pixels[index + rle];
} else {
// check if the exit condition was the start of
// a repeating colour
if (col == pixels[index + rle]) rle -= 2;
break;
}
rle++;
}
// write uncompressed chunk
output.write(rle - 1);
if (format == ARGB) {
for (int i = 0; i < rle; i++) {
col = currChunk[i];
output.write(col & 0xff);
output.write(col >> 8 & 0xff);
output.write(col >> 16 & 0xff);
output.write(col >>> 24 & 0xff);
}
} else {
for (int i = 0; i < rle; i++) {
col = currChunk[i];
output.write(col & 0xff);
output.write(col >> 8 & 0xff);
output.write(col >> 16 & 0xff);
}
}
}
index += rle;
}
}
output.flush();
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
/**
* Use ImageIO functions from Java 1.4 and later to handle image save.
* Various formats are supported, typically jpeg, png, bmp, and wbmp.
* To get a list of the supported formats for writing, use: <BR>
* <TT>println(javax.imageio.ImageIO.getReaderFormatNames())</TT>
*/
protected void saveImageIO(String path) throws IOException {
try {
BufferedImage bimage =
new BufferedImage(width, height, (format == ARGB) ?
BufferedImage.TYPE_INT_ARGB :
BufferedImage.TYPE_INT_RGB);
/*
Class bufferedImageClass =
Class.forName("java.awt.image.BufferedImage");
Constructor bufferedImageConstructor =
bufferedImageClass.getConstructor(new Class[] {
Integer.TYPE,
Integer.TYPE,
Integer.TYPE });
Field typeIntRgbField = bufferedImageClass.getField("TYPE_INT_RGB");
int typeIntRgb = typeIntRgbField.getInt(typeIntRgbField);
Field typeIntArgbField = bufferedImageClass.getField("TYPE_INT_ARGB");
int typeIntArgb = typeIntArgbField.getInt(typeIntArgbField);
Object bimage =
bufferedImageConstructor.newInstance(new Object[] {
new Integer(width),
new Integer(height),
new Integer((format == ARGB) ? typeIntArgb : typeIntRgb)
});
*/
bimage.setRGB(0, 0, width, height, pixels, 0, width);
/*
Method setRgbMethod =
bufferedImageClass.getMethod("setRGB", new Class[] {
Integer.TYPE, Integer.TYPE,
Integer.TYPE, Integer.TYPE,
pixels.getClass(),
Integer.TYPE, Integer.TYPE
});
setRgbMethod.invoke(bimage, new Object[] {
new Integer(0), new Integer(0),
new Integer(width), new Integer(height),
pixels, new Integer(0), new Integer(width)
});
*/
File file = new File(path);
String extension = path.substring(path.lastIndexOf('.') + 1);
ImageIO.write(bimage, extension, file);
/*
Class renderedImageClass =
Class.forName("java.awt.image.RenderedImage");
Class ioClass = Class.forName("javax.imageio.ImageIO");
Method writeMethod =
ioClass.getMethod("write", new Class[] {
renderedImageClass, String.class, File.class
});
writeMethod.invoke(null, new Object[] { bimage, extension, file });
*/
} catch (Exception e) {
e.printStackTrace();
throw new IOException("image save failed.");
}
}
protected String[] saveImageFormats;
/**
* Save this image to disk.
* <p>
* As of revision 0100, this function requires an absolute path,
* in order to avoid confusion. To save inside the sketch folder,
* use the function savePath() from PApplet, or use saveFrame() instead.
* As of revision 0116, savePath() is not needed if this object has been
* created (as recommended) via createImage() or createGraphics() or
* one of its neighbors.
* <p>
* As of revision 0115, when using Java 1.4 and later, you can write
* to several formats besides tga and tiff. If Java 1.4 is installed
* and the extension used is supported (usually png, jpg, jpeg, bmp,
* and tiff), then those methods will be used to write the image.
* To get a list of the supported formats for writing, use: <BR>
* <TT>println(javax.imageio.ImageIO.getReaderFormatNames())</TT>
* <p>
* To use the original built-in image writers, use .tga or .tif as the
* extension, or don't include an extension. When no extension is used,
* the extension .tif will be added to the file name.
* <p>
* The ImageIO API claims to support wbmp files, however they probably
* require a black and white image. Basic testing produced a zero-length
* file with no error.
*/
public void save(String path) { // ignore
boolean success = false;
File file = new File(path);
if (!file.isAbsolute()) {
if (parent != null) {
//file = new File(parent.savePath(filename));
path = parent.savePath(path);
} else {
String msg = "PImage.save() requires an absolute path. " +
"Use createImage(), or pass savePath() to save().";
PGraphics.showException(msg);
}
}
// Make sure the pixel data is ready to go
loadPixels();
try {
OutputStream os = null;
if (saveImageFormats == null) {
saveImageFormats = javax.imageio.ImageIO.getWriterFormatNames();
}
if (saveImageFormats != null) {
for (int i = 0; i < saveImageFormats.length; i++) {
if (path.endsWith("." + saveImageFormats[i])) {
saveImageIO(path);
return;
}
}
}
if (path.toLowerCase().endsWith(".tga")) {
os = new BufferedOutputStream(new FileOutputStream(path), 32768);
success = saveTGA(os); //, pixels, width, height, format);
} else {
if (!path.toLowerCase().endsWith(".tif") &&
!path.toLowerCase().endsWith(".tiff")) {
// if no .tif extension, add it..
path += ".tif";
}
os = new BufferedOutputStream(new FileOutputStream(path), 32768);
success = saveTIFF(os); //, pixels, width, height);
}
os.flush();
os.close();
} catch (IOException e) {
//System.err.println("Error while saving image.");
e.printStackTrace();
success = false;
}
if (!success) {
throw new RuntimeException("Error while saving image.");
}
}
}