//
// Jala Project [http://opensvn.csie.org/traccgi/jala]
//
// Copyright 2004 ORF Online und Teletext GmbH
//
// Licensed under the Apache License, Version 2.0 (the ``License'');
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an ``AS IS'' BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// $Revision$
// $LastChangedBy$
// $LastChangedDate$
// $HeadURL$
//
/**
* @fileoverview Fields and methods of the jala.ImageFilter class.
*/
// Define the global namespace for Jala modules
if (!global.jala) {
global.jala = {};
}
/**
* Constructs a new ImageFilter object
* @class This class provides several image manipulating
* methods. Most of this filter library is based on filters created
* by Janne Kipin� for JAlbum. For more information have a look
* at http://www.ratol.fi/~jakipina/java/
* @param {Object} img Either
<ul>
<li>an instance of helma.image.ImageWrapper</li>
<li>the path to the image file as String</li>
<li>an instance of helma.File representing the image file</li>
<li>an instance of java.io.File representing the image file</li>
</ul>
* @constructor
*/
jala.ImageFilter = function(img) {
/**
* The buffered image to work on
* @type java.awt.image.BufferedImage
* @private
*/
var bi;
/**
* Perfoms a gaussian operation (unsharp masking or blurring)
* on the image using the kernelFactory passed as argument
* @param {Number} radius The radius
* @param {Number} amount The amount
* @param {Function} kernelFactory Factory method to call for building the kernel
* @private
*/
var gaussianOp = function(radius, amount, kernelFactory) {
var DEFAULT_RADIUS = 2;
var MINIMUM_RADIUS = 1;
var MAXIMUM_RADIUS = 10;
var DEFAULT_AMOUNT = 15;
var MINIMUM_AMOUNT = 1;
var MAXIMUM_AMOUNT = 100;
// correct arguments if necessary
if (isNaN(radius = Math.min(Math.max(radius, MINIMUM_RADIUS), MAXIMUM_RADIUS)))
radius = DEFAULT_RADIUS;
if (isNaN(amount = Math.min(Math.max(amount, MINIMUM_AMOUNT), MAXIMUM_AMOUNT)))
amount = DEFAULT_AMOUNT;
if ((bi.getWidth() < bi.getHeight()) && (radius > bi.getWidth())) {
radius = bi.getWidth();
} else if ((bi.getHeight() < bi.getWidth()) && (radius > bi.getHeight())) {
radius = bi.getHeight();
}
var size = (radius * 2) + 1;
var deviation = amount / 20;
var elements = kernelFactory(size, deviation);
var large = jala.ImageFilter.getEnlargedImageWithMirroring(bi, radius);
var resultImg = new java.awt.image.BufferedImage(large.getWidth(), large.getHeight(), large.getType());
var kernel = new java.awt.image.Kernel(size, size, elements);
var cop = new java.awt.image.ConvolveOp(kernel, java.awt.image.ConvolveOp.EDGE_NO_OP, null);
cop.filter(large, resultImg);
// replace the wrapped buffered image with the modified one
bi = resultImg.getSubimage(radius, radius, bi.getWidth(), bi.getHeight());
return;
};
/**
* Sharpens the image using a plain sharpening kernel.
* @param {Number} amount The amount of sharpening to apply
*/
this.sharpen = function(amount) {
var DEFAULT = 20;
var MINIMUM = 1;
var MAXIMUM = 100;
// correct argument if necessary
if (isNaN(Math.min(Math.max(amount, MINIMUM), MAXIMUM)))
amount = DEFAULT;
var sharpened = new java.awt.image.BufferedImage(bi.getWidth(), bi.getHeight(), bi.getType());
var kernel = new java.awt.image.Kernel(3, 3, jala.ImageFilter.getSharpeningKernel(amount));
var cop = new java.awt.image.ConvolveOp(kernel, java.awt.image.ConvolveOp.EDGE_NO_OP, null);
cop.filter(bi, sharpened);
bi = sharpened;
return;
};
/**
* Performs an unsharp mask operation on the image
* @param {Number} radius The radius
* @param {Number} amount The amount
*/
this.unsharpMask = function(radius, amount) {
gaussianOp(radius, amount, jala.ImageFilter.getUnsharpMaskKernel);
return;
};
/**
* Performs a gaussian blur operation on the image
* @param {Number} radius The radius
* @param {Number} amount The amount
*/
this.gaussianBlur = function(radius, amount) {
gaussianOp(radius, amount, jala.ImageFilter.getGaussianBlurKernel);
return;
};
/**
* Returns the image that has been worked on
* @return An instance of helma.image.ImageWrapper
* @type helma.image.ImageWrapper
*/
this.getImage = function() {
var generator = Packages.helma.image.ImageGenerator.getInstance();
return new Packages.helma.image.ImageWrapper(bi,
bi.getWidth(),
bi.getHeight(),
generator);
};
/**
* Returns the wrapped image as byte array, to use eg. in conjunction
* with res.writeBinary()
* @returns The wrapped image as byte array
* @type byte[]
*/
this.getBytes = function() {
var outStream = new java.io.ByteArrayOutputStream();
Packages.javax.imageio.ImageIO.write(bi, "jpeg", outStream);
var bytes = outStream.toByteArray();
outStream.close();
return bytes;
};
/**
* constructor body
* @ignore
*/
if (arguments.length == 0 || img == null) {
throw "jala.ImageFilter: insufficient arguments";
} else if (img instanceof Packages.helma.image.ImageWrapper) {
bi = img.getBufferedImage();
} else {
if (typeof(img) == "string") {
var inStream = new java.io.FileInputStream(new java.io.File(img));
} else {
var inStream = new java.io.FileInputStream(img);
}
var decoder = Packages.com.sun.image.codec.jpeg.JPEGCodec.createJPEGDecoder(inStream);
bi = decoder.decodeAsBufferedImage();
}
return this;
};
/** @ignore */
jala.ImageFilter.prototype.toString = function() {
return "[jala.ImageFilter]";
};
/**
* Transforms an image into a bigger one while mirroring the edges
* This method is used to apply the filtering up to the edges
* of an image (otherwise the image would keep an unmodified
* border).
* @param {java.awt.image.BufferedImage} bi The buffered image to transform
* @param {Number} size The size of the border area
* @returns The transformed image
* @type java.awt.image.BufferedImage
* @private
*/
jala.ImageFilter.getEnlargedImageWithMirroring = function(bi, size) {
var doFlip = function(bi, sx, sy, dist) {
var out = new java.awt.image.BufferedImage(bi.getWidth(), bi.getHeight(), bi.getType());
var transform = java.awt.geom.AffineTransform.getScaleInstance(sx, sy);
(sx < sy) ? transform.translate(-dist, 0) : transform.translate(0, -dist);
var atop = new java.awt.image.AffineTransformOp(transform,
java.awt.image.AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
out = atop["filter(java.awt.image.BufferedImage,java.awt.image.BufferedImage)"](bi, null);
return out;
}
var doHorizontalFlip = function(bi) {
return doFlip(bi, -1, 1, bi.getWidth());
}
var doVerticalFlip = function(bi) {
return doFlip(bi, 1, -1, bi.getHeight());
}
var width = bi.getWidth() + 2 * size;
var height = bi.getHeight() + 2 * size;
var out = new java.awt.image.BufferedImage(width, height, bi.getType());
var g = out.createGraphics();
// due to method overloading exactly define the method to be called
var func = "drawImage(java.awt.Image,int,int,java.awt.image.ImageObserver)";
g[func](bi, size, size, null);
var part;
//top-left corner
part = bi.getSubimage(0, 0, size, size);
part = doHorizontalFlip(part);
part = doVerticalFlip(part);
g[func](part, 0, 0, null);
//top-right corner
part = bi.getSubimage(bi.getWidth()-size, 0, size, size);
part = doHorizontalFlip(part);
part = doVerticalFlip(part);
g[func](part, width-size, 0, null);
//bottom-left corner
part = bi.getSubimage(0, bi.getHeight()-size, size, size);
part = doHorizontalFlip(part);
part = doVerticalFlip(part);
g[func](part, 0, height-size, null);
//bottom-right corner
part = bi.getSubimage(bi.getWidth()-size, bi.getHeight()-size, size, size);
part = doHorizontalFlip(part);
part = doVerticalFlip(part);
g[func](part, width-size, height-size, null);
//left border
part = bi.getSubimage(0, 0, size, bi.getHeight());
part = doHorizontalFlip(part);
g[func](part, 0, size, null);
//right border
part = bi.getSubimage(bi.getWidth()-size, 0, size, bi.getHeight());
part = doHorizontalFlip(part);
g[func](part, width-size, size, null);
//top border
part = bi.getSubimage(0, 0, bi.getWidth(), size);
part = doVerticalFlip(part);
g[func](part, size, 0, null);
//bottom border
part = bi.getSubimage(0, bi.getHeight()-size, bi.getWidth(), size);
part = doVerticalFlip(part);
g[func](part, size, height-size, null);
return out;
};
/**
* Factory method for a gaussian blur kernel
* @returns The gaussian blur kernel
* @param {Number} size The size of the kernel
* @param {Number} deviation The deviation to use
* @returns The gaussian blur kernel
* @type float[]
* @private
*/
jala.ImageFilter.getGaussianBlurKernel = function(size, deviation) {
var nominator = 2 * deviation * deviation;
var kernel = java.lang.reflect.Array.newInstance(java.lang.Float.TYPE, size*size);
var center = (size - 1) / 2;
var limit = size - 1;
var xx, yy;
var sum = 0;
var value = 0;
for (var y=0; y<size; y++) {
for (var x=0; x<size; x++) {
if ((y <= center) && (x <= center)) {
if (x >= y) {
//calculate new value
xx = center - x;
yy = center - y;
value = Math.exp(-(xx*xx + yy*yy) / nominator);
kernel[(y*size)+x] = value;
sum += value;
} else {
//copy existing value
value = kernel[(x*size)+y];
kernel[(y*size)+x] = value;
sum += value;
}
} else {
xx = x;
yy = y;
if (yy > center)
yy = limit - yy;
if (xx > center)
xx = limit - xx;
value = kernel[(yy*size)+xx];
kernel[(y*size)+x] = value;
sum += value;
}
}
}
for (var i=0; i<kernel.length; i++) {
kernel[i] = kernel[i] / sum;
}
return kernel;
};
/**
* Factory method for an unsharp mask kernel
* @param {Number} size The size of the kernel
* @param {Number} deviation The deviation to use
* @returns The unsharp mask kernel
* @type float[]
* @private
*/
jala.ImageFilter.getUnsharpMaskKernel = function(size, deviation) {
var elements = jala.ImageFilter.getGaussianBlurKernel(size, deviation);
var center = ((size * size) - 1) / 2;
elements[center] = 0;
var sum = 0;
for (var i=0; i<elements.length; i++) {
sum += elements[i];
elements[i] = -elements[i];
}
elements[center] = sum + 1;
return elements;
};
/**
* Factory method for a sharpening kernel
* @param {Number} amount The amount of sharpening to use
* @return The sharpening kernel
* @type float[]
* @private
*/
jala.ImageFilter.getSharpeningKernel = function(amount) {
var kernel = java.lang.reflect.Array.newInstance(java.lang.Float.TYPE, 9);
var corner = 0;
var side = amount / -50;
var center = (side * -4.0) + (corner * -4.0) + 1.0;
kernel[0] = kernel[2] = kernel[6] = kernel[8] = corner;
kernel[1] = kernel[3] = kernel[5] = kernel[7] = side;
kernel[4] = center;
return kernel;
};