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