/* * 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; } }