/* -*- mode: jde; c-basic-offset: 2; indent-tabs-mode: nil -*- */ /* Part of the Processing project - http://processing.org Copyright (c) 2004-06 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.lang.reflect.*; import javax.imageio.*; /** * Storage class for pixel data. *
* Code for copying, resizing, scaling, and blending contributed * by toxi *
*/
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;
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;
//////////////////////////////////////////////////////////////
/**
* 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;
}
/**
* 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
* 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) {
//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;
}
}
//public void pixelsUpdated() {
//mx1 = Integer.MAX_VALUE;
//my1 = Integer.MAX_VALUE;
//mx2 = -Integer.MAX_VALUE;
//my2 = -Integer.MAX_VALUE;
//modified = false;
//}
//////////////////////////////////////////////////////////////
// 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).
*
* 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.
*
* If the image is in ALPHA format, this returns a white color
* that has its alpha value set.
*
* 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;
}
/**
* 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;
}
}
/**
* 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 dx, int dy, PImage src) {
int sx = 0;
int sy = 0;
int sw = src.width;
int sh = src.height;
if (dx < 0) { // off left edge
sx -= dx;
sw += dx;
dx = 0;
}
if (dy < 0) { // off top edge
sy -= dy;
sh += dy;
dy = 0;
}
if (dx + sw > width) { // off right edge
sw = width - dx;
}
if (dy + sh > height) { // off bottom edge
sh = height - dy;
}
// this could be nonexistant
if ((sw <= 0) || (sh <= 0)) return;
setImpl(dx, dy, 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;
}
}
//////////////////////////////////////////////////////////////
// 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.
*
* 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);
}
/**
* Method to apply a variety of basic filters to this image.
*
*
*
*
* Luminance conversion code contributed by
* toxi
*
* Gaussian blur code contributed by
* Mario Klingemann
*/
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;
// [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.
*
*
* Gaussian blur code contributed by
* Mario Klingemann
* and later updated by toxi for better speed.
*/
public void filter(int kind, float param) {
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)");
}
// 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;
// [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
}
/* 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];
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, k;
int pixel, 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;
}
}
// end of original blur code.......
*/
/**
* 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, /*cr, cg,*/ cb; //, k;
int /*pixel,*/ read, ri, /*roff,*/ ym, ymi, /*riw,*/ 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
* 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 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:
* println(javax.imageio.ImageIO.getReaderFormatNames())
*
* To use the original built-in image writers, use .tga as the extension, * or don't include an extension, in which case .tif will be added. *
* 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 filename) { // ignore boolean success = false; File file = new File(filename); if (!file.isAbsolute()) { System.err.println("PImage.save() requires an absolute path, " + "you might need to use savePath()."); return; } try { OutputStream os = null; if (PApplet.javaVersion >= 1.4f) { if (saveImageFormats == null) { //saveImageFormats = javax.imageio.ImageIO.getWriterFormatNames(); try { Class ioClass = Class.forName("javax.imageio.ImageIO"); Method getFormatNamesMethod = ioClass.getMethod("getWriterFormatNames", (Class[]) null); saveImageFormats = (String[]) getFormatNamesMethod.invoke((Class[]) null, (Object[]) null); } catch (Exception e) { e.printStackTrace(); } } if (saveImageFormats != null) { for (int i = 0; i < saveImageFormats.length; i++) { //System.out.println(saveImageFormats[i]); if (filename.endsWith("." + saveImageFormats[i])) { saveImageIO(filename); return; } } } } if (filename.toLowerCase().endsWith(".tga")) { os = new BufferedOutputStream(new FileOutputStream(filename), 32768); success = saveTGA(os); //, pixels, width, height, format); } 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); 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."); } } }