Add utility functions for printing figures and graphical viewers #999#1001
Add utility functions for printing figures and graphical viewers #999#1001ptziegler merged 1 commit intoeclipse-gef:masterfrom
Conversation
|
@ptziegler Thanks for working on this it looks great! I did a test of one use case in Archi where we create a preview image of a figure and that looked good with no clipping. We have another use case where we get the printable layer of a diagram and then do some scaling (in case use exports image at 1x, 2x, or 3x size) and bounds limiting and account for negative space like this: static Image createDiagramImage(IFigure figure, double scale, int margin) {
Rectangle bounds = getMinimumBounds(figure); // Get smallest bounds of diagram from child figures
bounds.expand(margin / scale, margin / scale);
Image image = new Image(Display.getDefault(), (int)(bounds.width * scale), (int)(bounds.height * scale) );
GC gc = new GC(image);
SWTGraphics graphics = new SWTGraphics(gc);
graphics.scale(scale);
// Compensate for negative co-ordinates
graphics.translate(bounds.x * -1, bounds.y * -1);
// Paint onto graphics
figure.paint(graphics);
// Dispose
gc.dispose();
graphics.dispose();
return image;
}How could I use the |
I'd simply adapt the Perhaps it makes sense to also add a |
| public Image run() { | ||
| Objects.requireNonNull(printSource, "Print source must not be null"); //$NON-NLS-1$ | ||
| try { | ||
| preparePrintSource(); |
There was a problem hiding this comment.
This doesn't work when using the CachedImageDataProvider, because the image data is only generated when painting the image. But this happens after the method returns.
I can't see how that can be done as |
By "I'd simply adapt" I mean that I'll update this PR so that the internal |
The English language, eh? ;-) |
|
When creating an image from a Figure from a In Archi we create thumbnail images for each diagram by using a How to know when the Image has been initialised at all zoom levels and so the Shell can be disposed? Image createImageFromGraphicalViewer(Shell tmpShell) {
GraphicalViewer viewer = new GraphicalViewerImpl();
viewer.setControl(tmpShell);
LayerManager layerManager = (LayerManager)viewer.getEditPartRegistry().get(LayerManager.ID);
IFigure printableLayer = layerManager.getLayer(LayerConstants.PRINTABLE_LAYERS);
Image img = new ImagePrintFigureOperation(Display.getDefault(), printableLayer).run();
// When can tmpShell be disposed?
return img;
} |
I don't think that's possible with the current implementation. I've thought a little bit about this and perhaps it makes more sense to require a I need a way to get the zoom at which the image should be painted. If I have the The consequence is that the image only contains the image data at the current monitor zoom. So a new image needs to be created after every zoom change. |
|
We have these use cases:
|
85ed40c to
5f49d68
Compare
efc8e6c to
db52781
Compare
|
I've updated the PR so that the The example #1001 (comment) in could be implemented similar to this: public class ImagePrintDiagramOperation extends ImagePrintFigureOperation {
private final Rectangle boundsOrig;
private final double scale;
private final int margin;
public ImagePrintDiagramOperation(Control control, IFigure printSource, double scale, int margin) {
super(control, printSource);
this.scale = scale;
this.margin = margin;
this.boundsOrig = getMinimumBounds(printSource); // Get smallest bounds of diagram from child figures
}
private Rectangle getMinimumBounds(IFigure figure) {
return figure.getBounds().scale(scale);
}
@Override
protected Dimension getSize() {
Rectangle bounds = boundsOrig.getCopy();
bounds.expand(margin / scale, margin / scale);
return bounds.getSize();
}
protected void preparePrintSource(Graphics graphics) {
super.preparePrintSource(graphics);
graphics.scale(scale);
// Compensate for negative co-ordinates
graphics.translate(boundsOrig.x * -1, boundsOrig.y * -1);
}
} |
|
@ptziegler Thanks for these changes. I'm testing this now. The only problem I'm getting in the above use case is if graphics.translate(boundsOrig.x * -1, boundsOrig.y * -1); |
|
I think I've nailed it now. I was scaling the bounds when I should have been scaling the size. So far this works: class ImagePrintDiagramOperation extends ImagePrintFigureOperation {
private final Rectangle originalBounds;
private final double scale;
private final int margin;
public ImagePrintDiagramOperation(Control control, IFigure printSource, double scale, int margin) {
super(control, printSource);
this.scale = scale;
this.margin = margin;
originalBounds = getOriginalBounds(printSource);
}
private Rectangle getOriginalBounds(IFigure figure) {
// Get smallest bounds of diagram from child figures (helper method elsewhere)
Rectangle bounds = getMinimumBounds(figure);
bounds.expand(margin / scale, margin / scale);
return bounds;
}
@Override
protected Dimension getSize() {
// Scale the size not the bounds
return originalBounds.getSize().scale(scale);
}
@Override
protected void preparePrintSource(Graphics graphics) {
graphics.scale(scale);
// Translate x,y by original bounds x, y and compensate for negative co-ordinates
graphics.translate(originalBounds.x * -1, originalBounds.y * -1);
}
} |
242d163 to
e508e19
Compare
But you would still need a |
I've not noticed any problems. I went from 175% to 200% and it scaled just fine. |
Because if the figure doesn't belong to a FigureCanvas, its layout should be calculated using SWT scaling, which generally should match the way the shell is scaled. If that works, great, but it is not the intended purpose of this class. |
azoitl
left a comment
There was a problem hiding this comment.
I think this would also be good to have for 3.27, or?
|
There's a problem with Images generated using java.lang.IllegalArgumentException: Argument not valid |
9dab3f8 to
784abba
Compare
|
This error indicates that the image data is not properly sized after a zoom change. But I don't really see how this can happen with the default implementation. If Draw2D-based scaling is disabled, the image data is created at 100% zoom and then scaled by SWT. Otherwise the image data is always scaled by the zoom factor. Dimension size = getImageSize().getCopy();
if (isDraw2DAutoScalingEnabled()) {
size.scale(scale);
}I've also added a small test case to make sure that this is the case. private static void testPrintWithOperation(ImagePrintFigureOperation printer) {
Image image = printer.run();
assertImageDataSize(image.getImageData(100), 70, 80);
assertImageDataSize(image.getImageData(150), 105, 120);
assertImageDataSize(image.getImageData(200), 140, 160);
assertImageDataSize(image.getImageData(400), 280, 320);
image.dispose();
} |
@azoitl Let's wait until the next release to see if there are any edge cases that were missed. |
|
Regarding the exception thrown using the Image in a Nebula Gallery Item, this only occurs if protected ImageData getImageDataAtZoom(int zoom) {
if (zoom < 100) {
return null;
}
Image image = createImageAtScale(zoom / 100.0);
try {
return image.getImageData(100);
} finally {
image.dispose();
}
} |
I managed to reproduce this issue. It happens if you e.g. have an image of size 16x16 and try to get the image data at 1% zoom. The scaled size is (16 * 0.01, 16 * 0.01), which is rounded down to (0, 0) and therefore invalid. |
5b46a8a to
30052fd
Compare
Right. I'm not sure if my suggested workaround (return null on zoom < 100) is the right solution here. |
|
I played around with this problem and in the end, the only way I managed to reproduce the exact stack-trace is via this example: package org.eclipse.swt.tests.manual;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.BorderData;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Slider;
public class DrawGC {
public static void main(String[] args) {
Shell shell = new Shell();
shell.setSize(100, 100);
Display display = shell.getDisplay();
Image image = createTestImage(display, 16);
shell.addPaintListener(event -> {
event.gc.drawImage(image, 2, 2, 16, 16, 0, 0, 16, 16);
});
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
image.dispose();
}
private static Image createTestImage(Display display, int size) {
Image image = new Image(display, size, size);
GC gc = new GC(image);
gc.setBackground(display.getSystemColor(SWT.COLOR_RED));
gc.fillRectangle(0, 0, size, size);
gc.dispose();
return image;
}
}The a) the source rectangle differs too much from the target rectangle Until I see a reproducer, I would argue that this is a bug in whatever widget is painting the image. Because as far as I can tell, the |
|
This is the more complete stack trace with the latest SWT build: |
|
The Gallery widget is working on the raw image size to determine some obscure width and height which is passed to the But I will not go and try out every possible combination of width and height to see which one the widget doesn't like. So as said before: As long as there isn't a reproducer, I consider this to be a Nebula bug. |
I tend to agree with that. My reports here are just to document the (potential) issue. |
…pse-gef#999 This adds the new utility classes `ImagePrintFigureOperation` and `ImagePrintGraphicalViewerOperation` which can be used to paint the contents of a Figure/GraphicalViewer onto an SWT Image. When capturing a figure, clipping may occur because text sizes are not consistent across different zoom levels. When Draw2D-based scaling is enabled, the `FigureCanvas` is configured to always draw as if at 100% zoom and then scale its contents to match the actual monitor zoom. However, when painting on an Image, the Image GC may use the monitor zoom, leading to inconsistencies. To avoid this, the GC must be forced into "100% mode" by first calling `Image.getImageData(100)`. Then the SWTGraphics instance must be scaled by the monitor zoom so that the Figure can be painted. The result is an exact representation of the source figure.
Sure. I'll come back to this one at some point. Thanks for your work on this! |





This adds the new utility classes
ImagePrintFigureOperationandImagePrintGraphicalViewerOperationwhich can be used to paint the contents of a Figure/GraphicalViewer onto an SWT Image.When capturing a figure, clipping may occur because text sizes are not consistent across different zoom levels. When Draw2D-based scaling is enabled, the
FigureCanvasis configured to always draw as if at 100% zoom and then scale its contents to match the actual monitor zoom.However, when painting on an Image, the Image GC may use the monitor zoom, leading to inconsistencies. To avoid this, the GC must be forced into "100% mode" by first calling
Image.getImageData(100). Then the SWTGraphics instance must be scaled by the monitor zoom so that the Figure can be painted.The result is an exact representation of the source figure.