[TUT] 24bit Farben im 16bit Screen

Hier kannst du häufig gestellte Fragen/Antworten und Tutorials lesen und schreiben.
Kaeru Gaman
Beiträge: 17389
Registriert: 10.11.2004 03:22

[TUT] 24bit Farben im 16bit Screen

Beitrag von Kaeru Gaman »

Die Enträtselung des 16bit Modes

0) Einleitung

ich habe diesen thread wie ein tutorial verfasst
a) um der wirkungsweise der funktionen im 16bit-mode auf die spur zu kommen, und meinen kollegen zu helfen, mich auf dieser 'reise' zu begleiten
b) um diskussionen anzuregen, wie diese problematik in den folgeversionen von PB zufriedenstellend gelöst werden kann.
weiterhin habe ich den text in sieben posts aufgeteilt, um die übersichtlichkeit zu vergrössern.
Zuletzt geändert von Kaeru Gaman am 20.02.2005 00:43, insgesamt 6-mal geändert.
Kaeru Gaman
Beiträge: 17389
Registriert: 10.11.2004 03:22

Beitrag von Kaeru Gaman »

1) Grundlagen

jedem sollte der aufbau eines byte vertraut sein. wir werden nicht auf den aufbau des binärsystems eingehen.
da wir uns mit den unterschieden 16bit-mode/24bit mode ausseinandersetzen werden, hier noch einmal grafisch die wertigkeiten der bits für 1, 2 und 3 byte (8bit,16bit,24bit)

Bild - - - Bild - - - Bild

der aufbau von 24bit farbwerten ist für die meisten ganz einfach logisch nachvollziehbar (24/3=8 ), er sieht folgendermassen aus:

Bild

der aufbau von 16bit farbwerten ist nicht ganz so einfach (16/3=5.333). herkömmlich sind 16bit farben folgendermassen aufgebaut:

Bild
[edit]hier steckt ein fehler drin: die korrekte darstellung bitte dem post von lebostein (s.u.) entnehmen[/edit]

der vollständigkeit halber müssen wir jetzt noch die übersetzung von 16bit in 24bit farbwerte zumindest theoretisieren.
logischerweise sollte sie folgendermassen aussehen:

Bild

wie man sieht, sind in jedem farbkanal die unteren 3 bit irrelevant.
[edit] korrektur: bei grün nur die unteren zwei (s.u.) [/edit]

soweit die grundlagen.
Zuletzt geändert von Kaeru Gaman am 03.12.2004 18:38, insgesamt 1-mal geändert.
Kaeru Gaman
Beiträge: 17389
Registriert: 10.11.2004 03:22

Beitrag von Kaeru Gaman »

2) 24bit in der Praxis

kommen wir jetzt zur praxis.

zunächst verdeutlichen wir uns die farbdarstellung im 24bit-mode. hierzu folgender erster

Code: Alles auswählen

InitSprite()
InitKeyboard()
OpenScreen(800,600,32,"x")

For n=0 To 255
    StartDrawing(ScreenOutput())
    Plot(n, 0, RGB(n,0,0))
    f = Point(n,0)
    a$ = Str(n)+" , "+Str(f)
    Debug a$
    StopDrawing()
Next

FlipBuffers()
Repeat:ExamineKeyboard():Until KeyboardPushed(#PB_Key_Escape)
wir öffnen also einen 32bit screen (24bit screen geht unter 3.92 anscheinend nicht)
dann plotten wir die farbscala von red(0) bis red(255)
die letzten beiden zeilen ermöglichen uns eine visuelle kontrolle des grafikoutputs.
das debug-fenster gibt uns den schleifenwert und den dazu ermittelten farbwert aus.

(auch wenn es überflüssig erscheint, möchte ich euch dennoch einladen, dieses beispiel wirklich laufen zu lassen. der visuelle eindruck wird das verständniss der späteren ausführungen erleichtern.)

das grafikoutput zeigt uns einen rot-farbverlauf.
im debug-fenster sehen wir wie erwartet ein aufzählen der werte von 0 bis 255 in beiden spalten.

so weit, so gut.

testen wir nun einmal den grün-kanal.

wir ändern Zeile 7 im

Code: Alles auswählen

    Plot(n, 0, RGB(0,n,0))
und, da wir auch den 'reduzierten' zahlenwert betrachten wollen, ergänzen wir Zeile 9 im

Code: Alles auswählen

    a$ = Str(n)+" , "+Str(f)+" , "+Str(f/256)
wenn wir dieses beispiel jetzt laufen lassen, sehen wir im grafikoutput einen grün-farbverlauf.
das debug-fenster zeigt uns in der zweiten spalte ein hochzählen in 256er schritten, die dritte spalte den reinen 1byte-wert aufsteigend.

den gleichen test führen wir auch für den blau-kanal durch.

analog ändern wir Zeile 7

Code: Alles auswählen

    Plot(n, 0, RGB(0,0,n))
und zeile 9

Code: Alles auswählen

    a$ = Str(n)+" , "+Str(f)+" , "+Str(f/65536)
auch dieser test verläuft wie erwartet.

wir definieren einmal den farbwert 128 als kontrollwert.

in den bisher erzeugten tabellen sehen wir in dieser zeile eine korrekte übertragung:
grün:
128 , 32768 , 128
und blau:
128, 8388608 , 128

warum ich das so ausführlich mache? nun, sehen heisst glauben. im nächsten kapitel wird klarer werden, warum es so wichtig ist, erst einmal die funktionierenden standard-werte zu sehen.
Kaeru Gaman
Beiträge: 17389
Registriert: 10.11.2004 03:22

Beitrag von Kaeru Gaman »

3) 16bit in der Praxis
oder:
Das Abenteuer beginnt!

dann wollen wir mal die farbdarstellung und ihre übertragung durch die funktionen im 16bit-mode ausprobieren.

wir nehmen den obenstehenden ersten code für der rot-kanal, und ändern lediglich Zeile 3:

Code: Alles auswählen

OpenScreen(800,600,16,"x")
wenn wir dieses beispiel jetzt laufen lassen, bekommen wir folgende ausgabe:

Code: Alles auswählen

0 , 0
1 , 0
...
7 , 0
8 , 8
9 , 8
...
127 , 123
128 , 132
129 , 132
...
254 , 255
255 , 255
die werte um 0 und die werte um 255 verhalten sich wie erwartet. aber was ist mit den werten um 128?
fehlanzeige, wir bekommen hier ganz abstruse ergebnisse, anscheinend verhalten sich die unteren 3 'unwichtigen' bit nicht still.

wir überprüfen dieses ergebniss anhand der anderen farbkanäle.

aus taktischen gründen zunächst blau (den ganzen code nochmal, um veränderungen zu vereinfachen):

Code: Alles auswählen

InitSprite()
InitKeyboard()
OpenScreen(800,600,16,"x")

For n=0 To 255
    StartDrawing(ScreenOutput())
    Plot(n, 0, RGB(0,0,n))
    f = Point(n,0)
    a$ = Str(n)+" , "+Str(f)+" , "+Str(f/65536)
    Debug a$
    StopDrawing()
Next

FlipBuffers()
Repeat:ExamineKeyboard():Until KeyboardPushed(#PB_Key_Escape)
hier ist der vorgang exakt analog, lediglich die mittlere spalte zeigt den 65536fachen wert.
um die 128 herum sehen wir folgendes:

Code: Alles auswählen

...
127 , 8060928 , 123
128 , 8650752 , 132
129 , 8650752 , 132
...
also völlig analog zu rot.

wirklich spannend wird es jetzt bei grün (deshalb dieses beispiel an dritter stelle):

Code: Alles auswählen

InitSprite()
InitKeyboard()
OpenScreen(800,600,16,"x")

For n=0 To 255
    StartDrawing(ScreenOutput())
    Plot(n, 0, RGB(0,n,0))
    f = Point(n,0)
    a$ = Str(n)+" , "+Str(f)+" , "+Str(f/256)
    Debug a$
    StopDrawing()
Next

FlipBuffers()
Repeat:ExamineKeyboard():Until KeyboardPushed(#PB_Key_Escape)
der code ist absolut analog, es wurden lediglich wieder die zeilen 7 und 9 angepasst.

dennoch kommt das ergebniss gänzlich unerwartet:

Code: Alles auswählen

...
3 , 0 , 0
4 , 1024 , 4
...
127 , 32000 , 125
128 , 33280 , 130
...
251 , 64256 , 251
252 , 65280 , 255
...
255 , 65280 , 255
was ist denn hier jetzt passiert?
anscheinend fliesst das sonst insignifikante 3.bit plötzlich in die berechnungen mit ein, da sich der wert nicht alle 8, sondern alle 4 zeilen ändert.

[edit] die ursache hierfür liegt in der 565 codierung, die lebostein unten beschrieben hat.[/edit]

anhand dieser beispiele wird klar, warum der ausgabewert der Point(x,y)-funktion im 16bit-mode anscheinend unvorhersehbar ist.
Zuletzt geändert von Kaeru Gaman am 03.12.2004 18:36, insgesamt 1-mal geändert.
Kaeru Gaman
Beiträge: 17389
Registriert: 10.11.2004 03:22

Beitrag von Kaeru Gaman »

4) Die schnelle Lösung

in den vorangehenden beispielen haben wir gesehen, das die unteren 3 bit nicht auf NULL bleiben, und somit einen fehler produzieren.

wir versuchen, diesen fehler zu beheben, indem wir mittels einer AND-verküpfung diese bits ausschalten.

wir ändern also Zeile 8 wie folgt:

Code: Alles auswählen

    f = (Point(n,0) & 16317688)
zur verdeutlichung: diese zahl ist der filter
%111110001111110011111000 oder
$F8FCF8
[edit] zahlen korrigiert, grünkanal angepasst, die grafik ist noch fehlerhaft [/edit]
der ergibt sich so:

Bild

wenn wir diese änderung für alle drei farbkanäle durchlaufen lassen sehen wir, dass nun alle zahlen in der folge stimmen, mit einer ausnahme: 255 wird zu 248 (logisch, die unteren 3 bit sind auf 0 gesetzt)

davon lassen wir uns jedoch nicht beirren.

wir erzeugen uns eine kleine bitmap (PAINT ist hierfür allemal ausreichend), bei der wir sicherstellen, dass die obere linke ecke die hintergrundfarbe enthält.
diese speichern wir mal unter "coltest.bmp"

dann benutzen wir zum testen folgenden

Code: Alles auswählen

InitSprite()
InitKeyboard()
OpenScreen(800,600,16,"x")
LoadSprite(0,"coltest.bmp")

    DisplaySprite(0,0,0)
    StartDrawing(ScreenOutput())
        f = (Point(0,0) & 16316664)
    StopDrawing()

TransparentSpriteColor(0,Red(f),Green(f),Blue(f))

    ClearScreen(0,0,0)
    DisplayTransparentSprite(0,0,0)

    FlipBuffers()
Repeat : ExamineKeyboard() : Until KeyboardPushed(#PB_Key_Escape)
wir verändern mal grausam willkürlich die hintergrundfarbe im sprite, und lassen dieses beispiel wieder und wieder durchlaufen.

für die meissten fälle tut es diese lösung.

aber nicht für alle, daher setzen wir unsere reise fort ins nächste kapitel:
Zuletzt geändert von Kaeru Gaman am 03.12.2004 18:41, insgesamt 1-mal geändert.
Kaeru Gaman
Beiträge: 17389
Registriert: 10.11.2004 03:22

Beitrag von Kaeru Gaman »

5) Die Ausnahme

wenn wir mal folgende farbe (nur als beispiel) für den hintergrund nehmen:

RGB(132,165,87)

stellen wir fest, dass der hintergrund nicht transparent wird.

ich habe es für verschiedene farben ausprobiert, manche funktionieren, wenn jeder farbkanal durch 4 teilbar ist, aber nicht alle. (und das trifft auch zu, wenn es nicht der grün-kanal ist, der durch 4 teilbar ist)

hingegen scheint es immer zu funktionieren, wenn jeder farbkanal durch 8 teilbar ist. (natürlich habe ich nicht alle 32768 farben ausprobiert)

das ist leider nicht logisch und einleuchtend.

anscheinend besitzt TransparentSpriteColor() einen anderen filter als $F8F8F8 oder gar keinen. damit bleiben die ergebnisse weiter unvorhersehbar.

als 'schnelle lösung' bietet sich also leider nur folgende an:

- in den sprites nur farben verwenden, deren farbkanäle alle durch 8 teilbar sind (grün-kanal durch 4 teilbar - ohne gewähr)
(also reine 16bit farben)
- die point()-funktion zur ermittlung der transparentfarbe mit obenstehendem filter versehen.

ja, der zusätzliche filter ist notwendig, damit 128 auch 128 bleibt, und nicht auf geheimnissvolle weise zu 130 oder 132 mutiert.
Zuletzt geändert von Kaeru Gaman am 03.12.2004 18:44, insgesamt 2-mal geändert.
Kaeru Gaman
Beiträge: 17389
Registriert: 10.11.2004 03:22

Beitrag von Kaeru Gaman »

6) Die dauerhafte Lösung

sinn dieses thread soll es ja nun sein, eine dauerhafte lösung für dieses problem zu finden, die in die folgeversionen unserer geliebten programmiersprache implementiert werden kann, ohne

a) diese fehlergebnisse zu liefern
b) zu verwirrend zu sein

dafür stelle ich folgende zwei lösungsvorschläge zur diskussion:
(natürlich begrüsse ich jeden darüber hinaus gehenden lösungsvorschlag)

1] die Point()-funktion bekommt den filter für die unteren 3 bit implementiert.
ebenso die TransparentSpriteColor()-funktion, damit ihre ergebnisse korrekt miteinander korrespondieren.

2] die Point() und TransparentSpriteColor() funktionen arbeiten in zukunft mit echten 16bit-werten,
somit wird aus grau128 [ RGB(128,128,128) ] zukünftig das 16bit-grau16 [ RGB(16,16,16) ]
das setzt eine anpassung sämtlicher funktionen, die sich mit farbwerten befassen, an den momentanen bildschirmmodus voraus, ebenso die geistesgegenwart des programmierers, sich seines benutzten bildschimmodus bewusst zu sein.

für den 16-farben-modus (1nibble-mode), dessen verarbeitung ja das jüngste problem war (thread von kollege LittleFurz), habe ich diese testreihe jetzt nicht gefahren, das stellt noch einmal mindestens den gleichen aufwand dar.
Benutzeravatar
Lebostein
Beiträge: 674
Registriert: 13.09.2004 11:31
Wohnort: Erzgebirge

Beitrag von Lebostein »

Zur Veranschaulichung hier mal ein kleines Beispiel, dass den tatsächlichen Farbwert mit dem Point()-Farbwert im 16-Bit-Mode vergleicht. Die Zahl oben in jedem Farbkästchen stellt den originalen Rot-Wert dar. Die Zahl darunter den mit Point() ermittelten Rot-Wert. Stimmen beide Werte überein, werden die Zahlen grün dargestellt:

Code: Alles auswählen

InitSprite() 
InitKeyboard() 

OpenScreen(800,600,16,"Farbtest") 

Repeat 

  ExamineKeyboard() 

  ClearScreen(0,0,0) 

  StartDrawing(ScreenOutput()) 
    For y = 0 To 15 
    For x = 0 To 15 
      farbe = y * 16 + x 
      plotx = 12 + x * 36 
      ploty = 12 + y * 36 
      Box(plotx, ploty, 35, 35, RGB(farbe,0,0)) 
      gefunden = Red(Point(plotx, ploty))
      DrawingMode(1) 
      If farbe = gefunden: FrontColor(0,255,0): Else: FrontColor(0,0,255): EndIf 
      Locate(plotx, ploty): DrawText(Str(farbe)) 
      Locate(plotx, ploty + 12): DrawText(Str(gefunden)) 
    Next x 
    Next y 
  StopDrawing() 

  FlipBuffers() 

Until KeyboardPushed(#PB_Key_Escape) 
Das Problem ist aber jetzt immer noch der Befehl TransparentSpriteColor(). Er funktioniert nur für die grün hervorgehobenen Farben. Habe ich zum Beispiel ein Sprite mit dem Rot-Wert 154 eingeladen, so wird dieser im 16-Bit-Modus (laut Tabelle) mit dem Rot-Wert 156 abgebildet und TransparentSpriteColor() würde damit nicht funktionieren.

TransparentSpriteColor() bezieht sich auf die Originalfarben des Sprites und nicht auf die wirklich dargestellte Farben. Besser wäre folgendes: Setze ich im 16-Bit-Modus zum Beispiel die Farbe mit dem Rot-Wert 156 auf Transparent, so müssten alle Rot-Werte zwischen 152 bis 159 des Sprites transparent dargestellt werden.

@ Kaeru Gaman:

1. Ich glaube deine Annahme ist nicht ganz richtig: Der 16-Bit Modus verwendet doch das Schema 565, und nicht 555, sons wäre es ja der 15-Bit-Modus (einfach mal die Bits zusammenzählen :) ). Also 5 Bits für Rot, 6 Bits für Grün und wieder 5 Bits für Blau macht 16-Bit. Grün ist feiner abgestuft als die anderen Farben.

Bild

2. Das mit dem internen Handeln der Byte-Werten von 0-31 für Rot und Blau sowie 0-63 für Grün finde ich eine gute Idee! Damit könnte man den Darstellung auf dem Screen als auch die Darstellung im Speicher eindeutig beschreiben.
Benutzeravatar
NicTheQuick
Ein Admin
Beiträge: 8675
Registriert: 29.08.2004 20:20
Computerausstattung: Ryzen 7 5800X, 32 GB DDR4-3200
Ubuntu 22.04.3 LTS
GeForce RTX 3080 Ti
Wohnort: Saarbrücken
Kontaktdaten:

Beitrag von NicTheQuick »

Und hier noch zahlreiche Funktionen um Farben in den verschiedenen Systemen auszulesen, zu setzen, zu ändern oder umzurechnen:

Code: Alles auswählen

Procedure.l RGB32(r.l, G.l, b.l, a.l)
  RGBA.l = a << 24 + b << 16 + G << 8 + r
  ProcedureReturn RGBA
EndProcedure
Procedure.l R32(RGBA.l)
  r.l = RGBA & $FF
  ProcedureReturn r
EndProcedure
Procedure.l G32(RGBA.l)
  G.l = RGBA >> 8 & $FF
  ProcedureReturn G
EndProcedure
Procedure.l B32(RGBA.l)
  b.l = RGBA >> 16 & $FF
  ProcedureReturn b
EndProcedure
Procedure.l A32(RGBA.l)
  a.l = RGBA >> 24
  ProcedureReturn a
EndProcedure

Procedure.l RGB24from16(RGB.l)
  RGB = (RGB & $1F) << 3 + (RGB >> 5 & $3F) << 10 + (RGB >> 11 & $1F) << 19
  ProcedureReturn RGB
EndProcedure
Procedure.l RGB24(r.l, G.l, b.l)
  RGB.l = b << 16 + G << 8 + r
  ProcedureReturn RGB
EndProcedure
Procedure.l R24(RGB.l)
  r.l = RGB & $FF
  ProcedureReturn r
EndProcedure
Procedure.l G24(RGB.l)
  G.l = RGB >> 8 & $FF
  ProcedureReturn G
EndProcedure
Procedure.l B24(RGB.l)
  b.l = RGB >> 16
  ProcedureReturn b
EndProcedure


Procedure.l RGB16from24(RGB.l)
  RGB.l = ((RGB & $FF0000) >> 19) << 11 + ((RGB & $FF00) >> 10) << 5 + ((RGB & $FF) >> 3)
  ProcedureReturn RGB
EndProcedure
Procedure.l RGB16(r.l, G.l, b.l)
  r >> 3
  G >> 2
  b >> 3
  RGB.l = b << 11 + G << 5 + r
  ProcedureReturn RGB
EndProcedure
Procedure.l R16(RGB.l)
  r.l = (RGB & $1F) << 3
  ProcedureReturn r
EndProcedure
Procedure.l G16(RGB.l)
  G.l = (RGB >> 5 & $3F) << 2
  ProcedureReturn G
EndProcedure
Procedure.l B16(RGB.l)
  b.l = (RGB >> 11 & $1F) << 3
  ProcedureReturn b
EndProcedure


Procedure.l RGB15(r.l, G.l, b.l)
  r >> 3
  G >> 3
  b >> 3
  RGB.l = b << 10 + G << 5 + r
  ProcedureReturn RGB
EndProcedure
Procedure.l R15(RGB.l)
  r.l = (RGB & $1F) << 3
  ProcedureReturn r
EndProcedure
Procedure.l G15(RGB.l)
  G.l = (RGB >> 5 & $1F) << 3
  ProcedureReturn G
EndProcedure
Procedure.l B15(RGB.l)
  b.l = (RGB >> 10 & $1F) << 3
  ProcedureReturn b
EndProcedure

Debug Hex(RGB16(255, 255, 255))

Debug A32(RGB32(1, 2, 3, 4))

Debug R24(RGB24(5, 6, 7))

Debug G16(RGB16(88, 99, 110))

Debug B15(RGB15(63, 127, 255))

RGB24.l = RGB24(63, 127, 255)
RGB16.l = RGB16from24(RGB24)
Debug "RGB24: " + Str(RGB24)
Debug "RGB24: " + Str(R24(RGB24)) + ", " + Str(G24(RGB24)) + ", " + Str(B24(RGB24))
Debug "RGB16: " + Str(RGB16)
Debug "RGB16: " + Str(R16(RGB16)) + ", " + Str(G16(RGB16)) + ", " + Str(B16(RGB16))
RGB24n = RGB24from16(RGB16)
Debug "RGB24_Neu: " + Str(RGB24n)
Debug "RGB24_Neu: " + Str(R24(RGB24n)) + ", " + Str(G24(RGB24n)) + ", " + Str(B24(RGB24n))
Bild
Kaeru Gaman
Beiträge: 17389
Registriert: 10.11.2004 03:22

Beitrag von Kaeru Gaman »

@lebostein

das wäre dann natürlich die erklärung für das beobachtete phänomen, wie sich der ausgelesene grün-wert verhält.
tatsächlich war mir das schema, das ich verwendet habe, von früher her bekannt.
weiss nicht mehr genau, ich glaub aus den QBasic und QuickC zeiten anfang der 90er.

soll ich jetzt den ganzen kram umschreiben? :cry:

[edit]
ich habe mal kommentare ergänzt, um der korrekten beschreibung des 16bit modes rechnung zu tragen.

deine idee, den transparent-mode auf mehrere farben zu beziehen finde ich gut,
allerdings bezweifele ich, ob da DX mitspielt.
vielleicht sollte der transparenz-befehl die korrekte korrespondierende 16bit-farbe ermitteln.
[/edit]

btw: wer hat denn den thread in die FAQs verschoben?
ich fühle mich geehrt.
Der Narr denkt er sei ein weiser Mann.
Der Weise weiß, dass er ein Narr ist.
Antworten