^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
BE ENGINEERING INSIGHTS:
  I.  Making Your BLoopers More Responsive
  II. Gamma Correction
  By Jean-Baptiste Queru -- <jbq@be.com>
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Some of my fellow engineers tell me that I'm lucky to
have so many ideas for my newsletter articles. They
couldn't be more wrong. Having to choose between two
ideas is much harder (at least for me) than having to
find one idea. After whittling a pile of ideas down to
just two, I couldn't choose between them, so I'm using
both for this article.

You can find the source for them here:
ftp://ftp.be.com/pub/samples/graphics/gamma.zip

Part I: Responsive BLoopers

Something that BeOS developers must keep in mind when
programming their user interface is that users don't
like interfaces to feel slow or sluggish. One key to
interface responsiveness is to let the BLooper run
freely and process messages. To achieve this goal, you
must make sure that your program doesn't spend too much
time sitting in the various virtual functions that the
BLooper will call in its BHandlers. The sample code for
this article shows two techniques that should cover a
reasonable number of common cases.

Use Asynchronous Controls

In older versions of BeOS, the only way to track the
mouse to dynamically change a control was to repeatedly
call GetMouse() in a loop. The BeBook mistakenly suggested
doing this in MouseMoved(), which would prevent the
BLooper from processing message as long as the mouse
button was held down, which could be a very long time.
The correct way to do this was to poll in a separate
thread, a tricky thing to set up and to tear down without
deadlocking.

Fortunately, R4 added an elegant way to solve the
problem: the view event mask, which lets you create
asynchronous controls. Since this has already been
explained in other Newsletter articles, I won't say
much about it, but you can look at the sample code to
see one of those beasts in action. For more information,
see:<http://www-classic.be.com/aboutbe/benewsletter/volume_II/Issue45.html>
and
<http://www-classic.be.com/aboutbe/benewsletter/volume_II/Issue46.html>.

Use Other Threads to Do the Actual Work

We're now reaching the heart of the problem. The naive
way to implement real time user feedback with asynchronous
controls is to redraw the display each time MouseMoved()
is called. Change the #if in the sample code to see what
happens in that case. This is not what a user wants to
see, and it gets worse as the processing time gets longer.
A temporary solution might be to eliminate some of the
B_MOUSE_MOVED messages by hand in the BMessageQueue
before they're dispatched; this only works if the
processing stays reasonably short and if the BLooper
is a BWindow.

A more general solution is to have another thread do
the work for the BLooper, so the BLooper can continue
processing messages (e.g., update messages) as they
come along. Since setting up and tearing down threads
can be painful, and can easily create deadlocks and
other race conditions, I've encapsulated all that work
in two classes -- called SignalSender and SignalCatcher --
that I hope will be reasonably easy to use. Those classes
are an example of how easy it is to create your own
synchronization classes, and you might even find them
useful in their current state.


Part II: Gamma correction.

The sample code is not only interesting because of the
programming techniques it uses, but also because of what
it does. This small program is actually intended to
calibrate a gamma correction system and to use this
system to draw anti-aliased lines.

Gamma is a very simple process. The intensity of light
created by a CRT is a nonlinear function of its input
voltage. Gamma is a parameter that represents this
nonlinearity, and gamma correction is the process of
compensating for this nonlinearity. Surprisingly, this
nonlinearity is the same for all CRTs: the intensity of
light produced by the CRT is proportional to the input
voltage raised to the power 2.5. You can thus repeat after
me: "Gamma is 2.5."

There are, however, many components between the framebuffer
(or, more precisely, the DAC) and the CRT's input: various
amplifiers, filters, brighness/contrast controls. Overall,
those components scale and offset the signal between the
DAC and the CRT.

All the math is done in a GammaCorrect class, so you
don't have to know exactly how it works if you don't
want to.

The patterns displayed by the program are these:

* In the top-left corner, a median blend of black and
  white (note that I'm using lines, because some monitors,
  although expensive, have some limitations that make
  other patterns yield incorrect results).

* In the top-right corner, a plain gray that you can
  select using the control in the bottom of the screen
  and that you must try to adjust to the brightness of
  the black and white pattern.

* In the bottom-left corner, a system of high-frequency
  ripples that show some circular moire patterns if the
  gamma correction is not properly calibrated.

* In the bottom-right corner, some gamma-corrected
  anti-aliased lines, compared to the standard lines.

Right now, nothing is done by the OS, and everything has
to be done by the applications. This will probably change
in a future release, but this sample code shows how to
use some gamma correction without any OS support.

If you want to know more about how it really works, here's
the beginning of an explanation. If we call "f" the value
for a pixel in the framebuffer and "l" the lightness on
the screen for this pixel, we can write that l is
proportional to (f+e)^2.5. Since it's more convenient to
use values between 0 and 1, we'll renormalize l in that
range with to renormalization factors that we'll call
a and b, so that l=a*l'+b (l' is the normalized version
of l). We can finally write that f=((l'-b)/a)^0.4-e,
where f will vary between 0 and 1 when l' varies between
0 and 1.

The sample code will compute a, b, and e from another
value m that I call the "mid-light" value; i.e., the
gray level that is as light as a median blend of black
and white.  With ideal monitor settings, m should be
approximately 3/4, so that we get a=1, b=0 and e=0.

Part III: Conclusion

I hope this article casts some light on a few useful
programming tricks. Please send your feedback to
jbq@be.com.
