/******************************************************************************* * Copyright (c) 2007- VTT Technical Research Centre of Finland. * 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: * VTT Technical Research Centre of Finland - initial API and implementation *******************************************************************************/ package org.simantics.image.ui.editor; import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.awt.image.DirectColorModel; import java.awt.image.IndexColorModel; import java.awt.image.WritableRaster; import java.io.ByteArrayInputStream; import org.eclipse.jface.layout.GridDataFactory; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.resource.LocalResourceManager; import org.eclipse.jface.resource.ResourceManager; import org.eclipse.swt.SWT; import org.eclipse.swt.SWTError; import org.eclipse.swt.SWTException; 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.Rectangle; import org.eclipse.swt.widgets.Canvas; 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.simantics.Simantics; import org.simantics.databoard.Bindings; import org.simantics.db.ReadGraph; import org.simantics.db.Resource; import org.simantics.db.common.request.ParametrizedRead; import org.simantics.db.common.request.ResourceRead; import org.simantics.db.exception.DatabaseException; import org.simantics.db.layer0.request.combinations.Combinators; import org.simantics.image2.ontology.ImageResource; import org.simantics.scenegraph.ScenegraphUtils; import org.simantics.scenegraph.utils.GeometryUtils; import org.simantics.ui.workbench.IResourceEditorInput; import org.simantics.ui.workbench.ResourceEditorPart; import org.simantics.ui.workbench.editor.input.InputValidationCombinators; import org.simantics.utils.ui.ErrorLogger; import org.simantics.utils.ui.LayoutUtils; import com.kitfox.svg.SVGDiagram; import com.kitfox.svg.SVGException; import com.kitfox.svg.SVGUniverse; /** * @author Tuukka Lehtonen */ public class ImageEditor extends ResourceEditorPart { public static final String EDITOR_ID = "org.simantics.wiki.ui.image.editor"; //$NON-NLS-1$ protected boolean disposed = false; protected Image image; protected SVGDiagram svgDiagram; private ResourceManager resourceManager; private Canvas canvas; private double zoomLevel = 1; private Point2D previousTranslate = new Point2D.Double(); private Point2D translate = new Point2D.Double(); private boolean zoomToFit = false; private SVGUniverse svgUniverse = new SVGUniverse(); static ImageData convertToSWT(BufferedImage bufferedImage) { if (bufferedImage.getColorModel() instanceof DirectColorModel) { DirectColorModel colorModel = (DirectColorModel) bufferedImage .getColorModel(); PaletteData palette = new PaletteData(colorModel.getRedMask(), colorModel.getGreenMask(), colorModel.getBlueMask()); ImageData data = new ImageData(bufferedImage.getWidth(), bufferedImage.getHeight(), colorModel.getPixelSize(), palette); WritableRaster raster = bufferedImage.getRaster(); int[] pixelArray = new int[4]; for (int y = 0; y < data.height; y++) { for (int x = 0; x < data.width; x++) { raster.getPixel(x, y, pixelArray); int pixel = palette.getPixel(new RGB(pixelArray[0], pixelArray[1], pixelArray[2])); data.setPixel(x, y, pixel); } } return data; } else if (bufferedImage.getColorModel() instanceof IndexColorModel) { IndexColorModel colorModel = (IndexColorModel) bufferedImage .getColorModel(); int size = colorModel.getMapSize(); byte[] reds = new byte[size]; byte[] greens = new byte[size]; byte[] blues = new byte[size]; colorModel.getReds(reds); colorModel.getGreens(greens); colorModel.getBlues(blues); RGB[] rgbs = new RGB[size]; for (int i = 0; i < rgbs.length; i++) { rgbs[i] = new RGB(reds[i] & 0xFF, greens[i] & 0xFF, blues[i] & 0xFF); } PaletteData palette = new PaletteData(rgbs); ImageData data = new ImageData(bufferedImage.getWidth(), bufferedImage.getHeight(), colorModel.getPixelSize(), palette); data.transparentPixel = colorModel.getTransparentPixel(); WritableRaster raster = bufferedImage.getRaster(); int[] pixelArray = new int[1]; for (int y = 0; y < data.height; y++) { for (int x = 0; x < data.width; x++) { raster.getPixel(x, y, pixelArray); data.setPixel(x, y, pixelArray[0]); } } return data; } return null; } ParametrizedRead INPUT_VALIDATOR = Combinators.compose( InputValidationCombinators.hasURI(), InputValidationCombinators.extractInputResource() ); @Override protected ParametrizedRead getInputValidator() { return INPUT_VALIDATOR; } @Override public void createPartControl(final Composite parent) { this.resourceManager = new LocalResourceManager(JFaceResources.getResources(), parent); parent.setLayout(LayoutUtils.createNoBorderGridLayout(1)); canvas = new Canvas(parent, SWT.DOUBLE_BUFFERED); canvas.setBackground(resourceManager.createColor(new RGB(255, 255, 255))); GridDataFactory.fillDefaults().grab(true, true).applyTo(canvas); canvas.addListener(SWT.Paint, new Listener() { @Override public void handleEvent(Event event) { if (svgDiagram != null) { Rectangle2D r = svgDiagram.getViewRect(); if (r.isEmpty()) return; // FIXME: this is unsafe, renders unnecessarily large // buffered images when only control size is ultimately needed. AffineTransform tr = AffineTransform.getScaleInstance(zoomLevel, zoomLevel); tr.translate(translate.getX(), translate.getY()); try { BufferedImage bi = ScenegraphUtils.paintSVG(svgDiagram, tr, 0f); Image img = new Image(parent.getDisplay(), convertToSWT(bi)); drawImage(event.gc, img, false); img.dispose(); } catch (SVGException e) { } } else if (image != null) { drawImage(event.gc, image, true); } } private void drawImage(GC gc, Image image, boolean fitToCanvas) { Rectangle r = image.getBounds(); if (r.isEmpty()) return; Point destSize = canvas.getSize(); int xSpace = destSize.x - r.width; int ySpace = destSize.y - r.height; boolean fitsX = xSpace >= 0; boolean fitsY = ySpace >= 0; boolean fitsCanvas = fitsX && fitsY; // if the image is larger than the canvas, zoom it to fit if ((!fitsCanvas && fitToCanvas) || zoomToFit) { gc.setAntialias(SWT.ON); // Zoom to fit propertionally int leftMargin = 0; int topMargin = 0; if (xSpace > ySpace) { double yr = (double) destSize.y / r.height; double xo = (int)(r.width * yr); leftMargin = (int) Math.round(((destSize.x - r.width) + (r.width - xo)) * 0.5); gc.drawImage(image, 0, 0, r.width, r.height, leftMargin, topMargin, (int) xo, destSize.y); } else { double xr = (double) destSize.x / r.width; double yo = (int)(r.height * xr); topMargin = (int) Math.round(((destSize.y - r.height) + (r.height - yo)) * 0.5); gc.drawImage(image, 0, 0, r.width, r.height, leftMargin, topMargin, destSize.x, (int) yo); } } else { if (!fitsCanvas) { int srcX = fitsX ? 0 : (int) Math.round(-xSpace * 0.5); int srcY = fitsY ? 0 : (int) Math.round(-ySpace * 0.5); int srcW = fitsX ? r.width : r.width + xSpace; int srcH = fitsY ? r.height : r.height + ySpace; int destX = fitsX ? (int) Math.round(xSpace * 0.5) : 0; int destY = fitsY ? (int) Math.round(ySpace * 0.5) : 0; int destW = fitsX ? r.width : destSize.x; int destH = fitsY ? r.height : destSize.y; gc.drawImage(image, srcX, srcY, srcW, srcH, destX, destY, destW, destH); } else { // Center on the canvas. int leftMargin = (int) Math.round((destSize.x - r.width) * 0.5); int topMargin = (int) Math.round((destSize.y - r.height) * 0.5); gc.drawImage(image, 0, 0, r.width, r.height, leftMargin, topMargin, r.width, r.height); } } } }); canvas.addListener(SWT.MouseDoubleClick, new Listener() { @Override public void handleEvent(Event event) { zoomToFit ^= true; canvas.redraw(); } }); // Mouse tracking support Listener listener = new Listener() { boolean pan = false; Point panStart = new Point(0, 0); @Override public void handleEvent(Event e) { switch (e.type) { case SWT.MouseUp: if (e.button == 3) { pan = false; previousTranslate.setLocation(translate); } break; case SWT.MouseDown: if (e.button == 3) { panStart.x = e.x; panStart.y = e.y; pan = true; previousTranslate.setLocation(translate); } break; case SWT.MouseMove: if (pan) { int dx = e.x - panStart.x; int dy = e.y - panStart.y; translate.setLocation( previousTranslate.getX() + dx / zoomLevel, previousTranslate.getY() + dy / zoomLevel); canvas.redraw(); } break; case SWT.MouseVerticalWheel: double scroll = Math.min(0.9, -e.count / 20.0); double z = 1 - scroll; zoomLevel = limitScaleFactor(zoomLevel, z); canvas.redraw(); break; } } private double limitScaleFactor(double zoomLevel, double scaleFactor) { double inLimit = 200.0; double outLimit = 10; AffineTransform view = AffineTransform.getScaleInstance(zoomLevel, zoomLevel); double currentScale = GeometryUtils.getScale(view) * 100.0; double newScale = currentScale * scaleFactor; if (newScale > currentScale && newScale > inLimit) { if (currentScale < inLimit) scaleFactor = inLimit / currentScale; else return inLimit / 100.0; } else if (newScale < currentScale && newScale < outLimit) { if (currentScale > outLimit) scaleFactor = outLimit / currentScale; else return outLimit / 100.0; } return zoomLevel * scaleFactor; } }; canvas.addListener(SWT.MouseUp, listener); canvas.addListener(SWT.MouseDown, listener); canvas.addListener(SWT.MouseMove, listener); canvas.addListener(SWT.MouseWheel, listener); // Start tracking editor input validity. activateValidation(); loadAndTrackInput(); } private void loadAndTrackInput() { final Resource input = getInputResource(); Simantics.getSession().asyncRequest(new ResourceRead(input) { @Override public Object perform(ReadGraph graph) throws DatabaseException { ImageResource img = ImageResource.getInstance(graph); if (graph.isInstanceOf(input, img.SvgImage)) { String text = graph.getPossibleValue(input, Bindings.STRING); return text; } else if (graph.isInstanceOf(input, img.Image)) { byte data[] = graph.getPossibleValue(input, Bindings.BYTE_ARRAY); return data; } return null; } }, new org.simantics.db.procedure.Listener() { @Override public void execute(Object result) { if (result instanceof String) { // svg text try { svgDiagram = ScenegraphUtils.loadSVGDiagram(svgUniverse, (String) result); scheduleRedraw(); } catch (SVGException e) { ErrorLogger.defaultLogError(e); } } else if (result instanceof byte[]) { // bitmap image data Display display = canvas.getDisplay(); if (!display.isDisposed()) { try { image = new Image(canvas.getDisplay(), new ByteArrayInputStream((byte[]) result)); scheduleRedraw(); } catch (SWTException e) { ErrorLogger.defaultLogError(e); } catch (SWTError e) { ErrorLogger.defaultLogError(e); } } } } private void scheduleRedraw() { Display d = canvas.getDisplay(); if (!d.isDisposed()) d.asyncExec(new Runnable() { @Override public void run() { if (!canvas.isDisposed()) canvas.redraw(); } }); } @Override public void exception(Throwable t) { ErrorLogger.defaultLogError(t); } @Override public boolean isDisposed() { return disposed; } }); } @Override public void dispose() { disposed = true; if (image != null) { image.dispose(); image = null; } svgUniverse.clear(); } @Override public void setFocus() { if (canvas != null) canvas.setFocus(); } @SuppressWarnings("rawtypes") @Override public Object getAdapter(Class adapter) { return null; } }