]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.image.ui/src/org/simantics/image/ui/editor/ImageEditor.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.image.ui / src / org / simantics / image / ui / editor / ImageEditor.java
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
7  *\r
8  * Contributors:\r
9  *     VTT Technical Research Centre of Finland - initial API and implementation\r
10  *******************************************************************************/\r
11 package org.simantics.image.ui.editor;\r
12 \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
21 \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
57 \r
58 import com.kitfox.svg.SVGDiagram;\r
59 import com.kitfox.svg.SVGException;\r
60 import com.kitfox.svg.SVGUniverse;\r
61 \r
62 /**\r
63  * @author Tuukka Lehtonen\r
64  */\r
65 public class ImageEditor extends ResourceEditorPart {\r
66 \r
67     public static final String EDITOR_ID   = "org.simantics.wiki.ui.image.editor";\r
68 \r
69     protected boolean          disposed          = false;\r
70 \r
71     protected Image            image;\r
72     protected SVGDiagram       svgDiagram;\r
73     private ResourceManager    resourceManager;\r
74     private Canvas             canvas;\r
75 \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
81 \r
82     static ImageData convertToSWT(BufferedImage bufferedImage) {\r
83         if (bufferedImage.getColorModel() instanceof DirectColorModel) {\r
84             DirectColorModel colorModel = (DirectColorModel) bufferedImage\r
85                     .getColorModel();\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
90                     palette);\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
99                 }\r
100             }\r
101             return data;\r
102         } else if (bufferedImage.getColorModel() instanceof IndexColorModel) {\r
103             IndexColorModel colorModel = (IndexColorModel) bufferedImage\r
104                     .getColorModel();\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
115                         blues[i] & 0xFF);\r
116             }\r
117             PaletteData palette = new PaletteData(rgbs);\r
118             ImageData data = new ImageData(bufferedImage.getWidth(),\r
119                     bufferedImage.getHeight(), colorModel.getPixelSize(),\r
120                     palette);\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
128                 }\r
129             }\r
130             return data;\r
131         }\r
132         return null;\r
133     }\r
134 \r
135     ParametrizedRead<IResourceEditorInput, Boolean> INPUT_VALIDATOR =\r
136             Combinators.compose(\r
137                     InputValidationCombinators.hasURI(),\r
138                     InputValidationCombinators.extractInputResource()\r
139             );\r
140 \r
141     @Override\r
142     protected ParametrizedRead<IResourceEditorInput, Boolean> getInputValidator() {\r
143         return INPUT_VALIDATOR;\r
144     }\r
145 \r
146     @Override\r
147     public void createPartControl(final Composite parent) {\r
148         this.resourceManager = new LocalResourceManager(JFaceResources.getResources(), parent);\r
149 \r
150         parent.setLayout(LayoutUtils.createNoBorderGridLayout(1));\r
151 \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
155 \r
156         canvas.addListener(SWT.Paint, new Listener() {\r
157             @Override\r
158             public void handleEvent(Event event) {\r
159                 if (svgDiagram != null) {\r
160                     Rectangle2D r = svgDiagram.getViewRect();\r
161                     if (r.isEmpty())\r
162                         return;\r
163 \r
164                     // FIXME: this is unsafe, renders unnecessarily large\r
165                     // buffered images when only control size is ultimately needed.\r
166 \r
167                     AffineTransform tr = AffineTransform.getScaleInstance(zoomLevel, zoomLevel);\r
168                     tr.translate(translate.getX(), translate.getY());\r
169                     try {\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
173                         img.dispose();\r
174                     } catch (SVGException e) {\r
175                     }\r
176                 } else if (image != null) {\r
177                     drawImage(event.gc, image, true);\r
178                 }\r
179             }\r
180             private void drawImage(GC gc, Image image, boolean fitToCanvas) {\r
181                 Rectangle r = image.getBounds();\r
182                 if (r.isEmpty())\r
183                     return;\r
184 \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
191 \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
195 \r
196                     // Zoom to fit propertionally\r
197                     int leftMargin = 0;\r
198                     int topMargin = 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
204                     } else {\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
209                     }\r
210                 } else {\r
211                     if (!fitsCanvas) {\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
221                     } else {\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
226                     }\r
227                 }\r
228             }\r
229         });\r
230         canvas.addListener(SWT.MouseDoubleClick, new Listener() {\r
231             @Override\r
232             public void handleEvent(Event event) {\r
233                 zoomToFit ^= true;\r
234                 canvas.redraw();\r
235             }\r
236         });\r
237 \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
242             @Override\r
243             public void handleEvent(Event e) {\r
244                 switch (e.type) {\r
245                     case SWT.MouseUp:\r
246                         if (e.button == 3) {\r
247                             pan = false;\r
248                             previousTranslate.setLocation(translate);\r
249                         }\r
250                         break;\r
251                     case SWT.MouseDown:\r
252                         if (e.button == 3) {\r
253                             panStart.x = e.x;\r
254                             panStart.y = e.y;\r
255                             pan = true;\r
256                             previousTranslate.setLocation(translate);\r
257                         }\r
258                         break;\r
259                     case SWT.MouseMove:\r
260                         if (pan) {\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
266                             canvas.redraw();\r
267                         }\r
268                         break;\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
273                         canvas.redraw();\r
274                         break;\r
275                 }\r
276             }\r
277             private double limitScaleFactor(double zoomLevel, double scaleFactor) {\r
278                 double inLimit = 200.0;\r
279                 double outLimit = 10;\r
280 \r
281                 AffineTransform view = AffineTransform.getScaleInstance(zoomLevel, zoomLevel);\r
282                 double currentScale = GeometryUtils.getScale(view) * 100.0;\r
283                 double newScale = currentScale * scaleFactor;\r
284 \r
285                 if (newScale > currentScale && newScale > inLimit) {\r
286                     if (currentScale < inLimit)\r
287                         scaleFactor = inLimit / currentScale;\r
288                     else\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
293                     else\r
294                         return outLimit / 100.0;\r
295                 }\r
296                 return zoomLevel * scaleFactor;\r
297             }\r
298         };\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
303 \r
304         // Start tracking editor input validity.\r
305         activateValidation();\r
306 \r
307         loadAndTrackInput();\r
308     }\r
309 \r
310     private void loadAndTrackInput() {\r
311         final Resource input = getInputResource();\r
312         Simantics.getSession().asyncRequest(new ResourceRead<Object>(input) {\r
313             @Override\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
318                     return text;\r
319                 } else if (graph.isInstanceOf(input, img.Image)) {\r
320                     byte data[] = graph.getPossibleValue(input, Bindings.BYTE_ARRAY);\r
321                     return data;\r
322                 }\r
323                 return null;\r
324             }\r
325         }, new org.simantics.db.procedure.Listener<Object>() {\r
326             @Override\r
327             public void execute(Object result) {\r
328                 if (result instanceof String) {\r
329                     // svg text\r
330                     try {\r
331                         svgDiagram = ScenegraphUtils.loadSVGDiagram(svgUniverse, (String) result);\r
332                         scheduleRedraw();\r
333                     } catch (SVGException e) {\r
334                         ErrorLogger.defaultLogError(e);\r
335                     }\r
336                 } else if (result instanceof byte[]) {\r
337                     // bitmap image data\r
338                     Display display = canvas.getDisplay();\r
339                     if (!display.isDisposed()) {\r
340                         try {\r
341                             image = new Image(canvas.getDisplay(), new ByteArrayInputStream((byte[]) result));\r
342                             scheduleRedraw();\r
343                         } catch (SWTException e) {\r
344                             ErrorLogger.defaultLogError(e);\r
345                         } catch (SWTError e) {\r
346                             ErrorLogger.defaultLogError(e);\r
347                         }\r
348                     }\r
349                 }\r
350             }\r
351             private void scheduleRedraw() {\r
352                 Display d = canvas.getDisplay();\r
353                 if (!d.isDisposed())\r
354                     d.asyncExec(new Runnable() {\r
355                         @Override\r
356                         public void run() {\r
357                             if (!canvas.isDisposed())\r
358                                 canvas.redraw();\r
359                         }\r
360                     });\r
361             }\r
362             @Override\r
363             public void exception(Throwable t) {\r
364                 ErrorLogger.defaultLogError(t);\r
365             }\r
366             @Override\r
367             public boolean isDisposed() {\r
368                 return disposed;\r
369             }\r
370         });\r
371     }\r
372 \r
373     @Override\r
374     public void dispose() {\r
375         disposed = true;\r
376         if (image != null) {\r
377             image.dispose();\r
378             image = null;\r
379         }\r
380         svgUniverse.clear();\r
381     }\r
382 \r
383     @Override\r
384     public void setFocus() {\r
385         if (canvas != null)\r
386             canvas.setFocus();\r
387     }\r
388 \r
389     @SuppressWarnings("rawtypes")\r
390     @Override\r
391     public Object getAdapter(Class adapter) {\r
392         return null;\r
393     }\r
394 \r
395 }\r