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