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