Rund um TBitmap


Inhaltsverzeichnis:

Einführung
Wie ist eine DIB aufgebaut
RGB Konvertierungen
Wie dreht man eine Bitmap um 90°
Wandlung in Graustufen und Ändern der Palette
Wie kopiere ich ein Byte-Array in eine Bitmap (ab Delphi 3)
Wie benutze ich ein JPEG (ab Delphi 3)
Wie kann ich eine Vorschau der JPEG-Kompression machen (ab Delphi 3)


Einführung

Bei den meisten der hier beschriebenen Lösungen werden DIB's benutzt.

Ein DIB ist ein geräteunabhängiges Bitmap.

Um ein TBitmap in ein DIB zu verwandeln, kann TBitmap.SaveToStream benutzt werden.
Zurück geht es dann mit TBitmap.LoadFromStream .
Seit Delphi 3 wird es einfacher, hier kann man auch direkt auf die ScanLines zugreifen.

zurück zum Inhaltsverzeichnis

Wie ist eine DIB aufgebaut

Ich gehe hier nur auf unkomprimierte Bitmap mit einer Ebene ein, da diese den "Standard"-Fall darstellen.

Die Scanlines sind am Ende auf 4 Byte ausgerichtet, deshalb wird die Größe einer Scanline mit WidthBytes berechnet.
Bei Monochromen Bitmaps sind es nur 2 Byte.

Die erste Scanline enthält die unterste Zeile einer Bitmap.

Die Pixel werden wie folgt dargestellt:

Bits Farben 1 Pixel Pixel-Beschreibung (nicht erwähnte Bits/Bytes sind Null)
1 2 1Bit Palettenindex
4 16 4Bit Palettenindex
8 256 8Bit Palettenindex
15 32768 16Bit Bit14..10:Rot (5Bit), Bit9..5:Grün (5Bit), Bit4..0:Blau (5Bit)
16 65536 16Bit Bit15..11:Rot (5Bit), Bit10..6:Grün (5Bit), Bit5..0:Blau (6Bit)
24 16777216 16Bit Bit23..16 (Byte2):Rot, Bit15..8 (Byte1):Grün, Bit7..0 (Byte0):Blau
32 16777216 32Bit Bit23..16 (Byte2):Rot, Bit15..8 (Byte1):Grün, Bit7..0 (Byte0):Blau

Werde ich später noch genauer ausführen, jetzt erstmal nur der Verweis auf die Online-Hilfe.

Delphi 1: WinAPI.hlp (in deutsch außer Graphics File Formats)
Themen: TBitmapCoreHeader, TBitmapCoreInfo, TBitmapFileHeader, TBitmapInfo, TBitmapInfoHeader, Graphics File Formats

Delphi 2/3: MAPI.hlp (nur in englisch)
Themen: BitmapCoreHeader, BitmapCoreInfo,
BitmapFileHeader, BitmapInfo, BitmapInfoHeader

zurück zum Inhaltsverzeichnis

RGB Konvertierungen

Frage:
Wie kann ich aus der Angabe der R-, G- und B-Anteile eines Pixels den dazugehörigen Wert für TColor errechnen?

Antwort:
Mit der Funktion RGB.

Weitere Funktionen stehen in Windows.pas bereit:

Funktion Umsetzung
function RGB(r, g, b: Byte): COLORREF; Result := (r or (g shl 8) or (b shl 16));
function PaletteRGB(r, g, b: Byte): COLORREF; Result := $02000000 or RGB(r,g,b);
function PaletteIndex(i: Word): COLORREF; Result := $01000000 or i;
function GetRValue(rgb: DWORD): Byte; Result := Byte(rgb);
function GetGValue(rgb: DWORD): Byte; Result := Byte(rgb shr 8);
function GetBValue(rgb: DWORD): Byte; Result := Byte(rgb shr 16);

zurück zum Inhaltsverzeichnis

Wie dreht man eine Bitmap um 90°

Dazu habe ich ein kleines Demo geschrieben, daß 256-Farben und TrueColor Bitmaps um 90° (in beide Richtungen) dreht. Die Unit Bmp90D12 enthält dabei die Umsetzung für Delphi 1 und 2, die Unit Bmp90D34 die Umsetzung für Delphi 3 und 4.          Download (6 kByte)   (überarbeitet am 28.12.98)

Die Routinen WidthBytes und GetDInColors sowie bei Delphi 1 die Routine OffsetPointer entstammen den Original VCL-Sourcen von Graphics und Classes.

Das Demo ist überarbeitet, läuft jetzt auch unter Delphi 2. Zusätzlich ist ein Fehler behoben, der bei von 8Bit nach 24Bit konvertierten Bitmaps auftritt.

zurück zum Inhaltsverzeichnis

Wandlung in Graustufen und Ändern der Palette

Dazu habe ich ein kleines Demo geschrieben, daß 8, 24 und 32 Bit-Bitmaps in ein Graustufenbild wandelt und in einem zweiten Schritt die Palette dieser Bitmap ändert. Die Unit BmpGrD12 enthält dabei die Umsetzung für Delphi 1 und 2, das Demo läuft aber auch unter Delphi 3 und 4. Die Umsetzung für Delphi 1 ist unvollständig, Bitmaps > 64kByte werden falsch bearbeitet. Das werde ich zu einem späteren Zeitpunkt noch mal überarbeiten.          Download (6 kByte)   (erstellt am 02.03.99).

zurück zum Inhaltsverzeichnis

Wie kopiere ich ein Byte-Array in eine Bitmap

Problem:
Ich habe ein Byte-Array mit den Pixeln und eines mit den Paletteneinträgen, wie erzeuge ich daraus eine Bitmap ?

Arraydefinitionen:

  Pal     : array[0..255,0.. 2] of Byte;
  Pixels  : array[0..255,0..19] of Byte;

Lösung für Delphi 3:

Du brauchst ein Formular mit einem Button und zwei TImage mit AutoSize=True.

Es gibt 2 verschiedene Methoden, die erste arbeitet über eine 8 Bit-Bitmap und Palette, die zweite über eine 32 Bit-Bitmap.

Bei der ersten Methode bin ich mir nicht sicher, ob durch das CreatePalette nicht ein Ressourcenleck entsteht. Normalerweise muß noch ein DeleteObject(hPal) kommen, das würde jedoch die benutzte Palette zerstören.

Zuerst noch eine Hilfsfunktion, die die Funktion RGB für Scanlines umsetzt, da dort die Reihenfolge genau umgekehrt ist: B,G,R,0 (sonst R,G,B).

function ScanLineRGB32Bit(R,G,B:Byte):LongInt;
begin
  Result:=(R shl 16) or (G shl 8) or B;
end;

Jetzt die eigentliche Funktion:

procedure TForm1.Button1Click(Sender:TObject);
var
  i,x,y   : Integer;
  Pal     : array[0..255,0..2] of Byte;
  Pixels  : array[0..255,0..19] of Byte;
  b       : TBitmap;
  pb      : PByte;
  LogPal  : TMaxLogPalette;
  hPal    : hPalette;
  pl      : PLongInt;
  PalI    : array[Byte] of LongInt;

begin
 // Quellpalette für Demo mit sinnvollen Werten füllen
  for i:=0 to 255 do begin
    Pal[i,0]:=$FF;
    Pal[i,1]:=i;
    Pal[i,2]:=0;
  end;
  // Quellpixels für Demo mit sinnvollen Werten füllen
  for y:=0 to 19 do begin
    for x:=0 to 255 do begin
      Pixels[x,y]:=x;
    end;
  end;

  // Methode 1, über 8 Bit und Erzeugen einer Palette

  // Palette erzeugen
  with LogPal do begin
    palVersion:=$0300;
    palNumEntries:=256;
    for i:=0 to 255 do begin
      with palPalEntry[i] do begin
        peRed  :=Pal[i,0];
        peGreen:=Pal[i,1];
        peBlue :=Pal[i,2];
        peFlags:=0;
      end;
    end;
  end;
  pLogPal:=@LogPal;
  hPal:=CreatePalette(pLogPal^);
  // Bitmap mit Pixel füllen
  b:=TBitmap.Create;
  try
    with b do begin
      Width:=256;
      Height:=20;
      PixelFormat:=pf8Bit;
      Palette:=hPal;
      for y:=0 to 19 do begin
        pb:=ScanLine[y];
        for x:=0 to 255 do begin
          pb^:=Pixels[x,y];
          Inc(pb);
        end;
      end;
    end;
    Image1.Picture.Assign(b);
  finally
    b.Free;
  end;

  // Methode 2, über 32 Bit

  // Palette umschreiben
  for i:=0 to 255 do
    PalI[i]:=ScanLineRGB32Bit(Pal[i,0],Pal[i,1],Pal[i,2]);
  // Bitmap mit Pixel füllen
  b:=TBitmap.Create;
  try
    with b do begin
      Width:=256;
      Height:=20;
      PixelFormat:=pf32Bit;
      for y:=0 to 19 do begin
        pl:=ScanLine[y];
        for x:=0 to 255 do begin
          pl^:=PalI[Pixels[x,y]];
          Inc(pl);
        end;
      end;
    end;
    Image2.Picture.Assign(b);
  finally
    b.Free;
  end;
end;

Zum Schluß noch die Definitionen der Paletteneinträge
(aus der Unit windows):

  PPaletteEntry = ^TPaletteEntry;
  TPaletteEntry = packed record
    peRed: Byte;
    peGreen: Byte;
    peBlue: Byte;
    peFlags: Byte;
  end;


  { Logical Palette }
  PLogPalette = ^TLogPalette;
  TLogPalette = packed record
    palVersion: Word;
    palNumEntries: Word;
    palPalEntry: array[0..0] of TPaletteEntry;
  end;

  PMaxLogPalette = ^TMaxLogPalette;
  TMaxLogPalette = packed record
    palVersion: Word;
    palNumEntries: Word;
    palPalEntry: array [Byte] of TPaletteEntry;
  end;
zurück zum Inhaltsverzeichnis

Wie benutze ich ein JPEG (ab Delphi 3)

Frage:
Ich möchte eine JPEG-Datei einem eigenen TBitmap-Objekt zuweisen, damit ich dann im Hintergrund auf das Bild zugreifen und es verändern kann, bzw. Teile davon ausschneiden etc.

Antwort:
Die Unit JPEG in die uses-Liste aufnehmen.

dann ungefähr folgendes:

var
  xBMPImage     : TBitmap;
  xJPEGImage    : TJPEGImage;
begin
  xJPEGImage:=TJPEGImage.Create;
  try
    xBMPImage:=TBitmap.Create;
    try
      xJPEGImage.LoadFromFile('abcd.jpg');
      // -> JPG
      xBMPImage.Assign(xJPEGImage);
      // bearbeiten, z.B. abspeichern
      xBMPImage.SaveToFile('abcd.bmp');
    finally
      xBMPImage.Free;
    end;
  finally
    xJPEGImage.Free;
  end;
end;

Ein wenig Theorie:

Im Prinzip hält TJPEGImage das JPEG-File im Speicher und gleichzeitig eine nicht bearbeitbare Bitmap, die bei Bedarf erzeugt wird.

TJPEGImage hat nur den Befehl Draw implementiert, d.h. mit Canvas.Draw(X,Y,xJPEGImage); kann man die komplette JPEG auf ein Canvas zeichnen.

Um die Grafik einer JPEG bearbeiten zu können, müssen wir sie einem TBitmap zuordnen:

  xBMPImage.Assign(xJPEGImage);

Jetzt kann man mit dem Bitmap machen, was man möchte.

Bug in der Unit JPEG: (mindestens in Delphi 3)

Ein Bug tritt zu Tage, wenn man eine JPEG-Datei lädt, den Bitmap-Teil aktiviert (z.B. durch Assign oder Anzeigen) und anschließend eine andere JPEG-Datei lädt.

Was passiert:
Der Bitmap-Teil wird nicht für ungültig erklärt, so das nach wie vor das alte Bild im Bitmap-Teil steht.

Workaround:
Vor dem Laden des nächsten Bildes folgende Anweisung benutzen:

  with xJPEGImage do Smoothing:=Not Smoothing;

Das erklärt den Bitmap-Teil für ungültig.

zurück zum Inhaltsverzeichnis

Wie kann ich eine Vorschau der JPEG-Kompression machen

Frage:
Ich habe ein Problem mit TJPEGImage. Ich schreibe gerade eine Dialogbox zur JPEG-Kompression, die eine JPEG-Vorschau hat. Ich möchte nun die komprimierte Version des JPEGs sehen, ich bekomme aber grundsätzlich nur das Original.

Antwort:
Folgende Anweisungen sollten das gewünschte Ergebnis bringen.

var
  j : TJPEGImage;
begin
  j:=TJPEGImage.Create;
  try
    j.Assign(Image1.Picture.Graphic);
    j.CompressionQuality:=20;
    j.Compress;
    j.Smoothing:=Not j.Smoothing;
    Image2.Picture.Assign(j);
  finally
    j.Free;
  end;
end;
zurück zum Inhaltsverzeichnis

Mails an mich unter webmaster@pjh2.de. Haftungsausschluss
Bitte keine E-Mails zu Delphi, die sich nicht auf diese Seiten beziehen. Sie werden von mir nicht beantwortet.