Extracting Color and Transparency from Flash
The original source of this post is at the IMVU engineering blog. Subscribe now!
For clarity, I slightly oversimplified my previous discussion on efficiently rendering Flash in a 3D scene. The sticky bit is extracting transparency information from the Flash framebuffer so we can composite the overlay into the scene.
Flash does not give you direct access to its framebuffer. It does, with IViewObject::Draw, allow you to composite the Flash framebuffer onto a DIB section of your choice.
Remembering your Porter-Duff, composition of source over dest is:
Color = SourceColor * SourceAlpha + DestColor * (1 - SourceAlpha)
If the source color is premultiplied, you get:
Color = SourceColor + DestColor * (1 - SourceAlpha)
Assuming we want premultiplied color and alpha from Flash for efficient rendering in the 3D scene, applying the above requires solving for FlashAlpha and FlashColor:
RenderedColor = FlashColor * FlashAlpha + DestColor * (1 - FlashAlpha) RenderedColor = FlashColor * FlashAlpha + DestColor - DestColor * FlashAlpha RenderedColor - DestColor = FlashColor * FlashAlpha - DestColor * FlashAlpha RenderedColor - DestColor = FlashAlpha * (FlashColor - DestColor) FlashAlpha = (RenderedColor - DestColor) / (FlashColor - DestColor)
If FlashColor and DestColor are equal, then FlashAlpha is undefined. Intuitively, this makes sense. If you render a translucent black SWF on a black background, you can't know the transparency data because all of the pixels are still black. This doesn't matter, as I'll show in a moment.
FlashColor is trivial:
RenderedColor = FlashColor * FlashAlpha + DestColor * (1 - FlashAlpha) RenderedColor - DestColor * (1 - FlashAlpha) = FlashColor * FlashAlpha FlashColor = (RenderedColor - DestColor * (1 - FlashAlpha)) / FlashAlpha
FlashColor is undefined if FlashAlpha is 0. Transparency has no color.
What do these equations give us? We know RenderedColor, since it's the result of calling IViewObject::Draw. We have control over DestColor, since we configure the DIB Flash is drawn atop. What happens if we set DestColor to black (0)?
FlashColor = (RenderedColorOnBlack) / FlashAlpha
What happens if we set it to white (1)?
FlashColor = (RenderedColorOnWhite - (1 - FlashAlpha)) / FlashAlpha
Now we're getting somewhere! Since FlashColor and FlashAlpha are constant, we can define a relationship between FlashAlpha and RenderedColorOnBlack and RenderedColorOnWhite:
(RenderedColorOnBlack) / FlashAlpha = (RenderedColorOnWhite - (1 - FlashAlpha)) / FlashAlpha RenderedColorOnBlack = RenderedColorOnWhite - 1 + FlashAlpha FlashAlpha = RenderedColorOnBlack - RenderedColorOnWhite + 1 FlashAlpha = RenderedColorOnWhite - RenderedColorOnBlack
So all we have to do is render the SWF on a white background and a black background and subtract the two to extract the alpha channel.
Now what about color? Just plug the calculated FlashAlpha into the following when rendering on black.
FlashColor = (RenderedColor - DestColor * (1 - FlashAlpha)) / FlashAlpha FlashColor = RenderedColorOnBlack / FlashAlpha
Since we want premultiplied alpha:
FlashColor = RenderedColorOnBlack
Now that we know FlashColor and FlashAlpha for the overlay, we can copy it into a texture and render the scene!
We've been using this approach for a long time in Gecko (we call it alpha recovery). It's a bit painful and slow though :-(. Sometimes you get slightly odd results when transparency is changing and the alpha values used for the white and black buffers were not the same. Doing alpha recovery on the GPU would probably help performance, but we're not doing that (yet).
We need Flash, at least NPAPI Flash, to grow an API that will render directly to a D3D surface.