1 package org.simantics.diagram.export;
\r
3 import java.awt.Point;
\r
4 import java.awt.geom.AffineTransform;
\r
5 import java.awt.image.BufferedImage;
\r
7 import java.util.concurrent.Semaphore;
\r
8 import java.util.concurrent.atomic.AtomicReference;
\r
10 import org.eclipse.core.runtime.IProgressMonitor;
\r
11 import org.eclipse.core.runtime.SubMonitor;
\r
12 import org.simantics.Simantics;
\r
13 import org.simantics.db.ReadGraph;
\r
14 import org.simantics.db.Resource;
\r
15 import org.simantics.db.common.ResourceArray;
\r
16 import org.simantics.db.common.request.PossibleIndexRoot;
\r
17 import org.simantics.db.common.request.UniqueRead;
\r
18 import org.simantics.db.common.utils.NameUtils;
\r
19 import org.simantics.db.exception.DatabaseException;
\r
20 import org.simantics.db.exception.ValidationException;
\r
21 import org.simantics.db.management.ISessionContext;
\r
22 import org.simantics.diagram.elements.DiagramNodeUtil;
\r
23 import org.simantics.g2d.canvas.ICanvasContext;
\r
24 import org.simantics.g2d.canvas.impl.CanvasContext;
\r
25 import org.simantics.g2d.chassis.ICanvasChassis;
\r
26 import org.simantics.g2d.chassis.SWTChassis;
\r
27 import org.simantics.g2d.participant.TransformUtil;
\r
28 import org.simantics.g2d.scenegraph.ICanvasSceneGraphProvider;
\r
29 import org.simantics.g2d.scenegraph.SceneGraphConstants;
\r
30 import org.simantics.modeling.ModelingResources;
\r
31 import org.simantics.scenegraph.g2d.nodes.NavigationNode;
\r
32 import org.simantics.scenegraph.utils.NodeUtil;
\r
33 import org.simantics.structural2.StructuralVariables;
\r
34 import org.simantics.utils.DataContainer;
\r
35 import org.simantics.utils.datastructures.Pair;
\r
36 import org.simantics.utils.threads.AWTThread;
\r
37 import org.simantics.utils.threads.IThreadWorkQueue;
\r
38 import org.simantics.utils.threads.ThreadUtils;
\r
39 import org.simantics.utils.threads.WorkerThread;
\r
41 public class ImagePrinter {
\r
43 public static class ImageExportPlan {
\r
44 public File exportLocation;
\r
46 public Resource diagram;
\r
48 // use dpi or size, not both.
\r
51 public double margin;
\r
56 * the progress monitor to use for reporting progress to the
\r
57 * user. It is the caller's responsibility to call done() on the
\r
58 * given monitor. Accepts <code>null</code>, indicating that no
\r
59 * progress should be reported and that the operation cannot be
\r
65 public static BufferedImage printToImage(IProgressMonitor monitor, ImageExportPlan exportPlan) throws Exception {
\r
68 SubMonitor progress = SubMonitor.convert(monitor, "Export to Image", 1);
\r
70 WorkerThread workerThread = new WorkerThread("Diagram Image Painter");
\r
71 workerThread.start();
\r
73 if (exportPlan.exportLocation == null)
\r
76 loc = exportPlan.exportLocation.getAbsolutePath();
\r
77 progress.beginTask("Writing " + exportPlan.name + " to " + loc, 1);
\r
79 return render(workerThread, Simantics.getSessionContext(), exportPlan);
\r
85 * Renders diagram to BufferedImage
\r
88 * @param sessionContext
\r
93 public static BufferedImage render(
\r
94 final IThreadWorkQueue thread,
\r
95 final ISessionContext sessionContext,
\r
96 final ImageExportPlan exportPlan)
\r
99 final DataContainer<BufferedImage> result = new DataContainer<BufferedImage>(null);
\r
100 final DataContainer<Exception> exception = new DataContainer<Exception>();
\r
102 final CanvasContext ctx = new CanvasContext(thread);
\r
103 final AtomicReference<ICanvasSceneGraphProvider> sgProvider = new AtomicReference<ICanvasSceneGraphProvider>();
\r
106 final Semaphore done = new Semaphore(0);
\r
107 // IMPORTANT: Load diagram in a different thread than the canvas context thread!
\r
108 ThreadUtils.getBlockingWorkExecutor().execute(new Runnable() {
\r
110 public void run() {
\r
112 Pair<Resource, String> modelAndRVI = sessionContext.getSession().syncRequest(new UniqueRead<Pair<Resource, String>>() {
\r
114 public Pair<Resource, String> perform(ReadGraph graph) throws DatabaseException {
\r
115 return new Pair<Resource, String>( resolveModel(graph, exportPlan.diagram ), resolveRVI(graph, exportPlan.diagram) );
\r
119 ICanvasSceneGraphProvider provider = DiagramNodeUtil.loadSceneGraphProvider(ctx, modelAndRVI.first, exportPlan.diagram, modelAndRVI.second);
\r
120 sgProvider.set( provider );
\r
122 ThreadUtils.asyncExec(thread, new Runnable() {
\r
124 public void run() {
\r
126 ImageBuilder chassis = new ImageBuilder(exportPlan.exportLocation,exportPlan.dpi,exportPlan.size,exportPlan.margin);
\r
128 result.set(chassis.paint(ctx));
\r
129 } catch (Exception e) {
\r
136 } catch (DatabaseException e) {
\r
139 } catch (Throwable e) {
\r
141 exception.set(new DatabaseException(e));
\r
149 if (exception.get() != null)
\r
150 throw exception.get();
\r
151 return result.get();
\r
153 if (sgProvider.get() != null)
\r
154 sgProvider.get().dispose();
\r
160 * Renders diagram to BufferedImage using existing CanvasContext.
\r
162 * Note: While the method tries to restore setting as they were, there may be visible side effects in the diagram.
\r
166 * @param exportPlan
\r
168 * @throws Exception
\r
170 public static BufferedImage renderLocal(final ICanvasContext context, final ICanvasChassis chassis, final ImageExportPlan exportPlan) throws Exception {
\r
171 final Semaphore done = new Semaphore(0);
\r
172 final DataContainer<BufferedImage> result = new DataContainer<BufferedImage>(null);
\r
173 final DataContainer<Exception> exception = new DataContainer<Exception>(null);
\r
174 ThreadUtils.asyncExec(AWTThread.getThreadAccess(), new Runnable() {
\r
177 public void run() {
\r
179 result.set(renderLocal(context, exportPlan, chassis));
\r
180 } catch (Exception e) {
\r
188 if (exception.get() != null)
\r
189 throw exception.get();
\r
190 return result.get();
\r
193 private static BufferedImage renderLocal(ICanvasContext context, ImagePrinter.ImageExportPlan exportPlan, ICanvasChassis chassis) throws Exception {
\r
194 TransformUtil util = context.getSingleItem(TransformUtil.class);
\r
195 AffineTransform at = util.getTransform();
\r
196 final NavigationNode node = NodeUtil.findNodeById(context.getSceneGraph(), SceneGraphConstants.NAVIGATION_NODE_PATH);
\r
197 boolean adapt = true;
\r
198 if (node != null) {
\r
199 adapt = node.getAdaptViewportToResizedControl();
\r
200 // prevent NavigationNode from re-scaling when it detects canvas size change.
\r
201 node.setAdaptViewportToResizedControl(false);
\r
204 ImageBuilder builder = new ImageBuilder(exportPlan.exportLocation,exportPlan.dpi,exportPlan.size,exportPlan.margin);
\r
205 BufferedImage image = builder.paint(context);
\r
207 util.setTransform(at);
\r
210 if (node != null && adapt != false) {
\r
211 if (chassis instanceof SWTChassis) {
\r
212 ((SWTChassis) chassis).getAWTComponent().repaint();
\r
214 // restore re-scaling setting (need to be scheduled, so that Diagram canvas is re-painted once.
\r
215 final boolean b = adapt;
\r
216 ThreadUtils.asyncExec(AWTThread.getThreadAccess(), new Runnable() {
\r
218 public void run() {
\r
219 node.setAdaptViewportToResizedControl(b);
\r
229 private static Resource resolveModel(ReadGraph graph, Resource diagram) throws DatabaseException {
\r
230 ModelingResources mod = ModelingResources.getInstance(graph);
\r
231 Resource composite = graph.getSingleObject(diagram, mod.DiagramToComposite);
\r
232 Resource model = graph.syncRequest(new PossibleIndexRoot(composite));
\r
234 throw new ValidationException("no model found for composite " + NameUtils.getSafeName(graph, composite));
\r
240 private static String resolveRVI(ReadGraph graph, Resource diagram) throws DatabaseException {
\r
241 ModelingResources mod = ModelingResources.getInstance(graph);
\r
242 Resource composite = graph.getSingleObject(diagram, mod.DiagramToComposite);
\r
243 final ResourceArray compositePath = StructuralVariables.getCompositeArray(graph, composite);
\r
244 final ResourceArray variablePath = compositePath.removeFromBeginning(1);
\r
245 return StructuralVariables.getRVI(graph, variablePath);
\r