Farbtiefe von 24Bit zu 16Bit



  • Hi,

    Ich habe einen Farbwert von z.B. 0x808040 (24 Bit) dieses will ich konvertieren in ein 16 Bit Farbraum.
    Inmoment schneide ich die überflüssigen Bits einfach ab:

    uint32_t = 0x808040;
    
    printf("Color A: %x\n", ((((color & 0x1F) << 3) | ((color >> 8) & 0x07)));
    printf("Color B: %x\n", ((((color >> 7) & 0xE0) | ((color >> 16) & 0x1F)));
    

    Bei Blau Gelb oder Rot funktioniert das auch recht gut.
    Aber ich bekomme Probleme bei einigen "Mittelwert" Farben z.B. Braun wird zu Schwarz etc.

    Gibt es dazu ein Algorithmus um das zu vermeiden wie den nächst passende Farbe berechnen oder an die Umgebungsfarbe anpassen?

    Gruss,
    xmarvel



  • kaum geschrieben schon hab ich ein Algorithmus gefunden 😃
    Anscheinend ist der Floyd-Steinberg-Algorithmus dafür geeignet also wenn jemand dazu Informationen hat oder Beispiel Code wäre ich dankbar aber werde erstmal im I-Net nach weitern Infos suchen.

    Gruss,
    xmarvel



  • Dieser Thread wurde von Moderator/in rüdiger aus dem Forum Rund um die Programmierung in das Forum Spiele-/Grafikprogrammierung verschoben.

    Im Zweifelsfall bitte auch folgende Hinweise beachten:
    C/C++ Forum :: FAQ - Sonstiges :: Wohin mit meiner Frage?

    Dieses Posting wurde automatisch erzeugt.



  • wie wärs, wenn du für rot, grün und blau einen wert zwischen 0 und 1 definierst. 0 ist kein anteil und 1 der volle anteil. also wenn du 0x808080 hast ist das ja z.b für rot 80/255. das sind 100/255*80 = 31.37% rotanteil.

    leider kenne ich mich mit highcolor(16bit) nicht sehr gut aus. ich glaube es sind 5bit für rot, 5bit für blau und 6bit für grün. das würde bedeuten dass das maximum von rot und blau 31 ist --> 0b11111 (5bit) = 31 dezimalsystem.
    grün hat ein maximum von 0b11111 (6bit) = 63.

    du hast nun von den vorherigen rechnungen erfahren, dass du ca. 32 prozent von den jeweiligen farben hast. nun kannst du das für die neuen werte ausrechnen:
    r = 31 * 0.3137 --> 31.37% vom maximalwert 31 (5bit)
    g = 63 * 0.3137 --> 31.37% vom maximalwert 63 (6bit)
    b = 31 * 0.3137 --> 31.37% vom maximalwert 31 (5bit)

    denk daran dass du auf natürliche zahlen runden musst. das gibt uns:
    r = 10
    g = 20
    b = 10

    oder im hexadezimalsystem: 0x0a140a

    dies sollte nun ungefähr dem farbwert entsprechen.
    ich hoffe ich liege mit meinen vermutungen richtig.
    grs grave



  • wenn du 0x808080 hast ist das ja z.b für rot 80/255. das sind 100/255*80 = 31.37% rotanteil.

    gruss vom hexadezimalsystem 😉

    grundlegend skaliert man jede farbkomponente von 0..255 nach 0..31 (bzw gruen nach 0..63 bei rgb565), ergibt also einen faktor von ~1/8.2258.
    aus performancegruenden entscheidet man sich fuer 1/8 um arithmetik zu sparen.
    da das auge mehr als 32 farbabstufungen wahrnehmen kann, resultiert color banding, abhilfe schafft dithering.

    im einfachsten fall kann man sich fuer jeden pixel entscheiden, die farbkomponenten entweder auf- oder abzurunden, was das eigentliche problem auf 1bit-dithering reduziert.
    eine komplexere vorgehensweise findet man hier.



  • also 0x00808080 soll zb in 16 bit umgewandelt werden ömm:

    DWORD a; //24 bit farbwert
    	DWORD b; //16 bit farbwert
    	a=0x00FFFFFF; 
    	DWORD ra;
    	DWORD ga;
    	DWORD ba;
    	ra=(a&16711680)/(65536*8);
    	ga=(a&65280)/(256*8);
    	ba=(a&255)/8;
    	b=ra*2048+ga*64+ba;
    	std::cout<<b;
    


  • LinkeT: dein code konvertiert in das eher unueblich farbformat r5g5x1b5...



  • uint32_t Rgb24ToBgr16_555(uint32_t rgbColor)
    {
    	return
    		((rgbColor <<  7) & 0x7C00) | // R
    		((rgbColor >>  6) & 0x03E0) | // G
    		((rgbColor >> 19) & 0x001F);  // B
    }
    
    uint32_t Rgb24ToBgr16_565(uint32_t rgbColor)
    {
    	return
    		((rgbColor <<  8) & 0xF800) | // R
    		((rgbColor >>  5) & 0x07E0) | // G
    		((rgbColor >> 19) & 0x001F);  // B
    }
    


  • hellihjb schrieb:

    LinkeT: dein code konvertiert in das eher unueblich farbformat r5g5x1b5...

    ich dachte da eher an r5g6b5, das x1 gehört mit zum r5 ?!



  • Gibt es dazu ein Algorithmus um das zu vermeiden wie den nächst passende Farbe berechnen oder an die Umgebungsfarbe anpassen?

    Ja, gibts, nennt sich Dithering. Floyd Steinberg ist ein einfacher error-difusion Dither. Liefert mittelprächtige Ergebnisse.

    Je nach Anwendung ist es aber u.U. sogar besser einen noch einfacheren Algorithmus zu verwenden, nämlich nen Matrix-Dither.
    Dazu bastelt man üblicherweise eine NxN Matrix, die z.B. so aussieht:

    7, 3, 6, 2
    1, 5, 0, 4
    6, 2, 7, 3
    0, 4, 1, 5
    

    Diese Matrix legt man dann über das Bild drüber, wie fliesen, also jeder Pixel in Eintrag in der Matrix, und eben "gekachelt", also die Matrix wiederholt sich immer. Dann addiert man einfach "R_neu = R + M[X modulo W][Y modulo H]" (wobei W und H die Höhe/Breite der Matrix sind und "modulo" eine Restklassendivision also "%" in C++), und analog für G und B.

    Wenn das Original-Bild Werte von 0-255 in z.B. Kanal R haben kann, dann kann R_neu von 0-262 gehen (der höchste Wert in der dither Matrix ist 7, 255+7=262), wobei ein 100% Rot nach dem aufaddieren der Matrix Werte von 255-262 haben kann.

    Als nächstes sieht man sich an auf welchen Wertebereich man nach dem Draufaddieren der Matrix runterquantisieren will. Nehmen wir mal die 5 Bit an, das wäre dann ein Bereich von 0-31.

    Zum Quantisieren auf 0-31 rechnest du dann einfach "R_neu = R * 31 / 255". Die einfachere, durch Shiften erreichbare Rechnung "R_neu = R * 32 / 256" bzw. durchgekürzt "R_neu = R / 8" ist nicht optimal, da so Werte im Weissbereich falsch abgebildet werden.

    ----

    Und zu Floyd Steinberg: der Algorithmus ist auch nicht wirklich wahnsinnig kompliziert, aber würde doch etwas länger zu erklären dauern. Davon abgesehen dass ich die genauen Details selbst noch nachlesen müsste. Kannst du aber sicher im Netz an 100 Stellen finden -- das ist einer dieser "den kennt jeder" Algorithmen ala Bresenham.
    Floyd Steinberg ist im übrigen für Standbilder ganz OK, für Animationen aber z.B. nicht zu gebrauchen.


  • Mod

    da haben sich dir einige fehler eingeschlichen

    hustbaer schrieb:

    Ja, gibts, nennt sich Dithering. Floyd Steinberg ist ein einfacher error-difusion Dither. Liefert mittelprächtige Ergebnisse.

    Je nach Anwendung ist es aber u.U. sogar besser einen noch einfacheren Algorithmus zu verwenden, nämlich nen Matrix-Dither.

    eigentlich ist jeder dither ein matrix-dither, was du hier beschriebst ist ordered dithering.

    Zum Quantisieren auf 0-31 rechnest du dann einfach "R_neu = R * 31 / 255". Die einfachere, durch Shiften erreichbare Rechnung "R_neu = R * 32 / 256" bzw. durchgekürzt "R_neu = R / 8" ist nicht optimal, da so Werte im Weissbereich falsch abgebildet werden.

    das stimmt leider nicht.
    beim hochrechnen muss man R_new=R*255/31 rechnen damit man gute resultate bekommt, beim runterrechnen muss man hingegen nur simpel durch 8 teilen. wuerde man R_neu = R * 31 / 255 rechnen, dann wuerde exakt nur 255 dem wert 31 zugewiesen, dafuer wuerde aber jedem anderen wert von 0 bis einschliesslich 30 9 werte zugewiesen bekommen. bei R/8 ist die aufteilung hingegen linear.



  • rapso schrieb:

    da haben sich dir einige fehler eingeschlichen

    hustbaer schrieb:

    Ja, gibts, nennt sich Dithering. Floyd Steinberg ist ein einfacher error-difusion Dither. Liefert mittelprächtige Ergebnisse.

    Je nach Anwendung ist es aber u.U. sogar besser einen noch einfacheren Algorithmus zu verwenden, nämlich nen Matrix-Dither.

    eigentlich ist jeder dither ein matrix-dither, was du hier beschriebst ist ordered dithering.

    Hupps. Ja, ok, soll sein 😃

    Zum Quantisieren auf 0-31 rechnest du dann einfach "R_neu = R * 31 / 255". Die einfachere, durch Shiften erreichbare Rechnung "R_neu = R * 32 / 256" bzw. durchgekürzt "R_neu = R / 8" ist nicht optimal, da so Werte im Weissbereich falsch abgebildet werden.

    das stimmt leider nicht.
    beim hochrechnen muss man R_new=R*255/31 rechnen damit man gute resultate bekommt, beim runterrechnen muss man hingegen nur simpel durch 8 teilen. wuerde man R_neu = R * 31 / 255 rechnen, dann wuerde exakt nur 255 dem wert 31 zugewiesen, dafuer wuerde aber jedem anderen wert von 0 bis einschliesslich 30 9 werte zugewiesen bekommen. bei R/8 ist die aufteilung hingegen linear.

    Das stimmt wohl.

    Wenn man einfach ohne dithering runterrechnet dann ja, dann ist R/8 die richtige Umrechnung.

    Wenn man allerdings vorher über ne fixe Matrix (oder auch Zufallszahlen) Werte von 0-7 draufrechnet, dann erweitert man den Wertebereich ja von 0-255 auf 0-262. Die richtige Umrechnung ist dann *31/255. Glaubs mir oder probiers aus.
    Ganz ganz sicher. Ehrenwort.

    Ich hätte vielleicht dazuschreiben sollen dass ich die Umrechnung auf mein Beispiel darüber beziehe, also wo eben Werte von 0 bis 7 vorher draufaddiert werden -- dachte mir aber das wäre klar.

    (abgesehen davon dass das alles natürlich sowieso nicht 100% korrekt ist weil man mit Gamma-korrigierten Werten arbeitet wo man mit Power-Levels arbeiten müsste, aber bei Umrechnung von 8 auf 5 Bit sieht man den Unterschied kaum)

    EDIT: mit dem "9 Ausgangswerte pro Ausgabewert" hast du natürlich recht, das ist halt ne blöde Limitierung beim ordered dither. Kann man sich auch höchstens über ne grössere Dither-Matrix helfen. Oder eben clippen, was ich persönlich aber für die weniger attraktive Lösung halte.


  • Mod

    hustbaer schrieb:

    Ich hätte vielleicht dazuschreiben sollen dass ich die Umrechnung auf mein Beispiel darüber beziehe, also wo eben Werte von 0 bis 7 vorher draufaddiert werden -- dachte mir aber das wäre klar.

    ja das ist echt nicht ersichtlich, nett waere es wenn du uns noch eklraerst wie man auf diese formel kommt. Ich haette simpel *31/263 gerechnet, was wohl erstmal eher nachvollziehbar ist.



  • [quote="rapso"]

    hustbaer schrieb:

    Ich haette simpel *31/263 gerechnet, was wohl erstmal eher nachvollziehbar ist.

    für mathe-genies ist *31/263 noch im kopf errechnbar und das in (für menschlicher zeit) ziemlich schneller zeit ... aber für den CPU wirds mit ohne floating "schwer" sein , oder?, also nen DWORD kann man da nicht benutzten

    wie schon gesagt wurde /8, bzw um 3bits "Logik shift right" (LSR), sollte dasfür die performace optimalste sein.

    ob das ganze nun als matritze oder DWORD-array behandelt wird (wobei eine matritze nix anderes ist - zumindest nicht im Speicher) ist nun "egal"

    das einzege problen was es hier zu lösen gibt ist doch: aus einen 32/24-bit wert einen 16-bit wert zuerstellen.

    ich glaube das der thread-starter sich schon seine lösung heraus gesucht hat - aber für die, die es noch nen bissel im gehirn spielen lassen wollen:

    32bit: AAAAAAAARRRRRRRRGGGGGGGGBBBBBBBB
    24bit: XXXXXXXXRRRRRRRRGGGGGGGGBBBBBBBB
    16bit: 0000000000000000RRRRRGGGGGGBBBBB
    die "sinnvollen" bits aus32/24 bit sind:
    ...... xxxxxxxxRRRRRrrrGGGGGGggBBBBBbbb (Groß=wichtig klein=unwichtig-> zu löschen9


  • Mod

    [quote="LinkeT"]

    rapso schrieb:

    hustbaer schrieb:

    Ich haette simpel *31/263 gerechnet, was wohl erstmal eher nachvollziehbar ist.

    hatte mich vertippt, meinte natuerlich 32/263. aber dennoch...

    für mathe-genies ist *31/263 noch im kopf errechnbar und das in (für menschlicher zeit) ziemlich schneller zeit

    emm ja und? sprechen wir hier nicht mehr uebers programmieren? oder waren all deine beispiele vorher fuer kopfrechnen gedacht?

    aber für den CPU wirds mit ohne floating "schwer" sein , oder?, also nen DWORD kann man da nicht benutzten

    wieso kannst du ein dword nicht mehr nutzen? soviel anders ist das ergebnis nicht als mit *31/255 oder meinst du im hinblick auf die range vom resultat? also dass mehr als 31 rauskommen kann?

    wie schon gesagt wurde /8, bzw um 3bits "Logik shift right" (LSR), sollte dasfür die performace optimalste sein.

    jo, und liefert die richtigen resultate im bereich von 0 bis 255

    ich glaube das der thread-starter sich schon seine lösung heraus gesucht hat - aber für die, die es noch nen bissel im gehirn spielen lassen wollen:

    32bit: AAAAAAAARRRRRRRRGGGGGGGGBBBBBBBB
    24bit: XXXXXXXXRRRRRRRRGGGGGGGGBBBBBBBB
    16bit: 0000000000000000RRRRRGGGGGGBBBBB
    die "sinnvollen" bits aus32/24 bit sind:
    ...... xxxxxxxxRRRRRrrrGGGGGGggBBBBBbbb (Groß=wichtig klein=unwichtig-> zu löschen9

    also da du meinen beitrag oben gequotet hast, muss ich dir sagen, dass dein ganzer text trotzdem nicht beantwortet, wie du auf *31/255 kommst, wenn du den bereich abdecken moechtest, den du von 0 bis 262 gesetzt hast.



  • 0-262 ?
    2^8=256 -> 0-255 was soll da der bereich von 256-262?!

    32/263=0,12167300.... -> FLOAT so weit ich weis muss dazu die FPU benutzt werden und somit weren mehere Befehle ausgeführt (im native code) -> es wird langsamer

    habs eben mal getestet, mit:

    int main()
    {
    	DWORD start;
    	DWORD ende;
    	DWORD t;
    	DWORD x;
    	for(DWORD x=0;x<10;x++)
    	{
    		start=timeGetTime();
    		for(DWORD i=0;i<10000000;i++)
    		{
    			t=x/8;
    			//t=x*32/263;
    		}
    		ende=timeGetTime();
    		ende=ende-start;
    		std::cout<<ende<<std::endl;
    	}
    	int y;
    	std::cin>>y;
    }
    

    im disassembly steht
    t=x/8;
    00411687 mov eax,dword ptr [x]
    0041168A shr eax,3
    0041168D mov dword ptr [t],eax

    und

    t=x*32/263;
    00411687 mov eax,dword ptr [x]
    0041168A shl eax,5
    0041168D xor edx,edx
    0041168F mov ecx,107h
    00411694 div eax,ecx
    00411696 mov dword ptr [t],eax

    Fazit
    /8 ist ca 4 mal schneller



  • Auch wenn für diese Division nicht die FPU (x*32/263 ist eine Ganzzahldivision) verwendet werden muß, hast du im Ergebnis recht - die Division durch 8 kann vom Compiler zu einem Shift-Right optimiert werden, die Division durch 263 nicht.


  • Mod

    LinkeT schrieb:

    0-262 ?
    2^8=256 -> 0-255 was soll da der bereich von 256-262?!

    liess bitte den thread durch bevor du sowas fragst.

    hustbaer schrieb:

    Wenn das Original-Bild Werte von 0-255 in z.B. Kanal R haben kann, dann kann R_neu von 0-262 gehen (der höchste Wert in der dither Matrix ist 7, 255+7=262), wobei ein 100% Rot nach dem aufaddieren der Matrix Werte von 255-262 haben kann.

    Fazit
    /8 ist ca 4 mal schneller

    Zum Quantisieren auf 0-31 rechnest du dann einfach "R_neu = R * 31 / 255". Die einfachere, durch Shiften erreichbare Rechnung "R_neu = R * 32 / 256" bzw. durchgekürzt "R_neu = R / 8" ist nicht optimal, da so Werte im Weissbereich falsch abgebildet werden.

    4mal schneller etwas falsches zu machen ist keine kunst.



  • Fazit /8 ist ca 4 mal schneller

    ich hab jetzt die genaue zyklenzahl der integerdivision aktueller prozessoren nicht im kopf aber wuerde mindestens faktor 20 schaetzen.
    darum erweitert man die faktoren so, dass man durch eine zweierpotenz dividiert.



  • hellihjb schrieb:

    Fazit /8 ist ca 4 mal schneller

    ich hab jetzt die genaue zyklenzahl der integerdivision aktueller prozessoren nicht im kopf aber wuerde mindestens faktor 20 schaetzen.
    darum erweitert man die faktoren so, dass man durch eine zweierpotenz dividiert.

    habs errechnet ist "nur" faktor 4

    waarum sollen verfälschungen im Weisbereich auftretten? dies passiert doch in jedem fall da es nun nicht mehr so genau ist. Um bei grün nicht zuviel an daten wegzu schneiden könnte man auch durch 4 teilen (SHR 2).

    255-248 -> 31
    247-240 -> 30

    ausser das es nicht mehr so genau ist seh ich keine weitere verfälschung


Log in to reply