Entwickler-Ecke

Multimedia / Grafik - Optimierte Palette erstellen...


Andreas Pfau - Fr 28.02.03 21:26
Titel: Optimierte Palette erstellen...
Hallo,

ich arbeite grade an einem Icon-Editor. Da ist natürlich wichtig, dass ich die Palette optimieren kann, also z.B. auf 8-Bit reduzieren. Wie ich die Farben auslese und eine Palette draus mache ist mir klar.

Aber... jetzt habe ich einen Array voller Farben... wie entscheide ich, welche Farben ich rausschmeiße? Oder sollte ich besser Farben mischen? Wenn ja, welche mit welcher? Ich hätte schon ein paar Ideen... z.b. Umwandenl in HSB (wie?), dann nach Farbton sortieren, und von jedem Farbton eine rauschmeißen, bis es nur noch 256 sind... aber wie machen das professionelle Anwendungen? Wenn ich in PhotoImpact auf 8-Bit klicke, habe ich in Sekundenbruchteilen 'ne tolle Palette. Kann mir jemand das erklären? Ich brauche keinen Code, nur 'n Schema, wie so was geht.


Andreas Pfau - Sa 01.03.03 21:30

Keiner weiß es? Also, ich habe rumprobiert... Ich habe die Häufigkeit der Farben gezählt und nur die 256 häufigsten verwendet... bringt aber nix, z.b. bei großen, einfarbigen Hintergründen.

Was ich suche, wäre 'ne Formel, um die "Varianz" zweier Farben zu berechnen (z.B. Farbe1 unterscheidet sich von Farbe2 um 375). Wie erreiche ich das? Also, mit verlässlichen Ergebnissen, so dass sich Fabrn mit gleichem Farbton weniger unterscheiden als verschiedene... Also, dass sich Hellblau on Dunkelblau kaum unterscheidet, aber stark von Hellrot... Da bräuchte ich HSL... oder? Wer hat Ideen?


FriFra - So 16.03.03 18:41

:?: Wenn Du schon irgendwo eine Lösung gefunden hast wäre es schön, wenn Du diese hier posten würdest...
:roll: Ich suche auch nach so einer Funktion...


Andreas Pfau - So 16.03.03 20:11

Na ja, unter http://www.efg2.com gibt's so was, irgendwo. Quadtree Quantization nennt sich das. Funzt, nur hätte ich gerne einen Code, den ich auch verstehe. Und es gibt keine Erklärung dazu.


Eternail - Do 24.04.03 16:04
Titel: Re: Optimierte Palette erstellen...
Andreas Pfau hat folgendes geschrieben:
Wie ich die Farben auslese und eine Palette draus mache ist mir klar.


Genau danach suche ich. Kannst du eventuell den posten, wie man aus bekannten Farben eine Palette für ein TBitMap erstellt?


Andreas Pfau - Do 24.04.03 16:28

Ach, so. Also, Farben auslesen mache ich so, dass ich Pixel für Pixel auslese, und den Wert in eine Liste schreibe, sofern der Wert nicht schon vorhanden ist. Ich denke, das bedarf keinem Code. Ist zwar eine Slow-Motion-Lösung, aber ich weiß nicht, wie sonst.

Gut, dann aus deiner Lsite eine Palette machen. Erstmal weise deinem Bitmap ein Pixelformat zu, klar, oder? Dann Die Palette erstellen:

Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
procedure Make8BitPalette(Bitmap: TBitmap; Palette: Array Of TColor);
const
 NumColors = 256;
var
 I: Integer;
 H: HPalette;
 P: PLogPalette;
begin
 Bitmap.PixelFormat := pf8Bit;

 GetMem(P, SizeOf(TLogPalette) + (SizeOf(TPaletteEntry) * (NumColors - 1)));

 P.palVersion := $300; // Ist immer $300 !!!
 P.palNumEntries := NumColors;

 For I := 0 To NumColors - 1 Do begin
  If (Max(Palette) <= I) Then begin
   // Farben aus Liste extrahieren
   P.palPalEntry[I].peRed   := Palette[I] And $FF;
   P.palPalEntry[I].peGreen := Palette[I] And $FF00 Shr 8;
   P.palPalEntry[I].peBlue  := Palette[I] And $FF0000 Shr 16;
  end Else begin
   // Wenn Palette zu klein, Schwarz nehmen
   P.palPalEntry[I].peRed   := 0;
   P.palPalEntry[I].peGreen := 0;
   P.palPalEntry[I].peBlue  := 0;
  end;
  // Ist normalerweise 0
  P.palPalEntry[I].peFlags := 0;
 end;

 H := CreatePalette(P^);
 If H <> 0 Then
  Bitmap.Palette := H;

 FreeMem(P);
end;

Ich denke, das erklärt sich von selbst.


Eternail - Do 24.04.03 18:36

Auch wenn mir so Sachen mit GetMem eigentlich eher suspekt sind - es funktioniert. Ich musste nur das "max" gegen ein "high" vertauschen.

Allerdings kann ich, wenn ich die Palette auf acht Bit beschränke auch nur noch acht-Bit-Farben verwenden. Gibt es eine Möglichkeit (ausser mit gif) ZB nur die Farben $00 bis $FF (also die ganzen Rottöne zu verwenden)?

Danke soweit erstmal,
Michael Nagel :)


Andreas Pfau - Do 24.04.03 18:58

1) WAS??? WAS??? Max() gegen Min()? Erklär mir Bitte, warum? Das kann in hunterdttausendmillionenmilliarden Jahren nicht sein! :lol:

2) Nun, dan musst du nur die Farbzuweisung ändern, und so eine rote Palette erstellen:

Quelltext
1:
2:
3:
4:
5:
6:
 For I := 0 To NumColors - 1 Do begin
  P.palPalEntry[I].peRed   := I;
  P.palPalEntry[I].peGreen := 0;
  P.palPalEntry[I].peBlue  := 0;
  P.palPalEntry[I].peFlags := 0;
end;

NumColors MUSS jetzt "256" sein, sonst fataler Error. Es gibt keine mir bekannte Möglichkeit, das zu umgehen.

Übrigens: Ich weiß nicht, ob du es weißt, aber du kannst ruhig einem farbigen Bitmap eine monochrome Palette zuweisen... Windows nimmt automaitsch dien nächstgelegene Farbe.

GetMem() könnte man umgehen... Aber dann bräuchte man für jedes Pixelformat eine andere Struktur... Mach es doch so, es funzt, der Speicher wird freigegeben, kein Problem. Don't touch a running system, heißt es doch :wink:


Eternail - Do 24.04.03 19:40

Andreas Pfau hat folgendes geschrieben:
1) WAS??? WAS??? Max() gegen Min()? Erklär mir Bitte, warum? Das kann in hunterdttausendmillionenmilliarden Jahren nicht sein! :lol:


Kennst du "lesen"? :wink: Ich habe geschrieben (hoffentlich!) "gegen HIGH()". Weil "max() geht nicht bei dynamischen arrays!"!



Andreas Pfau hat folgendes geschrieben:
2) Nun, dan musst du nur die Farbzuweisung ändern, und so eine rote Palette erstellen:

Quelltext
1:
2:
3:
4:
5:
6:
 For I := 0 To NumColors - 1 Do begin
  P.palPalEntry[I].peRed   := I;
  P.palPalEntry[I].peGreen := 0;
  P.palPalEntry[I].peBlue  := 0;
  P.palPalEntry[I].peFlags := 0;
end;

NumColors MUSS jetzt "256" sein, sonst fataler Error. Es gibt keine mir bekannte Möglichkeit, das zu umgehen.

Übrigens: Ich weiß nicht, ob du es weißt, aber du kannst ruhig einem farbigen Bitmap eine monochrome Palette zuweisen... Windows nimmt automaitsch dien nächstgelegene Farbe.


Irgendwas klappt da nicht. Wenn ich es so mache, wie gesagt, bekomm ich immer noch die breiten Farbbalken, den entstehen, wenn zu wenig Farben zur VErfügung stehen. Sogar wenn ich alles auf := 0 stelle kommen noch die Balken, anstatt, dass alles schwarz wird. irgendwo ist da noch ein Fehler...

Andreas Pfau hat folgendes geschrieben:
GetMem() könnte man umgehen... Aber dann bräuchte man für jedes Pixelformat eine andere Struktur... Mach es doch so, es funzt, der Speicher wird freigegeben, kein Problem. Don't touch a running system, heißt es doch :wink:

Wegen mir...


Andreas Pfau - Do 24.04.03 19:53

:oops: tiefe Schande über main Haupt... es muss High() heißen. :oops:

Lesen... davon habe ich mal im TV gesehen, das ist doch so eine Süßspeise, oder? :beer:

OK, dann zu deinem Problem: Probier als Flag stat "0" die Werte "PC_NOCOLLAPSE" und/oder "PC_RESERVED". Es kann nämlich sein, dass Windows keine passende Farbe findet, und einfahc eneue Indizes erstellt. Und, ähem, Pixelformat muss pf8Bit sein, aber ich denke, das muss ich nicht sagen.


Eternail - Do 24.04.03 21:24

Kann nicht sein, dass Windows die passende Farbe nicht findet, weil...

...weil die BitMap nur aus den Farben aufgebaut wird, die ich später auch zum Farbpalette erstellen nutze. Ist doch Sinn der Sache. Ich will die Größe von der BitMap dritteln, indem ich die ganzen grün/blau-Informationen verwerfe, die ich eh nicht brauche. Deshalb will ich eine Colormap, wo nur die roten Farben drin sind, aus denen ich zuvor mein Bild erstellt habe...

PS: ...d.h. ich brauche 24bit Farben, damit ich 8bit "Rots" habe. Aber ich brauche nur 8bit verschienden Farben, weil blau und grün eh immer null sind. Geht das überhaupt mit BitMaps? Muss ja, sonst wäre die ganze Farbtabellen-Geschichte unsinnig.


Andreas Pfau - Do 24.04.03 21:51

Lass' mich zusammenfassen: Du hast Bilddaten, bei denen G und B immer 0 sind, nur R ist unterschiedlich. Dazu sind Bitmaps da.

ERklär mir mal, wie sich das äußert, wie sieht dein Bitmap aus? Evtl liegt der Fehler wo andres: Du hast vollfarben konvertierst sie zu 8Bit, und machst die Geschichte Rot... Versuch es so: Erstelle eine 2. Bitmap, mach da ein erote Palette, und kopiere dein Bitmap in das 2.Bitmap (das rote), nicht mit Assign() sondern über's Canvas. Wenn das nicht funzt, weiß ich auch net... aber informier mich drüber!


Andreas Pfau - Sa 26.04.03 15:10

Also, nochmal zu deinem Problem. Ich habe es jetzt selber ausprobiert. Ich ahbe mir eine bitmap erstellt, die NUR aus rottönen enthält, d.h. ALLE Grün- und Blauwerte sind 0. Diese Btimap hat 24Bit. Ich habe die Bitmap in Image1 geladen und folgenden Code angewendet:

Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
procedure MakeRedPalette8(Bitmap: TBitmap);
var
 I: Integer;
 H: HPalette;
 P: PLogPalette;
begin
 Bitmap.PixelFormat := pf8Bit;

 GetMem(P, SizeOf(TLogPalette) + (SizeOf(TPaletteEntry) * 255));

 P.palVersion := $300;
 P.palNumEntries := 256;

 For I := 0 To 255 Do begin
  P.palPalEntry[I].peRed   := I;
  P.palPalEntry[I].peGreen := 0;
  P.palPalEntry[I].peBlue  := 0;
  P.palPalEntry[I].peFlags := 0;
 end;

 H := CreatePalette(P^);
 If H <> 0 Then
  Bitmap.Palette := H;

 FreeMem(P);
end;

procedure TMain.Button6Click(Sender: TObject);
begin
 Image2.Picture.Bitmap.Width := Image1.Picture.Bitmap.Width;
 Image2.Picture.Bitmap.Height := Image1.Picture.Bitmap.Height;
 MakeRedPalette8(Image2.Picture.Bitmap);
 Image2.Picture.Bitmap.Canvas.Draw(0, 0, Image1.Picture.Bitmap);
end;

Jetzt habe ich in Image2 GENAU das geliche Bitmap, nur dass es halt nur aus 8Bit besteht. Zeig mir mal deinen Code, und ich helfe dir bei der Fehelrsuche! Oder hat sich's schon erledigt?


Eternail - Sa 26.04.03 16:46

Das Problem hat sich noch nicht geklärt, aber ich werde erst nach dem Wochenende dazu kommen, mich nochmal ausführlich zu melden.


Eternail - So 27.04.03 21:49

Ist früher was gewoeden, als ich gedacht habe. Und besser als ich gedacht habe. Es klappt auf einmal. Hier mein Code (noch ohne Fehlerkorrektur):


Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
procedure TForm1.Image1Click(Sender: TObject);
var
  BM:    TBitMap;
  myCol: array of TColor;
  i,j:   word;
begin
if Tag = 0 then
  begin
    //BitMap erstellen
    BM := TBitMap.Create;
    BM.Width  := 256;
    BM.Height := 256;
    Image1.Height := 256;
    Image1.Width  := 256;

    //Die 256 Farbwerte für die Palette festlegen - (von schwarz nach grün)
    SetLength(myCol, 256);
    for i := 0 to 255 do
      myCol[i] := i shl 8;

    //Das Bild einfärben - Farbverlauf schwarz nach grün
    for i := 0 to 255 do
      for j := 0 to 255 do
        BM.Canvas.Pixels[i,j] := myCol[i];

    //Das Bild übernehmen
    Image1.Picture.Bitmap := BM;
    BM.Free;
  end

else
  begin
    //Die Palette des Bildes nur auf die verwendeten Grüntöne beschränken
    //falls es funktioniert, ändert sich nichts
    Image1.Picture.Bitmap.PixelFormat := pf24bit;
    Image1.Picture.Bitmap.Palette     := Get8BitPalette(myCol);
  end;

Tag := not Tag;
end;

function TForm1.Get8BitPalette(farben: array of TColor): HPalette;
var
  i: Integer;
  P: PLogPalette;
begin
GetMem(P, SizeOf(TLogPalette) + (SizeOf(TPaletteEntry) *  255));

P.palVersion    := $300;
P.palNumEntries :=  256;

for i := 0 to 255 do
  begin
    //Farben aus Liste extrahieren
    P.palPalEntry[i].peRed   := (farben[i] and $0000FF)       ;
    P.palPalEntry[i].peGreen := (farben[i] and $00FF00) shr  8;
    P.palPalEntry[i].peBlue  := (farben[i] and $FF0000) shr 16;
    P.palPalEntry[I].peFlags := 0;
  end;

Result := CreatePalette(P^);

FreeMem(P);
end;


vielen Dank nochmal,
Michael