1 /*******************************************************************************
2 * Copyright (c) 2007- VTT Technical Research Centre of Finland.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
9 * VTT Technical Research Centre of Finland - initial API and implementation
10 *******************************************************************************/
11 package org.simantics.image.ui.editor;
13 import java.awt.geom.AffineTransform;
14 import java.awt.geom.Point2D;
15 import java.awt.geom.Rectangle2D;
16 import java.awt.image.BufferedImage;
17 import java.awt.image.DirectColorModel;
18 import java.awt.image.IndexColorModel;
19 import java.awt.image.WritableRaster;
20 import java.io.ByteArrayInputStream;
22 import org.eclipse.jface.layout.GridDataFactory;
23 import org.eclipse.jface.resource.JFaceResources;
24 import org.eclipse.jface.resource.LocalResourceManager;
25 import org.eclipse.jface.resource.ResourceManager;
26 import org.eclipse.swt.SWT;
27 import org.eclipse.swt.SWTError;
28 import org.eclipse.swt.SWTException;
29 import org.eclipse.swt.graphics.GC;
30 import org.eclipse.swt.graphics.Image;
31 import org.eclipse.swt.graphics.ImageData;
32 import org.eclipse.swt.graphics.PaletteData;
33 import org.eclipse.swt.graphics.Point;
34 import org.eclipse.swt.graphics.RGB;
35 import org.eclipse.swt.graphics.Rectangle;
36 import org.eclipse.swt.widgets.Canvas;
37 import org.eclipse.swt.widgets.Composite;
38 import org.eclipse.swt.widgets.Display;
39 import org.eclipse.swt.widgets.Event;
40 import org.eclipse.swt.widgets.Listener;
41 import org.simantics.Simantics;
42 import org.simantics.databoard.Bindings;
43 import org.simantics.db.ReadGraph;
44 import org.simantics.db.Resource;
45 import org.simantics.db.common.request.ParametrizedRead;
46 import org.simantics.db.common.request.ResourceRead;
47 import org.simantics.db.exception.DatabaseException;
48 import org.simantics.db.layer0.request.combinations.Combinators;
49 import org.simantics.image2.ontology.ImageResource;
50 import org.simantics.scenegraph.ScenegraphUtils;
51 import org.simantics.scenegraph.utils.GeometryUtils;
52 import org.simantics.ui.workbench.IResourceEditorInput;
53 import org.simantics.ui.workbench.ResourceEditorPart;
54 import org.simantics.ui.workbench.editor.input.InputValidationCombinators;
55 import org.simantics.utils.ui.ErrorLogger;
56 import org.simantics.utils.ui.LayoutUtils;
58 import com.kitfox.svg.SVGDiagram;
59 import com.kitfox.svg.SVGException;
60 import com.kitfox.svg.SVGUniverse;
63 * @author Tuukka Lehtonen
65 public class ImageEditor extends ResourceEditorPart {
67 public static final String EDITOR_ID = "org.simantics.wiki.ui.image.editor"; //$NON-NLS-1$
69 protected boolean disposed = false;
71 protected Image image;
72 protected SVGDiagram svgDiagram;
73 private ResourceManager resourceManager;
74 private Canvas canvas;
76 private double zoomLevel = 1;
77 private Point2D previousTranslate = new Point2D.Double();
78 private Point2D translate = new Point2D.Double();
79 private boolean zoomToFit = false;
80 private SVGUniverse svgUniverse = new SVGUniverse();
82 static ImageData convertToSWT(BufferedImage bufferedImage) {
83 if (bufferedImage.getColorModel() instanceof DirectColorModel) {
84 DirectColorModel colorModel = (DirectColorModel) bufferedImage
86 PaletteData palette = new PaletteData(colorModel.getRedMask(),
87 colorModel.getGreenMask(), colorModel.getBlueMask());
88 ImageData data = new ImageData(bufferedImage.getWidth(),
89 bufferedImage.getHeight(), colorModel.getPixelSize(),
91 WritableRaster raster = bufferedImage.getRaster();
92 int[] pixelArray = new int[4];
93 for (int y = 0; y < data.height; y++) {
94 for (int x = 0; x < data.width; x++) {
95 raster.getPixel(x, y, pixelArray);
96 int pixel = palette.getPixel(new RGB(pixelArray[0],
97 pixelArray[1], pixelArray[2]));
98 data.setPixel(x, y, pixel);
102 } else if (bufferedImage.getColorModel() instanceof IndexColorModel) {
103 IndexColorModel colorModel = (IndexColorModel) bufferedImage
105 int size = colorModel.getMapSize();
106 byte[] reds = new byte[size];
107 byte[] greens = new byte[size];
108 byte[] blues = new byte[size];
109 colorModel.getReds(reds);
110 colorModel.getGreens(greens);
111 colorModel.getBlues(blues);
112 RGB[] rgbs = new RGB[size];
113 for (int i = 0; i < rgbs.length; i++) {
114 rgbs[i] = new RGB(reds[i] & 0xFF, greens[i] & 0xFF,
117 PaletteData palette = new PaletteData(rgbs);
118 ImageData data = new ImageData(bufferedImage.getWidth(),
119 bufferedImage.getHeight(), colorModel.getPixelSize(),
121 data.transparentPixel = colorModel.getTransparentPixel();
122 WritableRaster raster = bufferedImage.getRaster();
123 int[] pixelArray = new int[1];
124 for (int y = 0; y < data.height; y++) {
125 for (int x = 0; x < data.width; x++) {
126 raster.getPixel(x, y, pixelArray);
127 data.setPixel(x, y, pixelArray[0]);
135 ParametrizedRead<IResourceEditorInput, Boolean> INPUT_VALIDATOR =
137 InputValidationCombinators.hasURI(),
138 InputValidationCombinators.extractInputResource()
142 protected ParametrizedRead<IResourceEditorInput, Boolean> getInputValidator() {
143 return INPUT_VALIDATOR;
147 public void createPartControl(final Composite parent) {
148 this.resourceManager = new LocalResourceManager(JFaceResources.getResources(), parent);
150 parent.setLayout(LayoutUtils.createNoBorderGridLayout(1));
152 canvas = new Canvas(parent, SWT.DOUBLE_BUFFERED);
153 canvas.setBackground(resourceManager.createColor(new RGB(255, 255, 255)));
154 GridDataFactory.fillDefaults().grab(true, true).applyTo(canvas);
156 canvas.addListener(SWT.Paint, new Listener() {
158 public void handleEvent(Event event) {
159 if (svgDiagram != null) {
160 Rectangle2D r = svgDiagram.getViewRect();
164 // FIXME: this is unsafe, renders unnecessarily large
165 // buffered images when only control size is ultimately needed.
167 AffineTransform tr = AffineTransform.getScaleInstance(zoomLevel, zoomLevel);
168 tr.translate(translate.getX(), translate.getY());
170 BufferedImage bi = ScenegraphUtils.paintSVG(svgDiagram, tr, 0f);
171 Image img = new Image(parent.getDisplay(), convertToSWT(bi));
172 drawImage(event.gc, img, false);
174 } catch (SVGException e) {
176 } else if (image != null) {
177 drawImage(event.gc, image, true);
180 private void drawImage(GC gc, Image image, boolean fitToCanvas) {
181 Rectangle r = image.getBounds();
185 Point destSize = canvas.getSize();
186 int xSpace = destSize.x - r.width;
187 int ySpace = destSize.y - r.height;
188 boolean fitsX = xSpace >= 0;
189 boolean fitsY = ySpace >= 0;
190 boolean fitsCanvas = fitsX && fitsY;
192 // if the image is larger than the canvas, zoom it to fit
193 if ((!fitsCanvas && fitToCanvas) || zoomToFit) {
194 gc.setAntialias(SWT.ON);
196 // Zoom to fit propertionally
199 if (xSpace > ySpace) {
200 double yr = (double) destSize.y / r.height;
201 double xo = (int)(r.width * yr);
202 leftMargin = (int) Math.round(((destSize.x - r.width) + (r.width - xo)) * 0.5);
203 gc.drawImage(image, 0, 0, r.width, r.height, leftMargin, topMargin, (int) xo, destSize.y);
205 double xr = (double) destSize.x / r.width;
206 double yo = (int)(r.height * xr);
207 topMargin = (int) Math.round(((destSize.y - r.height) + (r.height - yo)) * 0.5);
208 gc.drawImage(image, 0, 0, r.width, r.height, leftMargin, topMargin, destSize.x, (int) yo);
212 int srcX = fitsX ? 0 : (int) Math.round(-xSpace * 0.5);
213 int srcY = fitsY ? 0 : (int) Math.round(-ySpace * 0.5);
214 int srcW = fitsX ? r.width : r.width + xSpace;
215 int srcH = fitsY ? r.height : r.height + ySpace;
216 int destX = fitsX ? (int) Math.round(xSpace * 0.5) : 0;
217 int destY = fitsY ? (int) Math.round(ySpace * 0.5) : 0;
218 int destW = fitsX ? r.width : destSize.x;
219 int destH = fitsY ? r.height : destSize.y;
220 gc.drawImage(image, srcX, srcY, srcW, srcH, destX, destY, destW, destH);
222 // Center on the canvas.
223 int leftMargin = (int) Math.round((destSize.x - r.width) * 0.5);
224 int topMargin = (int) Math.round((destSize.y - r.height) * 0.5);
225 gc.drawImage(image, 0, 0, r.width, r.height, leftMargin, topMargin, r.width, r.height);
230 canvas.addListener(SWT.MouseDoubleClick, new Listener() {
232 public void handleEvent(Event event) {
238 // Mouse tracking support
239 Listener listener = new Listener() {
241 Point panStart = new Point(0, 0);
243 public void handleEvent(Event e) {
248 previousTranslate.setLocation(translate);
256 previousTranslate.setLocation(translate);
261 int dx = e.x - panStart.x;
262 int dy = e.y - panStart.y;
263 translate.setLocation(
264 previousTranslate.getX() + dx / zoomLevel,
265 previousTranslate.getY() + dy / zoomLevel);
269 case SWT.MouseVerticalWheel:
270 double scroll = Math.min(0.9, -e.count / 20.0);
271 double z = 1 - scroll;
272 zoomLevel = limitScaleFactor(zoomLevel, z);
277 private double limitScaleFactor(double zoomLevel, double scaleFactor) {
278 double inLimit = 200.0;
279 double outLimit = 10;
281 AffineTransform view = AffineTransform.getScaleInstance(zoomLevel, zoomLevel);
282 double currentScale = GeometryUtils.getScale(view) * 100.0;
283 double newScale = currentScale * scaleFactor;
285 if (newScale > currentScale && newScale > inLimit) {
286 if (currentScale < inLimit)
287 scaleFactor = inLimit / currentScale;
289 return inLimit / 100.0;
290 } else if (newScale < currentScale && newScale < outLimit) {
291 if (currentScale > outLimit)
292 scaleFactor = outLimit / currentScale;
294 return outLimit / 100.0;
296 return zoomLevel * scaleFactor;
299 canvas.addListener(SWT.MouseUp, listener);
300 canvas.addListener(SWT.MouseDown, listener);
301 canvas.addListener(SWT.MouseMove, listener);
302 canvas.addListener(SWT.MouseWheel, listener);
304 // Start tracking editor input validity.
305 activateValidation();
310 private void loadAndTrackInput() {
311 final Resource input = getInputResource();
312 Simantics.getSession().asyncRequest(new ResourceRead<Object>(input) {
314 public Object perform(ReadGraph graph) throws DatabaseException {
315 ImageResource img = ImageResource.getInstance(graph);
316 if (graph.isInstanceOf(input, img.SvgImage)) {
317 String text = graph.getPossibleValue(input, Bindings.STRING);
319 } else if (graph.isInstanceOf(input, img.Image)) {
320 byte data[] = graph.getPossibleValue(input, Bindings.BYTE_ARRAY);
325 }, new org.simantics.db.procedure.Listener<Object>() {
327 public void execute(Object result) {
328 if (result instanceof String) {
331 svgDiagram = ScenegraphUtils.loadSVGDiagram(svgUniverse, (String) result);
333 } catch (SVGException e) {
334 ErrorLogger.defaultLogError(e);
336 } else if (result instanceof byte[]) {
338 Display display = canvas.getDisplay();
339 if (!display.isDisposed()) {
341 image = new Image(canvas.getDisplay(), new ByteArrayInputStream((byte[]) result));
343 } catch (SWTException e) {
344 ErrorLogger.defaultLogError(e);
345 } catch (SWTError e) {
346 ErrorLogger.defaultLogError(e);
351 private void scheduleRedraw() {
352 Display d = canvas.getDisplay();
354 d.asyncExec(new Runnable() {
357 if (!canvas.isDisposed())
363 public void exception(Throwable t) {
364 ErrorLogger.defaultLogError(t);
367 public boolean isDisposed() {
374 public void dispose() {
384 public void setFocus() {
389 @SuppressWarnings("rawtypes")
391 public Object getAdapter(Class adapter) {