Closed Bug 379786 Opened 17 years ago Closed 17 years ago

Text with 'opacity' gets grayscale antialiasing, inconsistent with rendering with RGBA color

Categories

(Core :: Graphics, defect)

x86
macOS
defect
Not set
normal

Tracking

()

RESOLVED WONTFIX

People

(Reporter: roc, Assigned: jtd)

References

Details

Attachments

(7 files)

In the attached testcase, both lines of text should be the same. But if I look closely with the Pixie tool or some other tool, they're antialiased differently on my laptop. rgba(0,0,0,0.5) text is subpixel-antialiased like the rest of my text. opacity:0.5 text is antialiased using grayscale only. My guess is that  _cairo_quartz_surface_create_similar is creating a surface which doesn't have the LCD characteristics associated with it.

This breaks layout/reftests/bugs/379316-1.html on my Mac.
I suppose that this isn't showing up on Tinderbox because those Macs are configured to not use subpixel rendering (implicitly or explicitly).
I'm not sure what the best thing is to do here. I don't see any API to configure the bitmap context to do subpixel AA (although I might have missed it). 10.4 has a "CGLayer" type that we could use to implement _cairo_quartz_surface_create_similar, which might do the right thing, but that's 10.4 only.
This affects the address bar (post bug 366797 landing).  Watch the "r", the second-to-last letter in "bugzilla.mozilla.org", while moving the cursor into or out of the address bar.  It temporarily becomes grayscale and appears to shift by half a pixel.  You can see similar things happening to the other letters in the hostname if you zoom in.
Flags: blocking1.9?
Summary: Antialiasing inconsistency between 'opacity' and 'rgba' --- _cairo_quartz_surface_create_similar issue → Text with 'opacity' gets grayscale antialiasing instead of subpixel (inconsistent with 'rgba') (_cairo_quartz_surface_create_similar issue)
Now that we're dropping support for 10.3 I think we can change cairo-quartz to use CGLayers (which I think, but can't be sure, will fix this).
Assignee: nobody → jdaggett
Not gonna block on this, but we should try to fix it.. I don't think we want to use CGLayers for this, though.  However, I do have a patch that does basically that (uses CGLayer instead of push_group), but I need to do a bunch of cairo work to expose push/pop group to backends.
Flags: blocking1.9? → blocking1.9-
I'm seeing the same issue on Linux. If you have subpixel rendering active ("Subpixel smoothing" option in gnome-font-properties), the reftest on bug 379316 fails.

I guess the linux Tinderboxes have their default to non-subpixel rendering, that's why it's not failing there too.

For information, the output of "xrdb -query|grep Xft" with "Subpixel smoothing" on is:
Xft.antialias:  1
Xft.dpi:        86.000000
Xft.hinting:    1
Xft.hintstyle:  hintfull
Xft.rgba:       rgb

And with "Best shapes" (the default on most distributions)
Xft.antialias:  1
Xft.dpi:        86.000000
Xft.hinting:    1
Xft.hintstyle:  hintmedium
Xft.rgba:       none
(In reply to comment #6)
> Not gonna block on this, but we should try to fix it.. I don't think we want to
> use CGLayers for this, though.  However, I do have a patch that does basically
> that (uses CGLayer instead of push_group), but I need to do a bunch of cairo
> work to expose push/pop group to backends.

I don't quite understand ... ideally, wouldn't create_surface_similar create a surface that has the same subpixel rendering characteristics as the original surface? CGLayer is the only way to get that in Quartz, as far as I know.
Supposedly, you have to specify a screen CGContext when creating an ATSU layout to get subpixel AA, even if you don't draw to that context.  You can't specify NULL, either, it has to be a real screen context.  I'm not sure that actually helps us though, since our final drawing is done using CG, and not using ATSU, so there's probably some magic flag we're missing for CG.
Maybe John's secret backchannel can help us here...
The first line of the testcase shows grey-level rendering, the second one shows LCD screen rendering.

  <div style="opacity: 0.5">Test 17</div>
  <div style="color: rgba(0, 0, 0, 0.5)">Test 17</div>    

The first line will be first rendered to an offscreen, then blit to the screen context.  The second one will be drawn directly to the screen context.  In either case, ATSUI is not involved in the rendering, the rendering occurs via calls to CGContextShowGlyphsWithAdvances.

I could be wildly offbase but isn't this a problem of the colorspace used with the context?

http://mxr.mozilla.org/mozilla/source/gfx/cairo/cairo/src/cairo-atsui-font.c#698

Will investigate more later.
Isn't that just for fallback? I don't think we'll touch that code during normal rendering.

Here's some info:
http://mjtsai.com/blog/2006/02/04/font-smoothing-in-pages/
http://michelf.com/weblog/2006/subpixel-antialiasing-achilles-heel/

The latter one seems to be right on the money ... the problem being there's really no way to do subpixel AA right in an RGBA surface, we want an RAGABA surface and Quartz doesn't have one.

So I guess we have to WONTFIX this bug and emasculate the tests that depend on it.
Don't tag this WONTFIX just yet, let me play around with this for a bit.
I don't think we can solve this problem without some extremely fancy footwork that's probably not worth it.  The root of the problem is the ever-so-subtle difference between the semantics of the opacity setting and RGBA colors in combination with the way in which Apple renders text.

Setting opacity on an element forces a two-stage process, that element is first rendered and *then* blended with the background.  With RGBA colors, elements can be blended directly into the background.  

On the Mac, text is rendered based on settings in the Appearance panel and font smoothing settings of the current context.  If the Appearance panel is set to render to a LCD display and font smoothing is enabled in the context, text is *blended* into the background in such a way that leading edges of black text appear reddish and trailing edges appear blueish.  As roc has suggested, the font scaler is essentially taking the LCD geometry into account and generating separate alpha values for each channel.  When text is rendered these separate alphas are used to blend the text color into the background.  So when black text is rendered against a white background, leading and trailing edges will be shaded differently.  But if a transparent layer is used (i.e. all background pixels have alpha = 0), the result is just black pixels with a variety of alpha values, hence the gray-scale effect when subsequently blended into a background.  In short, when transparent offscreens are used, the LCD-sensitive coverage info is lost.

We could simulate this with a mask operation.  Rendering white text into a solid black (alpha=1, color=black) background would give us a mask that would capture the separate r,g,b alpha information.  But we would need to keep this around until all subelements were rendered, then blit the final result to the main context with a custom blitter.  Might be possible to approximate this using a special packed alpha format (e.g. Ar: 2bits, Ag: 3bits, Ab: 2 bits), but this would lead to a lot of headaches.

I suggest we mark this as WONTFIX (ever so sorry...).

Note that this strictly related to Quartz font smoothing, ATSUI and CGLayer are orthogonal to the issue.

More examples to follow. 
Summary: Text with 'opacity' gets grayscale antialiasing instead of subpixel (inconsistent with 'rgba') (_cairo_quartz_surface_create_similar issue) → Text with 'opacity' gets grayscale antialiasing, inconsistent with rendering with RGBA color
The attached code renders text using a variety of ways.  The first just draws directly into the window context, the others render into an offscreen and then copy to the screen.  For example, with a bitmap context that has been filled white, black text shows the same sub-pixel anti-aliasing as in the direct case.  With a gradient as a background, you can see that the text drawing code is clearing blending the text with the background color.  Using a CGLayer gives the same result as using a transparent bitmap context.
Note the different shading on the leading and trailing edges.
This example shows how the semantics of using RGBA colors with A=0.5 differs from using opacity = 0.5.  With RGBA colors, the paint operation immediately blends the content into the background.  With opacity, elements are first rendered without opacity, then blended in with the background using the opacity value.  Note how the red border doesn't show in the second case, that's because the blue div occludes it *before* the blend operation occurs.

Note: for both examples, the "Using..." text should be the same.  It looks like the rgba color specified in the first case is "leaking" into the second case, that's a separate bug.
As per comments above, changing to won't fix.
Status: NEW → RESOLVED
Closed: 17 years ago
Resolution: --- → WONTFIX
I opened bug 400829 for the Linux version if this issue, in case it is easier to solve there.
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: