/******************************************************************************* * Copyright (c) 2008, 2015 Angelo Zerr and others. * 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: * Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation * IBM Corporation * Kai Toedter - added radial gradient support * Lars Vogel <Lars.Vogel@vogella.com> - Bug 461688 * Robert Roth <robert.roth.off@gmail.com> - Bug 283255 * Simon Scholz <simon.scholz@vogella.com> - Bug 466646, 461690 *******************************************************************************/ package org.eclipse.e4.ui.css.swt.properties; import java.awt.Graphics2D; import java.awt.MultipleGradientPaint.CycleMethod; import java.awt.RadialGradientPaint; import java.awt.geom.Point2D; import java.awt.image.BufferedImage; import java.awt.image.DataBufferInt; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.e4.ui.css.core.dom.properties.Gradient; import org.eclipse.e4.ui.css.swt.helpers.CSSSWTColorHelper; import org.eclipse.swt.SWT; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.ImageData; import org.eclipse.swt.graphics.PaletteData; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.graphics.RGBA; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Listener; public class GradientBackgroundListener implements Listener { private static Map<Control, GradientBackgroundListener> handlers = new HashMap<Control, GradientBackgroundListener>(); private Gradient grad; private final Control control; private boolean radialGradient; Image gradientImage; private DisposeListener disposeListener = new DisposeListener() { @Override public void widgetDisposed(DisposeEvent e) { GradientBackgroundListener.remove(control); } }; private GradientBackgroundListener(Control control, Gradient grad) { this.grad = grad; this.control = control; control.addListener(SWT.Resize, this); control.addDisposeListener(disposeListener); } public void dispose() { grad = null; if (control != null && !control.isDisposed()) { control.removeListener(SWT.Resize, this); control.removeDisposeListener(disposeListener); if (control.getBackgroundImage() == gradientImage) { control.setBackgroundImage(null); } } if (gradientImage != null && !gradientImage.isDisposed()) { gradientImage.dispose(); } gradientImage = null; } public static void handle(Control control, Gradient grad) { GradientBackgroundListener handler = handlers.get(control); if (handler == null) { handler = new GradientBackgroundListener(control, grad); handlers.put(control, handler); handler.handleEvent(null); } else { handler.grad = grad; handler.handleEvent(null); } } public static void remove(Control control) { GradientBackgroundListener handler = handlers.remove(control); if (handler != null) { handler.dispose(); } } @Override public void handleEvent(Event event) { Point size = control.getSize(); if (size.x <= 0 || size.y <= 0) { return; } // hold onto our old image for disposal, if necessary Image oldImage = control.getBackgroundImage(); if(oldImage != gradientImage) { oldImage = null; } /* * Draw the new background. Radial backgrounds have to be generated * for the full size of the control's size; linear backgrounds are * just a slice for the control's height that is then repeated. */ if (grad.isRadial()) { List<java.awt.Color> colors = new ArrayList<java.awt.Color>(); for (Object rgbObj : grad.getRGBs()) { if (rgbObj instanceof RGBA) { RGBA rgba = (RGBA) rgbObj; java.awt.Color color = new java.awt.Color(rgba.rgb.red, rgba.rgb.green, rgba.rgb.blue, rgba.alpha); colors.add(color); } else if (rgbObj instanceof RGB) { RGB rgb = (RGB) rgbObj; java.awt.Color color = new java.awt.Color(rgb.red, rgb.green, rgb.blue); colors.add(color); } } BufferedImage image = getBufferedImage(size.x, size.y, colors, CSSSWTColorHelper.getPercents(grad)); // long startTime = System.currentTimeMillis(); ImageData imagedata = convertToSWT(image); // System.out.println("Conversion took " // + (System.currentTimeMillis() - startTime) + " ms"); gradientImage = new Image(control.getDisplay(), imagedata); radialGradient = true; } else if (oldImage == null || oldImage.isDisposed() || oldImage.getBounds().height != size.y || radialGradient || event == null) { radialGradient = false; boolean verticalGradient = grad.getVerticalGradient(); int x = verticalGradient? 2 : size.x; int y = verticalGradient ? size.y : 2; gradientImage = new Image(control.getDisplay(), x, y); GC gc = new GC(gradientImage); List<Color> colors = new ArrayList<Color>(); for (Object rgbObj : grad.getRGBs()) { if (rgbObj instanceof RGBA) { RGBA rgba = (RGBA) rgbObj; Color color = new Color(control.getDisplay(), rgba); colors.add(color); } else if (rgbObj instanceof RGB) { RGB rgb = (RGB) rgbObj; Color color = new Color(control.getDisplay(), rgb); colors.add(color); } } fillGradient(gc, new Rectangle(0, 0, x, y), colors, CSSSWTColorHelper.getPercents(grad), grad.getVerticalGradient()); gc.dispose(); for (Color c : colors) { c.dispose(); // Dispose colors too. } } if (gradientImage != null) { control.setBackgroundImage(gradientImage); } if (oldImage != null && oldImage != gradientImage) { oldImage.dispose(); } } /* * Fills a gradient rectangle in the specified gc with the specified colors * and percentages. * * @param gc @param rect @param gradientColors @param gradientPercents * * @param gradientVertical */ private static void fillGradient(GC gc, Rectangle rect, List<Color> gradientColors, int[] gradientPercents, boolean gradientVertical) { Color background = gradientColors.get(gradientColors.size() - 1); if (gradientColors.size() == 1) { if (gradientColors.get(0) != null) { gc.setBackground(gradientColors.get(0)); } gc.fillRectangle(rect.x, rect.y, rect.width, rect.height); } else { Color lastColor = gradientColors.get(0); int pos = (gradientVertical) ? rect.y : rect.x; int loopCount = Math.min(gradientColors.size() - 1, gradientPercents.length); for (int i = 0; i < loopCount; ++i) { gc.setForeground(lastColor); lastColor = gradientColors.get(i + 1); if (lastColor == null) { lastColor = background; } gc.setBackground(lastColor); int grpercent = ((Integer) gradientPercents[i]).intValue(); if (gradientVertical) { final int gradientHeight = (grpercent * rect.height / 100) - (pos - rect.y); gc.fillGradientRectangle(rect.x, pos, rect.width, gradientHeight, true); pos += gradientHeight; } else { final int gradientWidth = (grpercent * rect.width / 100) - (pos - rect.x); gc.fillGradientRectangle(pos, rect.y, gradientWidth, rect.height, false); pos += gradientWidth; } } if (gradientVertical && pos < rect.height) { gc.setBackground(background); gc.fillRectangle(rect.x, pos, rect.width, rect.height - pos); } if (!gradientVertical && pos < rect.width) { gc.setBackground(background); gc.fillRectangle(pos, rect.y, rect.width - pos, rect.height); } } } /** * Returns a BufferedImage that renders a radial gradient. This is a * workaround since SWT does not support radial gradients yet. * * @param width * image width * @param height * image height * @param colors * a list of colors that define the gradients * @param percents * a list of percents that define the percents of above colors * @return the image */ private BufferedImage getBufferedImage(int width, int height, List<java.awt.Color> colors, int[] percents) { java.awt.Color[] colorArray = colors.toArray(new java.awt.Color[] {}); float[] fractions = new float[percents.length + 1]; fractions[0] = 0.0f; for (int i = 1; i <= percents.length; i++) { fractions[i] = percents[i - 1] / 100.0f; } BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); Graphics2D g2 = (Graphics2D) image.getGraphics(); RadialGradientPaint radialGradientPaint = new RadialGradientPaint(new Point2D.Double(width / 2.0, 0), width, new Point2D.Double(width / 2.0, 0.0), fractions, colorArray, CycleMethod.NO_CYCLE); g2.setPaint(radialGradientPaint); g2.fillRect(0, 0, width, height); return image; } /** * Converts a AWT BufferedImage to an SWT ImageData. This is a workaround * since SWT does not support radial gradients yet. * * @param bufferedImage * the source AWT BufferedImage * @return the converted SWT ImageData */ private ImageData convertToSWT(BufferedImage bufferedImage) { int[] bufferedImageData = ((DataBufferInt) bufferedImage.getData() .getDataBuffer()).getData(); ImageData imageData = new ImageData(bufferedImage.getWidth(), bufferedImage.getHeight(), 24, new PaletteData(0xff0000, 0x00ff00, 0x0000ff)); imageData.setPixels(0, 0, bufferedImageData.length, bufferedImageData, 0); return imageData; } }