libGD

GD is an open source code library for the dynamic creation of images by programmers. GD is written in C, and “wrappers” are available for Perl, PHP and other languages. GD creates PNG, JPEG and GIF images, among other formats. GD is commonly used to generate charts, graphics, thumbnails, and most anything else, on the fly. While not restricted to use on the web, the most common applications of GD involve web site development.

See the GD website for more informations.

| Tasklist |

FS#153 — CopyMerge clobbers destination alpha transparency

Attached to Project — libGD
Opened by Miles Spielberg (mspielberg) - Saturday, 08 March 2008, 02:51 GMT+2
Last edited by Pierre Joye (Pierre) - Thursday, 13 March 2008, 09:38 GMT+2
Task Type Feature Request
Category General
Status Unconfirmed   Reopened
Assigned To Pierre Joye (Pierre)
Operating System All
Severity High
Priority Normal
Reported Version 2.0.36RC1
Due in Version 2.1.0
Due Date Undecided
Percent Complete 0%
Votes 0
Private No

Details

CopyMerge, no matter the requested blending level, always seems to set the alpha of the destination pixels to 00, opaque. Also, when doing the merge, the

See the short attached Perl script, which a black opaque pixel (alpha 0) on top of a transparent white pixel (alpha 127), the result of which is fully opaque white (alpha 0). I expect to get a semi-transparent pixel, alpha 63, as in the last line of output should be 3fffffff instead of 007f7f7f.

More generally, the amount that the source and destination pixels affect the color of the final pixel should be weighted by the alpha value. A pixel with alpha 127 in either source or destination should not affect the output at all.

This task depends upon

Comment by Pierre Joye (Pierre) - Saturday, 08 March 2008, 11:36 GMT+2

Hi!

gdImateCopyMerge is a substitute for real alpha channel operations, so it doesn't pay attention to the alpha channel. if you like to blend two images, you can use gdImageCopy ($dest→copyMerge($src,0,0,0,0,1,1,50);) and active the alpha blending ($src→alphaBlending(1)). I may have to double check the documentation and update it if necessary, this feature is described in the sources but may not be clear enough in the normal documentation.

I hope my answer hepls, let me know if it works, let me know if you need more info.

Comment by Miles Spielberg (mspielberg) - Monday, 10 March 2008, 23:11 GMT+2

What I want to do is, starting with an opaque image (logo) and a destination image with some transparent areas, to overlay the opaque image onto the destination as a semi-transparent (50% blend) image. After the blending operation is complete, where the destination image was opaque, the result should be opaque with color blended from the source and destination. Where the destination image was transparent, I want the color of the overlay, and a semi-transparent alpha, so that when the result is later shown over a background, the background will show through the overlay.

However, when I try what you suggested, the result image is always fully opaque (alpha 0).

Comment by Pierre Joye (Pierre) - Monday, 10 March 2008, 23:14 GMT+2

Can you copy here the code you use please? And upload the sample sources image if you use some.

Comment by Miles Spielberg (mspielberg) - Tuesday, 11 March 2008, 01:00 GMT+2

Here is a short test script, the two input images, and the two output images produced by 2.0.36RC1. Note that in the area where the green and red overlap, the red is properly overlaid on the green with 50% transparency. However, where there is red alone, and greenhaze.png is transparent, in the output the color is *opaque* red, instead of 50% alpha red.

Comment by Pierre Joye (Pierre) - Tuesday, 11 March 2008, 02:04 GMT+2

I understand what you men now. copyMerge does not take care of the alpha channel. As I said earlier copyMerge does not take care of the alpha channel.

I wrote a little C application to achieve the same thing but using a circle with 50% alpha. See the application and the images as attachment. It should work the same way in perl. Is it what you like to do?

Comment by Miles Spielberg (mspielberg) - Tuesday, 11 March 2008, 02:06 GMT+2

Yes, but the problem is that I don't have control over the source or destination images. The source comes in without the 50% alpha applied.

Comment by Miles Spielberg (mspielberg) - Tuesday, 11 March 2008, 03:41 GMT+2

*accidental duplicate comment*

Comment by Pierre Joye (Pierre) - Tuesday, 11 March 2008, 09:05 GMT+2

"However, when I try what you suggested, the result image is always fully opaque (alpha 0)."

It looks like a bug in perl's gd then, the attached image is the result of this little code snippet (which is what you expect, if I'm not mistaken).

Can you try the c version? If not I can try to figure out what's wrong in the perl module (not this week though).

   1.png (2.1 KiB)
Comment by Miles Spielberg (mspielberg) - Tuesday, 11 March 2008, 21:31 GMT+2

I think you read the duplicate comment I accidentally submitted above. The problem is that your "circlealpha.png" has the alpha already applied: the circle has alpha 126 (0-255), with alpha 0 surrounding. I only have something of the form "reddot.png" to work with, where the circle is fully opaque (alpha 255) and the background is alpha 0.

Nor can I find a way in gd to manipulate the alpha channel of an image, except for with gdImageGetPixel/gdImageSetPixel, which makes it difficult/very expensive to derive circlealpha.png from reddot.png.

Comment by Pierre Joye (Pierre) - Tuesday, 11 March 2008, 22:01 GMT+2

If the original image has no alpha component, or the image is 100% opaque then you are right. There is no easy way to add an alpha component. CopyMerge only blend two images together but does not add an alpha channel. Once the alpha is added, it will behave like you expect.

gd 2.1.0 (or php 5.2+) has a function to add/sub a given value to any channel (red, green, blue or alpha). See:

http://blog.thepimp.net/index.php/post/2005/12/12/29-what-s-new-in-gd-51x-2

for some examples.

The underlying C code is very simple, it could be easy to add it to 2.0.x if you need it right now.

On the same topic, one thing I like to add is a true blend function with more options. It should work in a consistent manner like gimp or photoshop does. The current set of functions, setAlphaBlending and ColorTransparent are rather confusing.

Let me know if it works well for you with the C function.

Comment by Miles Spielberg (mspielberg) - Tuesday, 11 March 2008, 23:01 GMT+2

I'm sorry, I don't see anything in that blog entry that would allow me to convert an image with some transparent (alpha 0) areas and some opaque (alpha 255) areas to one with transparent areas and semi-transparent (alpha 127) areas.

I am right now moving my project to use Imager instead of GD. I believe the Imager's map() function is what I was looking for: Color Mappings

Comment by Pierre Joye (Pierre) - Wednesday, 12 March 2008, 00:02 GMT+2
I'm sorry, I don't see anything in that blog entry that would allow me to convert an image with some transparent (alpha 0) areas and some opaque (alpha 255) areas to one with transparent areas and semi-transparent (alpha 127) areas.

I think you should really read the documentation a little bit :) You got confused with a couple of things.

ok, some basis:

  • the alpha component is part of the image. In GD, it defines the level of transparency and can have a value from 0 (opaque) and 127 (translucide)
  • In order to do some blending operation where the result images contains non opaque areas, one of the images or both have to contain alpha. In your case, the src image has to contain 50% alpha in the red circle
  • The map you are refering to in Imager is actually the Palette. It defines a set of colors which can be used by the image. A palette can have a maximum of 256 colors. The pixels in the images will then contain the color index (color position in the palette). That's not what you are trying to do.

Now, when the red circle does not have any alpha and has: red 255, green 0, blue 0 and alpha 0 (« full opaque), you have to add it manually. That's where the Colorize filter helps (php code):

imagefilter($im, IMG_FILTER_COLORIZE, 0, 0, 0, 127/2);  // Will add 50% alpha to each pixel of the image

I hope it is clearer now,

Cheers,

Comment by Miles Spielberg (mspielberg) - Wednesday, 12 March 2008, 00:40 GMT+2

This discussion is being made more complicated because GD uses a different way of handling alpha than most image manipulation progams and the PNG format. From section 4.1c of the PNG specification:

The alpha sample determines a pixel's degree of opacity, where zero means fully transparent and the maximum value means fully opaque.

For 8-bit samples, this means 0..255 where 0 is transparent and 255 is opaque, whereas in GD the range is 127..0 where 127 is fully transparent and 0 is opaque. (GD's "alpha" value would probably be better termed a "tranparency" value, since alpha is a real number in the range 0.0..1.0 which is multiplied in front of the color values, but I digress.)

IMG_FILTER_COLORIZE will not do what I want. I do not want to "add 50% alpha to each pixel of the image." I want to linearly map the alpha values of each pixel such that opaque pixels become 50% transparent, 50% transparent pixels become 75% transparent, and fully transparent pixels are unaffected. In GD terms, I want to linearly map alpha values 127..0 to the range 127..63. In PNG terms, from 0..255 to 0..127.

Imager's map() function operates on "the values of each channel of an image." In a direct-mapped image there is no palette to manipulate. I have tested the line below, and it does in fact do what I desire:

# linearly map from 0..255 to 0..127
$img->map( alpha => [ map { int($_ / 2) } 0 .. 255 ] );
Comment by Pierre Joye (Pierre) - Wednesday, 12 March 2008, 01:07 GMT+2
This discussion is being made more complicated because GD uses a different way of handling alpha than most image manipulation progams and the PNG format.

First, PNG is one format, only one format supported by GD. Secondly, PNG supports both level of transparency or level of opacity. GD currently supports only a level of opacity from 0 to 127. 2.1 supports both levels and in a range from 0.255 for 8bits per channels, or 2^16 for 16bit alpha.

IMG_FILTER_COLORIZE will not do what I want. I do not want to "add 50% alpha to each pixel of the image." I want to linearly map the alpha values of each pixel such that opaque pixels become 50% transparent, 50% transparent pixels become 75% transparent, and fully transparent pixels are unaffected. In GD terms, I want to linearly map alpha values 127..0 to the range 127..63. In PNG terms, from 0..255 to 0..127.

Indeed it does what you like, it adds the alpha you need to blend the red circle over the green one. But for that you have to try. Please read some paper about blending operations (I have some good links if you like).

Imager's map() function operates on "the values of each channel of an image." In a direct-mapped image there is no palette to manipulate.

Ah right, sorry, I was wrong. I misread the description.

Anyway, if you would have tried the filter and the code I gave, you would have seen that it works and does what you want. You do need the alpha component to achieve your goal. see the circlealpha I uploaded earlier, it was created using this exact function and the result is correct.

Earlier you said:

in the output the color is *opaque* red, instead of 50% alpha red.

That's exactly why you do need the alpha in the first place and why adding the 50% does do what you want. given that in the destination image the backround is 100% translucide, you end with a 50% alpha red (red: 255, alpha: 67).

I'm now out of good explanations and time, I'll close this issue.

Comment by Miles Spielberg (mspielberg) - Wednesday, 12 March 2008, 01:39 GMT+2

We're still having a misunderstanding on what I want to accomplish. I apologize that I haven't been sufficiently clear.

Suppose I have 3 pixels in GD, with the following RGBA values (here I will use the GD convetion of 127 for fully transparent, 0 for fully opaque):

(255,255,255,127) (transparent white)
(255,0,0,63)      (50% translucent red)
(255,0,0,0)       (opaque red)

What I want is a transformation is a transformation that gives these three color values as a result:

(255,255,255,127) (transparent white)
(255,0,0,95)      (75% translucent red)
(255,0,0,63)      (50% translucent red)

Once I have these values, I can use gdImageCopy() as you suggested in your first comment.

You indicated that I should use this line of PHP code:

imagefilter($im, IMG_FILTER_COLORIZE, 0, 0, 0, 127/2);  // Will add 50% alpha to each pixel of the image

The problem is, IMG_FILTER_COLORIZE adds its arguments to the color values, then clamps to the acceptable range. This will give me the following 3 color values:

(255,255,255,127) (transparent white)
(255,0,0,126)     (transparent red)
(255,0,0,63)      (50% translucent red)

This is correct for the first and third color values, but it's not what I want for the middle color value.

This is what I mean by I want a linear scaling, not addition. I think the major confusion is that the original sample images I provided were not sufficiently complex, with intermediate alpha values.

Comment by Pierre Joye (Pierre) - Wednesday, 12 March 2008, 01:47 GMT+2
This is correct for the first and third color values, but it's not what I want for the middle color value.
This is what I mean by I want a linear scaling, not addition. I think the major confusion is that the original sample images I provided were not sufficiently complex, with intermediate alpha values.

Aha! I got it now. You can use the same function but use a factor instead of an offset (a multiplication instead of the addition). I can modify the code accordingly tomorrow if you like. I can be easily added to the perl binding.

Comment by Miles Spielberg (mspielberg) - Wednesday, 12 March 2008, 02:13 GMT+2

That would be great!

I think it would be a little more complex than

alpha = alpha * factor

Probably something like

alpha = alpha + (127-alpha) * (100-pct/100)

where 100pct is no change, 0 makes everything transparent, and 50 is the operation I outlined above? (Using the gdImageCopyMerge percentage convention.)

Is that what you had in mind? What would such an operation be called?

Comment by Pierre Joye (Pierre) - Thursday, 13 March 2008, 09:25 GMT+2
alpha = alpha + (127-alpha) * (100-pct/100)

That's not what I had in mind but it would make sense to have a gdImageCopyMergeWithAlpha (long name but clearly state the difference) which would behave like this.

The gdImageColorOp will also be added. A good signature I can think about was:

gdImageColorOp(gdImagePtr im, gdChannelOp op, float fred, float fgreen, float fblue, float falpha);

where op can be multiplication, addition or division.

I sadly did not have the time to do it yesterday but expect these functions to hit the CVS during the weekend.

I played with something neat, closely related to these two functions, a custom expression parser. The idea is to give an expression which will be evaluated for each pixel in the active area (image or clip). For example, you could achieve your need using:

"pct=80; alpha = alpha + (127 - alpha) * (100 - pct/100)" directly. It can also use more complex operation based on the position of the pixel: "pct=80; alpha = alpha + (127 - alpha) * (100 - pct * y*cos(x)/100)"

It will not do it in 2.1 but will certainly be available as a separate module until it is stable :)

Loading...