HowTo:
Gamma-Korrekturen an Bitmap-Darstellern
oder: Wie spare ich 50 Euro durch Lingo?
|
|
Home
Workshop-Seite
|
Dieser
Artikel ist ein Beitrag von Joachim Baur (baur@medien-werkstatt.com).
Inhaltsübersicht
Gammakorrekturen
mit Lingo anstelle der schnelleren Filter Factory...
Eigentlich
wollte ich mit der Filter Factory (vgl. hier)
eine nützliche Gammakorrektur zusammenbauen, die FF kennt aber
leider keine Exponentialfunktionen. Um aber dennoch nicht auf kostenpflichtige
Xtras (da gibt es u.a. ein Gamma-Xtra für 50 Euro unter http://www.magisterludi.com/xtras/gammaxtra.html)
zurückgreifen zu müssen, kann das (wie im Folgenden zu
sehen, durchaus zu Recht) undokumentierte getpixel/setpixel weiterhelfen.
Zuerst
eine kurze Einführung zum Begriff und Problem der Gamma-Korrektur.
|


 |
|
|
Was
ist der Gammawert?
Die
Gammafunktion
y =
x hoch (1 / Gammawert)
ist
eine nichtlineare Funktion, mit der die Helligkeitsverteilung von
schwarz (x=0) nach weiss (x=1) z.B. eines Röhrenmonitors annähernd
korrekt beschrieben werden kann. Der x-Wert steht dabei für
den dem Gerät angelieferten Helligkeitswert, der y-Wert ist
die Helligkeit, die dann auch wirklich zu sehen ist. Ein gleichmäßig
abgestufter Grauverlauf erscheint daher auf einem Monitor (Gammawert
so zwischen 2.2 und 2.5) heller als z.B. die eingescannte Fotovorlage.
Um das auszugleichen, gibt es eine Gamma-Korrektur in den Bildbearbeitungsprogrammen,
und z.T. auch im Betriebssystem selbst. Die Gamma-Korrektur verändert
den Weiss- bzw. Schwarzpunkt eines Bilds nicht, sondern nur die
Grauverteilung dazwischen!
Grob
gesagt, führt ein
Gammawert von 0...1 zu einem dunkleren Bild, ein
Gammawert > 1 zu einem helleren Bild, ein
Gammawert = 1 zu keiner Bildänderung und ein
Gammawert
< 0 zu keinem vernünftigen Ergebnis
Eine
ausführliche und mathematisch korrektere Erklärung gibt's
hier:
ftp://ftp.igd.fhg.de//pub/doc/colour/GammaFAQ.pdf
ftp://ftp.igd.fhg.de//pub/doc/colour/ColorFAQ.pdf
(Achtung: FTP-Übertragungsart auf "Binär" statt
"Text"/"Automatisch" stellen...)
|


 |
|
|
Sind
Mac-Monitore zu hell eingestellt?
Es
wird oft behauptet/beklagt, daß Mac-Monitore "heller
eingestellt" seien als PC-Monitore. Das ist in sofern nicht
richtig, weil ich auf einem Mac-Monitor natürlich genauso ein
richtiges, tiefes Schwarz sehe wie am PC-Monitor und außerdem
ein am Mac angeschlossener PC-Monitor ohne Änderung der Helligkeits-/Kontrast-Reglereinstellungen
sofort ein "helleres" Bild liefert. Der Grund für
den Unterschied liegt im Mac-Betriebssystem auf der untersten Ebene,
dem sogenannten "QuickDraw", das u.a. für die Übertragung
des Bildschirmspeicherinhalts an den Monitor zuständig ist.
QuickDraw selbst nimmt (unabhängig von irgendwelchen
Benutzereinstellungen, Kontrollfeldern, etc) eine durchgehende Gammakorrektur
von ca. 1.4 am Ausgangsbildsignal vor - das Bild wird somit am Monitor
heller dargestellt als an einem PC (unahängig vom Monitor selbst).
Um
die - heller dargestellte - Grafik wieder "korrekt" zu
sehen, macht der (Mac-)Designer sie natürlich dunkler... und
prompt ist sie für einen PC zu dunkel...
Der
in Newsgroups (von PC-Usern ;-) gerne verbreitete Tip für die
Macianer, "doch einfach den Monitor dunkler einzustellen"
ist natürlich völliger Quatsch, da der Helligkeitsregler
keinen Einfluß auf die Gammakurve hat, sondern im "besten"
Fall den Weißwert auf hellgrau herunterregelt... Wer also
eine für Mac und PC korrekte Bilddarstellungen erreichen
will, muß
- entweder
einen Gamma-Mittelwert für seine Grafiken wählen (=
am Mac etwas zu dunkel, am PC etwas zu hell)
- oder
zwei Versionen für die verschiedenen Plattformen parallel
ausliefern...
Auf
jeden Fall ist öfter mal eine nachträgliche Gammakorekktur
- auch in Director selbst - nötig, und dazu komme ich jetzt.
|


 |
|
|
Gammakorrektur
für Darsteller mit 32- und 16-Bit Farbtiefe
Die
"getpixel()"-Funktion liefert eine Zahl von 0...16 777
215 zurück, in der die Rot-, Grün- und Blauanteile des
Pixels anhand dieser Formel zusammenaddiert wurden:
getPixelWert
= Rotwert*256*256 + Grünwert*256 + Blauwert
Jeder
Farbkanal (rot, Grün, Blau) umfaßt bei einem
32-Bit-Darsteller
(der ja eigentlich nur 24-Bit-Farbtiefe aufweist, plus 8 Bit für
einen optionalen Alphakanal) einen
Wertebereich von 0...255 und bei einem
16-Bit-Darsteller
(auch nur reale 15-Bit, plus 1 Bit für einen optionalen Alphakanal)
einen
Wertebereich von 0...31
Für
eine Gammakorrektur liegt es nahe, jeden Pixel des Darstellers einzeln
mit getpixel() auszulesen, ihn mit der Gammaformel zu überarbeiten
und wieder mit setpixel() zurückzuschreiben.
Eine
mögliche Lingo-Umsetzung für 32-Bit Bitmapdarsteller:
on gammaKorrektur
g, m
--
g: gammaWert im Bereich von
-- 0...1: Bild wird dunkler
-- > 1 : Bild wird heller
-- < 0 : nicht erlaubt
--
m: castmember
farbtiefe = member( m ).depth
if farbtiefe < 32 then exit --nur bei 32-Bit-Darstellern
gammaWert = 1 / float( g )
-- ich rechne ( 1 / Gamma ) schon im voraus aus, dann
geht
-- nachher power( farbwert, ( 1 / Gamma ) ) schneller
skalierung = 255 / power( 255, gammaWert )
-- dieser Skalierungsfaktor sorgt dafür, daß
nach der
-- Berechnung wieder Werte von 0...255 rauskommen
h = member(m).height-1
w = member(m).width-1
repeat with y = 0 to h
--
alle y-Werte (= Zeilen des Bitmaps)...
repeat with x = 0 to w
--
...und innerhalb der Zeilen jeden Pixel auslesen
RGBalt = getpixel( member( m
), x, y )
Rot = RGBalt / 65536
Gruen = ( RGBalt - 65536*Rot
) / 256
Blau = RGBalt mod 256
-- Rot ist klar,
-- für Gruen wird zuerst
vom Startwert der Rotanteil abgezogen
-- der Rest kann dann schön
einfach durch 256 geteilt werden
-- Gruen = RGBalt / 256 mod
256
-- geht genauso, ist aber auf
meinem Mac auch nicht schneller.
-- Bei Blau müßte
eigentlich vom Startwert der Rot- und Gruenanteil
-- abgezogen werden, dann bleibt
der Blauwert automatisch übrig:
-- Blau = RGBalt - 65536*Rot
- 256*Gruen
-- aber das geht einfacher mit
"mod"
-- alle Einzelwerte werden jetzt
mit der Gammafunktion überarbeitet
RotNeu = integer( power( Rot,gammaWert
) * skalierung )
GruenNeu = integer( power( Gruen,gammaWert
) * skalierung )
BlauNeu = integer( power( Blau,gammaWert
) * skalierung )
-- und wieder zu einem einzigen
Wert zusammengebaut und zurückgeschrieben
RGBneu = ( RotNeu*65536 + GruenNeu*256
+ BlauNeu )
setpixel( member( m ), x, y
,RGBneu )
end
repeat
end
repeat
end
Hier
die Auswirkung am Beispiel eines Grauverlaufs (Darsteller-Farbtiefe
aber trotzdem 32 Bit RGB!) nach Eingabe von
gammaKorrektur
1.8, 1
im
Messagefenster:
 |
 |
| Member
1 (linearer Grauverlauf in RGB) vorher |
Member
1 nach Gammakorrektur mit Gamma 1.8 |
Schwarz-
und Weißwert sind gleichgeblieben, nur die Mitteltöne
sind heller geworden.
|


 |
|
|
Jetzt
zu den 16-Bit-Darstellern
Wie
oben schon erwähnt, werden für die Farbinformationen insgesamt
15 Bit benutzt, also 5 Bit je Farbe, was insgesamt 32 Abstufungen
pro Farbkanal zuläßt. Manche Programme packen diese 15
Bit in 2 Byte (2*8 Bit), Director hingegen nutzt anscheinend intern
dieselben 3 Byte wie für die 32-Bit-Darsteller, nur daß
nur jeweils die unteren 5 Bit je Byte belegt sind. Ein kleines Diagramm
soll das verdeutlichen:
Aufteilung
der Farbkanäle bei 24-Bit Farbtiefe:
|
|
Byte
1 ROT
|
Byte
2 GRÜN
|
Byte
3 BLAU
|
|
=
255
|
=
255
|
=
255
|
getpixel-Wert für Weiss: 255*256*256 + 255*256 + 255
= 16 777 215
Aufteilung der Farbkanäle bei 16-Bit Farbtiefe:
|
|
Byte
1 ROT
|
Byte
2 GRÜN
|
Byte
3 BLAU
|
|
=
31
|
=
31
|
=
31
|
|
getpixel-Wert für Weiss: 31*256*256 + 31*256 + 31 = 2
039 583
|
Ein
kurzer Test mit getpixel und verschiedenen Grautönen liefert
dann auch die erwarteten Resultate - aber setpixel reagiert
völlig unerwartet, indem es den Rotkanal einfach unterschlägt!
Ein kleiner Test mit einem Handler, der nur mit setpixel den mit
getpixel ausgelesenen Wert zurückschreibt:
on darfSichEigentlichNichtsAendern
m
-- m: castmember
repeat with y = 0 to ( member( m ).height - 1)
repeat with x = 0 to ( member( m ).width
- 1)
setpixel( member( m ), x,y ,getpixel(
member( 1 ), x, y ) )
end repeat
end repeat
end
zeigt
dieses Ergebnis:
 |
 |
| 16-Bit-Darsteller
vorher |
...und
nachher |
Das
Nachher-Bild mit der Cyan-Färbung zeigt, daß zwar die
Blau- und Grünwerte korrekt gesetzt wurden (sonst wäre
da gar kein Verlauf mehr), daß aber der Rotwert im ganzen
Bild auf 0 steht (deswegen die schöne Einfärbung). Es
ist mir nicht gelungen, irgendeinen Weg zu finden, den Rotwert korrekt
zurückzuschreiben (weder negative Vorzeichen, das Setzen der
unbenutzen 3 Bits, u.ä. haben sichtbare Ergebnisse gezeigt).
Das bedeutet, daß für 16-Bit-Darsteller die setpixel-Funktion
- zumindest auf meinem System - leider nicht verwendet werden kann,
und somit eine Gammakorrektur für 16-Bit-Darsteller nur über
den Umweg der Umwandlung nach 32-Bit, Korrektur und Rückwandlung
zu 16-Bit möglich ist. Speicherintensiv, langsamer und schade.
Wer eine Lösung hat, her damit!
|


 |
|
|
Gammakorrektur für Farbtiefen mit 8 Bit oder weniger
Für
alle Farbtiefen von 8-Bit oder weniger ist der oben stehende "GammaKorrektur"-Handler
nicht geeignet, da mit eingebauten bzw. selbstdefinierten
Paletten gearbeitet wird. Eine praktikable Lösung ist es, die
jeweilige Palette zu exportieren, in DeBabelizer o.ä. die
Gammakurve der Palette zu ändern und die neu importierte
Palette über die ursprüngliche zu kopieren. Alle mit der
ursprünglichen Palette verbundenen Bitmap-Darsteller sind dann
"auf einen Streich" korrigiert.
Kritik,
Anregungen und Kommentare (vor allem zu der 16-Bit-Sache) gerne
an:
Joachim
Baur | medienwerkstatt | grafik-design
|


 |
|
|
Ergänzung
für Director 8
In
Director 8 liefert die Funktion getpixel() in den Farbtiefen 16
und 32 Bit Farbobjekte vom Typ #rgb, in 8 Bit ein Farbobjekt vom
Typ #paletteIndex zurück. Ebenso läßt sich setpixel()
mit Farbobjekten "füttern". Damit ist es denkbar,
den von Joachim Baur für 32-Bit-Bilder beschriebenen Weg für
alle drei Farbtiefen anzuwenden.
in
16/32 Bit:
put member(1).image.getpixel(point(1,1))
-- rgb( 255, 255, 0 )
in
8 Bit:
put member(1).image.getpixel(point(1,1))
-- paletteIndex( 5 )
acolor = member(1).image.getpixel(point(1,1))
acolor.colortype = #rgb
put acolor
-- rgb( 255, 255, 0 )
Das
16-Bit-"Gegenbeispiel",
das in Director 7 noch zu Verfärbungen führte, funktioniert
in Director 8. Ich habe es für den Test folgendermaßen
umformuliert:
on darfSichEigentlichNichtsAendern
m
-- m: castmember
repeat with y = 0 to ( member( m ).height - 1)
repeat with x = 0 to ( member( m ).width
- 1)
member( m ).image.setpixel(
point(x,y) , \
member( 1 ).image.getpixel(
point( x, y ) ))
end repeat
end repeat
end
Joachim
Gola
|


 |
|