Blit semantics to RGBA surfaces?

Currently, in the new alpha blit semantics, copies to RGBA surfaces
have the following semantics:

RGB -> RGBA (no alpha value)
Copy src RGB to dst, alpha on destination set to opaque

RGBA -> RGBA (blending disabled)
Copy src RGB to dst, copy alpha channel from source to dest

RGB -> RGBA (constant alpha)
Blend src RGB with dst, alpha on destination set to opaque

RGBA -> RGBA (blend)
Blend src RGB with dst, alpha on destination untouched

Obviously, the semantics for the destination alpha are not consistent.
Copying the alpha channel from the source on non-blended blit makes sense,
and is very useful for SDL_OPENGLBLIT, but the semantics for a blended
blit are confusing. What does OpenGL do? Any suggestions for useful
semantics?

I see two solutions that make sense and are fast to perform:

  1. Set destination alpha opaque for blended blits as well
  2. Leave destination alpha untouched for blended blits.

I’m leaning towards the second option, but I’m interested in feedback.

See ya!
-Sam Lantinga, Lead Programmer, Loki Entertainment Software

Newsgroups: loki.open-source.sdl

RGB -> RGBA (constant alpha)
Blend src RGB with dst, alpha on destination set to opaque

Yuck.

I see two solutions that make sense and are fast to perform:

  1. Set destination alpha opaque for blended blits as well
  2. Leave destination alpha untouched for blended blits.

I see a need for two types of blits (and I think you do too, but I’ll
describe them anyway so that it’s explicit):

  1. Straight copy blits. Color keys are ignored. If the destination surface
    has per-pixel alpha, the source alpha (per-surface or per-pixel) is copied,
    otherwise it is ignored. Destination per-surface alpha is always ignored.

Variant: transparency-correct copy blit. Pixels are considered
"transparent" if they are color-keyed or have a per-pixel alpha value of 0.
Everything possible is done to ensure that transparent and only transparent
pixels in the source map onto transparent pixels in the destination.

  1. Normal blit. Color-keyed pixels in the source are not copied. For alpha
    (per-surface or per-pixel), the following formula applies:

new_pixel = src_alpha * src_pixel + (1.0 - src_alpha) * dest_pixel.

If the destination pixel contains alpha, this formula still applies in the
following form:

new_pixel = src_alpha * (src_r, src_g, src_b, 1.0) +
(1.0 - src_alpha) * (dest_r, dest_g, dest_b, dest_alpha)

Evaluating for the alpha channel, this yields:

new_alpha = src_alpha + (1.0 - src_alpha) * dest_alpha.

The effect is that blitting surface A on surface B (both with per-pixel
alpha) and blitting the result on background C is equivalent to blitting B
on C, and then blitting A on the result.


Rainer Deyke (root at rainerdeyke.com)
Shareware computer games - http://rainerdeyke.com
"In ihren Reihen zu stehen heisst unter Feinden zu kaempfen" - Abigor

----- Original Message -----
From: slouken@devolution.com (Sam Lantinga)
Sent: Saturday, August 26, 2000 7:00 PM
Subject: Blit semantics to RGBA surfaces?

Newsgroups: loki.open-source.sdl

I see a need for two types of blits (and I think you do too, but I’ll
describe them anyway so that it’s explicit):

I agree. This is much cleaner. We could almost call one
SDL_CopySurface (the non-blending version) and call the other
SDL_BlitSurface (the blending version). Just a layman’s thoughts. I’m
no expert.

-ArthurFrom: root@rainerdeyke.com (Rainer Deyke)
Subject: [SDL] Re: Blit semantics to RGBA surfaces?
Date: Sat, 26 Aug 2000 22:15:49 -0600

----- Original Message -----
From: “Sam Lantinga”
Sent: Saturday, August 26, 2000 7:00 PM
Subject: Blit semantics to RGBA surfaces?

I see a need for two types of blits (and I think you do too, but I’ll
describe them anyway so that it’s explicit):

  1. Straight copy blits. Color keys are ignored. If the destination surface
    has per-pixel alpha, the source alpha (per-surface or per-pixel) is copied,
    otherwise it is ignored. Destination per-surface alpha is always ignored.
    Whether or not the colorkey is used is up to the user not the blitter.
    Colorkey can be disabled on a surface just as easiliy as it can be
    enabled. A blit that ignores surface flags (SDL_SRCCOLORKEY) just adds
    more confusion to a very confusing issue:)

MartinOn Sat, 26 Aug 2000, Rainer Deyke wrote:

Bother, said Pooh, as he was butchered for his paws & liver.

new_pixel = src_alpha * (src_r, src_g, src_b, 1.0) +
(1.0 - src_alpha) * (dest_r, dest_g, dest_b, dest_alpha)

that’s an original way of using alpha. any justification?

The effect is that blitting surface A on surface B (both with per-pixel
alpha) and blitting the result on background C is equivalent to blitting B
on C, and then blitting A on the result.

no

I see a need for two types of blits (and I think you do too, but I’ll
describe them anyway so that it’s explicit):

  1. Straight copy blits. Color keys are ignored. If the destination surface
    has per-pixel alpha, the source alpha (per-surface or per-pixel) is copied,
    otherwise it is ignored. Destination per-surface alpha is always ignored.

This is in fact what’s already implemented, but instead of using separate
blitter functions, you can enable/disable SDL_SRCALPHA and SDL_SRCCOLORKEY
flags.

  1. Normal blit. Color-keyed pixels in the source are not copied. For alpha
    (per-surface or per-pixel), the following formula applies:

new_pixel = src_alpha * src_pixel + (1.0 - src_alpha) * dest_pixel.

If the destination pixel contains alpha, this formula still applies in the
following form:

new_pixel = src_alpha * (src_r, src_g, src_b, 1.0) +
(1.0 - src_alpha) * (dest_r, dest_g, dest_b, dest_alpha)

Evaluating for the alpha channel, this yields:

new_alpha = src_alpha + (1.0 - src_alpha) * dest_alpha.

The effect is that blitting surface A on surface B (both with per-pixel
alpha) and blitting the result on background C is equivalent to blitting B
on C, and then blitting A on the result.

I like this alot. Yorick? Can we implement this for the alpha blits?

RGB -> RGBA (constant alpha)
Blend src RGB with dst, alpha on destination set to opaque

Yuck.

Heh, this is actually correct given the above formula and an alpha of
1.0:

new_pixel = 1.0 * (src_r, src_g, src_b, 1.0) +
(1.0 - 1.0) * (dest_r, dest_g, dest_b, dest_alpha)

or

new_pixel = (src_r, src_g, src_b, 1.0)

Thank you very much for your feedback!

See ya,
-Sam Lantinga, Lead Programmer, Loki Entertainment Software

new_pixel = src_alpha * (src_r, src_g, src_b, 1.0) +
(1.0 - src_alpha) * (dest_r, dest_g, dest_b, dest_alpha)

that’s an original way of using alpha. any justification?

The effect is that blitting surface A on surface B (both with per-pixel
alpha) and blitting the result on background C is equivalent to blitting B
on C, and then blitting A on the result.

no

Why not?

-Sam Lantinga, Lead Programmer, Loki Entertainment Software

new_pixel = src_alpha * (src_r, src_g, src_b, 1.0) +
(1.0 - src_alpha) * (dest_r, dest_g, dest_b, dest_alpha)

that’s an original way of using alpha. any justification?

This is (I think) the way OpenGL handles destination alpha as well.

Corrections? Comments?

-Sam Lantinga, Lead Programmer, Loki Entertainment Software

new_pixel = src_alpha * (src_r, src_g, src_b, 1.0) +
(1.0 - src_alpha) * (dest_r, dest_g, dest_b, dest_alpha)

that’s an original way of using alpha. any justification?

The effect is that blitting surface A on surface B (both with per-pixel
alpha) and blitting the result on background C is equivalent to blitting B
on C, and then blitting A on the result.

no

Why not?

Let b(p1, a1, p2, a2) = (a1*p1 + (1-a1)*p2, a1 + (1-a1)*a2).

Then see if b(b(pA, aA, pB, aB), pC, aC) == b(pA, aA, b(pB, aB, pC, aC)).
You will find that isn’t the case.
This isn’t to say that the above could be a reasonable choice, just that
I would like to know why. The key criteria should be:

  1. what is actually needed in a game
  2. what can be accelerated in hardware
  3. what is easy to implement efficiently in software

in roughly that order.

(On the other hand, the associativity claimed above does hold if you use
the common Duff-Porter alpha compositioning. But they use premultiplied
alpha.)

“Mattias Engdeg?rd” wrote in message
news:200008271703.TAA03119 at octans.nada.kth.se

new_pixel = src_alpha * (src_r, src_g, src_b, 1.0) +
(1.0 - src_alpha) * (dest_r, dest_g, dest_b, dest_alpha)

that’s an original way of using alpha. any justification?

See below.

The effect is that blitting surface A on surface B (both with per-pixel
alpha) and blitting the result on background C is equivalent to blitting
B

on C, and then blitting A on the result.

no

No? I admit I haven’t tried doing this manually. However this yields 75%
opacity when blitting one pixel with 50% opacity onto another pixel with 50%
opacity, which makes sense to me.–
Rainer Deyke (root at rainerdeyke.com)
Shareware computer games - http://rainerdeyke.com
"In ihren Reihen zu stehen heisst unter Feinden zu kaempfen" - Abigor

“Sam Lantinga” wrote in message
news:E13T77f-0000PV-00 at roboto.devolution.com

RGB -> RGBA (constant alpha)
Blend src RGB with dst, alpha on destination set to opaque

Yuck.

Heh, this is actually correct given the above formula and an alpha of
1.0:

new_pixel = 1.0 * (src_r, src_g, src_b, 1.0) +
(1.0 - 1.0) * (dest_r, dest_g, dest_b, dest_alpha)

or

new_pixel = (src_r, src_g, src_b, 1.0)

Per-surface alpha should not be ignored in this case, IMO.–
Rainer Deyke (root at rainerdeyke.com)
Shareware computer games - http://rainerdeyke.com
"In ihren Reihen zu stehen heisst unter Feinden zu kaempfen" - Abigor

“Mattias Engdeg?rd” wrote in message
news:200008271930.VAA12934 at octans.nada.kth.se

Let b(p1, a1, p2, a2) = (a1*p1 + (1-a1)*p2, a1 + (1-a1)*a2).

Then see if b(b(pA, aA, pB, aB), pC, aC) == b(pA, aA, b(pB, aB, pC, aC)).

Actually I was only thinking of cases where C has no alpha channel.

Testing with pA = .1, aA = .2, pB = .3, aB = .4, pC = .5, aC = 1:

b(b(pA, aA, pB, aB), pC, aC)
b(b(.1, .2, .3, .4), .5, 1)
b(.2*.1 + (1-.2).3, .2 + (1-.2).4, .5, 1)
b(.26, .52, .5, 1)
.52 * .26 + (1 - .52) * .5, 1
.1352 + .24, 1
.3752, 1

b(pA, aA, b(pB, aB, pC, aC))
b(.1, .2, b(.3, .4, .5, 1))
b(.1, .2, .4*.3 + (1-.4).5, 1)
b(.1, .2, .42, 1)
.2
.1 + (1-.2)*.42, 1
.356, 1

You’re right, they’re different. However, for practical purposes, they are
still very similar.

You will find that isn’t the case.
This isn’t to say that the above could be a reasonable choice, just that
I would like to know why. The key criteria should be:

  1. what is actually needed in a game

This seems useful for compositing several images with alpha onto a single
surface.

  1. what can be accelerated in hardware

It’s certainly possible, but I’m not sure if many (or any) cards implement
this currently. I don’t think speed is very important here, since
compositing several images with alpha into a single image is itself a speed
optimization.

  1. what is easy to implement efficiently in software

This applies for some values of easy and efficient. :slight_smile: Since this
operation will be fairly rare, I’m not super if speed is very important.
Note: this method should never be used when blitting to the primary
surface, even in 32 bit modes.

(On the other hand, the associativity claimed above does hold if you use
the common Duff-Porter alpha compositioning. But they use premultiplied
alpha.)

I’m unfamiliar with Duff-Porter alpha compositioning. I’ll look into it.–
Rainer Deyke (root at rainerdeyke.com)
Shareware computer games - http://rainerdeyke.com
"In ihren Reihen zu stehen heisst unter Feinden zu kaempfen" - Abigor

If we want that blended surfaces (A+B)+C == A+(B+C) then
I suggest a formula that allows this to be true:

new_a = src_alpha + dest_alpha;
new_r = src_alpha * src_r + dest_alpha * dest_r;
new_g = src_alpha * src_g + dest_alpha * dest_g;
new_b = src_alpha * src_b + dest_alpha * dest_b;

I suppose we work with opaque=1 and transparent=0;
Otherwise, the alpha channel formula must change.

But if (src_alpha + dest_alpha>1), we have a alpha interpretation problem.
I suggest to consider the applied surface as an overidding surface,
a surface that hides what is behind.
For instance, if (src_alpha ==1), then dest-Surface shoudl be ignored.
Then I complete the formula:

new_a = src_alpha + dest_alpha;
if (new_a>1)
{
dest_alpha=1-src_alpha;
new_a=1;
}
new_r = src_alpha * src_r + dest_alpha * dest_r;
new_g = src_alpha * src_g + dest_alpha * dest_g;
new_b = src_alpha * src_b + dest_alpha * dest_b;

Which allows the following case to work fine:
src_alpha =1
dest_alpha=1

Luc-Olivier

Actually I was only thinking of cases where C has no alpha channel.

That doesn’t matter, since your blending operator ignores the destination
alpha.

Testing with pA = .1, aA = .2, pB = .3, aB = .4, pC = .5, aC = 1:

Do the math symbolically instead of blindly inserting random values

You’re right, they’re different. However, for practical purposes, they are
still very similar.

And how do you know that? You have just tried it with one set of numbers.

  1. what is actually needed in a game

This seems useful for compositing several images with alpha onto a single
surface.

There’s no point in including stuff in SDL just because “someone might
need it”. Building libraries correctly is hard. Ideally, the authors
should draw from their own experience and judge what is needed in real
projects. The second-best way is to include features as they are
needed in a current project (and a lot of SDL has been built this way),
but it can lead to many ad-hoc solutions and a general lack of coherency.
Experience shows that features included “just in case” are rarely used.

  1. what can be accelerated in hardware

It’s certainly possible, but I’m not sure if many (or any) cards implement
this currently. I don’t think speed is very important here, since
compositing several images with alpha into a single image is itself a speed
optimization.

Speed is always important. In fact, I’m reluctant to spend too much time
implementing the non-RLE versions since the RLE code is so much faster.

“Mattias Engdeg?rd” wrote in message
news:200008280920.LAA08838 at alv.nada.kth.se

Testing with pA = .1, aA = .2, pB = .3, aB = .4, pC = .5, aC = 1:

Do the math symbolically instead of blindly inserting random values

I’m too lazy.

You’re right, they’re different. However, for practical purposes, they
are

still very similar.

And how do you know that? You have just tried it with one set of numbers.

Ultimately, the equivalency of b(b(A, B), C) and b(A, b(B, C)) is
irrelevant. What matters is that the resulting visual effect is useful. I
think it is, although it is possible that there are other, more useful ways
to approach alpha blending.

There’s no point in including stuff in SDL just because “someone might
need it”. Building libraries correctly is hard. Ideally, the authors
should draw from their own experience and judge what is needed in real
projects. The second-best way is to include features as they are
needed in a current project (and a lot of SDL has been built this way),
but it can lead to many ad-hoc solutions and a general lack of coherency.
Experience shows that features included “just in case” are rarely used.

A valid point.

  1. what can be accelerated in hardware

It’s certainly possible, but I’m not sure if many (or any) cards
implement

this currently. I don’t think speed is very important here, since
compositing several images with alpha into a single image is itself a
speed

optimization.

Speed is always important. In fact, I’m reluctant to spend too much time
implementing the non-RLE versions since the RLE code is so much faster.

That’s a different issue. RLE and non-RLE are both used to generate the
same effect. At this point, the only justification for including non-RLE at
all is that it is sometimes faster.–
Rainer Deyke (root at rainerdeyke.com)
Shareware computer games - http://rainerdeyke.com
"In ihren Reihen zu stehen heisst unter Feinden zu kaempfen" - Abigor

There’s no point in including stuff in SDL just because “someone might
need it”. Building libraries correctly is hard. Ideally, the authors
should draw from their own experience and judge what is needed in real
projects. The second-best way is to include features as they are
needed in a current project (and a lot of SDL has been built this way),
but it can lead to many ad-hoc solutions and a general lack of coherency.
Experience shows that features included “just in case” are rarely used.

Well, this is something that is difficult argue with. But if enough
people have to go through complicated klugery in order to do a particular
task, given a library’s current interface, then I think that is sufficient
justification for it to be implemented as a library’s feature. More often
than not, implementing such tricks at the level of the library will
provide a cleaner solution, even in the library code, than ad-hoc
solutions people often have to come up with.On Mon, 28 Aug 2000, Mattias Engdeg?rd wrote:


Rafael R. Sevilla <@Rafael_R_Sevilla> +63 (2) 4342217
ICSM-F Development Team, UP Diliman +63 (917) 4458925
PGP Key available at http://home.pacific.net.ph/~dido/dido.pgp

That’s a different issue. RLE and non-RLE are both used to generate the
same effect. At this point, the only justification for including non-RLE at
all is that it is sometimes faster.

non-RLE is best for dynamically changing alpha surfaces.

See ya,
-Sam Lantinga, Lead Programmer, Loki Entertainment Software