/*
* Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of Business Objects nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* SimpleEffects.java
* Created: (12/20/00 8:08:25 AM)
* By: Luke Evans
*/
package org.openquark.gems.client.internal.effects;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.util.Random;
/**
* A set of effects utilities.
* Creation date: (12/20/00 8:08:25 AM)
* @author Luke Evans
*/
public abstract class SimpleEffects {
private static final int CLOUD_STRIP_MARGIN = 50;
private static final int CLOUD_BLUR_SIZE = 5;
private static final float CLOUD_WAISTING = 0.5F;
/**
* Generate a cloud image of the given size.
* Creation date: (12/20/00 8:03:04 AM)
* @param size java.awt.Dimension the size
* @return java.awt.image.BufferedImage the cloud image
*/
public static java.awt.image.BufferedImage makeCloud(java.awt.Dimension size, int layers, int averageFluffSize, float fluffSizeVarianceFraction) {
// Determine the deviation in size
int fluffDeviation = averageFluffSize / 2;
if (fluffDeviation < 1) {
fluffDeviation = 1;
}
int averageOverlap = fluffDeviation * 2;
// A cloud is generated by applied a set of randomly sized 'fluffy' balls into
// the area of specified size
// Determine the strip size
int maxFluffSize = averageFluffSize + fluffDeviation;
int stripHeight = maxFluffSize + CLOUD_STRIP_MARGIN * 2;
// Determine the intervals for the number of layers
int layerInterval = (size.height - stripHeight) / layers;
// Make the overall image
java.awt.image.BufferedImage bimage = new java.awt.image.BufferedImage(size.width, size.height, java.awt.image.BufferedImage.TYPE_INT_ARGB);
// Get the graphics context
java.awt.Graphics2D g2d = (java.awt.Graphics2D) bimage.getGraphics();
// Determine the maximum distance from centre
float maxCentreDistance = (float) (layers - 1) / 2;
// Make each layer
for (int i = 0; i < layers; i++) {
// How far are we from the centre
int distance = Math.abs(i - ((layers - 1) / 2));
// Determine the fractional waisting of the cloud at this layer
float waisting;
if (maxCentreDistance == 0.0) {
waisting = 1.0F;
} else {
waisting = 1.0F - (CLOUD_WAISTING * (distance / maxCentreDistance));
}
int targetwidth = (int) ((size.width - CLOUD_STRIP_MARGIN) * waisting);
// How many fluffs would fit in this size?
int numFluffs = (targetwidth / (maxFluffSize - averageOverlap)) - 2;
if (numFluffs <= 1) {
numFluffs = 1;
}
// Ask for a cloud strip with this many fluffs
java.awt.image.BufferedImage strip = makeCloudStrip(numFluffs, averageFluffSize, fluffSizeVarianceFraction);
// Paint this into the buffer so it's centre justified
g2d.drawImage(strip, null, (size.width - strip.getWidth()) / 2, i * layerInterval);
}
// Dispose of the graphics context
g2d.dispose();
return bimage;
}
/**
* Generate a cloud image strip of the given size.
* @param numFluffs
* @param averageFluffSize
* @param fluffSizeVarianceFraction
* @return java.awt.image.BufferedImage the cloud image
*/
public static java.awt.image.BufferedImage makeCloudStrip(int numFluffs, int averageFluffSize, float fluffSizeVarianceFraction) {
// Determine the deviation in size
int fluffDeviation = (int) (averageFluffSize * fluffSizeVarianceFraction);
if (fluffDeviation < 1) {
fluffDeviation = 1;
}
int averageOverlap = fluffDeviation * 2;
// A cloud is generated by applied a set of randomly sized 'fluffy' balls into
// the area of specified size
// Determine the strip size
int maxFluffSize = averageFluffSize + fluffDeviation;
int stripHeight = maxFluffSize + CLOUD_STRIP_MARGIN * 2;
int stripWidth = maxFluffSize * numFluffs + CLOUD_STRIP_MARGIN * 2 - averageOverlap * (numFluffs - 1);
java.awt.image.BufferedImage bimage = new java.awt.image.BufferedImage(stripWidth, stripHeight, java.awt.image.BufferedImage.TYPE_INT_ARGB);
// Spread a bunch of white/grey radial gradient balls with alpha across the strip
float fluffSize;
float cursorX;
float cursorY = stripHeight / (float)2;
float centreX, centreY;
// Loop along X axis
for (int i = 1; i <= numFluffs; i++) {
cursorX = CLOUD_STRIP_MARGIN + (maxFluffSize - averageOverlap) * i - maxFluffSize / 2;
// Pick a size of fluff ball
fluffSize = averageFluffSize + (int) (Math.random() * fluffDeviation * 2 - fluffDeviation);
// Get location (based on the maximum size of the ball +/- a random
// amount based on fluffDeviation
centreX = cursorX + (int) (Math.random() * fluffDeviation * 2 - fluffDeviation);
centreY = cursorY + (int) (Math.random() * fluffDeviation * 2 - fluffDeviation);
// Make this fluff ball
java.awt.geom.Ellipse2D.Float fluffy = new java.awt.geom.Ellipse2D.Float(centreX - fluffSize / 2, centreY - fluffSize / 2, fluffSize, fluffSize);
// Draw the stuff
java.awt.Graphics2D g2d = (java.awt.Graphics2D) bimage.getGraphics();
// Make a nice radial gradient at a random point on the fluffy
float[] wc = java.awt.Color.white.getRGBColorComponents(null);
float[] gc = { 0.999F, 0.999F, 0.999F }; // Really light grey!
float wRndAlpha = (float) (Math.random() * 0.5 + 0.5);
float gRndAlpha = (float) (Math.random() * 0.5 + 0.5);
java.awt.Color whiteColour = new java.awt.Color(wc[0], wc[1], wc[2], wRndAlpha);
java.awt.Color greyColour = new java.awt.Color(gc[0], gc[1], gc[2], gRndAlpha);
int gradOriginX = (int) (fluffy.x + (Math.random() * fluffDeviation * 2 - fluffDeviation) - fluffSize / 2);
int gradOriginY = (int) (fluffy.y + (Math.random() * fluffDeviation * 2 - fluffDeviation) - fluffSize / 2);
Point2D origin = new Point2D.Float(gradOriginX, gradOriginY);
Point2D radius = new Point2D.Float(fluffSize, fluffSize);
RadialGradientPaint radGrad = new RadialGradientPaint(origin, radius, whiteColour, greyColour);
g2d.setPaint(radGrad);
// Paint the fluffy
g2d.fill(fluffy);
// Free the graphics context
g2d.dispose();
}
// Run a nice gaussian filter over everything to soften it
java.awt.image.ConvolveOp blur = new java.awt.image.ConvolveOp(new GaussianKernel(CLOUD_BLUR_SIZE + 10));
bimage = blur.filter(bimage, null);
// Crop the image to remove the CLOUD_STRIP_MARGIN required by the convolve op
bimage = bimage.getSubimage(CLOUD_STRIP_MARGIN / 2, CLOUD_STRIP_MARGIN / 2, bimage.getWidth() - CLOUD_STRIP_MARGIN, bimage.getHeight() - CLOUD_STRIP_MARGIN);
return bimage;
}
/**
* Make an image of a horizontal strip of tape which looks 'ripped' at either end.
* This is designed to appear to be a label made of some kind of masking/labelling
* tape.
* @param bodySize the dimensions of the body (large enough for text)
* @param tearMarginMedian half of the 'tear zone' at either end. The tear will be centred on this.
* @param labelColour the colour of the label/tape
* @return BufferedImage an ARGB image buffer with the label image
*/
public static BufferedImage makeRippedTapeStripLabel(Dimension bodySize, int tearMarginMedian, Color labelColour) {
// A label is a coloured rectangle with tear margins at the left and right
// The tear margins are areas where a rough edge is calculated along a
// median tear line. This edge moves in and out by a random amount (up
// to a maximum defined by tearMarginSize.
// The edge is more likely to move toward the median by an amount
// (currently linearly) related to the distance from the
// median. The tear itself is painted with a varying 'alpha channel' component
// to simulate the transluscency of a fringe effect.
// Finally, the top and bottom 'raster lines' of the 'tape' are modulated
// slightly by alpha to create a 'stretch' look.
int tearMarginSize = tearMarginMedian * 2;
Dimension fullLabelSize = bodySize;
fullLabelSize.width += tearMarginSize * 2;
// Prepare an image of the label
BufferedImage labelImage = new BufferedImage(fullLabelSize.width, fullLabelSize.height, BufferedImage.TYPE_INT_ARGB);
Graphics2D labelg2d = (Graphics2D) labelImage.getGraphics();
// Labels are white
labelg2d.setColor(labelColour);
// Draw the non-margin part of the label
Rectangle labelBody = new Rectangle(tearMarginSize, 0, fullLabelSize.width - 2 * tearMarginSize, fullLabelSize.height);
labelg2d.fillRect(labelBody.x, labelBody.y + 1, labelBody.width, labelBody.height - 2); // Allow for 'tape modulation lines' top and bottom
// Calculate and draw margin area
// LHS...
Random tearRandom = new Random();
int leftTearX = tearRandom.nextInt(tearMarginSize);
int rightTearX = tearRandom.nextInt(tearMarginSize) + labelBody.x + labelBody.width;
for (int tearY = 1; tearY <= fullLabelSize.height - 2; tearY++) {
// Determine what the next leftTearX position should be
// The chance of it going further away from the median is proportional
// to its current distance
double goRightChance = (double) ((tearMarginSize - leftTearX)) / tearMarginSize;
if (tearRandom.nextDouble() <= goRightChance) {
// Go right
leftTearX += 1;
} else {
leftTearX -= 1;
}
// Draw from this position to the edge of the label rectangle
// Use a 'feathering' technique for the longer lines
if (leftTearX < tearMarginMedian) {
// Longer line. Draw half at half alpha, half at full alpha
labelg2d.setColor(new Color(labelColour.getRed(), labelColour.getGreen(), labelColour.getBlue(), 128));
labelg2d.drawLine(leftTearX, tearY, tearMarginMedian, tearY);
labelg2d.setColor(new Color(labelColour.getRed(), labelColour.getGreen(), labelColour.getBlue(), 255));
labelg2d.drawLine(tearMarginMedian, tearY, labelBody.x, tearY);
} else {
// Shorter line. All at almost full alpha
labelg2d.setColor(new Color(labelColour.getRed(), labelColour.getGreen(), labelColour.getBlue(), 200));
labelg2d.drawLine(leftTearX, tearY, labelBody.x, tearY);
}
// Determine what the next rightTearX position should be
// The chance of it going further away from the median is proportional
// to its current distance
double goLeftChance = (double) ((tearMarginSize - ((labelBody.x + labelBody.width + tearMarginSize) - rightTearX))) / tearMarginSize;
if (tearRandom.nextDouble() <= goLeftChance) {
// Go left
rightTearX -= 1;
} else {
rightTearX += 1;
}
// Draw from this position to the edge of the label rectangle
// Use a 'feathering' technique for the longer lines
if (rightTearX > labelBody.x + labelBody.width + tearMarginMedian) {
// Longer line. Draw half at full alpha, half at half alpha
labelg2d.setColor(new Color(labelColour.getRed(), labelColour.getGreen(), labelColour.getBlue(), 255));
labelg2d.drawLine(labelBody.x + labelBody.width, tearY, labelBody.x + labelBody.width + tearMarginMedian, tearY);
labelg2d.setColor(new Color(labelColour.getRed(), labelColour.getGreen(), labelColour.getBlue(), 128));
labelg2d.drawLine(labelBody.x + labelBody.width + tearMarginMedian, tearY, rightTearX, tearY);
} else {
// Shorter line. All at almost full alpha
labelg2d.setColor(new Color(labelColour.getRed(), labelColour.getGreen(), labelColour.getBlue(), 200));
labelg2d.drawLine(labelBody.x + labelBody.width, tearY, rightTearX, tearY);
}
}
// Draw random 'tape width' modulation lines
// Determine pixel increment for a random number of cycles
double topIncrement = (Math.PI / labelBody.width) * (tearRandom.nextInt(1) + 1);
double bottomIncrement = (Math.PI / labelBody.width) * (tearRandom.nextInt(1) + 1);
double topRotation = Math.PI * tearRandom.nextDouble();
double bottomRotation = Math.PI * tearRandom.nextDouble();
int bodyEnd = labelBody.x + labelBody.width;
for (int bodyX = labelBody.x; bodyX <= bodyEnd; bodyX++) {
// Do top
int alpha = (int) (Math.sin(topRotation += topIncrement) * 255);
if (alpha > 0) {
labelg2d.setColor(new Color(labelColour.getRed(), labelColour.getGreen(), labelColour.getBlue(), alpha));
labelg2d.drawLine(bodyX, 0, bodyX + 1, 0);
}
// Do bottom
alpha = (int) (Math.sin(bottomRotation += bottomIncrement) * 255);
if (alpha > 0) {
labelg2d.setColor(new Color(labelColour.getRed(), labelColour.getGreen(), labelColour.getBlue(), alpha));
labelg2d.drawLine(bodyX, fullLabelSize.height - 1, bodyX + 1, fullLabelSize.height - 1);
}
}
// Dispose of the label graphics context
labelg2d.dispose();
// Return the image
return labelImage;
}
}