blob: 6010864d6d86ad93c069a8c7f6911398f9fa7540 [file] [log] [blame]
/*******************************************************************************
* <copyright>
*
* Copyright (c) 2005, 2015 SAP AG.
* 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:
* SAP AG - initial API, implementation and documentation
* fvelasco - Bug 396247 - ImageDescriptor changes
* mwenz - Bug 413139 - Visibility of convertImageToBytes in DefaultSaveImageFeature
* mjagielski - Bug 472219 - ImageService is not handling imageFilePath with protocol bundleentry
* Laurent Le Moux (mwenz) - Bug 423018 - Direct Graphiti diagram exporter
*
* </copyright>
*
*******************************************************************************/
package org.eclipse.graphiti.ui.internal.services.impl;
import java.io.ByteArrayOutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import org.eclipse.draw2d.Figure;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.SWTGraphics;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.GraphicalViewer;
import org.eclipse.gef.Handle;
import org.eclipse.gef.LayerConstants;
import org.eclipse.gef.editparts.LayerManager;
import org.eclipse.gef.editparts.ScalableRootEditPart;
import org.eclipse.gef.ui.parts.AbstractEditPartViewer;
import org.eclipse.graphiti.dt.IDiagramTypeProvider;
import org.eclipse.graphiti.mm.pictograms.Diagram;
import org.eclipse.graphiti.ui.editor.DefaultRefreshBehavior;
import org.eclipse.graphiti.ui.editor.DiagramBehavior;
import org.eclipse.graphiti.ui.editor.DiagramEditor;
import org.eclipse.graphiti.ui.editor.IDiagramContainerUI;
import org.eclipse.graphiti.ui.internal.GraphitiUIPlugin;
import org.eclipse.graphiti.ui.internal.T;
import org.eclipse.graphiti.ui.internal.config.ConfigurationProvider;
import org.eclipse.graphiti.ui.internal.config.IConfigurationProviderInternal;
import org.eclipse.graphiti.ui.internal.parts.DiagramEditPart;
import org.eclipse.graphiti.ui.internal.platform.ExtensionManager;
import org.eclipse.graphiti.ui.platform.IConfigurationProvider;
import org.eclipse.graphiti.ui.platform.IImageProvider;
import org.eclipse.graphiti.ui.platform.PlatformImageProvider;
import org.eclipse.graphiti.ui.services.GraphitiUi;
import org.eclipse.graphiti.ui.services.IImageService;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.resource.ImageRegistry;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.ImageLoader;
import org.eclipse.swt.graphics.PaletteData;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.plugin.AbstractUIPlugin;
/**
* @noinstantiate This class is not intended to be instantiated by clients.
* @noextend This class is not intended to be subclassed by clients.
*/
public class ImageService implements IImageService {
public ImageDescriptor getImageDescriptorForId(String providerId, String imageId) {
if (imageId == null || providerId == null)
return null;
String registryKey = makeKey(providerId, imageId);
ImageRegistry imageRegistry = GraphitiUIPlugin.getDefault().getImageRegistry();
ImageDescriptor imageDescriptor = imageRegistry.getDescriptor(registryKey);
if (imageDescriptor != null) {
return imageDescriptor;
}
imageDescriptor = createImageDescriptorForId(providerId, imageId);
return imageDescriptor;
}
public Image getImageForId(String providerId, String imageId) {
if (imageId == null || providerId == null)
return null;
String registryKey = makeKey(providerId, imageId);
// if image already available take it
ImageRegistry imageRegistry = GraphitiUIPlugin.getDefault().getImageRegistry();
Image result = imageRegistry.get(registryKey);
if (result != null && !result.isDisposed()) {
return result;
}
createImageDescriptorForId(providerId, imageId);
Image image = imageRegistry.get(registryKey); // now there is an image
// registered
if (image == null) {
throw new IllegalStateException("No image could be retrieved for imageId '" + imageId + "'"); //$NON-NLS-1$ //$NON-NLS-2$
}
return image;
}
public void removeImageFromRegistry(String key) {
ImageRegistry imageRegistry = GraphitiUIPlugin.getDefault().getImageRegistry();
imageRegistry.remove(key);
}
public ImageDescriptor getPlatformImageDescriptorForId(String imageId) {
if (imageId == null)
return null;
String registryKey = makePlatformKey(imageId);
ImageRegistry imageRegistry = GraphitiUIPlugin.getDefault().getImageRegistry();
ImageDescriptor imageDescriptor = imageRegistry.getDescriptor(registryKey);
if (imageDescriptor != null) {
return imageDescriptor;
}
imageDescriptor = createPlatformImageDescriptorForId(imageId);
return imageDescriptor;
}
public Image getPlatformImageForId(String imageId) {
if (imageId == null)
return null;
String registryKey = makePlatformKey(imageId);
// if image already available take it
ImageRegistry imageRegistry = GraphitiUIPlugin.getDefault().getImageRegistry();
Image result = imageRegistry.get(registryKey);
if (result != null && !result.isDisposed()) {
return result;
}
createPlatformImageDescriptorForId(imageId);
Image image = imageRegistry.get(registryKey); // now there is an image
// registered
if (image == null) {
throw new IllegalStateException("No image could be retrieved for imageId '" + imageId + "'"); //$NON-NLS-1$ //$NON-NLS-2$
}
return image;
}
public byte[] convertImageToBytes(Image image, int format) {
ByteArrayOutputStream result = new ByteArrayOutputStream();
try {
ImageData imDat = null;
// Save as GIF is only working if not more than 256 colors are used
// in the image
if (format == SWT.IMAGE_GIF) {
imDat = create8BitIndexedPaletteImage(image);
}
if (imDat == null) {
imDat = image.getImageData();
}
ImageLoader imageLoader = new ImageLoader();
imageLoader.data = new ImageData[] { imDat };
try {
imageLoader.save(result, format);
} catch (SWTException e) {
String error = "Depth: " + Integer.toString(image.getImageData().depth) + "\n" + "X: " //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ Integer.toString(image.getImageData().x)
+ "\n" + "Y: " + Integer.toString(image.getImageData().y); //$NON-NLS-1$ //$NON-NLS-2$
throw new IllegalStateException(error, e);
}
} catch (Exception e) {
throw new IllegalStateException(e);
} finally {
image.dispose();
}
return result.toByteArray();
}
/**
* This method fixes a problem for ImageDescriptors. It returns a corrected
* ImageDescriptor for problematic ImageDescriptors.
* <p>
* There is a problem with transparent GIFs. If the RGB value of the
* transparent color index is identical to the RGB value of other colors
* indices in the palette, then all those color indices are considered as
* transparent. So as a result the transparency seems to be on an RGB value
* instead of a color index.
*/
private ImageDescriptor fixImageDescriptor(ImageDescriptor descriptor) {
// Typically the incoming ImageDescriptor is an URLImageDescriptor. The following lines create an ImageDataImageDescriptor
// from it, which basically describes the same image. But that one works. So there seems to be an error in the
// URLImageDescriptor.
ImageData data = descriptor.getImageData();
return ImageDescriptor.createFromImageData(data);
}
private ImageDescriptor createImageDescriptorForId(String providerId, String imageId) {
if (imageId == null || providerId == null)
return null;
String registryKey = makeKey(providerId, imageId);
// if image descriptor already exists return it
ImageRegistry imageRegistry = GraphitiUIPlugin.getDefault().getImageRegistry();
ImageDescriptor imageDescriptor = imageRegistry.getDescriptor(registryKey);
if (imageDescriptor != null) {
return imageDescriptor;
}
// try get the image-location from the image-providers
Collection<IImageProvider> imageProviders = ExtensionManager.getSingleton()
.getImageProvidersForDiagramTypeProviderId(providerId);
for (IImageProvider imageProvider : imageProviders) {
String imageFilePath = imageProvider.getImageFilePath(imageId);
if (imageFilePath != null) {
if (imageFilePath.startsWith("bundleentry://") || imageFilePath.startsWith("bundleresource://")) {
try {
URL imageFileUrl = new URL(imageFilePath);
imageDescriptor = ImageDescriptor.createFromURL(imageFileUrl);
} catch (MalformedURLException e) {
T.racer()
.error("Bundle entry/resource url for image could not be parsed, url was: "
+ imageFilePath, e);
}
} else {
String pluginId = imageProvider.getPluginId();
if (pluginId != null) {
// try to create Image from ImageDescriptor (initialize
// the ImageRegistry on the fly)
imageDescriptor = AbstractUIPlugin.imageDescriptorFromPlugin(pluginId, imageFilePath);
}
}
break;
}
}
if (imageDescriptor == null) {
imageDescriptor = ImageDescriptor.getMissingImageDescriptor();
}
imageDescriptor = fixImageDescriptor(imageDescriptor);
imageRegistry.put(registryKey, imageDescriptor);
return imageDescriptor;
}
private ImageDescriptor createPlatformImageDescriptorForId(String imageId) {
if (imageId == null)
return null;
String registryKey = makePlatformKey(imageId);
// if image descriptor already exists return it
ImageRegistry imageRegistry = GraphitiUIPlugin.getDefault().getImageRegistry();
ImageDescriptor imageDescriptor = imageRegistry.getDescriptor(registryKey);
if (imageDescriptor != null) {
return imageDescriptor;
}
// try get the image-location from the image-providers
IImageProvider platformImageProvider = ExtensionManager.getSingleton().getPlatformImageProvider();
String imageFilePath = platformImageProvider.getImageFilePath(imageId);
if (imageFilePath != null) {
String pluginId = platformImageProvider.getPluginId();
if (pluginId != null) {
// try to create Image from ImageDescriptor (initialize the
// ImageRegistry on the fly)
imageDescriptor = AbstractUIPlugin.imageDescriptorFromPlugin(pluginId, imageFilePath);
}
}
if (imageDescriptor == null) {
imageDescriptor = ImageDescriptor.getMissingImageDescriptor();
}
imageDescriptor = fixImageDescriptor(imageDescriptor);
imageRegistry.put(registryKey, imageDescriptor);
return imageDescriptor;
}
private String makeKey(String dtp, String imageId) {
return dtp + "||" + imageId;
}
private String makePlatformKey(String imageId) {
return makeKey(PlatformImageProvider.ID, imageId);
}
private ImageData create8BitIndexedPaletteImage(Image image) {
int upperboundWidth = image.getBounds().width;
int upperboundHeight = image.getBounds().height;
ImageData imageData = image.getImageData();
// determine number of used colors
ArrayList<Integer> colors = new ArrayList<Integer>();
for (int x = 0; x < upperboundWidth; x++) {
for (int y = 0; y < upperboundHeight; y++) {
int color = imageData.getPixel(x, y);
Integer colorInteger = new Integer(color);
if (!colors.contains(colorInteger))
colors.add(colorInteger);
}
}
// at the moment this is only working if not more than 256 colors are
// used in the image
if (colors.size() > 256) {
throw new IllegalStateException(
"Image contains more than 256 colors. \n Automated color reduction is currently not supported."); //$NON-NLS-1$
}
// create an indexed palette
RGB[] rgbs = new RGB[256];
for (int i = 0; i < 256; i++)
rgbs[i] = new RGB(255, 255, 255);
for (int i = 0; i < colors.size(); i++) {
int pixelValue = ((colors.get(i))).intValue();
int red = (pixelValue & imageData.palette.redMask) >>> Math.abs(imageData.palette.redShift);
int green = (pixelValue & imageData.palette.greenMask) >>> Math.abs(imageData.palette.greenShift);
int blue = (pixelValue & imageData.palette.blueMask) >>> Math.abs(imageData.palette.blueShift);
rgbs[i] = new RGB(red, green, blue);
}
// create new imageData
PaletteData palette = new PaletteData(rgbs);
ImageData newImageData = new ImageData(imageData.width, imageData.height, 8, palette);
// adjust imageData with regard to the palette
for (int x = 0; x < upperboundWidth; x++) {
for (int y = 0; y < upperboundHeight; y++) {
int color = imageData.getPixel(x, y);
newImageData.setPixel(x, y, colors.indexOf(new Integer(color)));
}
}
return newImageData;
}
@Override
public byte[] convertDiagramToBytes(Diagram diagram, int format) {
DummyEditor dummyEditor = new DummyEditor();
dummyEditor.getGraphicalViewer().setRootEditPart(new ScalableRootEditPart());
ImageGenerationDiagramBehavior imageGenerationDiagramBehavior = new ImageGenerationDiagramBehavior(dummyEditor,
diagram);
dummyEditor.getGraphicalViewer().setEditPartFactory(
((IConfigurationProviderInternal) imageGenerationDiagramBehavior.getConfigurationProvider())
.getEditPartFactory());
// Recursively launch all figure creations
dummyEditor.getGraphicalViewer().setContents(diagram);
// Set the Graphiti diagram bounds...
int width = diagram.getGraphicsAlgorithm().getWidth(), height = diagram.getGraphicsAlgorithm().getHeight();
IFigure diagramFigure = ((LayerManager) dummyEditor.getGraphicalViewer().getRootEditPart())
.getLayer(LayerConstants.PRINTABLE_LAYERS);
diagramFigure.setBounds(new Rectangle(0, 0, width, height));
// ...and trigger the figures validation
diagramFigure.validate();
// Set bounds to final narrowed image size
DiagramEditPart diagramEditPart = (DiagramEditPart) dummyEditor.getGraphicalViewer().getRootEditPart()
.getChildren().get(0);
int XMin = width;
int YMin = height;
int XMax = 0;
int YMax = 0;
for (Object o : diagramEditPart.getFigure().getChildren()) {
Figure f = (Figure) o;
if (f.getLocation().x < XMin) {
XMin = f.getLocation().x;
}
if (f.getLocation().y < YMin) {
YMin = f.getLocation().y;
}
if (f.getLocation().x + f.getBounds().width > XMax) {
XMax = f.getLocation().x + f.getBounds().width;
}
if (f.getLocation().y + f.getBounds().height > YMax) {
YMax = f.getLocation().y + f.getBounds().height;
}
}
width = XMin + XMax;
height = YMin + YMax;
// Workaround for default diagram creation limited to 1000 x 1000 bounds
if (width > diagramFigure.getBounds().width || height > diagramFigure.getBounds().height) {
diagramFigure.setBounds(new Rectangle(0, 0, width, height));
diagramFigure.validate();
}
Image diagramImage = new Image(Display.getDefault(), width, height);
GC gc = new GC(diagramImage);
SWTGraphics graphics = new SWTGraphics(gc);
diagramFigure.paint(graphics);
graphics.dispose();
gc.dispose();
return convertImageToBytes(diagramImage, format);
}
private class ImageGenerationDiagramBehavior extends DiagramBehavior {
private IConfigurationProviderInternal configurationProvider = null;
public ImageGenerationDiagramBehavior(IDiagramContainerUI diagramContainer, Diagram diagram) {
super(diagramContainer);
// Extract the diagram type from the given diagram and try to find
// the associated diagram type provider. That instance will have the
// correct image provider associated so that images will be rendered
// correctly.
// This will work only in case the graphical tool for the diagram
// type is installed.
IDiagramTypeProvider diagramTypeProvider = null;
String providerId = GraphitiUi.getExtensionManager().getDiagramTypeProviderId(diagram.getDiagramTypeId());
if (providerId != null) {
diagramTypeProvider = ExtensionManager.getSingleton().createDiagramTypeProvider(providerId);
}
if (diagramTypeProvider == null) {
// In case no tool was found, use a dummy diagram type provider
// for the rendering. This will still not show the correct
// images and platform graphics algorithms but will render a
// default image or shape stating that the image or shape
// is not available for all rendered images.
diagramTypeProvider = ExtensionManager.getSingleton().createDiagramTypeProvider(
"org.eclipse.graphiti.ui.internal.ExportDiagramAsImageDummyDiagramTypeProvider");
}
diagramTypeProvider.init(diagram, this);
configurationProvider = new ConfigurationProvider(this, diagramTypeProvider);
}
@Override
public IConfigurationProvider getConfigurationProvider() {
return configurationProvider;
}
@Override
public boolean isAlive() {
// To trigger refresh without check on non-existing edit domain
return true;
}
@Override
public DefaultRefreshBehavior getRefreshBehavior() {
return new DefaultRefreshBehavior(this); // To avoid NPE...
}
}
private class DummyGraphicalViewer extends AbstractEditPartViewer implements GraphicalViewer {
@Override
public EditPart findObjectAtExcluding(Point location, @SuppressWarnings("rawtypes") Collection exclusionSet,
Conditional conditional) {
return null;
}
@Override
public Handle findHandleAt(Point p) {
return null;
}
@Override
public Control createControl(Composite parent) {
return null;
}
}
private class DummyEditor extends DiagramEditor {
private DummyGraphicalViewer dummyGraphicalViewer = new DummyGraphicalViewer();
@Override
public GraphicalViewer getGraphicalViewer() {
return dummyGraphicalViewer;
}
}
}