Support for Pre-multiplied Alpha compositing

Magick++ is an object-oriented C++ interface to ImageMagick. Use this forum to discuss, make suggestions about, or report bugs concerning Magick++.
Post Reply
cthomas
Posts: 10
Joined: 2016-01-18T10:55:03-07:00
Authentication code: 1151

Support for Pre-multiplied Alpha compositing

Post by cthomas »

Hey, I ran into a rather bad issue using the Annotate feature of ImageMagick. I want to be able to use the Annotation feature to create labels, with a transparent background, and to then overlay these over other images in an app I am developing.

here is an example of the basic issue. When you composite images which both have alphas, they are combined using a non-pre-multipied alpha method. This results in a black outline around the text. You can see this issue here....

https://www.dropbox.com/s/4956bncpsb804 ... 5.png?dl=0

This appears to be the case with how the Annotate feature works. Its fine if you use it on an image with an opaque alpha. But if you Annotate on an image with transparent Alpha, you see the black outline you see above..

The image above, was actually produced using this image....

https://www.dropbox.com/s/c6my4zlu6t5xb ... 4.png?dl=0

Where as you can see, I have created the white text, by combining a white image, with a black and white text image (produced using Annotate) as its alpha. Here you can see that no black outline results when it is composited in my target app.

However, I then moved in in my code, to make a drop-shadow for this text, and to then composite this white text over the shadow image using IM .composite() method, using the Magick::OverCompositeOp. This resulted in the top image above, with the black outline.

So, having managed to dodge the non pre-multipied composite bullet with the White text, and also its drop Shadow image, when they are composited on another image SEPARATELY. I CANNOT avoid the issue when I composite the two together. The black outline issue resurfaces :/

Does anyone know of any way around this?

Alternatively are there any plans for a composite method that assumes that images have pre-multipied alpha. If that is the case, I guess (ideally) the Annotate method would also need updating to make use of the new pre-mult pipeline...)
Last edited by cthomas on 2016-01-20T11:57:19-07:00, edited 3 times in total.
Chris Thomas
Visualisation Engineer
SOASTA Inc.
User avatar
fmw42
Posts: 25562
Joined: 2007-07-02T17:14:51-07:00
Authentication code: 1152
Location: Sunnyvale, California, USA

Re: Support for Pre-multiplied Alpha compositing

Post by fmw42 »

We cannot see your example images. So it is hard to understand your issue. If you want to add drop shadows, you need to write your text onto a transparent background image. Typically one uses label: or caption: to create the text onto a black background, then add shadow, then composite that over some other background image.
snibgo
Posts: 12159
Joined: 2010-01-23T23:01:33-07:00
Authentication code: 1151
Location: England, UK

Re: Support for Pre-multiplied Alpha compositing

Post by snibgo »

Without seeing the images or the code that made them, comment is difficult.

Please also state which version of IM you are using.

IM does pre-multiply by alpha in composites. Or, at least, it does at the command-line interface.
snibgo's IM pages: im.snibgo.com
cthomas
Posts: 10
Joined: 2016-01-18T10:55:03-07:00
Authentication code: 1151

Re: Support for Pre-multiplied Alpha compositing

Post by cthomas »

OK, sorry, trying to share using DropBox, not tried it before. Anyway, I have replaced the IMG links in the original post with new links to native DB share links. Please give these a try and let me know if these are working for you?

In terms of software version, I am using 6.9.2-4, this is a custom compile of IM on OSX, installed using Homebrew...

here is the code, its for a method that is designed to provide text labels, with or without a drop-shadow...

Code: Select all

void ImageLab::CreateText( Str textString, unt16 offsetX, unt16 offsetY, float64 width, float64 height, const Str font , float32 fontSize = 300, bool dropShadow = false, float32 shadowOpacity = 0.2, float32 shadowOffset = 5, blurLevel blurType = low )
{
  //float32 Quanta = 65535;
  Magick::Color black = Magick::Color( Quanta * 0.0, Quanta * 0.0, Quanta * 0.0 );
  Magick::Color white = Magick::Color( Quanta * 1.0, Quanta * 1.0, Quanta * 1.0 );

  std::string textBitmapSmallSize = std::string( ToStr( unt32(width * 0.2)) + "x" + ToStr(unt32(height * 0.2)) );
  std::string textBitmapFullSize = std::string( ToStr(unt32(width)) + "x" + ToStr(unt32(height)) );

  Magick::Image textOpacity;
  textOpacity.magick("R");
  textOpacity = Magick::Image( textBitmapFullSize, black );
  textOpacity.alphaChannel(Magick::OpaqueAlphaChannel);
  textOpacity.fillColor(white);
  textOpacity.fontPointsize(fontSize);
  textOpacity.textInterlineSpacing(50);
  textOpacity.font( std::string(font) );
  textOpacity.annotate( std::string(textString), std::string(ToStr(width) + "x" + ToStr(height) + " " + ToStr(offsetX) + "," + ToStr(offsetY)) , Magick::NorthWestGravity );

  Magick::Image textColor;
  textColor.magick("RGBA");
  textColor.compose(Magick::MultiplyCompositeOp);
  textColor = Magick::Image( textBitmapFullSize, white );
  textColor.alphaChannel( Magick::OpaqueAlphaChannel );

  //textColor.write("textColor.png");

  std::list<Magick::Image> imagesList;
  imagesList.push_back(textColor);
  imagesList.push_back(textColor);
  imagesList.push_back(textColor);
  imagesList.push_back(textOpacity);

  Magick::Image textImage;
  textImage.magick("RGBA");
  CombineImages( textImage, imagesList );
  textImage.flip();

  if( dropShadow )
  {
    Magick::Color shadow = Magick::Color( Quanta * 0.05, Quanta * 0.1, Quanta * 0.2 );

    Magick::Image shadowColorImage;
    shadowColorImage.magick("RGBA");
    //shadowColorImage.compose(Magick::MultiplyCompositeOp);
    shadowColorImage = Magick::Image( textBitmapFullSize, shadow );
    shadowColorImage.alphaChannel( Magick::OpaqueAlphaChannel );

    Magick::Image shadowOpacityImage = Magick::Image(textOpacity);
    shadowOpacityImage.brightnessContrast( -100 + (shadowOpacity * 100),0); // SHADOW OPACITY: Range of adjustment is -100 to 100, 0 is no change. Brightness -100 = totally transparent, 0 = totally opaque. DO NOT adjust 2nd param, contrast.

    switch(blurType)
    {
        case none :
        break;

        case low  :
          shadowOpacityImage.resize( std::string( ToStr( unt32(width * 0.3)) + "x" + ToStr(unt32(height * 0.3)) ) ); // Shrink our copy of the Text opacity image...
        break;

        case mid:
          shadowOpacityImage.resize( std::string( ToStr( unt32(width * 0.2)) + "x" + ToStr(unt32(height * 0.2)) ) ); // Shrink our copy of the Text opacity image...
          shadowOpacityImage.blur( 0.25, 1.0 ); // SHADOW BLUR: Reduce overall level of blur, when dealing with smaller resized shadow images...
        break;

        case high :
          shadowOpacityImage.resize( std::string( ToStr( unt32(width * 0.1)) + "x" + ToStr(unt32(height * 0.1)) ) ); // Shrink our copy of the Text opacity image...
          shadowOpacityImage.blur( 2.0, 1.0 ); // SHADOW BLUR: Reduce overall level of blur, when dealing with smaller resized shadow images...
        break;

        case extreme :
          shadowOpacityImage.resize( std::string( ToStr( unt32(width * 0.1)) + "x" + ToStr(unt32(height * 0.1)) ) ); // Shrink our copy of the Text opacity image...
          shadowOpacityImage.blur( 8.0, 2.0 ); // SHADOW BLUR: Reduce overall level of blur, when dealing with smaller resized shadow images...
        break;
    }

    //shadowOpacityImage.write("opacity_small.png");
    shadowOpacityImage.resize(textBitmapFullSize); // Resize shadow channel image back up, to match that of users spec

    shadowColorImage.write("color.png");
    shadowOpacityImage.write("opacity_large.png");

    std::list<Magick::Image> shadowsImagesList;
    shadowsImagesList.push_back(shadowColorImage.separate(Magick::RedChannel));
    shadowsImagesList.push_back(shadowColorImage.separate(Magick::GreenChannel));
    shadowsImagesList.push_back(shadowColorImage.separate(Magick::BlueChannel));
    shadowsImagesList.push_back(shadowOpacityImage);

    Magick::Image shadowImage;
    shadowImage.magick("RGBA");
    CombineImages( shadowImage, shadowsImagesList );
    shadowImage.flip();

    shadowImage.composite( textImage, int(-(shadowOffset * 0.5)), int(shadowOffset * 1.0), Magick::OverCompositeOp);

    currentImage = shadowImage;
  }
  else
  {
    currentImage = textImage;
  }
}
Chris Thomas
Visualisation Engineer
SOASTA Inc.
snibgo
Posts: 12159
Joined: 2010-01-23T23:01:33-07:00
Authentication code: 1151
Location: England, UK

Re: Support for Pre-multiplied Alpha compositing

Post by snibgo »

Okay, I can see the images. Both images have a small "smudge" with black outline, at top-centre in the 15.42.34 image, and top-left in the other image. Is that what you mean? To diagnose this, we would need to see the image files used to make the images. (The actual files, not screenshots.)
snibgo's IM pages: im.snibgo.com
cthomas
Posts: 10
Joined: 2016-01-18T10:55:03-07:00
Authentication code: 1151

Re: Support for Pre-multiplied Alpha compositing

Post by cthomas »

OK, Here is the full forensic breakdown....

A. Firstly we create a GREYSCALE black image, with white text on it. This will be used as an alpha channel for the text....
Image

B. Then we create an RGB image, which will be the colour for the text. In this case, it just happens to be white...
Image

C. Then we combine Alpha of image A, with the RGB channels of image B, using CombineImages( textImage, imagesList );
Image

When this image is composited over a background, you'll see it works, as planned, no issue with a block border around it.


Next we create the drop shadow image

D.1 We take the Greyscale image from step A, resize it to 10% of its original size, blur it, alter its brightnessContrast to make it darker
Image

D.2 Then we resize it back up to its original size, so it can be composited with the colour image
Image

E. Next we create the RGB Colour image for the drop-shadow
Image

F. Then we combine Alpha from image D with the RGB from image E, using CombineImages( shadowImage, shadowsImagesList );
Image

Now we have a text image, with its own pure white RGB and alpha for the text. And a drop shadow image, with a flat colour in its RGB channels, and the blurred alpha to create the shadow effect.

We combine these two images, using shadowImage.composite( textImage, int(-(shadowOffset * 0.5)), int(shadowOffset * 1.0), Magick::OverCompositeOp);
Result. Image

Again, when composited with a background, this looks fine in most apps. However, when I overlay it in my app, I get this result..
Image

As you can see, it has a black outline around ONLY the white element of the text (derived from image C), the drop-shadow element (image F) looks fine.

I have tested overlaying images C and F separately in my target app. And they both render with no black border. However, when combined using composite (over) the curse of the black outline returns... Please ignore some of the images being upside down, I have to Flip() them to output to our app, as its displaying the images using OpenGL, and for some reason it sees the images as flipped on Y, so I need to accommodate in IM before mem transferring the finale images over....
Last edited by cthomas on 2016-01-21T14:07:18-07:00, edited 1 time in total.
Chris Thomas
Visualisation Engineer
SOASTA Inc.
snibgo
Posts: 12159
Joined: 2010-01-23T23:01:33-07:00
Authentication code: 1151
Location: England, UK

Re: Support for Pre-multiplied Alpha compositing

Post by snibgo »

In your final, "Lake" image, the only black pixels are around a small blob near the top-left corner.

The tops and left sides of the letters have a blue-grey border that is darker than the background sky. This outline isn't black, but is that what you mean?
snibgo's IM pages: im.snibgo.com
cthomas
Posts: 10
Joined: 2016-01-18T10:55:03-07:00
Authentication code: 1151

Re: Support for Pre-multiplied Alpha compositing

Post by cthomas »

Yes, its that "grey" outline that should not be there. Its not, if for example, I pass out image C, the white text...
Chris Thomas
Visualisation Engineer
SOASTA Inc.
snibgo
Posts: 12159
Joined: 2010-01-23T23:01:33-07:00
Authentication code: 1151
Location: England, UK

Re: Support for Pre-multiplied Alpha compositing

Post by snibgo »

I don't know the Magick++ interface. You seem to suggest the _gray_ outlines are being created at:

Code: Select all

shadowImage.composite( textImage, int(-(shadowOffset * 0.5)), int(shadowOffset * 1.0), Magick::OverCompositeOp);
I suggest you save the inputs to this command, and the output, and put them up here.
snibgo's IM pages: im.snibgo.com
cthomas
Posts: 10
Joined: 2016-01-18T10:55:03-07:00
Authentication code: 1151

Re: Support for Pre-multiplied Alpha compositing

Post by cthomas »

Snigbo, I already have, as requested by an earlier post. See my post above, to whit...

shadowImage = Image
textImage = Image

And the result of shadowImage.composite( textImage, int(-(shadowOffset * 0.5)), int(shadowOffset * 1.0), Magick::OverCompositeOp); is

Result. Image
Chris Thomas
Visualisation Engineer
SOASTA Inc.
snibgo
Posts: 12159
Joined: 2010-01-23T23:01:33-07:00
Authentication code: 1151
Location: England, UK

Re: Support for Pre-multiplied Alpha compositing

Post by snibgo »

Ah, sorry, I'm confused. Where in your code do you compose this over the photograph of the lake?
snibgo's IM pages: im.snibgo.com
cthomas
Posts: 10
Joined: 2016-01-18T10:55:03-07:00
Authentication code: 1151

Re: Support for Pre-multiplied Alpha compositing

Post by cthomas »

Snigbo. I dont, this code, is a part of a larger app, written in C++, that uses OpenGL to display various Media. I am using ImageMagick for general image processing tasks.In this case, to generate text for overlaying in my main app.

currentImage

in my code, is a shared Magick::Image object, that allows my app to create an Image via various methods i.e. from File, or from memory read, and then do things to it i.e. Blur it, FLip it etc.

In code, that I have not posted, the RGBA image present in currentImage at the end of my method, is copied by memory transfer, into my main app. And IT is what actually displays the text image over its own image, which in this case, is the image of blue mountains...

BTW, I have checked, and my main apps compositing method does make use of pre-mult alpha. So all should be good at that end...
Chris Thomas
Visualisation Engineer
SOASTA Inc.
snibgo
Posts: 12159
Joined: 2010-01-23T23:01:33-07:00
Authentication code: 1151
Location: England, UK

Re: Support for Pre-multiplied Alpha compositing

Post by snibgo »

I'm still confused. Are you saying that the two input images (text with shadow, and landscape) are good, but combining them makes a thin gray line around the text? Well, what code combines these images? It it isn't ImageMagick, what is it?
snibgo's IM pages: im.snibgo.com
Post Reply