[564658] New Feature : Export Diagram Comparison as images.

A new feature to export a diagram comparison as images. The
image is formed by the two compared diagrams with their phantoms and
markers, the two diagrams are separated by a black separator.

Bug: 564658
Change-Id: I762359439cfeaf25a4f449d852c8e4ec7c1138f2
Signed-off-by: Glenn Plouhinec <glenn.plouhinec@obeo.fr>
diff --git a/plugins/org.eclipse.emf.compare.diagram.ide.ui.sirius/META-INF/MANIFEST.MF b/plugins/org.eclipse.emf.compare.diagram.ide.ui.sirius/META-INF/MANIFEST.MF
index efb19b4..f948790 100644
--- a/plugins/org.eclipse.emf.compare.diagram.ide.ui.sirius/META-INF/MANIFEST.MF
+++ b/plugins/org.eclipse.emf.compare.diagram.ide.ui.sirius/META-INF/MANIFEST.MF
@@ -11,16 +11,18 @@
  org.eclipse.core.runtime,
  org.eclipse.core.resources,
  org.eclipse.ui.ide;bundle-version="3.5.0",
- org.eclipse.emf.edit;bundle-version="2.5.0",
  org.eclipse.emf.edit.ui;bundle-version="2.5.0",
  org.eclipse.emf.compare;bundle-version="3.2.0",
  org.eclipse.emf.compare.rcp.ui;bundle-version="4.1.0",
  org.eclipse.emf.compare.ide.ui;bundle-version="4.1.0",
  org.eclipse.emf.compare.diagram,
  org.eclipse.emf.compare.diagram.ide.ui;bundle-version="3.0.0",
- org.eclipse.sirius.diagram;bundle-version="3.0.0",
- org.eclipse.gmf.runtime.notation;bundle-version="1.8.0",
- org.eclipse.gef
+ org.eclipse.sirius.diagram.ui,
+ org.eclipse.gmf.runtime.diagram.ui,
+ org.eclipse.sirius.common,
+ org.eclipse.gmf.runtime.diagram.ui.render,
+ org.eclipse.gmf.runtime.draw2d.ui.render.awt,
+ org.eclipse.sirius.common.ui
 Import-Package: com.google.common.annotations;version="[27.0.0,28.0.0)",
  com.google.common.base;version="[27.0.0,28.0.0)",
  com.google.common.cache;version="[27.0.0,28.0.0)",
diff --git a/plugins/org.eclipse.emf.compare.diagram.ide.ui.sirius/build.properties b/plugins/org.eclipse.emf.compare.diagram.ide.ui.sirius/build.properties
index 6b5eea4..117a97f 100644
--- a/plugins/org.eclipse.emf.compare.diagram.ide.ui.sirius/build.properties
+++ b/plugins/org.eclipse.emf.compare.diagram.ide.ui.sirius/build.properties
@@ -4,5 +4,8 @@
                .,\
                about.html,\
                plugin.properties,\
-               plugin.xml
-src.includes = about.html
\ No newline at end of file
+               plugin.xml,\
+               icons/
+src.includes = about.html,\
+               icons/
+
diff --git a/plugins/org.eclipse.emf.compare.diagram.ide.ui.sirius/icons/screenshot.gif b/plugins/org.eclipse.emf.compare.diagram.ide.ui.sirius/icons/screenshot.gif
new file mode 100644
index 0000000..e3ebf21
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.diagram.ide.ui.sirius/icons/screenshot.gif
Binary files differ
diff --git a/plugins/org.eclipse.emf.compare.diagram.ide.ui.sirius/src/org/eclipse/emf/compare/diagram/ide/ui/sirius/CompareDiagramIDEUISiriusPlugin.java b/plugins/org.eclipse.emf.compare.diagram.ide.ui.sirius/src/org/eclipse/emf/compare/diagram/ide/ui/sirius/CompareDiagramIDEUISiriusPlugin.java
index 4728aed..394e78e 100644
--- a/plugins/org.eclipse.emf.compare.diagram.ide.ui.sirius/src/org/eclipse/emf/compare/diagram/ide/ui/sirius/CompareDiagramIDEUISiriusPlugin.java
+++ b/plugins/org.eclipse.emf.compare.diagram.ide.ui.sirius/src/org/eclipse/emf/compare/diagram/ide/ui/sirius/CompareDiagramIDEUISiriusPlugin.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2015 Obeo.
+ * Copyright (c) 2015, 2020 Obeo.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
@@ -10,6 +10,9 @@
  *******************************************************************************/
 package org.eclipse.emf.compare.diagram.ide.ui.sirius;
 
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.jface.resource.ImageDescriptor;
 import org.eclipse.ui.plugin.AbstractUIPlugin;
 import org.osgi.framework.BundleContext;
 
@@ -64,4 +67,25 @@
 		return plugin;
 	}
 
+	/**
+	 * Returns a plugin image descriptor.
+	 * 
+	 * @param imagePath
+	 *            plugin relative path to the image
+	 * @return ImageDescriptor image descriptor.
+	 */
+	public static ImageDescriptor getImageDescriptor(String imagePath) {
+		return imageDescriptorFromPlugin(PLUGIN_ID, imagePath);
+	}
+
+	/**
+	 * Log an {@link Exception} in the {@link #getLog() current logger}.
+	 * 
+	 * @param e
+	 *            the exception to be logged.
+	 */
+	public void log(Throwable e) {
+		getLog().log(new Status(IStatus.ERROR, PLUGIN_ID, e.getMessage(), e));
+	}
+
 }
diff --git a/plugins/org.eclipse.emf.compare.diagram.ide.ui.sirius/src/org/eclipse/emf/compare/diagram/ide/ui/sirius/internal/SiriusContentMergeViewer.java b/plugins/org.eclipse.emf.compare.diagram.ide.ui.sirius/src/org/eclipse/emf/compare/diagram/ide/ui/sirius/internal/SiriusContentMergeViewer.java
index 0d914ea..0fecee1 100644
--- a/plugins/org.eclipse.emf.compare.diagram.ide.ui.sirius/src/org/eclipse/emf/compare/diagram/ide/ui/sirius/internal/SiriusContentMergeViewer.java
+++ b/plugins/org.eclipse.emf.compare.diagram.ide.ui.sirius/src/org/eclipse/emf/compare/diagram/ide/ui/sirius/internal/SiriusContentMergeViewer.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2018 Obeo.
+ * Copyright (c) 2018, 2020 Obeo.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
@@ -16,8 +16,11 @@
 
 import org.eclipse.emf.compare.diagram.ide.ui.internal.accessor.IDiagramNodeAccessor;
 import org.eclipse.emf.compare.diagram.ide.ui.internal.contentmergeviewer.diagram.DiagramContentMergeViewer;
+import org.eclipse.emf.compare.diagram.ide.ui.sirius.internal.tools.actions.SiriusComparisonAsImageFileAction;
 import org.eclipse.emf.compare.ide.ui.internal.configuration.EMFCompareConfiguration;
 import org.eclipse.gmf.runtime.notation.Diagram;
+import org.eclipse.jface.action.ActionContributionItem;
+import org.eclipse.jface.action.ToolBarManager;
 import org.eclipse.sirius.diagram.DDiagram;
 import org.eclipse.sirius.diagram.business.api.componentization.DiagramDescriptionMappingsManager;
 import org.eclipse.sirius.diagram.business.api.componentization.DiagramDescriptionMappingsRegistry;
@@ -26,15 +29,34 @@
 import org.eclipse.swt.events.DisposeEvent;
 import org.eclipse.swt.widgets.Composite;
 
+/**
+ * Specialized {@link org.eclipse.compare.contentmergeviewer.ContentMergeViewer} used to display Sirius
+ * diagram comparison.
+ * 
+ * @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
+ */
 public class SiriusContentMergeViewer extends DiagramContentMergeViewer {
 	private Set<DiagramMappingsManager> siriusMappingManagers = new LinkedHashSet<>();
 
 	private Set<DiagramDescriptionMappingsManager> siriusDescriptionMappingManagers = new LinkedHashSet<>();
 
+	/**
+	 * Default constructor.
+	 * 
+	 * @see org.eclipse.emf.compare.diagram.ide.ui.internal.contentmergeviewer.diagram.DiagramContentMergeViewer#DiagramContentMergeViewer(Composite,
+	 *      EMFCompareConfiguration)
+	 * @param parent
+	 *            the parent composite to build the UI in.
+	 * @param config
+	 *            the {@link EMFCompareConfiguration}.
+	 */
 	public SiriusContentMergeViewer(Composite parent, EMFCompareConfiguration config) {
 		super(parent, config);
 	}
 
+	/**
+	 * {@inheritDoc}
+	 */
 	@Override
 	protected void updateContent(Object ancestor, Object left, Object right) {
 		super.updateContent(ancestor, left, right);
@@ -43,6 +65,9 @@
 		getSiriusDiagram(right).ifPresent(this::storeManagers);
 	}
 
+	/**
+	 * {@inheritDoc}
+	 */
 	@Override
 	protected void handleDispose(DisposeEvent event) {
 		super.handleDispose(event);
@@ -72,4 +97,14 @@
 		siriusDescriptionMappingManagers.add(DiagramDescriptionMappingsRegistry.INSTANCE
 				.getDiagramDescriptionMappingsManager(null, diagram.getDescription()));
 	}
+
+	/**
+	 * {@inheritDoc}
+	 */
+	@Override
+	protected void createToolItems(ToolBarManager toolBarManager) {
+		super.createToolItems(toolBarManager);
+		SiriusComparisonAsImageFileAction saveAsImageFileAction = new SiriusComparisonAsImageFileAction(this);
+		toolBarManager.insert(0, new ActionContributionItem(saveAsImageFileAction));
+	}
 }
diff --git a/plugins/org.eclipse.emf.compare.diagram.ide.ui.sirius/src/org/eclipse/emf/compare/diagram/ide/ui/sirius/internal/tools/actions/ExportComparisonAction.java b/plugins/org.eclipse.emf.compare.diagram.ide.ui.sirius/src/org/eclipse/emf/compare/diagram/ide/ui/sirius/internal/tools/actions/ExportComparisonAction.java
new file mode 100644
index 0000000..3b15f41
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.diagram.ide.ui.sirius/src/org/eclipse/emf/compare/diagram/ide/ui/sirius/internal/tools/actions/ExportComparisonAction.java
@@ -0,0 +1,113 @@
+/*******************************************************************************
+ * Copyright (c) 2020 Obeo.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * 
+ * Contributors:
+ *     Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.diagram.ide.ui.sirius.internal.tools.actions;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.Collection;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.emf.compare.diagram.ide.ui.internal.contentmergeviewer.diagram.AbstractDecoratorManager.AbstractDecorator;
+import org.eclipse.emf.compare.diagram.ide.ui.sirius.internal.tools.image.EMFCompareCopyToImageUtil;
+import org.eclipse.gmf.runtime.diagram.ui.editparts.DiagramEditPart;
+import org.eclipse.gmf.runtime.diagram.ui.image.ImageFileFormat;
+import org.eclipse.ui.actions.WorkspaceModifyOperation;
+
+/**
+ * Export action for diagram comparison.
+ * 
+ * @author <a href="mailto:glenn.plouhinec@obeo.fr">Glenn Plouhinec</a>
+ */
+@SuppressWarnings("restriction")
+public class ExportComparisonAction extends WorkspaceModifyOperation {
+
+	/**
+	 * The diagram to be copied from the left side of the comparison in the ContentMergeViewer.
+	 */
+	private DiagramEditPart leftDiagramEditPart;
+
+	/**
+	 * The diagram to be copied from the right side of the comparison in the ContentMergeViewer.
+	 */
+	private DiagramEditPart rightDiagramEditPart;
+
+	/**
+	 * Decorators representing Phantoms and Markers in the ContentMergeViewer.
+	 */
+	private Collection<AbstractDecorator> decorators;
+
+	/**
+	 * The destination folder chosen in the dialog.
+	 */
+	private IPath outputPath;
+
+	/**
+	 * The image format chosen in the dialog.
+	 */
+	private ImageFileFormat imageFormat;
+
+	/**
+	 * Constructor used to export leftDiagramEditPart and rightDiagramEditPart as image.
+	 * 
+	 * @param path
+	 *            the folder in which store the images, result of the export.
+	 * @param format
+	 *            the format of the image, result of the export.
+	 * @param leftDEP
+	 *            The left {@link DiagramEditPart} to be copied.
+	 * @param rightDEP
+	 *            The right {@link DiagramEditPart} to be copied.
+	 * @param abstractDecorators
+	 *            The {@link AbstractDecorator}s of the ContentMergeViewer.
+	 */
+	public ExportComparisonAction(IPath path, ImageFileFormat format, DiagramEditPart leftDEP,
+			DiagramEditPart rightDEP, Collection<AbstractDecorator> abstractDecorators) {
+		outputPath = path;
+		imageFormat = format;
+		leftDiagramEditPart = leftDEP;
+		rightDiagramEditPart = rightDEP;
+		decorators = abstractDecorators;
+	}
+
+	/**
+	 * Overridden to do the export work. {@inheritDoc}
+	 */
+	@Override
+	protected void execute(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
+		try {
+			createImageFiles(monitor);
+		} catch (final OutOfMemoryError | CoreException e) {
+			// Catching OOM as this could be thrown if the image to export requires too large a buffer
+			throw new InvocationTargetException(e, e.getLocalizedMessage());
+		} finally {
+			monitor.done();
+			if (monitor.isCanceled()) {
+				throw new InterruptedException("The operation was cancelled");
+			}
+		}
+	}
+
+	/**
+	 * Create the image files.
+	 *
+	 * @param monitor
+	 *            the progress monitor
+	 * @throws CoreException
+	 *             if one or several images creation failed. The Exception contains a Status with the causes
+	 *             of this Exception.
+	 */
+	protected void createImageFiles(final IProgressMonitor monitor) throws CoreException {
+		EMFCompareCopyToImageUtil tool = new EMFCompareCopyToImageUtil(leftDiagramEditPart,
+				rightDiagramEditPart, decorators);
+		tool.copyAllToImage(outputPath, imageFormat, monitor);
+	}
+}
diff --git a/plugins/org.eclipse.emf.compare.diagram.ide.ui.sirius/src/org/eclipse/emf/compare/diagram/ide/ui/sirius/internal/tools/actions/SiriusComparisonAsImageFileAction.java b/plugins/org.eclipse.emf.compare.diagram.ide.ui.sirius/src/org/eclipse/emf/compare/diagram/ide/ui/sirius/internal/tools/actions/SiriusComparisonAsImageFileAction.java
new file mode 100644
index 0000000..bbf4c6a
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.diagram.ide.ui.sirius/src/org/eclipse/emf/compare/diagram/ide/ui/sirius/internal/tools/actions/SiriusComparisonAsImageFileAction.java
@@ -0,0 +1,129 @@
+/*******************************************************************************
+ * Copyright (c) 2020 Obeo.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * 
+ * Contributors:
+ *     Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.diagram.ide.ui.sirius.internal.tools.actions;
+
+import java.lang.reflect.InvocationTargetException;
+
+import org.eclipse.emf.compare.diagram.ide.ui.internal.contentmergeviewer.diagram.DiagramContentMergeViewer;
+import org.eclipse.emf.compare.diagram.ide.ui.internal.contentmergeviewer.diagram.DiagramMergeViewer;
+import org.eclipse.emf.compare.diagram.ide.ui.sirius.CompareDiagramIDEUISiriusPlugin;
+import org.eclipse.emf.compare.diagram.ide.ui.sirius.internal.tools.dialog.ExportComparisonDialog;
+import org.eclipse.gef.RootEditPart;
+import org.eclipse.gmf.runtime.diagram.ui.editparts.DiagramEditPart;
+import org.eclipse.gmf.runtime.diagram.ui.editparts.DiagramRootEditPart;
+import org.eclipse.jface.action.Action;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.dialogs.ProgressMonitorDialog;
+import org.eclipse.jface.window.Window;
+import org.eclipse.sirius.ui.tools.api.actions.export.SizeTooLargeException;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+
+/**
+ * This class allows to run the comparison export as image.
+ * 
+ * @author <a href="mailto:glenn.plouhinec@obeo.fr">Glenn Plouhinec</a>
+ */
+@SuppressWarnings("restriction")
+public class SiriusComparisonAsImageFileAction extends Action {
+
+	/**
+	 * The {@link DiagramContentMergeViewer} which contains the left, right and ancestor
+	 * {@link DiagramMergeViewer} once initialized.
+	 */
+	private DiagramContentMergeViewer contentMergeViewer;
+
+	/**
+	 * The left {@link DiagramEditPart} to export as image.
+	 */
+	private DiagramEditPart leftDiagramEditPart;
+
+	/**
+	 * The right {@link DiagramEditPart} to export as image.
+	 */
+	private DiagramEditPart rightDiagramEditPart;
+
+	/**
+	 * Constructor.
+	 * 
+	 * @param mergeViewer
+	 *            the {@link DiagramContentMergeViewer}.
+	 */
+	public SiriusComparisonAsImageFileAction(DiagramContentMergeViewer mergeViewer) {
+		super("Export diagram comparison as image",
+				CompareDiagramIDEUISiriusPlugin.getImageDescriptor("icons/screenshot.gif")); //$NON-NLS-1$
+		contentMergeViewer = mergeViewer;
+	}
+
+	@Override
+	public void run() {
+		setEditParts();
+		exportRepresentation();
+	}
+
+	/**
+	 * Used to initialize leftDiagramEditPart and rightDiagramEditPart once the
+	 * {@link DiagramContentMergeViewer} has been set.
+	 */
+	private void setEditParts() {
+		RootEditPart leftRootEP = contentMergeViewer.getLeftMergeViewer().getGraphicalViewer()
+				.getRootEditPart();
+		RootEditPart rightRootEP = contentMergeViewer.getRightMergeViewer().getGraphicalViewer()
+				.getRootEditPart();
+		if (leftRootEP instanceof DiagramRootEditPart && rightRootEP instanceof DiagramRootEditPart) {
+			DiagramRootEditPart leftRootEditPart = (DiagramRootEditPart)leftRootEP;
+			DiagramRootEditPart rightRootEditPart = (DiagramRootEditPart)rightRootEP;
+			if (leftRootEditPart.getContents() instanceof DiagramEditPart
+					&& rightRootEditPart.getContents() instanceof DiagramEditPart) {
+				leftDiagramEditPart = (DiagramEditPart)leftRootEditPart.getContents();
+				rightDiagramEditPart = (DiagramEditPart)rightRootEditPart.getContents();
+			}
+		}
+	}
+
+	/**
+	 * Display the export path and file format dialog and then export the representations.
+	 */
+	protected void exportRepresentation() {
+
+		final Shell shell = Display.getCurrent().getActiveShell();
+		ExportComparisonDialog dialog = new ExportComparisonDialog(shell);
+
+		if (dialog.open() == Window.OK) {
+			final ExportComparisonAction exportAction = new ExportComparisonAction(dialog.getOutputPath(),
+					dialog.getImageFormat(), leftDiagramEditPart, rightDiagramEditPart,
+					contentMergeViewer.getDecoratorsManager().getAllDecorators());
+			final ProgressMonitorDialog pmd = new ProgressMonitorDialog(shell);
+
+			try {
+				pmd.run(false, false, exportAction);
+			} catch (final InvocationTargetException e) {
+				Throwable cause = e.getCause();
+				if (cause instanceof OutOfMemoryError) {
+					MessageDialog.openError(shell, "Not enough memory available to create image files",
+							cause.getMessage());
+				} else if (cause instanceof SizeTooLargeException) {
+					MessageDialog.openError(shell, "Image export impossible", cause.getMessage());
+					CompareDiagramIDEUISiriusPlugin.getDefault().log(cause);
+				} else {
+					MessageDialog.openError(shell, "Error", cause.getMessage());
+				}
+			} catch (final InterruptedException e) {
+				MessageDialog.openInformation(shell, "Error", e.getMessage());
+			} finally {
+				pmd.close();
+			}
+
+		} else {
+			dialog.close();
+		}
+	}
+}
diff --git a/plugins/org.eclipse.emf.compare.diagram.ide.ui.sirius/src/org/eclipse/emf/compare/diagram/ide/ui/sirius/internal/tools/dialog/ExportComparisonDialog.java b/plugins/org.eclipse.emf.compare.diagram.ide.ui.sirius/src/org/eclipse/emf/compare/diagram/ide/ui/sirius/internal/tools/dialog/ExportComparisonDialog.java
new file mode 100644
index 0000000..8a0b5d0
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.diagram.ide.ui.sirius/src/org/eclipse/emf/compare/diagram/ide/ui/sirius/internal/tools/dialog/ExportComparisonDialog.java
@@ -0,0 +1,599 @@
+/*******************************************************************************
+ * Copyright (c) 2020 Obeo.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * 
+ * Contributors:
+ *     Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.diagram.ide.ui.sirius.internal.tools.dialog;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.List;
+
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.emf.compare.diagram.ide.ui.sirius.CompareDiagramIDEUISiriusPlugin;
+import org.eclipse.gmf.runtime.diagram.ui.image.ImageFileFormat;
+import org.eclipse.jface.dialogs.Dialog;
+import org.eclipse.jface.dialogs.IDialogConstants;
+import org.eclipse.jface.dialogs.IDialogSettings;
+import org.eclipse.jface.resource.JFaceResources;
+import org.eclipse.sirius.common.ui.tools.api.util.SWTUtil;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.graphics.GC;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Combo;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.DirectoryDialog;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Shell;
+
+/**
+ * Dialog used to export a graphical comparison as image.
+ * 
+ * @author <a href="mailto:glenn.plouhinec@obeo.fr">Glenn Plouhinec</a>
+ */
+public class ExportComparisonDialog extends Dialog {
+
+	/**
+	 * The id for the persistent settings for this dialog.
+	 */
+	public static final String DIALOG_SETTINGS_ID = "ExportComparisonAsImagesDialog"; //$NON-NLS-1$
+
+	/**
+	 * Default extension image.
+	 */
+	protected static final ImageFileFormat DEFAULT_VALUE = ImageFileFormat.JPG;
+
+	/**
+	 * The list of values for this enumerated type.
+	 */
+	protected static final ImageFileFormat[] SAFE_VALUES = {DEFAULT_VALUE, ImageFileFormat.PNG,
+			ImageFileFormat.BMP, ImageFileFormat.GIF, };
+
+	/**
+	 * The empty string.
+	 */
+	protected static final String EMPTY_STRING = ""; //$NON-NLS-1$
+
+	/**
+	 * The image format label text.
+	 */
+	protected static final String IMAGE_FORMAT_LABEL = "&Image Format";
+
+	/**
+	 * The browse button text.
+	 */
+	protected static final String BROWSE_LABEL = "&Browse...";
+
+	/**
+	 * The id for the persistent image format setting for this dialog.
+	 */
+	protected static final String DIALOG_SETTINGS_IMAGE_FORMAT = "ExportComparisonAsImageDialog.imageFormat"; //$NON-NLS-1$
+
+	/**
+	 * An error access message.
+	 */
+	protected static final String ACCES_ERROR_MESSAGE = "Access to path is impossible";
+
+	/**
+	 * An error folder not exist message.
+	 */
+	protected static final String FOLDER_NOT_EXIST_MESSAGE = "Folder does not exist";
+
+	/**
+	 * The id for the persistent folder setting for this dialog.
+	 */
+	private static final String DIALOG_SETTINGS_FOLDER = "ExportComparisonAsImageDialog.folder"; //$NON-NLS-1$
+
+	/**
+	 * The directory dialog message.
+	 */
+	private static final String DIRECTORY_DIALOG_MESSAGE = "Select the destination folder";
+
+	/**
+	 * The directory dialog text.
+	 */
+	private static final String DIRECTORY_DIALOG_TEXT = DIRECTORY_DIALOG_MESSAGE;
+
+	/**
+	 * Popup file field label.
+	 */
+	private static final String TO_DIRECTORY = "To directory";
+
+	/**
+	 * The dialog window title.
+	 */
+	private static final String DIALOG_TITLE = "Export comparison as image file";
+
+	/**
+	 * Combo length history path.
+	 */
+	private static final int COMBO_HISTORY_LENGTH = 5;
+
+	/**
+	 * Height of the browse button.
+	 */
+	private static final int HEIGHT_BROWSE_BUTTON = 14;
+
+	/**
+	 * Width of the folder combo field.
+	 */
+	private static final int WIDTH_FOLDER_TEXT = 350;
+
+	/**
+	 * Width of the message field.
+	 */
+	private static final int WIDTH_MESSAGE = 300;
+
+	/**
+	 * The folder combo field.
+	 */
+	protected Combo folderText;
+
+	/**
+	 * The extension image combo field.
+	 */
+	protected Combo imageFormatCombo;
+
+	/**
+	 * The image format selected in the image format pulldown field.
+	 */
+	protected ImageFileFormat imageFormat;
+
+	/**
+	 * The text entered into the folder text field.
+	 */
+	protected String folder;
+
+	/**
+	 * The message image field, displays the error (X) icon when the file name or folder is invalid.
+	 */
+	private Label messageImageLabel;
+
+	/**
+	 * The message field, displays an error message when the file name or folder is invalid.
+	 */
+	private Label messageLabel;
+
+	/**
+	 * Creates an instance of the copy to image dialog.
+	 * 
+	 * @param parentShell
+	 *            the parent shell
+	 */
+	public ExportComparisonDialog(Shell parentShell) {
+		super(parentShell);
+		initDialogSettings();
+	}
+
+	/**
+	 * Resolve the selected image format to an enumerated type.
+	 * 
+	 * @param ordinal
+	 *            the selected format in the pulldown
+	 * @return the image format enumerated type
+	 */
+	public static ImageFileFormat resolveImageFormat(final int ordinal) {
+		return SAFE_VALUES[ordinal];
+	}
+
+	/**
+	 * Resolve the selected image format to an enumerated type.
+	 * 
+	 * @param imageFormat
+	 *            the selected format.
+	 * @return the image format enumerated type
+	 */
+	public static ImageFileFormat resolveImageFormat(final String imageFormat) {
+		for (ImageFileFormat element : SAFE_VALUES) {
+			if (element.getName().toLowerCase().equals(imageFormat.toLowerCase())) {
+				return element;
+			}
+		}
+		return getDefaultImageFormat();
+	}
+
+	/**
+	 * Retrieves the default image format.
+	 * 
+	 * @return the default image format.
+	 */
+	public static ImageFileFormat getDefaultImageFormat() {
+		return ImageFileFormat.JPG;
+	}
+
+	/**
+	 * Returns height and width data in a GridData for the button that was passed in. You can call
+	 * button.setLayoutData with the returned data.
+	 * 
+	 * @param button
+	 *            which has already been made. We'll be making the GridData for this button, so be sure that
+	 *            the text has already been set.
+	 * @return GridData for this button with the suggested height and width
+	 */
+	public static GridData makeButtonData(Button button) {
+		GC gc = new GC(button);
+		gc.setFont(button.getFont());
+
+		GridData data = new GridData();
+		data.heightHint = Dialog.convertVerticalDLUsToPixels(gc.getFontMetrics(), HEIGHT_BROWSE_BUTTON);
+		data.widthHint = Math.max(
+				Dialog.convertHorizontalDLUsToPixels(gc.getFontMetrics(), IDialogConstants.BUTTON_WIDTH),
+				button.computeSize(SWT.DEFAULT, SWT.DEFAULT, true).x);
+
+		gc.dispose();
+
+		return data;
+	}
+
+	/**
+	 * Returns the output path.
+	 * 
+	 * @return folder
+	 */
+	public IPath getOutputPath() {
+		return new Path(folder);
+	}
+
+	/**
+	 * Returns the destination image file format selected by the user.
+	 * 
+	 * @return the selected image file format.
+	 */
+	public ImageFileFormat getImageFormat() {
+		return imageFormat;
+	}
+
+	/**
+	 * Retrieves the persistent settings for this dialog.
+	 * 
+	 * @return the persistent settings for this dialog.
+	 */
+	protected IDialogSettings getDialogSettings() {
+		IDialogSettings settings = CompareDiagramIDEUISiriusPlugin.getDefault().getDialogSettings();
+		settings = settings.getSection(DIALOG_SETTINGS_ID);
+		if (settings == null) {
+			settings = CompareDiagramIDEUISiriusPlugin.getDefault().getDialogSettings()
+					.addNewSection(DIALOG_SETTINGS_ID);
+		}
+		return settings;
+	}
+
+	/**
+	 * Initialize the settings for this dialog.
+	 */
+	protected void initDialogSettings() {
+		final IDialogSettings dialogSettings = getDialogSettings();
+		folder = "/"; //$NON-NLS-1$
+		String workspace = ResourcesPlugin.getWorkspace().getRoot().getLocation().toOSString();
+		if (workspace != null && !workspace.equals(EMPTY_STRING)) {
+			folder = workspace;
+		}
+
+		final String persistentFolder = dialogSettings.get(DIALOG_SETTINGS_FOLDER);
+		if (persistentFolder != null) {
+			folder = persistentFolder;
+		}
+
+		final String persistentImageFormat = dialogSettings.get(DIALOG_SETTINGS_IMAGE_FORMAT);
+		if (persistentImageFormat == null) {
+			imageFormat = getDefaultImageFormat();
+		} else {
+			imageFormat = resolveImageFormat(persistentImageFormat);
+		}
+	}
+
+	/**
+	 * Retrieves the persistent settings for this dialog.
+	 */
+	protected void saveDialogSettings() {
+		final IDialogSettings dialogSettings = getDialogSettings();
+		if (dialogSettings != null) {
+			String[] directoryNames = dialogSettings.getArray(DIALOG_SETTINGS_FOLDER);
+			if (directoryNames == null || directoryNames.length == 0) {
+				directoryNames = new String[1];
+				directoryNames[0] = folder;
+			}
+			directoryNames = addToHistory(directoryNames, folder);
+			dialogSettings.put(DIALOG_SETTINGS_FOLDER, directoryNames);
+			dialogSettings.put(DIALOG_SETTINGS_IMAGE_FORMAT, imageFormat.getName());
+		}
+	}
+
+	@Override
+	protected void okPressed() {
+		imageFormat = resolveImageFormat(imageFormatCombo.getSelectionIndex());
+		super.okPressed();
+		saveDialogSettings();
+	}
+
+	/**
+	 * Handle a browse button pressed selection.
+	 */
+	protected void handleBrowseButtonPressed() {
+		final DirectoryDialog dialog = new DirectoryDialog(Display.getCurrent().getActiveShell());
+		dialog.setMessage(DIRECTORY_DIALOG_MESSAGE);
+		dialog.setText(DIRECTORY_DIALOG_TEXT);
+
+		final String dirName = folderText.getText();
+		if (!dirName.equals(EMPTY_STRING)) {
+			final File path = new File(dirName);
+			if (path.exists()) {
+				dialog.setFilterPath(new Path(dirName).toOSString());
+			}
+		}
+
+		final String selectedDirectory = dialog.open();
+		if (selectedDirectory != null) {
+			folderText.setText(selectedDirectory);
+		}
+	}
+
+	/**
+	 * Validate the folder text field.
+	 */
+	protected void validateFolderText() {
+
+		if (folderText.getText().equals(EMPTY_STRING)) {
+
+			setDialogErrorState("Folder cannot be blank");
+
+		} else {
+
+			IPath path = new Path(EMPTY_STRING);
+			if (!path.isValidPath(folderText.getText())) {
+				setDialogErrorState("Folder is not a valid path");
+				return;
+			}
+
+			final File file = new File(folderText.getText());
+			final File directory = file.getParentFile();
+
+			if (file.exists()) {
+				if (!file.canWrite()) {
+					setDialogErrorState(ACCES_ERROR_MESSAGE);
+					return;
+				}
+			} else if (directory != null && !directory.canWrite()) {
+				setDialogErrorState(ACCES_ERROR_MESSAGE);
+				return;
+			}
+			folder = folderText.getText();
+			setDialogOKState();
+		}
+	}
+
+	/**
+	 * Set the dialog into error state mode. The error image (x) label and error label are made visible and
+	 * the ok button is disabled.
+	 * 
+	 * @param message
+	 *            the error message
+	 */
+	protected void setDialogErrorState(final String message) {
+		messageLabel.setText(message);
+		messageImageLabel.setVisible(true);
+		messageLabel.setVisible(true);
+		getButton(IDialogConstants.OK_ID).setEnabled(false);
+		getButton(IDialogConstants.CANCEL_ID).getShell()
+				.setDefaultButton(getButton(IDialogConstants.CANCEL_ID));
+	}
+
+	/**
+	 * Get the supported image formats from the enumerated type.
+	 * 
+	 * @return array of supported image formats.
+	 */
+	protected String[] getImageSafeFormatItems() {
+		final String[] items = new String[SAFE_VALUES.length];
+		for (int i = 0; i < SAFE_VALUES.length; i++) {
+			items[i] = SAFE_VALUES[i].getName();
+		}
+		return items;
+	}
+
+	@Override
+	protected Control createContents(Composite parent) {
+		Control result = super.createContents(parent);
+		validateFolderText();
+		return result;
+	}
+
+	@Override
+	protected Control createDialogArea(final Composite parent) {
+		final Composite composite = (Composite)super.createDialogArea(parent);
+		createFolderGroup(composite);
+		createImageFormatGroup(composite);
+		createMessageGroup(composite);
+		initListeners();
+		return composite;
+	}
+
+	/**
+	 * Create the folder group in the dialog.
+	 * 
+	 * @param parent
+	 *            the parent widget
+	 */
+	protected void createFolderGroup(final Composite parent) {
+		final Composite composite = SWTUtil.createCompositeHorizontalFill(parent, 3, false);
+		SWTUtil.createLabel(composite, TO_DIRECTORY);
+
+		folderText = new Combo(composite, SWT.SINGLE | SWT.BORDER);
+		restoreWidgetValues();
+		folderText.setText(folder);
+		final GridData gridData = new GridData(GridData.FILL_HORIZONTAL);
+		gridData.widthHint = WIDTH_FOLDER_TEXT;
+		folderText.setLayoutData(gridData);
+
+		final Button button = new Button(composite, SWT.PUSH);
+		button.setText(BROWSE_LABEL);
+		button.setLayoutData(makeButtonData(button));
+		button.addSelectionListener(new SelectionAdapter() {
+			@Override
+			public void widgetSelected(final SelectionEvent event) {
+				handleBrowseButtonPressed();
+			}
+		});
+	}
+
+	/**
+	 * Create the image format group in the dialog.
+	 * 
+	 * @param parent
+	 *            the parent widget
+	 */
+	protected void createImageFormatGroup(final Composite parent) {
+		final Composite composite = SWTUtil.createCompositeHorizontalFill(parent, 2, false);
+		SWTUtil.createLabel(composite, IMAGE_FORMAT_LABEL);
+
+		String[] imageSafeFormatItems = getImageSafeFormatItems();
+
+		Control imageFormatControl;
+		if (imageSafeFormatItems != null && imageSafeFormatItems.length > 0) {
+
+			if (imageSafeFormatItems.length == 1) {
+				imageFormatControl = SWTUtil.createLabel(composite, imageSafeFormatItems[0]);
+
+			} else {
+				imageFormatCombo = new Combo(composite, SWT.DROP_DOWN | SWT.READ_ONLY);
+				imageFormatCombo.setItems(imageSafeFormatItems);
+				imageFormatCombo.setText(imageFormat.getName());
+				imageFormatControl = imageFormatCombo;
+			}
+		} else {
+			imageFormatControl = SWTUtil.createLabel(composite, DEFAULT_VALUE.getName());
+		}
+		final GridData gridData = new GridData(SWT.FILL, SWT.CENTER, true, true);
+		imageFormatControl.setLayoutData(gridData);
+
+	}
+
+	/**
+	 * {@inheritDoc} Configures the shell in preparation for opening this window in it.
+	 *
+	 * @see org.eclipse.jface.window.Window#configureShell(org.eclipse.swt.widgets.Shell)
+	 */
+	@Override
+	protected void configureShell(final Shell shell) {
+		super.configureShell(shell);
+		shell.setText(DIALOG_TITLE);
+	}
+
+	/**
+	 * Hook method for restoring widget values to the values that they held last time this wizard was used to
+	 * completion.
+	 */
+	protected void restoreWidgetValues() {
+		IDialogSettings settings = getDialogSettings();
+		if (settings != null) {
+			String[] directoryNames = settings.getArray(DIALOG_SETTINGS_FOLDER);
+			if (directoryNames == null || directoryNames.length == 0) {
+				return;
+			}
+			folder = directoryNames[0];
+			for (String directoryName : directoryNames) {
+				folderText.add(directoryName);
+			}
+		}
+	}
+
+	/**
+	 * Create the message group in the dialog used to display error messages.
+	 * 
+	 * @param parent
+	 *            the parent widget
+	 */
+	private void createMessageGroup(final Composite parent) {
+		final Composite composite = SWTUtil.createCompositeHorizontalFill(parent, 2, false);
+
+		messageImageLabel = new Label(composite, SWT.NONE);
+		messageImageLabel.setImage(JFaceResources.getImage(DLG_IMG_MESSAGE_ERROR));
+		messageImageLabel.setVisible(false);
+
+		messageLabel = new Label(composite, SWT.NONE);
+		final GridData gridData = new GridData(GridData.HORIZONTAL_ALIGN_FILL | GridData.FILL_HORIZONTAL);
+		gridData.widthHint = WIDTH_MESSAGE;
+		messageLabel.setLayoutData(gridData);
+		messageLabel.setVisible(false);
+	}
+
+	/**
+	 * Set the dialog into ok state mode. The error image (x) label and error label and made not visible and
+	 * the ok button is enabled.
+	 */
+	private void setDialogOKState() {
+		messageImageLabel.setVisible(false);
+		messageLabel.setVisible(false);
+		getButton(IDialogConstants.OK_ID).setEnabled(true);
+		getButton(IDialogConstants.OK_ID).getShell().setDefaultButton(getButton(IDialogConstants.OK_ID));
+	}
+
+	/**
+	 * Adds an entry to a history, while taking care of duplicate history items and excessively long
+	 * histories. The assumption is made that all histories should be of length
+	 * <code>WizardDataTransferPage.COMBO_HISTORY_LENGTH</code>.
+	 * 
+	 * @param history
+	 *            the current history
+	 * @param newEntry
+	 *            the entry to add to the history
+	 * @return String[] tab of history
+	 */
+	protected String[] addToHistory(String[] history, String newEntry) {
+		java.util.ArrayList<String> l = new java.util.ArrayList<String>(Arrays.asList(history));
+		addToHistory(l, newEntry);
+		String[] r = new String[l.size()];
+		l.toArray(r);
+		return r;
+	}
+
+	/**
+	 * Adds an entry to a history, while taking care of duplicate history items and excessively long
+	 * histories. The assumption is made that all histories should be of length
+	 * <code>WizardDataTransferPage.COMBO_HISTORY_LENGTH</code>.
+	 * 
+	 * @param history
+	 *            the current history
+	 * @param newEntry
+	 *            the entry to add to the history
+	 */
+	protected void addToHistory(List<String> history, String newEntry) {
+		history.remove(newEntry);
+		history.add(0, newEntry);
+		if (history.size() > COMBO_HISTORY_LENGTH) {
+			history.remove(COMBO_HISTORY_LENGTH);
+		}
+	}
+
+	/**
+	 * Initialize the field listeners when all field are created.
+	 */
+	protected void initListeners() {
+		folderText.addModifyListener(new ModifyListener() {
+			@Override
+			public void modifyText(final ModifyEvent e) {
+				validateFolderText();
+			}
+		});
+		imageFormatCombo.addSelectionListener(new SelectionAdapter() {
+			@Override
+			public void widgetSelected(final SelectionEvent e) {
+				imageFormat = resolveImageFormat(imageFormatCombo.getSelectionIndex());
+			}
+		});
+	}
+}
diff --git a/plugins/org.eclipse.emf.compare.diagram.ide.ui.sirius/src/org/eclipse/emf/compare/diagram/ide/ui/sirius/internal/tools/image/EMFCompareCopyToImageUtil.java b/plugins/org.eclipse.emf.compare.diagram.ide.ui.sirius/src/org/eclipse/emf/compare/diagram/ide/ui/sirius/internal/tools/image/EMFCompareCopyToImageUtil.java
new file mode 100644
index 0000000..2883db9
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.diagram.ide.ui.sirius/src/org/eclipse/emf/compare/diagram/ide/ui/sirius/internal/tools/image/EMFCompareCopyToImageUtil.java
@@ -0,0 +1,300 @@
+/*******************************************************************************
+ * Copyright (c) 2020 Obeo.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * 
+ * Contributors:
+ *     Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.diagram.ide.ui.sirius.internal.tools.image;
+
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.awt.Image;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import javax.imageio.ImageIO;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.emf.compare.diagram.ide.ui.internal.contentmergeviewer.diagram.AbstractDecoratorManager.AbstractDecorator;
+import org.eclipse.emf.compare.rcp.ui.mergeviewer.IMergeViewer.MergeViewerSide;
+import org.eclipse.gmf.runtime.diagram.ui.editparts.DiagramEditPart;
+import org.eclipse.gmf.runtime.diagram.ui.image.ImageFileFormat;
+import org.eclipse.gmf.runtime.diagram.ui.render.clipboard.DiagramGenerator;
+import org.eclipse.gmf.runtime.draw2d.ui.render.awt.internal.image.ImageExporter;
+import org.eclipse.gmf.runtime.notation.Diagram;
+import org.eclipse.sirius.common.tools.api.util.FileUtil;
+import org.eclipse.sirius.diagram.DDiagram;
+import org.eclipse.sirius.diagram.ui.tools.api.part.DiagramEditPartService;
+
+/**
+ * This class is used to manage the creation of images from DiagramEditPart, using the Sirius API.
+ * 
+ * @author <a href="mailto:glenn.plouhinec@obeo.fr">Glenn Plouhinec</a>
+ */
+@SuppressWarnings("restriction")
+public class EMFCompareCopyToImageUtil extends DiagramEditPartService {
+
+	/**
+	 * The diagram to be copied from the left side of the comparison in the ContentMergeViewer.
+	 */
+	private DiagramEditPart leftDiagramEditPart;
+
+	/**
+	 * The diagram to be copied from the right side of the comparison in the ContentMergeViewer.
+	 */
+	private DiagramEditPart rightDiagramEditPart;
+
+	/**
+	 * Decorators representing Phantoms and Markers in the ContentMergeViewer.
+	 */
+	private Collection<AbstractDecorator> decorators;
+
+	/**
+	 * Constructor.
+	 * 
+	 * @param leftDEP
+	 *            The left {@link DiagramEditPart} to be copied.
+	 * @param rightDEP
+	 *            The right {@link DiagramEditPart} to be copied.
+	 * @param abstractDecorators
+	 *            The {@link AbstractDecorator}s of the ContentMergeViewer.
+	 */
+	public EMFCompareCopyToImageUtil(DiagramEditPart leftDEP, DiagramEditPart rightDEP,
+			Collection<AbstractDecorator> abstractDecorators) {
+		leftDiagramEditPart = leftDEP;
+		rightDiagramEditPart = rightDEP;
+		decorators = abstractDecorators;
+	}
+
+	/**
+	 * Get the left {@link DiagramEditPart} to be copied.
+	 * 
+	 * @return the left {@link DiagramEditPart} to be copied.
+	 */
+	public DiagramEditPart getLeftDiagramEditPart() {
+		return leftDiagramEditPart;
+	}
+
+	/**
+	 * Get the right {@link DiagramEditPart} to be copied.
+	 * 
+	 * @return the right {@link DiagramEditPart} to be copied.
+	 */
+	public DiagramEditPart getRightDiagramEditPart() {
+		return rightDiagramEditPart;
+	}
+
+	/**
+	 * Get the {@link AbstractDecorator}s of the ContentMergeViewer.
+	 * 
+	 * @return the {@link AbstractDecorator}s of the ContentMergeViewer.
+	 */
+	public Collection<AbstractDecorator> getDecorators() {
+		return decorators;
+	}
+
+	/**
+	 * Only overridden to use {@link EMFCompareDiagramImageGenerator} instead of
+	 * org.eclipse.sirius.diagram.ui.tools.internal.render.SiriusDiagramImageGenerator.
+	 */
+	@Override
+	protected DiagramGenerator getDiagramGenerator(DiagramEditPart diagramEP, ImageFileFormat format) {
+		return new EMFCompareDiagramImageGenerator(diagramEP, decorators, getSide(diagramEP));
+	}
+
+	/**
+	 * Used to find out which side of the MergeViewer the diagram belongs to.
+	 * 
+	 * @param diagramEP
+	 *            the {@link DiagramEditPart} for which we are looking for the {@link MergeViewerSide}.
+	 * @return the corresponding {@link MergeViewerSide}.
+	 */
+	protected MergeViewerSide getSide(DiagramEditPart diagramEP) {
+		if (diagramEP.equals(leftDiagramEditPart)) {
+			return MergeViewerSide.LEFT;
+		} else {
+			return MergeViewerSide.RIGHT;
+		}
+	}
+
+	/**
+	 * Executes the creation of all images. One image is created for the LeftDiagramEditPart, and another one
+	 * for the RightDiagramEditPart, then the two images are joined together to return the image resulting
+	 * from the diagram comparison.
+	 * 
+	 * @param destination
+	 *            the destination folder chosen in the dialog.
+	 * @param format
+	 *            the image format chosen in the dialog.
+	 * @param monitor
+	 *            the progress monitor.
+	 * @throws CoreException
+	 *             if the export of the image to a file has failed.
+	 */
+	public void copyAllToImage(IPath destination, ImageFileFormat format, IProgressMonitor monitor)
+			throws CoreException {
+		IPath leftPath = getValidPath(destination, leftDiagramEditPart, format);
+		copyToImage(leftDiagramEditPart, leftPath, format, monitor);
+		IPath rightPath = getValidPath(destination, rightDiagramEditPart, format);
+		copyToImage(rightDiagramEditPart, rightPath, format, monitor);
+		try {
+			appendImages(destination, leftPath, rightPath, format, monitor);
+		} catch (CoreException | IOException e) {
+			e.printStackTrace();
+		}
+	}
+
+	/**
+	 * Used to recover the files of the two generated images, and create a new one with the two joined images.
+	 * 
+	 * @param destination
+	 *            the destination folder for the resulting image.
+	 * @param leftPath
+	 *            the path to the left image file.
+	 * @param rightPath
+	 *            the path to the right image file
+	 * @param format
+	 *            the image format.
+	 * @param monitor
+	 *            the progress monitor.
+	 * @throws CoreException
+	 *             if the export of the image to a file has failed.
+	 * @throws IOException
+	 *             if the images on the left or right could not be read.
+	 */
+	protected void appendImages(IPath destination, IPath leftPath, IPath rightPath, ImageFileFormat format,
+			IProgressMonitor monitor) throws CoreException, IOException {
+		IPath fileName = getValidPath(destination, null, format);
+
+		String exportFormat = ImageExporter.PNG_FILE;
+		java.awt.Image leftImage = ImageIO.read(leftPath.toFile());
+		java.awt.Image rightImage = ImageIO.read(rightPath.toFile());
+		if (leftImage instanceof BufferedImage && rightImage instanceof BufferedImage) {
+			java.awt.Image finalImage = joinImage((BufferedImage)leftImage, (BufferedImage)rightImage);
+			monitor.worked(1);
+			ImageExporter.exportToFile(fileName, (BufferedImage)finalImage, exportFormat, monitor,
+					format.getQuality());
+
+		}
+		List<IPath> filesToDelete = new ArrayList<IPath>();
+		filesToDelete.add(rightPath);
+		filesToDelete.add(leftPath);
+		deleteFiles(filesToDelete);
+	}
+
+	/**
+	 * Used to delete files.
+	 * 
+	 * @param filesToDelete
+	 *            the list of files to delete.
+	 */
+	protected void deleteFiles(List<IPath> filesToDelete) {
+		for (IPath path : filesToDelete) {
+			path.toFile().delete();
+		}
+	}
+
+	/**
+	 * Joins the two images and adds a separator.
+	 * 
+	 * @param leftImage
+	 *            the image on the left.
+	 * @param rightImage
+	 *            the image on the right.
+	 * @return the new generated image.
+	 */
+	protected Image joinImage(BufferedImage leftImage, BufferedImage rightImage) {
+
+		int separator = (leftImage.getWidth() + rightImage.getWidth()) / 100;
+		int wid = leftImage.getWidth() + rightImage.getWidth() + separator;
+		int height = Math.max(leftImage.getHeight(), rightImage.getHeight());
+
+		BufferedImage newImage = new BufferedImage(wid, height, leftImage.getType());
+
+		Graphics2D g2 = newImage.createGraphics();
+
+		Color oldColor = g2.getColor();
+		g2.setPaint(Color.WHITE);
+		g2.fillRect(0, 0, wid + separator, height);
+		g2.setPaint(Color.BLACK);
+		g2.fillRect(leftImage.getWidth(), 0, separator, height);
+		g2.setColor(oldColor);
+
+		g2.drawImage(leftImage, 0, 0, null);
+		g2.drawImage(rightImage, leftImage.getWidth() + separator, 0, null);
+
+		g2.dispose();
+		return newImage;
+	}
+
+	/**
+	 * Get the {@link IPath} of the file to write. If the file already exists, it appends a number to its
+	 * name.
+	 * 
+	 * @param destination
+	 *            the folder where to write the file.
+	 * @param diagramEP
+	 *            the {@link DiagramEditPart} used to retrieve the name of the diagram.
+	 * @param format
+	 *            the file format.
+	 * @return the path of the file to write.
+	 */
+	protected IPath getValidPath(IPath destination, DiagramEditPart diagramEP, ImageFileFormat format) {
+		String filename = null;
+		String extension = format.getName().toLowerCase();
+		if (diagramEP != null) {
+			Diagram diagram = (Diagram)diagramEP.getModel();
+			DDiagram ddiagram = (DDiagram)diagram.getElement();
+			filename = ddiagram.getDescription().getName();
+			if (diagramEP == getLeftDiagramEditPart()) {
+				filename = "left_" + filename; //$NON-NLS-1$
+			} else if (diagramEP == getRightDiagramEditPart()) {
+				filename = "right_" + filename; //$NON-NLS-1$
+			}
+		} else {
+			filename = "Comparison_Image"; //$NON-NLS-1$
+		}
+		IPath filePath;
+		final StringBuffer file = new StringBuffer(filename).append('.').append(extension);
+
+		final String filenameWithExtension = validFilename(file.toString());
+		if (destination.append(filenameWithExtension).toFile().exists()) {
+			int version = 1;
+
+			do {
+				final String newFileName = validFilename(new StringBuffer(filename).append('_')
+						.append(String.valueOf(version)).append('.').append(extension).toString());
+				filePath = destination.append(newFileName);
+				version++;
+			} while (filePath.toFile().exists());
+		} else {
+			filePath = destination.append(filenameWithExtension);
+		}
+		return filePath;
+	}
+
+	/**
+	 * Check if the filename is valid or not.
+	 * 
+	 * @param filename
+	 *            the filename to check.
+	 * @return a valid filename
+	 */
+	protected String validFilename(String filename) {
+		final FileUtil util = new FileUtil(filename);
+		if (util.isValid()) {
+			return filename;
+		} else {
+			return util.getValidFilename();
+		}
+	}
+}
diff --git a/plugins/org.eclipse.emf.compare.diagram.ide.ui.sirius/src/org/eclipse/emf/compare/diagram/ide/ui/sirius/internal/tools/image/EMFCompareDiagramImageGenerator.java b/plugins/org.eclipse.emf.compare.diagram.ide.ui.sirius/src/org/eclipse/emf/compare/diagram/ide/ui/sirius/internal/tools/image/EMFCompareDiagramImageGenerator.java
new file mode 100644
index 0000000..e2a5b5e
--- /dev/null
+++ b/plugins/org.eclipse.emf.compare.diagram.ide.ui.sirius/src/org/eclipse/emf/compare/diagram/ide/ui/sirius/internal/tools/image/EMFCompareDiagramImageGenerator.java
@@ -0,0 +1,99 @@
+/*******************************************************************************
+ * Copyright (c) 2020 Obeo.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ * 
+ * Contributors:
+ *     Obeo - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.emf.compare.diagram.ide.ui.sirius.internal.tools.image;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.eclipse.draw2d.Graphics;
+import org.eclipse.draw2d.IFigure;
+import org.eclipse.draw2d.geometry.Point;
+import org.eclipse.emf.compare.diagram.ide.ui.internal.contentmergeviewer.diagram.AbstractDecoratorManager.AbstractDecorator;
+import org.eclipse.emf.compare.rcp.ui.mergeviewer.IMergeViewer.MergeViewerSide;
+import org.eclipse.gef.LayerConstants;
+import org.eclipse.gef.editparts.LayerManager;
+import org.eclipse.gmf.runtime.diagram.ui.editparts.DiagramEditPart;
+import org.eclipse.sirius.diagram.ui.tools.internal.render.SiriusDiagramImageGenerator;
+import org.eclipse.swt.graphics.Rectangle;
+
+/**
+ * This class is used to draw the {@link DiagramEditPart} in an image. Any existing Phantoms and Markers
+ * decorators in the ContentMergeViewer are added to the generated images.
+ * 
+ * @author <a href="mailto:glenn.plouhinec@obeo.fr">Glenn Plouhinec</a>
+ */
+@SuppressWarnings("restriction")
+public class EMFCompareDiagramImageGenerator extends SiriusDiagramImageGenerator {
+
+	/**
+	 * The {@link AbstractDecorator}s to draw.
+	 */
+	private List<AbstractDecorator> decorators;
+
+	/**
+	 * The layer on which the {@link AbstractDecorator}s are drawn.
+	 */
+	private IFigure layer;
+
+	/**
+	 * Constructor.
+	 * 
+	 * @param dgrmEP
+	 *            the {@link DiagramEditPart} to draw.
+	 * @param abstractDecorators
+	 *            the list of all {@link AbstractDecorator}s
+	 * @param side
+	 *            the list of all decorators on each side of the ContentMergeViewer.
+	 */
+	public EMFCompareDiagramImageGenerator(DiagramEditPart dgrmEP,
+			Collection<AbstractDecorator> abstractDecorators, MergeViewerSide side) {
+		super(dgrmEP);
+		decorators = new ArrayList<>();
+		for (AbstractDecorator decorator : abstractDecorators) {
+			if (decorator.getSide() == side) {
+				decorators.add(decorator);
+			}
+		}
+		layer = getLayer();
+	}
+
+	/**
+	 * Get the layer on which the {@link DiagramEditPart} is represented on the ContentMergeViewer.
+	 * 
+	 * @return the returns the layer corresponding to the {@link DiagramEditPart}.
+	 */
+	protected IFigure getLayer() {
+		return LayerManager.Helper.find(getDiagramEditPart()).getLayer(LayerConstants.PRINTABLE_LAYERS);
+	}
+
+	/**
+	 * {@inheritDoc} Overridden to draw the {@link AbstractDecorator}s.
+	 */
+	@SuppressWarnings("rawtypes")
+	@Override
+	protected void renderToGraphics(Graphics graphics, Point translateOffset, List editparts) {
+		super.renderToGraphics(graphics, translateOffset, editparts);
+		for (AbstractDecorator decorator : decorators) {
+			paintFigure(graphics, decorator.getFigure());
+		}
+	}
+
+	/**
+	 * {@inheritDoc} Overridden to get the layer size.
+	 */
+	@SuppressWarnings("rawtypes")
+	@Override
+	public Rectangle calculateImageRectangle(List editparts) {
+		return new Rectangle(layer.getBounds().x, layer.getBounds().y, layer.getBounds().width,
+				layer.getBounds().height);
+	}
+}
diff --git a/plugins/org.eclipse.emf.compare.diagram.ide.ui/src/org/eclipse/emf/compare/diagram/ide/ui/internal/contentmergeviewer/diagram/AbstractDecoratorManager.java b/plugins/org.eclipse.emf.compare.diagram.ide.ui/src/org/eclipse/emf/compare/diagram/ide/ui/internal/contentmergeviewer/diagram/AbstractDecoratorManager.java
index 1043020..b677504 100644
--- a/plugins/org.eclipse.emf.compare.diagram.ide.ui/src/org/eclipse/emf/compare/diagram/ide/ui/internal/contentmergeviewer/diagram/AbstractDecoratorManager.java
+++ b/plugins/org.eclipse.emf.compare.diagram.ide.ui/src/org/eclipse/emf/compare/diagram/ide/ui/internal/contentmergeviewer/diagram/AbstractDecoratorManager.java
@@ -54,7 +54,7 @@
 	 * 
 	 * @author <a href="mailto:cedric.notot@obeo.fr">Cedric Notot</a>
 	 */
-	protected abstract class AbstractDecorator {
+	public abstract class AbstractDecorator {
 
 		/** The reference <code>View</code> for this decorator. */
 		protected View fOriginView;
diff --git a/plugins/org.eclipse.emf.compare.diagram.ide.ui/src/org/eclipse/emf/compare/diagram/ide/ui/internal/contentmergeviewer/diagram/DecoratorsManager.java b/plugins/org.eclipse.emf.compare.diagram.ide.ui/src/org/eclipse/emf/compare/diagram/ide/ui/internal/contentmergeviewer/diagram/DecoratorsManager.java
index 7b8ebb3..51f4ecc 100644
--- a/plugins/org.eclipse.emf.compare.diagram.ide.ui/src/org/eclipse/emf/compare/diagram/ide/ui/internal/contentmergeviewer/diagram/DecoratorsManager.java
+++ b/plugins/org.eclipse.emf.compare.diagram.ide.ui/src/org/eclipse/emf/compare/diagram/ide/ui/internal/contentmergeviewer/diagram/DecoratorsManager.java
@@ -10,7 +10,11 @@
  *******************************************************************************/
 package org.eclipse.emf.compare.diagram.ide.ui.internal.contentmergeviewer.diagram;
 
+import java.util.ArrayList;
+import java.util.Collection;
+
 import org.eclipse.emf.compare.Diff;
+import org.eclipse.emf.compare.diagram.ide.ui.internal.contentmergeviewer.diagram.AbstractDecoratorManager.AbstractDecorator;
 import org.eclipse.emf.compare.ide.ui.internal.configuration.EMFCompareConfiguration;
 import org.eclipse.emf.compare.rcp.ui.mergeviewer.ICompareColor;
 
@@ -96,4 +100,16 @@
 		fPhantomManager.removeAll();
 	}
 
+	/**
+	 * {@inheritDoc}
+	 * 
+	 * @see org.eclipse.emf.compare.diagram.ide.ui.internal.contentmergeviewer.diagram.IDecoratorManager#getAllDecorators()
+	 */
+	public Collection<AbstractDecorator> getAllDecorators() {
+		Collection<AbstractDecorator> decorators = new ArrayList<AbstractDecorator>();
+		decorators.addAll(fMarkerManager.getAllDecorators());
+		decorators.addAll(fPhantomManager.getAllDecorators());
+		return decorators;
+	}
+
 }
diff --git a/plugins/org.eclipse.emf.compare.diagram.ide.ui/src/org/eclipse/emf/compare/diagram/ide/ui/internal/contentmergeviewer/diagram/DiagramContentMergeViewer.java b/plugins/org.eclipse.emf.compare.diagram.ide.ui/src/org/eclipse/emf/compare/diagram/ide/ui/internal/contentmergeviewer/diagram/DiagramContentMergeViewer.java
index 7280677..6e49ea3 100644
--- a/plugins/org.eclipse.emf.compare.diagram.ide.ui/src/org/eclipse/emf/compare/diagram/ide/ui/internal/contentmergeviewer/diagram/DiagramContentMergeViewer.java
+++ b/plugins/org.eclipse.emf.compare.diagram.ide.ui/src/org/eclipse/emf/compare/diagram/ide/ui/internal/contentmergeviewer/diagram/DiagramContentMergeViewer.java
@@ -93,6 +93,15 @@
 	}
 
 	/**
+	 * Get the {@link DecoratorsManager}.
+	 * 
+	 * @return the {@link DecoratorsManager}.
+	 */
+	public DecoratorsManager getDecoratorsManager() {
+		return fDecoratorsManager;
+	}
+
+	/**
 	 * {@inheritDoc}
 	 * 
 	 * @see org.eclipse.emf.compare.ide.ui.internal.contentmergeviewer.EMFCompareContentMergeViewer#getAncestorMergeViewer()
diff --git a/plugins/org.eclipse.emf.compare.diagram.ide.ui/src/org/eclipse/emf/compare/diagram/ide/ui/internal/contentmergeviewer/diagram/DiagramMergeViewer.java b/plugins/org.eclipse.emf.compare.diagram.ide.ui/src/org/eclipse/emf/compare/diagram/ide/ui/internal/contentmergeviewer/diagram/DiagramMergeViewer.java
index 38bb1ff..314c222 100644
--- a/plugins/org.eclipse.emf.compare.diagram.ide.ui/src/org/eclipse/emf/compare/diagram/ide/ui/internal/contentmergeviewer/diagram/DiagramMergeViewer.java
+++ b/plugins/org.eclipse.emf.compare.diagram.ide.ui/src/org/eclipse/emf/compare/diagram/ide/ui/internal/contentmergeviewer/diagram/DiagramMergeViewer.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2013, 2017 Obeo and others.
+ * Copyright (c) 2013, 2020 Obeo and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
@@ -52,7 +52,7 @@
  * 
  * @author <a href="mailto:cedric.notot@obeo.fr">Cedric Notot</a>
  */
-class DiagramMergeViewer extends AbstractGraphicalMergeViewer {
+public class DiagramMergeViewer extends AbstractGraphicalMergeViewer {
 
 	/**
 	 * Selection manager to forbid manual selections on graphical objects.
diff --git a/plugins/org.eclipse.emf.compare.diagram.ide.ui/src/org/eclipse/emf/compare/diagram/ide/ui/internal/contentmergeviewer/diagram/IDecoratorManager.java b/plugins/org.eclipse.emf.compare.diagram.ide.ui/src/org/eclipse/emf/compare/diagram/ide/ui/internal/contentmergeviewer/diagram/IDecoratorManager.java
index f5e3a5b..0bbd141 100644
--- a/plugins/org.eclipse.emf.compare.diagram.ide.ui/src/org/eclipse/emf/compare/diagram/ide/ui/internal/contentmergeviewer/diagram/IDecoratorManager.java
+++ b/plugins/org.eclipse.emf.compare.diagram.ide.ui/src/org/eclipse/emf/compare/diagram/ide/ui/internal/contentmergeviewer/diagram/IDecoratorManager.java
@@ -10,7 +10,10 @@
  *******************************************************************************/
 package org.eclipse.emf.compare.diagram.ide.ui.internal.contentmergeviewer.diagram;
 
+import java.util.Collection;
+
 import org.eclipse.emf.compare.Diff;
+import org.eclipse.emf.compare.diagram.ide.ui.internal.contentmergeviewer.diagram.AbstractDecoratorManager.AbstractDecorator;
 
 /**
  * Interface for the management of decorators.
@@ -53,4 +56,10 @@
 	 */
 	void removeAll();
 
+	/**
+	 * Get all registered decorators.
+	 * 
+	 * @return the list of all decorators.
+	 */
+	Collection<AbstractDecorator> getAllDecorators();
 }
diff --git a/plugins/org.eclipse.emf.compare.diagram.ide.ui/src/org/eclipse/emf/compare/diagram/ide/ui/internal/contentmergeviewer/diagram/MarkerManager.java b/plugins/org.eclipse.emf.compare.diagram.ide.ui/src/org/eclipse/emf/compare/diagram/ide/ui/internal/contentmergeviewer/diagram/MarkerManager.java
index 0075fe6..4d052db 100644
--- a/plugins/org.eclipse.emf.compare.diagram.ide.ui/src/org/eclipse/emf/compare/diagram/ide/ui/internal/contentmergeviewer/diagram/MarkerManager.java
+++ b/plugins/org.eclipse.emf.compare.diagram.ide.ui/src/org/eclipse/emf/compare/diagram/ide/ui/internal/contentmergeviewer/diagram/MarkerManager.java
@@ -279,4 +279,17 @@
 		}
 	}
 
+	/**
+	 * {@inheritDoc}
+	 * 
+	 * @see org.eclipse.emf.compare.diagram.ide.ui.internal.contentmergeviewer.diagram.IDecoratorManager#getAllDecorators()
+	 */
+	public Collection<AbstractDecorator> getAllDecorators() {
+		Collection<AbstractDecorator> markers = new ArrayList<AbstractDecorator>();
+		for (AbstractDecorator marker : fMarkerRegistry.values()) {
+			markers.add(marker);
+		}
+		return markers;
+	}
+
 }
diff --git a/plugins/org.eclipse.emf.compare.diagram.ide.ui/src/org/eclipse/emf/compare/diagram/ide/ui/internal/contentmergeviewer/diagram/PhantomManager.java b/plugins/org.eclipse.emf.compare.diagram.ide.ui/src/org/eclipse/emf/compare/diagram/ide/ui/internal/contentmergeviewer/diagram/PhantomManager.java
index e72faa7..baf8275 100644
--- a/plugins/org.eclipse.emf.compare.diagram.ide.ui/src/org/eclipse/emf/compare/diagram/ide/ui/internal/contentmergeviewer/diagram/PhantomManager.java
+++ b/plugins/org.eclipse.emf.compare.diagram.ide.ui/src/org/eclipse/emf/compare/diagram/ide/ui/internal/contentmergeviewer/diagram/PhantomManager.java
@@ -821,4 +821,17 @@
 			handleDeleteDecorator(phantom, phantom.getLayer(), phantom.getFigure());
 		}
 	}
+
+	/**
+	 * {@inheritDoc}
+	 * 
+	 * @see org.eclipse.emf.compare.diagram.ide.ui.internal.contentmergeviewer.diagram.IDecoratorManager#getAllDecorators()
+	 */
+	public Collection<AbstractDecorator> getAllDecorators() {
+		Collection<AbstractDecorator> phantoms = new ArrayList<AbstractDecorator>();
+		for (AbstractDecorator phantom : fPhantomRegistry.values()) {
+			phantoms.add(phantom);
+		}
+		return phantoms;
+	}
 }