Drawing a dynamic transparent image over another Image using SWT Graphics
Asked Answered
S

2

6

I'm attempting to draw a transparent mask of combined shapes over the top of an already in place image. I have provided an example of the techniques I'm trying via the dialog code included in this post. Here's a screenshot of what it produces.

example of running dialog

Example 1 (top left) highlights the problem I want to solve, I wish to have the 2 circles Or any intersecting shapes/arcs, all draw together with the same level of alpha, ie without the compounding opaqueness caused by drawing over the top of each other.

Example 3 (bottom left) is my attempt to resolve the issue by creating a separate image with solid shapes on, then making that entire image transparent, what happens i think is that using this technique makes an image where the White is treated as the transparent colour, so the edge of the circle is blended with white so that when you draw it on it causes a "halo" effect around the shape.

Example 2 (top left) highlights this issue further by drawing the circles in the image as transparent too, so you can see the more pink colour caused by the highlight.

My question is, without any knowledge of the background colour, and without turning anti-aliasing off, how can I achieve the effect I am trying for? Is there a way, because all my research is coming up blank? Maybe I need to use a different image drawing solution and port back to SWT? I know it's capable of drawing Transparent images if loaded directly from a file so I know it can hold this sort of data, but how do I create it?

import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.swt.SWT;

import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;

public class FMLDialog extends Dialog
{   
    private Color red;
    private Color blue;

    public FMLDialog(Shell parentShell)
    {
        super(parentShell);
    }



    @Override
    protected void configureShell(Shell shell)
    {
        red = new Color(shell.getDisplay(), new RGB(255,0,0));
        blue = new Color(shell.getDisplay(), new RGB(0,100,255));
        super.configureShell(shell);
        shell.setSize(new Point(450,550));
        shell.setText("FML");
    }   

    @Override
    public Control createDialogArea(final Composite comp)
    {

        Composite content = (Composite) super.createDialogArea(comp);
        Composite parent = new Composite(content, SWT.NONE);        

        GridLayout gridLayout2 = new GridLayout(1, false);
        parent.setLayout(gridLayout2);
        parent.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));           

        final Canvas c = new Canvas(parent, SWT.BORDER);
        c.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
        c.addPaintListener(new PaintListener() {

            @Override
            public void paintControl(PaintEvent e) {
                e.gc.setAntialias(SWT.ON);
                drawFirstLayer(e.gc, 0, 0);
                drawFirstLayer(e.gc, 210, 0);
                drawFirstLayer(e.gc, 210, 210);
                drawFirstLayer(e.gc, 0, 210);

                drawSecondLayerTake1(e.gc, 0, 0);
                drawSecondLayerTake2(e.gc, 210, 0);
                drawSecondLayerTake3(e.gc, 0, 210);
                drawSecondLayerTake4(e.gc, 210, 210); 
            }
        });

        return content;     

    }

    private void drawFirstLayer(GC gc, int x, int y) {
        gc.setBackground(blue);
        gc.fillOval(x, y, 200 , 200);
    }

    private void drawSecondLayerTake1(GC gc, int x, int y) {
        // Simply draw 2 transparent circles
        // Issue here is the overlap between circles where the Alpha layers up
        gc.setAlpha(100);
        gc.setBackground(red);
        gc.fillOval(x + 70, y + 70, 60 , 60);
        gc.fillOval(x + 100, y + 100, 60 , 60);
        gc.setAlpha(255);
    }

    private void drawSecondLayerTake2(GC gc, int x, int y) {
        // Create an image with 2 transparent circles
        // Issue here is the overlap between circles where the Alpha layers up from the first
        // PLUS becasue my transparent colour is fixed to white the alpa on the circles is blended in to the white
        final Image src = new Image(null, 300, 300);
        final ImageData imageData = src.getImageData();
        imageData.transparentPixel = imageData.getPixel(0, 0);
        src.dispose();
        final Image processedImage = new Image(Display.getCurrent(), imageData);
        final GC imageGC = new GC(processedImage);      
        imageGC.setAntialias(SWT.ON);
        imageGC.setAlpha(100);
        imageGC.setBackground(red);
        imageGC.fillOval(70, 70, 60 , 60);
        imageGC.fillOval(100, 100, 60 , 60);
        imageGC.dispose();

        gc.drawImage(processedImage, x + 0, y + 0);

    }

    private void drawSecondLayerTake3(GC gc, int x, int y) {
        // Create an image with 2 solid circles, then draw that image on to the canvas with Alpha values.
        // Overlap issue goes away because the whole image is being made transparent together HOWEVER
        // there is a Halo effect around the edge of the red where the original circles were antialiased to blend into the "white"
        // background.

        final Image src = new Image(null, 300, 300);
        final ImageData imageData = src.getImageData();
        imageData.transparentPixel = imageData.getPixel(0, 0);
        src.dispose();
        final Image processedImage = new Image(Display.getCurrent(), imageData);
        final GC imageGC = new GC(processedImage);      
        imageGC.setAntialias(SWT.ON);
        imageGC.setBackground(red);
        imageGC.fillOval(70, 70, 60 , 60);
        imageGC.fillOval(100, 100, 60 , 60);
        imageGC.dispose();

        gc.setAlpha(100);
        gc.drawImage(processedImage, x + 0, y + 0);

    }

    private void drawSecondLayerTake4(GC gc, int x, int y) {
        // I need this one to draw like take 3 but without the white "halo" effect on the edge
        // How?!
    }

    public static void main(String[] args) {
        Display d = new Display();
        Shell s = new Shell();

        FMLDialog fml = new FMLDialog(s);
        fml.open();
    }

}
Saunder answered 23/10, 2018 at 13:25 Comment(0)
A
3

I was able to get the desired result using the method described by Sean Bright here: https://mcmap.net/q/935628/-drawing-on-a-transparent-image-using-java-swt.

Basically:

  1. we create an image src and with gc we fill it with transparent color
  2. we draw the ovals with solid color
  3. we get the resulting image data: now, the pixel data array of the image (imageData.data) is also going to contain the alpha values, while the alpha data array of the image (imageData.alphaData) is null
  4. we manually fix imageData.alphaData by extracting the alpha values at the right positions from imageData.data; this part assumes that we are working with 32 bit depth of color; it won't work otherwise
  5. now that the alphaData of imageData is fixed, we create an image processedImage with it
  6. with gc we finally draw processedImage with partial transparency

Here's the code (which is Sean's code with some changes):

private void drawSecondLayerTake4(GC gc, int x, int y) {

    final int width = 300;
    final int height = 300;

    final Image src = new Image(null, width, height);

    final GC imageGC = new GC(src);

    imageGC.setAntialias(SWT.ON);

    // This sets the alpha on the entire canvas to transparent
    imageGC.setAlpha(0);
    imageGC.fillRectangle(0, 0, width, height);

    // Reset our alpha and draw the ovals
    imageGC.setAlpha(255);
    imageGC.setBackground(red);
    imageGC.fillOval(70, 70, 60, 60);
    imageGC.fillOval(100, 100, 60, 60);

    // We're done with the GC, so dispose of it
    imageGC.dispose();

    final ImageData imageData = src.getImageData();
    imageData.alphaData = new byte[width * height];

    // This is the hacky bit that is making assumptions about
    // the underlying ImageData.  In my case it is 32 bit data
    // so every 4th byte in the data array is the alpha for that
    // pixel...
    for (int idx = 0; idx < (width * height); idx++) {
        final int coord = (idx * 4) + 3;
        imageData.alphaData[idx] = imageData.data[coord];
    }

    // Now that we've set the alphaData, we can create our
    // final image
    final Image processedImage = new Image(Display.getCurrent(), imageData);

    gc.setAlpha(100);
    gc.drawImage(processedImage, x + 0, y + 0);

    // And get rid of the canvas
    src.dispose();

}

And here's the result:

swt transpaency

Asymmetric answered 25/10, 2018 at 22:2 Comment(2)
You're my new favourite.Saunder
I still feel like this isn't working very cleanly, that edge halo effect still exist in some form, just diminished slightly?Saunder
A
3

You can use a Path to merge the 2 circles in a single entity and then fill it with the transparent color.

It is a much simpler solution than my previous answer and there is no halo effect.

The code:

private void drawSecondLayerTake4(GC gc, int x, int y) {

    final Path path = new Path(Display.getCurrent());
    path.addArc(x + 70, y + 70, 60, 60, 0, 360);
    path.addArc(x + 100, y + 100, 60, 60, 0, 360);

    gc.setAlpha(100);
    gc.setBackground(red);

    // needed to avoid holes in the path
    gc.setFillRule(SWT.FILL_WINDING);

    gc.fillPath(path);

    path.dispose();
}

And the result:

swt transparency

Asymmetric answered 26/10, 2018 at 20:22 Comment(1)
This of course works for the simplest solution, but the problem is much larger than the 2 circle scenario, I had to simplify it down to ask the question, but in reality i'm loading an image from a file, then making a bigger image with text and the source image in it. But potentially a good answer for other having similar issues.Saunder

© 2022 - 2024 — McMap. All rights reserved.