package org.archstudio.bna.ui.swt; import java.awt.Dimension; import java.awt.Font; import java.awt.Shape; import java.awt.geom.Ellipse2D; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.geom.RectangularShape; import java.awt.image.BufferedImage; import java.awt.image.ColorModel; import java.awt.image.IndexColorModel; import java.awt.image.WritableRaster; import java.util.Deque; import java.util.LinkedList; import org.archstudio.bna.IBNAModel; import org.archstudio.bna.IBNAView; import org.archstudio.bna.IThing; import org.archstudio.bna.IThingPeer; import org.archstudio.bna.facets.IHasAlpha; import org.archstudio.bna.facets.IHasHidden; import org.archstudio.bna.ui.IUIResources; import org.archstudio.bna.ui.utils.AbstractUIResources; import org.archstudio.bna.utils.BNAUtils; import org.archstudio.swtutils.constants.FontStyle; import org.archstudio.swtutils.constants.LineStyle; import org.archstudio.sysutils.ExpandableIntBuffer; import org.archstudio.sysutils.SystemUtils; import org.eclipse.swt.SWT; 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.Path; import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.graphics.Transform; import org.eclipse.swt.widgets.Display; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.cache.RemovalListener; import com.google.common.cache.RemovalNotification; public class SWTResources extends AbstractUIResources implements ISWTResources { public static final int GLOW_RESOLUTION = 2; protected final LoadingCache<RGB, Color> colors = CacheBuilder.newBuilder().maximumSize(16) .removalListener(new RemovalListener<RGB, Color>() { @Override public void onRemoval(RemovalNotification<RGB, Color> notification) { notification.getValue().dispose(); } }).build(new CacheLoader<RGB, Color>() { @Override public Color load(RGB rgb) throws Exception { return new Color(gc.getDevice(), rgb); } }); protected final LoadingCache<Font, org.eclipse.swt.graphics.Font> fonts = CacheBuilder.newBuilder().maximumSize(16) .removalListener(new RemovalListener<Font, org.eclipse.swt.graphics.Font>() { @Override public void onRemoval(RemovalNotification<Font, org.eclipse.swt.graphics.Font> notification) { notification.getValue().dispose(); } }).build(new CacheLoader<Font, org.eclipse.swt.graphics.Font>() { @Override public org.eclipse.swt.graphics.Font load(Font font) throws Exception { return new org.eclipse.swt.graphics.Font(// gc.getDevice(), font.getFamily(), font.getSize(), // FontStyle.fromAWT(font.getStyle()).toSWT()); } }); protected final LoadingCache<Integer, int[]> stipples = CacheBuilder.newBuilder().maximumSize(16) .build(new CacheLoader<Integer, int[]>() { @Override public int[] load(Integer key) throws Exception { return LineStyle.toSWTDashes(key); } }); protected GC gc; protected Transform currentTransform = null; protected Deque<Transform> matrices = new LinkedList<Transform>(); public SWTResources() { } public void invalidate() { colors.invalidateAll(); fonts.invalidateAll(); if (lastPath != null) { lastPath.dispose(); } lastPath = null; } @Override public void dispose() { invalidate(); super.dispose(); } Shape lastShape = null; int lastPoints; Rectangle lastBounds; float lastMinY, lastMaxY; ExpandableIntBuffer lastXYIntBuffer = new ExpandableIntBuffer(); int[] lastXY; Path lastPath = null; boolean lastClosed = false; private void updateLastShape(Shape localShape) { if (lastShape != localShape) { lastShape = localShape; // determine min y, max y lastBounds = BNAUtils.toRectangle(localShape.getBounds2D()); lastMinY = lastBounds.y; lastMaxY = lastBounds.y + lastBounds.height; // get xy positions lastXYIntBuffer.rewind(); BNAUtils.toXYIntBuffer(localShape, lastXYIntBuffer); lastPoints = lastXYIntBuffer.position() / 2; lastXY = new int[lastPoints * 2]; lastXYIntBuffer.rewind(); lastXYIntBuffer.get(lastXY); // create path if (lastPath != null) { lastPath.dispose(); } lastPath = new Path(gc.getDevice()); BNAUtils.toPath(lastPath, localShape); // check for closure lastClosed = false; if (lastXY.length >= 4) { lastClosed = lastXY[0] == lastXY[lastXY.length - 2] && lastXY[1] == lastXY[lastXY.length - 1]; } } } public void setGc(GC gc) { this.gc = gc; gc.setLineJoin(SWT.JOIN_BEVEL); } @Override public void setAntialiasGraphics(boolean antialiasGraphics) { super.setAntialiasGraphics(antialiasGraphics); gc.setAntialias(isAntialiasGraphics() ? SWT.ON : SWT.OFF); } @Override public void setAntialiasText(boolean antialiasText) { super.setAntialiasText(antialiasText); gc.setTextAntialias(isAntialiasText() ? SWT.ON : SWT.OFF); } protected void setForeground(RGB rgb) { gc.setForeground(colors.getUnchecked(rgb)); } protected void setBackground(RGB rgb) { gc.setBackground(colors.getUnchecked(rgb)); } @Override public double pushAlpha(double alpha) { alpha = super.pushAlpha(alpha); gc.setAlpha(SystemUtils.bound(0, SystemUtils.round(alpha * 255), 255)); return alpha; } protected Rectangle toRectangle(RectangularShape r) { int x = SystemUtils.round(r.getMinX()); int y = SystemUtils.round(r.getMinY()); int w = SystemUtils.round(r.getMaxX()) - x; int h = SystemUtils.round(r.getMaxY()) - y; return new Rectangle(x, y, w, h); } @Override public void renderTopLevelThings(IBNAView view, Rectangle localBounds) { setBackground(new RGB(255, 255, 255)); gc.fillRectangle(localBounds); renderThings(view, localBounds); } @Override public void renderThings(IBNAView view, Rectangle localBounds) { IBNAModel model = view.getBNAWorld().getBNAModel(); for (IThing thingToRender : model.getAllThings()) { if (thingToRender.has(IHasHidden.HIDDEN_KEY, true)) { continue; } try { pushAlpha(thingToRender.get(IHasAlpha.ALPHA_KEY, 1d)); try { IThingPeer<?> peer = view.getThingPeer(thingToRender); if (peer.draw(localBounds, this)) { peer.draw(gc, localBounds, this); } } finally { popAlpha(); } } catch (Exception e) { e.printStackTrace(); } } } @Override public IUIResources.FontMetrics getFontMetrics(Font font) { gc.setFont(fonts.getUnchecked(font)); org.eclipse.swt.graphics.FontMetrics metrics = gc.getFontMetrics(); return new AbstractUIResources.FontMetrics(metrics.getLeading(), metrics.getAscent(), metrics.getDescent()); } @Override public Dimension getTextSize(Font font, String text) { gc.setFont(fonts.getUnchecked(font)); return BNAUtils.toDimension(gc.textExtent(text, SWT.DRAW_TRANSPARENT)); } @Override public void drawText(Font font, String text, double x, double y, RGB color, double alpha) { if (color == null || text.length() == 0 || alpha == 0) { return; } gc.setFont(fonts.getUnchecked(font)); setForeground(color); pushAlpha(alpha); gc.drawString(text, SystemUtils.round(x), SystemUtils.round(y), true); popAlpha(); } @Override public void drawShape(Point2D localShape, RGB color, double alpha) { if (color == null || alpha == 0) { return; } setForeground(color); pushAlpha(alpha); gc.drawPoint(SystemUtils.round(localShape.getX()), SystemUtils.round(localShape.getY())); popAlpha(); } @Override public void drawShape(Shape localShape, RGB color, int width, LineStyle lineStyle, double alpha) { if (color == null || width == 0 || lineStyle == LineStyle.NONE || alpha == 0) { return; } gc.setLineWidth(width); setForeground(color); pushAlpha(alpha); if (lineStyle == LineStyle.DOT) { gc.setLineStyle(SWT.LINE_CUSTOM); gc.setLineDash(new int[] { 1, 1 }); gc.setAntialias(SWT.OFF); } else { gc.setLineStyle(lineStyle.toSwtStyle()); } if (localShape instanceof Rectangle2D) { gc.drawRectangle(toRectangle((Rectangle2D) localShape)); } else if (localShape instanceof Ellipse2D) { Rectangle r = toRectangle((Ellipse2D) localShape); gc.drawOval(r.x, r.y, r.width, r.height); } else { updateLastShape(localShape); gc.drawPath(lastPath); } if (lineStyle == LineStyle.DOT) { gc.setAntialias(isAntialiasGraphics() ? SWT.ON : SWT.OFF); } popAlpha(); } @Override public void drawShape(Shape localShape, RGB color, int width, int stipple, double alpha) { if (color == null || width == 0 || stipple == 0 || alpha == 0) { return; } gc.setLineWidth(width); setForeground(color); pushAlpha(alpha); if (localShape instanceof Rectangle2D) { gc.drawRectangle(toRectangle((Rectangle2D) localShape)); } else if (localShape instanceof Ellipse2D) { Rectangle r = toRectangle((Ellipse2D) localShape); gc.drawOval(r.x, r.y, r.width, r.height); } else { updateLastShape(localShape); gc.drawPath(lastPath); } popAlpha(); } @Override public void glowShape(Shape localShape, RGB color, int width, double alpha) { if (color == null || width == 0 || alpha == 0) { return; } updateLastShape(localShape); setForeground(color); double alphaDelta = alpha / width * GLOW_RESOLUTION; double cumulativeAlpha = 0; gc.setLineStyle(SWT.LINE_SOLID); for (int i = width; i >= 1; i -= GLOW_RESOLUTION) { double actualAlpha = alphaDelta * (width - i) - cumulativeAlpha; cumulativeAlpha += actualAlpha; gc.setLineWidth(i); pushAlpha(actualAlpha); gc.drawPath(lastPath); popAlpha(); } } @Override public void selectShape(Shape localShape, int offset) { offset = offset / 2 % 2; int[] lineDash = new int[] { 4, 4 }; RGB background = offset < 1 ? new RGB(0, 0, 0) : new RGB(255, 255, 255); RGB foreground = offset < 1 ? new RGB(255, 255, 255) : new RGB(0, 0, 0); gc.setLineWidth(1); gc.setLineStyle(SWT.LINE_SOLID); if (localShape instanceof Rectangle2D) { Rectangle r = toRectangle((Rectangle2D) localShape); setForeground(background); gc.drawRectangle(r); gc.setLineStyle(SWT.LINE_CUSTOM); gc.setLineDash(lineDash); setForeground(foreground); gc.drawRectangle(r); } else if (localShape instanceof Ellipse2D) { Rectangle r = toRectangle((Ellipse2D) localShape); setForeground(background); gc.drawOval(r.x, r.y, r.width, r.height); gc.setLineStyle(SWT.LINE_CUSTOM); gc.setLineDash(lineDash); setForeground(foreground); gc.drawOval(r.x, r.y, r.width, r.height); } else { updateLastShape(localShape); setForeground(background); gc.drawPath(lastPath); gc.setLineStyle(SWT.LINE_CUSTOM); gc.setLineDash(lineDash); setForeground(foreground); gc.drawPath(lastPath); } gc.setLineStyle(SWT.LINE_SOLID); } @Override public void fillShape(Shape localShape, RGB color1, RGB color2, double alpha) { if (color1 == null || alpha == 0) { return; } boolean isGradientFilled = isDecorativeGraphics() && color2 != null && !color1.equals(color2); pushAlpha(alpha); if (localShape instanceof Rectangle2D) { Rectangle r = toRectangle((Rectangle2D) localShape); if (isGradientFilled) { setForeground(color1); setBackground(color2); gc.fillGradientRectangle(r.x, r.y, r.width, r.height, true); } else { setBackground(color1); gc.fillRectangle(r.x, r.y, r.width, r.height); } } else { if (isGradientFilled) { updateLastShape(localShape); setForeground(color1); setBackground(color2); gc.setClipping(lastPath); gc.fillGradientRectangle(lastBounds.x, lastBounds.y, lastBounds.width, lastBounds.height, true); gc.setClipping((Rectangle) null); } else { if (localShape instanceof Ellipse2D) { Rectangle r = toRectangle((Ellipse2D) localShape); setBackground(color1); gc.fillOval(r.x, r.y, r.width, r.height); } else { updateLastShape(localShape); setBackground(color1); gc.fillPath(lastPath); } } } popAlpha(); } @Override public void pushMatrix(double x, double y, double angle) { matrices.push(currentTransform); Transform newTransform = new Transform(gc.getDevice()); if (currentTransform != null) { newTransform.multiply(currentTransform); } newTransform.translate((float) x, (float) y); newTransform.rotate((float) (angle / Math.PI * 180)); gc.setTransform(currentTransform = newTransform); } @Override public void popMatrix() { currentTransform.dispose(); gc.setTransform(currentTransform = matrices.pop()); } public BufferedImage renderToImage(IBNAView view, Rectangle lbb) { Image image = null; GC gc = null; try { PaletteData paletteData; // = new PaletteData(0xFF, 0xFF00, 0xFF0000); ImageData imageData; // = new ImageData(lbb.width, lbb.height, 32, paletteData); image = new Image(Display.getCurrent(), lbb); gc = new GC(image, SWT.TRANSPARENCY_ALPHA | SWT.TRANSPARENCY_MASK); setGc(gc); setAntialiasGraphics(isAntialiasGraphics()); setAntialiasText(isAntialiasText()); pushMatrix(-lbb.x, -lbb.y, 0); try { gc.setAlpha(0); gc.fillRectangle(lbb); gc.setAlpha(255); renderThings(view, lbb); } finally { popMatrix(); } imageData = image.getImageData(); paletteData = imageData.palette; if (paletteData.isDirect) { BufferedImage bufferedImage = new BufferedImage(imageData.width, imageData.height, BufferedImage.TYPE_4BYTE_ABGR); for (int y = 0; y < imageData.height; y++) { for (int x = 0; x < imageData.width; x++) { RGB rgb = paletteData.getRGB(imageData.getPixel(x, y)); int r = rgb.red; int g = rgb.green; int b = rgb.blue; int a = imageData.getAlpha(x, y); int i = a << 24 | (r & 0xFF) << 16 | (g & 0xFF) << 8 | b & 0xFF; bufferedImage.setRGB(x, y, i); } } return bufferedImage; } else { RGB[] rgbs = paletteData.getRGBs(); byte[] red = new byte[rgbs.length]; byte[] green = new byte[rgbs.length]; byte[] blue = new byte[rgbs.length]; for (int i = 0; i < rgbs.length; i++) { RGB rgb = rgbs[i]; red[i] = (byte) rgb.red; green[i] = (byte) rgb.green; blue[i] = (byte) rgb.blue; } ColorModel colorModel; if (imageData.transparentPixel != -1) { colorModel = new IndexColorModel(imageData.depth, rgbs.length, red, green, blue, imageData.transparentPixel); } else { colorModel = new IndexColorModel(imageData.depth, rgbs.length, red, green, blue); } BufferedImage bufferedImage = new BufferedImage(colorModel, colorModel.createCompatibleWritableRaster( imageData.width, imageData.height), false, null); WritableRaster raster = bufferedImage.getRaster(); int[] pixelArray = new int[1]; for (int y = 0; y < imageData.height; y++) { for (int x = 0; x < imageData.width; x++) { int pixel = imageData.getPixel(x, y); pixelArray[0] = pixel; raster.setPixel(x, y, pixelArray); } } return bufferedImage; } } finally { if (gc != null) { gc.dispose(); } if (image != null) { image.dispose(); } } } }