Export composite to image independent of the screen resolution
Asked Answered
I

1

1

Is there a way in SWT to export a composite to an image which always has the same size/resolution? The problem is that we have a Dashboard which looks always different when opening on screens with different display size/resolution. The question is now can I export the Dashboard to an image wich has a fixed size and always looks the same no matter on which screen size/resolution it has been created?

For the time being we do it like that but as said it depends on the display it has been created on:

Image image = new Image(Display.getCurrent(), this.content.getBounds().width, this.content.getBounds().height);
ImageLoader loader = new ImageLoader();
FileOutputStream fileOutputStream = new FileOutputStream(file);

GC gc = new GC(image);
this.content.print(gc);

gc.dispose();

loader.data = new ImageData[] { image.getImageData() };
loader.save(fileOutputStream, imageFormat);
fileOutputStream.close();

Is there for example some way to create a virtual screen with a certain resolution, which isn't actually displayed and only used for exporting the Dashboard? Any help or pointers would be appreciated.

Incunabula answered 2/9, 2015 at 14:17 Comment(0)
W
1

In one of my applications I was creating graphs and charts in SWT. The user was able to export these to an image of a size and format that they specified. My solution was to take the GC of the chart composite and redraw it to a new composite off screen, then export the newly drawn composite.

Here's my class which accomplished this:

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.widgets.Composite;

/**
 * The class used for writing an {@link Composite} to an image file
 * 
 * @author David L. Moffett
 * 
 */
public class CompositeImageWriter
{
  /**
   * Redraws the composite to the desired size off-screen and then writes that
   * composite as an image to the desired location and in the desired format
   * 
   * @param absolutePath
   *          the absolute path to the desired output file
   * @param compositeToDraw
   *          the composite to be written to file as an image
   * @param width
   *          the desired width in pixels that the composite should be redrawn
   *          to
   * @param height
   *          the desired height in pixels that the composite should be redrawn
   *          to
   * @param imageType
   *          an int representing the type of image that should be written
   */
  public static void drawComposite(String absolutePath, Composite compositeToDraw, int width,
      int height, int imageType)
  {
    Image image = new Image(compositeToDraw.getDisplay(), width, height);
    GC gc = new GC(image);
    int originalWidth = compositeToDraw.getBounds().width;
    int originalHeight = compositeToDraw.getBounds().height;
    compositeToDraw.setSize(width, height);
    compositeToDraw.print(gc);
    compositeToDraw.setSize(originalWidth, originalHeight);

    ImageLoader loader = new ImageLoader();
    loader.data = new ImageData[] { image.getImageData() };
    loader.save(absolutePath, imageType);

    image.dispose();
    gc.dispose();
  }
}

For your case of wanting to always export at a specific size, just replace the width and height arguments with appropriate constants.

Edit 1

I should add that the int imageType argument is the corresponding SWT modifier (for example: SWT.IMAGE_PNG, SWT.IMAGE_JPEG, SWT.IMAGE_BMP, etc...).

Edit 2

I updated the static reference to dynamically get the display from the compositeToDraw. Further, here's an example I put together which uses the CompositeImageWriter which you may be able to use to debug your issue:

import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;

public class CompositeToWrite extends Composite
{
  private int   width, height;

  private Label l;

  public CompositeToWrite(Composite parent, int style)
  {
    super(parent, style);
    this.setLayout(new GridLayout(1, true));
    this.addListener(SWT.Resize, new Listener()
    {

      @Override
      public void handleEvent(Event event)
      {
        updateText();
      }
    });

    Button b = new Button(this, SWT.NONE);
    b.setText("Export as image (500, 500)");
    b.addListener(SWT.Selection, new Listener()
    {

      @Override
      public void handleEvent(Event event)
      {
        CompositeImageWriter.drawComposite("./img/output.png", CompositeToWrite.this, 500, 500,
            SWT.IMAGE_PNG);
      }
    });

    l = new Label(this, SWT.CENTER);
    GridData gd = new GridData(SWT.FILL, SWT.FILL, true, true);
    gd.verticalAlignment = SWT.CENTER;
    l.setLayoutData(gd);
    updateText();
  }

  protected void updateText()
  {
    width = this.getBounds().width;
    height = this.getBounds().height;

    l.setText("My label is centered in composite (" + width + ", " + height + ")");
  }

}

In the case of this example I create a simple composite that once added to a shell will look something like this: Composite to Write

When I click the button it resizes the composite to 500 x 500 and writes the resized composite to a file. The result is this picture: Image of resized composite

I should note that I did notice the composite flicker when the button is clicked, so this may not be happening completely in the background or "off screen" as I initially suggested.

Watchcase answered 20/9, 2015 at 18:20 Comment(5)
Thanks for the answer, but can you describe in more detail how you created an "off-screen" display? I haven't found a solution to that one yet ... And after setting the size of the composite is it automatically resized or do I need to call some method for that?Incunabula
So in this case, "compositeToDraw" would be your Dashboard, in the first line we create a new image with the desired width and height, then create a new graphics component for that image, next we resize the original composite then tell it to write itself to the new graphics component for the image. Finally we set the original composite back to its original size, then write the new resized Image to a file. The resizing of the original composite is never realized "on screen" since the original composite's parent is not being updated, thus the "off-screen" comment.Watchcase
ok I see what you mean now with the "off screen" part, but it still doesn't work for me. After resizing the composite and printing it to the image the image itself has the right size but the composite is still in its original size... Which means that there is a lot of blank space on the image now. Any ideas what I'm doing wrong?Incunabula
Hmm, not quite sure why it might not be working for you without seeing the details of how you are invoking the method. I updated my answer with some example code that you may be able to use to debug why it's not working for you. If you can't find the issue you might want to update your initial question to include a more complete example of how you're using the class.Watchcase
Thanks I got it working now. It seems as the problem was that I had nested composites and just resizing the outer one won't do the trick...Incunabula

© 2022 - 2024 — McMap. All rights reserved.