X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=blobdiff_plain;f=bundles%2Forg.simantics.diagram%2Fsrc%2Forg%2Fsimantics%2Fdiagram%2Fexport%2FImagePrinter.java;fp=bundles%2Forg.simantics.diagram%2Fsrc%2Forg%2Fsimantics%2Fdiagram%2Fexport%2FImagePrinter.java;h=0b3e6c7237ecb2c87dd74377a280c07aaa34bf78;hb=969bd23cab98a79ca9101af33334000879fb60c5;hp=0000000000000000000000000000000000000000;hpb=866dba5cd5a3929bbeae85991796acb212338a08;p=simantics%2Fplatform.git diff --git a/bundles/org.simantics.diagram/src/org/simantics/diagram/export/ImagePrinter.java b/bundles/org.simantics.diagram/src/org/simantics/diagram/export/ImagePrinter.java new file mode 100644 index 000000000..0b3e6c723 --- /dev/null +++ b/bundles/org.simantics.diagram/src/org/simantics/diagram/export/ImagePrinter.java @@ -0,0 +1,249 @@ +package org.simantics.diagram.export; + +import java.awt.Point; +import java.awt.geom.AffineTransform; +import java.awt.image.BufferedImage; +import java.io.File; +import java.util.concurrent.Semaphore; +import java.util.concurrent.atomic.AtomicReference; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.SubMonitor; +import org.simantics.Simantics; +import org.simantics.db.ReadGraph; +import org.simantics.db.Resource; +import org.simantics.db.common.ResourceArray; +import org.simantics.db.common.request.PossibleIndexRoot; +import org.simantics.db.common.request.UniqueRead; +import org.simantics.db.common.utils.NameUtils; +import org.simantics.db.exception.DatabaseException; +import org.simantics.db.exception.ValidationException; +import org.simantics.db.management.ISessionContext; +import org.simantics.diagram.elements.DiagramNodeUtil; +import org.simantics.g2d.canvas.ICanvasContext; +import org.simantics.g2d.canvas.impl.CanvasContext; +import org.simantics.g2d.chassis.ICanvasChassis; +import org.simantics.g2d.chassis.SWTChassis; +import org.simantics.g2d.participant.TransformUtil; +import org.simantics.g2d.scenegraph.ICanvasSceneGraphProvider; +import org.simantics.g2d.scenegraph.SceneGraphConstants; +import org.simantics.modeling.ModelingResources; +import org.simantics.scenegraph.g2d.nodes.NavigationNode; +import org.simantics.scenegraph.utils.NodeUtil; +import org.simantics.structural2.StructuralVariables; +import org.simantics.utils.DataContainer; +import org.simantics.utils.datastructures.Pair; +import org.simantics.utils.threads.AWTThread; +import org.simantics.utils.threads.IThreadWorkQueue; +import org.simantics.utils.threads.ThreadUtils; +import org.simantics.utils.threads.WorkerThread; + +public class ImagePrinter { + + public static class ImageExportPlan { + public File exportLocation; + public String name; + public Resource diagram; + + // use dpi or size, not both. + public Double dpi; + public Point size; + public double margin; + } + + /** + * @param monitor + * the progress monitor to use for reporting progress to the + * user. It is the caller's responsibility to call done() on the + * given monitor. Accepts null, indicating that no + * progress should be reported and that the operation cannot be + * cancelled. + * + * @param exportPath + * @throws Exception + */ + public static BufferedImage printToImage(IProgressMonitor monitor, ImageExportPlan exportPlan) throws Exception { + + + SubMonitor progress = SubMonitor.convert(monitor, "Export to Image", 1); + + WorkerThread workerThread = new WorkerThread("Diagram Image Painter"); + workerThread.start(); + String loc; + if (exportPlan.exportLocation == null) + loc = "clipboard"; + else + loc = exportPlan.exportLocation.getAbsolutePath(); + progress.beginTask("Writing " + exportPlan.name + " to " + loc, 1); + + return render(workerThread, Simantics.getSessionContext(), exportPlan); + + + } + + /** + * Renders diagram to BufferedImage + * + * @param thread + * @param sessionContext + * @param exportPlan + * @return + * @throws Exception + */ + public static BufferedImage render( + final IThreadWorkQueue thread, + final ISessionContext sessionContext, + final ImageExportPlan exportPlan) + throws Exception + { + final DataContainer result = new DataContainer(null); + final DataContainer exception = new DataContainer(); + + final CanvasContext ctx = new CanvasContext(thread); + final AtomicReference sgProvider = new AtomicReference(); + + try { + final Semaphore done = new Semaphore(0); + // IMPORTANT: Load diagram in a different thread than the canvas context thread! + ThreadUtils.getBlockingWorkExecutor().execute(new Runnable() { + @Override + public void run() { + try { + Pair modelAndRVI = sessionContext.getSession().syncRequest(new UniqueRead>() { + @Override + public Pair perform(ReadGraph graph) throws DatabaseException { + return new Pair( resolveModel(graph, exportPlan.diagram ), resolveRVI(graph, exportPlan.diagram) ); + } + }); + + ICanvasSceneGraphProvider provider = DiagramNodeUtil.loadSceneGraphProvider(ctx, modelAndRVI.first, exportPlan.diagram, modelAndRVI.second); + sgProvider.set( provider ); + + ThreadUtils.asyncExec(thread, new Runnable() { + @Override + public void run() { + try { + ImageBuilder chassis = new ImageBuilder(exportPlan.exportLocation,exportPlan.dpi,exportPlan.size,exportPlan.margin); + + result.set(chassis.paint(ctx)); + } catch (Exception e) { + exception.set(e); + } finally { + done.release(); + } + } + }); + } catch (DatabaseException e) { + done.release(); + exception.set(e); + } catch (Throwable e) { + done.release(); + exception.set(new DatabaseException(e)); + } finally { + done.release(); + } + } + }); + + done.acquire(2); + if (exception.get() != null) + throw exception.get(); + return result.get(); + } finally { + if (sgProvider.get() != null) + sgProvider.get().dispose(); + ctx.dispose(); + } + } + + /** + * Renders diagram to BufferedImage using existing CanvasContext. + * + * Note: While the method tries to restore setting as they were, there may be visible side effects in the diagram. + * + * @param context + * @param chassis + * @param exportPlan + * @return + * @throws Exception + */ + public static BufferedImage renderLocal(final ICanvasContext context, final ICanvasChassis chassis, final ImageExportPlan exportPlan) throws Exception { + final Semaphore done = new Semaphore(0); + final DataContainer result = new DataContainer(null); + final DataContainer exception = new DataContainer(null); + ThreadUtils.asyncExec(AWTThread.getThreadAccess(), new Runnable() { + + @Override + public void run() { + try { + result.set(renderLocal(context, exportPlan, chassis)); + } catch (Exception e) { + exception.set(e); + } finally { + done.release(); + } + } + }); + done.acquire(); + if (exception.get() != null) + throw exception.get(); + return result.get(); + } + + private static BufferedImage renderLocal(ICanvasContext context, ImagePrinter.ImageExportPlan exportPlan, ICanvasChassis chassis) throws Exception { + TransformUtil util = context.getSingleItem(TransformUtil.class); + AffineTransform at = util.getTransform(); + final NavigationNode node = NodeUtil.findNodeById(context.getSceneGraph(), SceneGraphConstants.NAVIGATION_NODE_PATH); + boolean adapt = true; + if (node != null) { + adapt = node.getAdaptViewportToResizedControl(); + // prevent NavigationNode from re-scaling when it detects canvas size change. + node.setAdaptViewportToResizedControl(false); + } + + ImageBuilder builder = new ImageBuilder(exportPlan.exportLocation,exportPlan.dpi,exportPlan.size,exportPlan.margin); + BufferedImage image = builder.paint(context); + + util.setTransform(at); + + + if (node != null && adapt != false) { + if (chassis instanceof SWTChassis) { + ((SWTChassis) chassis).getAWTComponent().repaint(); + } + // restore re-scaling setting (need to be scheduled, so that Diagram canvas is re-painted once. + final boolean b = adapt; + ThreadUtils.asyncExec(AWTThread.getThreadAccess(), new Runnable() { + @Override + public void run() { + node.setAdaptViewportToResizedControl(b); + } + }); + } + + return image; + } + + + + private static Resource resolveModel(ReadGraph graph, Resource diagram) throws DatabaseException { + ModelingResources mod = ModelingResources.getInstance(graph); + Resource composite = graph.getSingleObject(diagram, mod.DiagramToComposite); + Resource model = graph.syncRequest(new PossibleIndexRoot(composite)); + if (model == null) + throw new ValidationException("no model found for composite " + NameUtils.getSafeName(graph, composite)); + return model; + } + + + + private static String resolveRVI(ReadGraph graph, Resource diagram) throws DatabaseException { + ModelingResources mod = ModelingResources.getInstance(graph); + Resource composite = graph.getSingleObject(diagram, mod.DiagramToComposite); + final ResourceArray compositePath = StructuralVariables.getCompositeArray(graph, composite); + final ResourceArray variablePath = compositePath.removeFromBeginning(1); + return StructuralVariables.getRVI(graph, variablePath); + } + + +}