Darkening artefacts with alpha channels

Here’s a white circle on a partially-transparent background:

image

Now compare GIMP (left) and G’MIC (right) blurring the circle after removing the background:

image image

Notice the halo, which results even when I don’t import an image and merely create one from scratch in G’MIC. On the left is a GIMP-blended circle blending with itself in normal mode in GIMP and in alpha mode in G’MIC:

image image

This is something that really puzzles me because all the values necessary to create the resultant image are there; all of the pixels which have a non-zero alpha have a colour here because the completely-transparent ones aren’t affected at all. The issue with this sort of bug is that it makes using both blurring and alpha blending in GIMP a very difficult business - I just so happen to be using both in a new filter that I’m building. It means that for now I’ll have to build my own commands which handle the values for the channels properly, but I am aware that other filters use these commands plenty of times while manipulating alpha channels.

Hmm I’m not seeing the same thing at all… some questions:

Is this using gmic gimp plugin, or command line?
Could you supply a .xcf file version?
Which exact steps are you performing, with which settings?

Also specify the GIMP and G’MIC versions and where you got them.

I’m using version 2.4.0 of the G’MIC plugin from the official site on GIMP 2.10.6 on a Debian-ish ‘custom’ distro with Ubuntu and Mint sources. I’ve updated the commands a few hours ago. The steps that I’m using are drawing a circle in G’MIC using the circle tool and then comparing the built-in GEGL blurring script with G’MIC’s blur, but running rm 300,300,1,4 ellipse 50%,50%,30%,30%,0,1,255 blur 5% in the plugin alone leads to this:

image

Using a simple G’MIC alpha blend on the GIMP-blurred circle yields the artefacts again but so does rm 300,300,1,4 ellipse 50%,50%,30%,30%,0,1,255 blur 5% f "[255,255,255,i3]" [0] blend alpha:

image

Here’s an XCF (6.3 KB) but it’s not even necessary to demonstrate the problem I’m having. The problems show up in the plugin GUI and persist afterwards.

1 Like

This is a long standing bug with the way gimp transfers data to plugins. Try this to see what’s happening:

split_opacity a x

You’ll see immediately the background is black. This is because gimp does not transfer the values of pixels in opacity 0 (fully transparent) regions. Even worse, any new values you set will not be transferred back either (unless you alter the opacity)!

The solution? Treat opacity 0 areas as “unknown” values. If you plan to “reveal” those pixels, begin by writing some value to the RGB before you do anything else.

Ideally this should be fixed in gimp…

I don’t think it was a GIMP problem because I added the rm to scrap any images beforehand. There’s a lot that I might be missing here in terms of what’s going on but it doesn’t look like GIMP is the culprit here. (It’s back-to-front with the input layer order but that’s for another thread.)

Meanwhile, I’ve just written the core of an alpha blend command which doesn’t exhibit the artefacts:

to_rgba a z f "opacity=j(0,0,1,3)/255;[i0*(1-opacity)+j(0,0,1,0)*opacity,i1*(1-opacity)+j(0,0,1,1)*opacity,i2*(1-opacity)+j(0,0,1,2)*opacity,max(0,min(255,i3+j(0,0,1,3)))]"

Blend strength should be nice and easy to add and I hope to make it eventually function just like the existing blend-alpha command without the nasty bit. I’m trying to recreate Paint.NET’s layer cake plugin but sorting out an extra blurring feature is proving nightmarish with the method I’m using.

Yes such problems can still appear without gimp getting in the way, but this bug is one to be aware of. There are other ways to handle it e.g. always write back opacity 1 as a minimum, or move images in the “stack” which forces gimp to treat it as a new layer.

We can be certain there’s no issue with gmic’s implementation of blur or blend in terms of algorithm correctness, so one way to troubleshoot would be to test some of your ideas at command line. Then you’ll know for sure whether gimp is at fault!

Edit:

If you blur a white circle on a black background (and also blur the opacity channel), then remove opacity replacing with white you should indeed see a halo. If you simply remove opacity to reveal the black then it just looks like a blurred circle. So nothing is wrong there.

The problem that I have with GIMP is that I believe the opacity range depends on the bit depth, whereas G’MIC keeps it at 0-1.

For blurring, the best think that I thought of before setting the minimum alpha to 1 was splitting the opacity into a new image while also keeping the opacity in the original image, using fx_td_solidify or a similar command on the original image, blurring the opacity and the solidified image and then appending the opacity. For alpha blending I can use fill. Here’s the current layer cake filter and what it can do (albeit very slowly):

#@gui Layer cake : fx_layer_cake, fx_layer_cake_preview(1)
#@gui : Iterations = int(4,1,32)
#@gui : Angle = float(360,-1080,1080)
#@gui : Angle times iteration = bool(0)
#@gui : Size = float(75,0,200)
#@gui : Centre = point(50,50,0,1,255,255,255,175)
#@gui : Boundary = choice(3,"None","Nearest","Periodic","Mirror")
#@gui : Blur = float(0,0,100)
#@gui : sep = separator()
#@gui : Anti-alias amplitude = float(30,0,100)
#@gui : Edge threshold (%) = float(0,0,100)
#@gui : Smoothness = float(3,0,10)
blend_alpha_lc :
f "opacity=j(#1,0,0,0,3)/255;[i0*(1-opacity)+j(#1,0,0,0,0)*opacity,i1*(1-opacity)+j(#1,0,0,0,1)*opacity,i2*(1-opacity)+j(#1,0,0,0,2)*opacity,max(0,min(255,i3+j(#1,0,0,0,3)))]" rm[1]
fx_layer_cake :
repeat $! l[$>]
iter=$1
angle=$2
if $3 angle*=$iter fi
size=$4/2
to_rgba
repeat $iter
[0] rotate[-1] {$angle/$iter*($>+1)},1,$7,$5%,$6%
100%,100%,1,4 ellipse. $5%,$6%,{$size/$iter*($iter-$>)}%,{$size/$iter*($iter-$>)}%,255,255,255
blur. {$8/$iter} f. "[255,255,255,i3]"
fx_smooth_antialias. ${9-11}
f.. "i*i(#-1)/255" rm[-1]
if {$!>=3} 
l[-1,-2] 
blend_alpha_lc 
endl fi done
blend_alpha_lc 
endl done
fx_layer_cake_preview:
fx_layer_cake $*

image

Of course, what’s weird is that this isn’t the only odd sort of artefact that G’MIC’s gaussian blur has. A pure white rectangle gets turned into something like this when the amplitude is very high:

image

What’s more, GC’s suggestion of keeping the alpha values non-zero works for blurring but not so much for blending; rm rm 300,300,1,4 ellipse 50%,50%,30%,30%,0,1,255 blur 5% f "[255,255,255,i3+1]" [0] blend alpha yields a similar result.

1 Like

Would it be possible to interpolate the blurring and the blending? I guess that could be the best solution there is. Or what about multiple blending/blurring iteration?

Also, it is possible to make the filter being able to output multiple layer. It’d go great with Krita layer effects.

Not sure about the first thing you’re discussing but the second is do-able despite it requiring a lot of work on the filter.

Edit: wasn’t as hard as I thought it would be.

#@gui Layer cake : fx_layer_cake, fx_layer_cake_preview(1)
#@gui : Iterations = int(4,1,32)
#@gui : Angle = float(360,-1080,1080)
#@gui : Angle times iteration = bool(0)
#@gui : Size = float(75,0,200)
#@gui : Centre = point(50,50,0,1,255,255,255,175)
#@gui : Boundary = choice(3,"None","Nearest","Periodic","Mirror")
#@gui : Blur = float(0,0,100)
#@gui : sep = separator()
#@gui : Anti-alias amplitude = float(30,0,100)
#@gui : Edge threshold (%) = float(0,0,100)
#@gui : Smoothness = float(3,0,10)
#@gui : Output layers = bool(0)
#@gui : sep = separator(), Preview type = choice("Full","Forward horizontal","Forward vertical","Backward horizontal","Backward vertical","Duplicate top","Duplicate left","Duplicate bottom","Duplicate right","Duplicate horizontal","Duplicate vertical","Checkered","Checkered inverse"), Preview split = point(50,50,0,0,200,200,200,0,10,0)
blend_alpha_lc :
f "opacity=j(#1,0,0,0,3)/255;[i0*(1-opacity)+j(#1,0,0,0,0)*opacity,i1*(1-opacity)+j(#1,0,0,0,1)*opacity,i2*(1-opacity)+j(#1,0,0,0,2)*opacity,max(0,min(255,i3+j(#1,0,0,0,3)))]" rm[1]
fx_layer_cake :
repeat $! l[$>]
iter=$1
angle=$2
if $3 angle*=$iter fi
size=$4/2
to_rgba
repeat $iter
[0] rotate[-1] {$angle/$iter*($>+1)},1,$7,$5%,$6%
100%,100%,1,4 ellipse. $5%,$6%,{$size/$iter*($iter-$>)}%,{$size/$iter*($iter-$>)}%,255,255,255
blur. {$8/$iter}
fx_smooth_antialias. ${9-11}
f. "[255,255,255,i3]"
f.. "i*i(#-1)/255" rm[-1]
if {$!>=3} 
l[-1,-2] 
if {!$12} blend_alpha_lc fi
endl fi done
if {!$12} blend_alpha_lc fi
endl done
fx_layer_cake_preview:
if {!$12} gui_split_preview "fx_layer_cake $*",${-3--1} else fx_layer_cake $* fi

Layer output seem to go backward. The biggest layer go on the top, and the smallest one go on the bottom.

However, I was able to do this by reordering the layer, and then assign non-destructive layer effect into layers.

That reverse ordering is a relatively-recent bug that I found when trying to make a different filter. GIMP and G’MIC don’t always play nicely and the former annoyingly feeds the latter everything in the reverse order. Besides that, I’ve updated the filter again to fix more problems with images that have alpha channels:

#@gui Layer cake : fx_layer_cake, fx_layer_cake_preview(1)
#@gui : Iterations = int(4,1,32)
#@gui : Angle = float(360,-1440,1440)
#@gui : Angle times iteration = bool(0)
#@gui : Size = float(75,0,200)
#@gui : Centre = point(50,50,0,1,255,255,255,175)
#@gui : Boundary = choice(3,"None","Nearest","Periodic","Mirror")
#@gui : Blur = float(0,0,100)
#@gui : sep = separator()
#@gui : Anti-alias amplitude = float(30,0,100)
#@gui : Edge threshold (%) = float(0,0,100)
#@gui : Smoothness = float(3,0,10)
#@gui : Output layers = choice("Off","On","Hollow")
#@gui : sep = separator(), Preview type = choice("Full","Forward horizontal","Forward vertical","Backward horizontal","Backward vertical","Duplicate top","Duplicate left","Duplicate bottom","Duplicate right","Duplicate horizontal","Duplicate vertical","Checkered","Checkered inverse"), Preview split = point(50,50,0,0,200,200,200,0,10,0)
blend_alpha_lc :
f "opacity=max(1,j(#1,0,0,0,3)*2/255);[i0*(1-opacity)+j(#1,0,0,0,0)*opacity,i1*(1-opacity)+j(#1,0,0,0,1)*opacity,i2*(1-opacity)+j(#1,0,0,0,2)*opacity,max(0,min(255,i3+j(#1,0,0,0,3)))]" rm[1]
fx_layer_cake :
repeat $! l[$>]
iter=$1
angle=$2
if $3 angle*=$iter fi
size=$4/2
to_rgba
repeat $iter
[0] rotate[-1] {$angle/$iter*($>+1)},1,$7,$5%,$6%
100%,100%,1,4 ellipse. $5%,$6%,{$size/$iter*($iter-$>)}%,{$size/$iter*($iter-$>)}%,0,1,255
if {$12!=1} ellipse. $5%,$6%,{$size/$iter*($iter-1-$>)}%,{$size/$iter*($iter-1-$>)}%,0,1,0 fi
blur. {$8/$iter}
#display
fx_smooth_antialias. ${9-11}
f. "[255,255,255,i3]"
f.. "i*i(#-1)/255" rm[-1]
if {$!>=3} 
l[-1,-2] 
if {!$12} blend_alpha_lc fi
endl fi done
l[0]
100%,100%,1,4 fc. 255,255,255,255 if {$12!=1} ellipse. $5%,$6%,{$size}%,{$size}%,0,1,0 fi
blur. {$8/$iter}
fx_smooth_antialias. ${9-11}
f.. "i*i(#-1)/255" rm[-1]
endl
if {!$12} blend_alpha_lc fi
endl done
fx_layer_cake_preview:
if {!$12} gui_split_preview "fx_layer_cake $*",${-3--1} else fx_layer_cake $* fi

I believe alpha channel processing isn’t the only bug. I have to say that there might be a memory leak into the plugin. I am processing a image of 14 MB, and the filter takes up 1 GB with a transparent image of that size (Really?).

Also, just had another idea. I wonder if it were possible to extend the boundary to see all of the circles.

It is likely due to dumbass unassociated alpha (aka dreadful term straight or unpremultiplied) versus associated alpha (aka dreadful term premultiplied) handling.

When values are associated, the RGBA are bound together and can be subjected to convolutions, smudges, etc. as one unit. This is also the only type of alpha channels that raytracing engines can generate. It is essentially a rasterized measurement of occluding geometry. It is the only format that can represent both occlusion and emission, including luminescent pixels with zero alpha. Such is required to represent candle flames, lens flares, glows, etc. For this reason, associated alpha is sometimes considered The One True Alpha™.

When unassociated, there is a disconnect between the RGB and the A.

Chances are your image format is using unassociated alpha such as the worthless PNG format, and that your circle is composited against black. It may not be being interpreted correctly.

It has nothing to do with image format at all. But, most of the time, the latest patch works well.

There is a reason why many G’MIC filters separate RGBA, manipulate RGB and then append A to the modified RGB. To me, I want nothing to do with the A channel. I usually drop it. If I need to blend two or more layers, I simply create separate masks.

That said, I do use it when I make vector graphics using Inkscape. Maybe our digital painters need it too when using GIMP or Krita.

Image formats matter, and alpha storage is a key reason why PNG was never adopted in post production pipelines.

Filter fixed yet again:

#@gui Layer cake : fx_layer_cake, fx_layer_cake_preview(1)
#@gui : Iterations = int(4,1,32)
#@gui : Angle = float(360,-1440,1440)
#@gui : Angle times iteration = bool(0)
#@gui : Size = float(75,0,200)
#@gui : Centre = point(50,50,0,1,255,255,255,175)
#@gui : Boundary = choice(3,"None","Nearest","Periodic","Mirror")
#@gui : Blur = float(0,0,100)
#@gui : sep = separator()
#@gui : Anti-alias amplitude = float(30,0,100)
#@gui : Edge threshold (%) = float(0,0,100)
#@gui : Smoothness = float(3,0,10)
#@gui : Output layers = choice("Off","On","Hollow")
#@gui : sep = separator(), Preview type = choice("Full","Forward horizontal","Forward vertical","Backward horizontal","Backward vertical","Duplicate top","Duplicate left","Duplicate bottom","Duplicate right","Duplicate horizontal","Duplicate vertical","Checkered","Checkered inverse"), Preview split = point(50,50,0,0,200,200,200,0,10,0)
blend_alpha_lc :
f "opacity=min(1,j(#1,0,0,0,3)*2/255);[i0*(1-opacity)+j(#1,0,0,0,0)*opacity,i1*(1-opacity)+j(#1,0,0,0,1)*opacity,i2*(1-opacity)+j(#1,0,0,0,2)*opacity,max(0,min(255,i3+j(#1,0,0,0,3)))]" rm[1]
fx_layer_cake :
repeat $! l[$>]
iter=$1
angle=$2
if $3 angle*=$iter fi
size=$4/2
to_rgba
repeat $iter
[0] rotate[-1] {$angle/$iter*($>+1)},1,$7,$5%,$6%
100%,100%,1,4 ellipse. $5%,$6%,{$size/$iter*($iter-$>)}%,{$size/$iter*($iter-$>)}%,0,1,255
if {$12!=1} ellipse. $5%,$6%,{$size/$iter*($iter-1-$>)}%,{$size/$iter*($iter-1-$>)}%,0,1,0 fi
blur. {$8/$iter}
fx_smooth_antialias. ${9-11}
f. "[255,255,255,i3]"
f.. "i*i(#-1)/255" rm[-1]
if {$!>=3}
l[-1,-2] 
if {!$12} blend_alpha_lc fi
endl fi done
l[0]
100%,100%,1,4 fc. 255,255,255,255 if {$12!=1} ellipse. $5%,$6%,{$size}%,{$size}%,0,1,0 fi
blur. {$8/$iter}
fx_smooth_antialias. ${9-11}
f.. "i*i(#-1)/255" rm[-1] 
endl 
if {!$12} blend_alpha_lc fi
endl done
fx_layer_cake_preview:
if {!$12} gui_split_preview "fx_layer_cake $*",${-3--1} else fx_layer_cake $* fi

I forgot to change that max in the blend command to a min.

Maybe one important thing to keep in mind :

G’MIC commands (like blur) generally don’t care about the alpha channel. Alpha is just considered as a 4th channel in the image, and it is treated just like the other channels. The user has to know what kind of data he manipulates, and if this 4th channel requires specific processing, then the user has to define the way to do it properly.

In G’MIC, if you have a 4-channel image in your list, you cannot determine if it is an alpha-channel, or a K channel (e.g. from a CMYK image), or something else. G’MIC doesn’t even want to know. Of course, all the time, the user knows what the 4th channel means.

But that is the main point : G’MIC commands are designed to work on generic image data (so an array of values with an arbitrary number of channels). Unless the command you use has been specifically designed for a certain type of input image ( split_opacity for instance, working with image having alpha), just consider every standard G’MIC commands is blind about the meaning of your 4th channel.

1 Like