Skip to main content
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to 'org.mihalis.opal/src/main/java/org/mihalis/opal/brushedMetalComposite/BrushedMetalComposite.java')
-rw-r--r--org.mihalis.opal/src/main/java/org/mihalis/opal/brushedMetalComposite/BrushedMetalComposite.java428
1 files changed, 428 insertions, 0 deletions
diff --git a/org.mihalis.opal/src/main/java/org/mihalis/opal/brushedMetalComposite/BrushedMetalComposite.java b/org.mihalis.opal/src/main/java/org/mihalis/opal/brushedMetalComposite/BrushedMetalComposite.java
new file mode 100644
index 0000000..e25335f
--- /dev/null
+++ b/org.mihalis.opal/src/main/java/org/mihalis/opal/brushedMetalComposite/BrushedMetalComposite.java
@@ -0,0 +1,428 @@
+/*******************************************************************************
+ * Copyright (c) 2011 Laurent CARON
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Laurent CARON (laurent.caron at gmail dot com) - initial API and implementation
+ *******************************************************************************/
+package org.mihalis.opal.brushedMetalComposite;
+
+import java.util.Random;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.SWTException;
+import org.eclipse.swt.events.DisposeEvent;
+import org.eclipse.swt.events.DisposeListener;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.graphics.ImageData;
+import org.eclipse.swt.graphics.PaletteData;
+import org.eclipse.swt.graphics.RGB;
+import org.eclipse.swt.graphics.Rectangle;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Listener;
+import org.eclipse.swt.widgets.Widget;
+import org.mihalis.opal.utils.SWTGraphicUtil;
+
+/**
+ * Instances of this class are controls which background's texture is brushed
+ * metal "a la Mac"
+ * This work is inspired by the article "Creating a brushed metal texture" by Jerry Huxtable (http://www.jhlabs.com/ip/brushed_metal.html)
+ */
+public class BrushedMetalComposite extends Composite {
+
+ /** The old image. */
+ private Image oldImage;
+
+ /** The radius. */
+ private int radius = 10;
+
+ /** The amount. */
+ private float amount = 0.1f;
+
+ /** The color. */
+ private int color = 0xff888888;
+
+ /** The shine. */
+ private float shine = 0.1f;
+
+ /** The monochrome. */
+ private boolean monochrome = true;
+
+ /** The random numbers. */
+ private Random randomNumbers;
+
+ /** The palette. */
+ private final PaletteData palette = new PaletteData(0xFF0000, 0x00FF00, 0x0000FF);
+
+ /** The image data. */
+ private ImageData imageData;
+
+ /**
+ * Constructs a new instance of this class given its parent and a style
+ * value describing its behavior and appearance.
+ * <p>
+ * The style value is either one of the style constants defined in class
+ * <code>SWT</code> which is applicable to instances of this class, or must
+ * be built by <em>bitwise OR</em>'ing together (that is, using the
+ * <code>int</code> "|" operator) two or more of those <code>SWT</code>
+ * style constants. The class description lists the style constants that are
+ * applicable to the class. Style bits are also inherited from superclasses.
+ * </p>
+ *
+ * @param parent a widget which will be the parent of the new instance
+ * (cannot be null)
+ * @param style the style of widget to construct
+ * @see Composite#Composite(Composite, int)
+ * @see SWT#NO_BACKGROUND
+ * @see SWT#NO_FOCUS
+ * @see SWT#NO_MERGE_PAINTS
+ * @see SWT#NO_REDRAW_RESIZE
+ * @see SWT#NO_RADIO_GROUP
+ * @see SWT#EMBEDDED
+ * @see SWT#DOUBLE_BUFFERED
+ * @see Widget#getStyle
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
+ * </ul>
+ * @exception SWTException <ul>
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
+ * thread that created the parent</li>
+ * </ul>
+ */
+ public BrushedMetalComposite(final Composite parent, final int style) {
+ super(parent, style);
+ addListeners(parent);
+ }
+
+ /**
+ * Adds the listeners.
+ *
+ * @param parent the parent
+ */
+ private void addListeners(final Composite parent) {
+ this.addListener(SWT.Resize, new Listener() {
+ @Override
+ public void handleEvent(final Event event) {
+ createBrushedMetalBackground();
+ paintControl();
+ }
+ });
+
+ parent.addDisposeListener(new DisposeListener() {
+ @Override
+ public void widgetDisposed(final DisposeEvent e) {
+ SWTGraphicUtil.safeDispose(BrushedMetalComposite.this.oldImage);
+ }
+ });
+ }
+
+ /**
+ * Paint the component.
+ */
+ private void paintControl() {
+ final Display display = getDisplay();
+ final Image newImage = new Image(display, this.imageData);
+
+ setBackgroundImage(newImage);
+ SWTGraphicUtil.safeDispose(this.oldImage);
+ this.oldImage = newImage;
+ }
+
+ /**
+ * Create a brushed metal background.
+ *
+ * @return an image data that contains the background
+ */
+ private void createBrushedMetalBackground() {
+
+ final Rectangle rect = getClientArea();
+ final int width = Math.max(1, rect.width);
+ final int height = Math.max(1, rect.width);
+
+ final int[] inPixels = new int[width];
+
+ this.imageData = new ImageData(width, height, 0x20, this.palette);
+
+ this.randomNumbers = new Random(0);
+ final int a = this.color & 0xff000000;
+ final int r = this.color >> 16 & 0xff;
+ final int g = this.color >> 8 & 0xff;
+ final int b = this.color & 0xff;
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ int tr = r;
+ int tg = g;
+ int tb = b;
+ if ( Float.compare( this.shine, (float) 0.0 ) != 0 ) {
+ final int f = (int) (255 * this.shine * Math.sin((double) x / width * Math.PI));
+ tr += f;
+ tg += f;
+ tb += f;
+ }
+ if (this.monochrome) {
+ final int n = (int) (255 * (2 * this.randomNumbers.nextFloat() - 1) * this.amount);
+ inPixels[x] = a | this.clamp(tr + n) << 16 | this.clamp(tg + n) << 8 | this.clamp(tb + n);
+ } else {
+ inPixels[x] = a | this.random(tr) << 16 | this.random(tg) << 8 | this.random(tb);
+ }
+ }
+
+ if (this.radius != 0) {
+ setDataElements(0, y, width, 1, this.blur(inPixels, width, this.radius));
+ } else {
+ setDataElements(0, y, width, 1, inPixels);
+ }
+ }
+
+ }
+
+ /**
+ * Sets the data for a rectangle of pixels from a primitive array.
+ *
+ * @param posX The X coordinate of the upper left pixel location.
+ * @param posY The Y coordinate of the upper left pixel location.
+ * @param width Width of the pixel rectangle.
+ * @param height Height of the pixel rectangle
+ * @param pixels An array containing the pixel data to place between x,y and
+ * x+w-1, y+h-1.
+ */
+ private void setDataElements(final int posX, final int posY, final int width, final int height, final int[] pixels) {
+ int cpt = 0;
+ for (int y = posY; y < posY + height; y++) {
+ for (int x = posX; x < posX + width; x++) {
+ final int rgb = pixels[cpt++];
+ final int pixel = this.palette.getPixel(new RGB(rgb >> 16 & 0xFF, rgb >> 8 & 0xFF, rgb & 0xFF));
+ this.imageData.setPixel(x, y, pixel);
+ this.imageData.setAlpha(x, y, rgb >> 24 & 0xFF);
+ }
+ }
+ }
+
+ /**
+ * Add a random number to the value. The result is between 0 and 255
+ *
+ * @param x the initial value
+ * @return the int
+ */
+ private int random(int x) {
+ x += (int) (255 * (2 * this.randomNumbers.nextFloat() - 1) * this.amount);
+ if (x < 0) {
+ x = 0;
+ } else if (x > 0xff) {
+ x = 0xff;
+ }
+ return x;
+ }
+
+ /**
+ * Clamp a number between 0 and 255.
+ *
+ * @param c the number to clamp
+ * @return the number. If c is negative, returns 0. If c is greater than
+ * 255, returns 255.
+ */
+ private int clamp(final int c) {
+ if (c < 0) {
+ return 0;
+ }
+
+ if (c > 255) {
+ return 255;
+ }
+
+ return c;
+ }
+
+ /**
+ * Apply a blur filter to an array of int that represents and image which
+ * size is width columns * 1 row.
+ *
+ * @param in the array of int that represents the image
+ * @param width the width of the image
+ * @param radius the "radius" blur parameter
+ * @return the int[]
+ */
+ private int[] blur(final int[] in, final int width, final int radius) {
+ final int[] out = new int[width];
+ final int widthMinus1 = width - 1;
+ final int r2 = 2 * radius + 1;
+ int tr = 0, tg = 0, tb = 0;
+
+ for (int i = -radius; i <= radius; i++) {
+ final int rgb = in[this.mod(i, width)];
+ tr += rgb >> 16 & 0xff;
+ tg += rgb >> 8 & 0xff;
+ tb += rgb & 0xff;
+ }
+
+ for (int x = 0; x < width; x++) {
+ out[x] = 0xff000000 | tr / r2 << 16 | tg / r2 << 8 | tb / r2;
+
+ int i1 = x + radius + 1;
+ if (i1 > widthMinus1) {
+ i1 = this.mod(i1, width);
+ }
+ int i2 = x - radius;
+ if (i2 < 0) {
+ i2 = this.mod(i2, width);
+ }
+ final int rgb1 = in[i1];
+ final int rgb2 = in[i2];
+
+ tr += (rgb1 & 0xff0000) - (rgb2 & 0xff0000) >> 16;
+ tg += (rgb1 & 0xff00) - (rgb2 & 0xff00) >> 8;
+ tb += (rgb1 & 0xff) - (rgb2 & 0xff);
+ }
+ return out;
+ }
+
+ /**
+ * Return a mod b. This differs from the % operator with respect to negative
+ * numbers.
+ *
+ * @param a the dividend
+ * @param b the divisor
+ * @return a mod b
+ */
+ private int mod(int a, final int b) {
+ final int n = a / b;
+
+ a -= n * b;
+ if (a < 0) {
+ return a + b;
+ }
+ return a;
+ }
+
+ // ------------------------------------ Getters and Setters
+
+ /**
+ * Gets the radius.
+ *
+ * @return the "radius" of the blur
+ */
+ public int getRadius() {
+ return this.radius;
+ }
+
+ /**
+ * Sets the radius.
+ *
+ * @param radius the "radius" of the blur
+ */
+ public void setRadius(final int radius) {
+ this.radius = radius;
+ createBrushedMetalBackground();
+ paintControl();
+ }
+
+ /**
+ * Gets the amount.
+ *
+ * @return the amount of noise to add
+ */
+ public float getAmount() {
+ return this.amount;
+ }
+
+ /**
+ * Sets the amount.
+ *
+ * @param amount the amount of noise to add
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_INVALID_ARGUMENT - if the value is not between 0
+ * and 1 inclusive</li>
+ * </ul>
+ */
+ public void setAmount(final float amount) {
+ if (amount < 0f || amount > 1f) {
+ SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+ }
+ this.amount = amount;
+ createBrushedMetalBackground();
+ paintControl();
+ }
+
+ /**
+ * Gets the color.
+ *
+ * @return the color of the metal. Please notice that this color is a new
+ * SWT object, so it has to be disposed !
+ */
+ public Color getColor() {
+ return new Color(this.getDisplay(), this.color >> 16 & 0xFF, this.color >> 8 & 0xFF, this.color & 0xFF);
+ }
+
+ /**
+ * Sets the color.
+ *
+ * @param color the color to set
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_NULL_ARGUMENT - if the value is null</li>
+ * </ul>
+ */
+ public void setColor(final Color color) {
+ if (color == null) {
+ SWT.error(SWT.ERROR_NULL_ARGUMENT);
+ return; // to satisfy SONAR
+ }
+ this.color = 0xFF << 24 | color.getRed() << 16 | color.getGreen() << 8 | color.getBlue();
+ createBrushedMetalBackground();
+ ;
+ paintControl();
+ }
+
+ /**
+ * Gets the shine.
+ *
+ * @return the shine to add
+ */
+ public float getShine() {
+ return this.shine;
+ }
+
+ /**
+ * Sets the shine.
+ *
+ * @param shine the shine to set
+ * @exception IllegalArgumentException <ul>
+ * <li>ERROR_INVALID_ARGUMENT - if the value is not between 0
+ * and 1 inclusive</li>
+ * </ul>
+ */
+ public void setShine(final float shine) {
+ if (this.amount < 0f || this.amount > 1f) {
+ SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+ }
+ this.shine = shine;
+ createBrushedMetalBackground();
+ paintControl();
+ }
+
+ /**
+ * Checks if is monochrome.
+ *
+ * @return the monochrome
+ */
+ public boolean isMonochrome() {
+ return this.monochrome;
+ }
+
+ /**
+ * Sets the monochrome.
+ *
+ * @param monochrome the monochrome to set
+ */
+ public void setMonochrome(final boolean monochrome) {
+ this.monochrome = monochrome;
+ createBrushedMetalBackground();
+ paintControl();
+ }
+
+}

Back to the top