123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559 |
- //
- // The graphics engine GLXEngine. The unit of GXScene for Delphi
- //
- unit GXS.FileBMP;
- (* Friendly loading of BMP image *)
- interface
- {$I Stage.Defines.inc}
- uses
- Winapi.OpenGL,
- Winapi.OpenGLext,
- System.Classes,
- System.SysUtils,
- GXS.Context,
- GXS.Graphics,
- Stage.TextureFormat,
- GXS.ApplicationFileIO;
- type
- TgxBMPImage = class(TgxBaseImage)
- private
- FTopDown: Boolean;
- RedMask, GreenMask, BlueMask: LongWord;
- RedShift, GreenShift, BlueShift: ShortInt;
- FLineBuffer: PByteArray;
- FReadSize: Integer;
- FDeltaX: Integer;
- FDeltaY: Integer;
- function CountBits(Value: byte): shortint;
- function ShiftCount(Mask: longword): shortint;
- function ExpandColor(Value: longword): TgxPixel32;
- procedure ExpandRLE4ScanLine(Row: Integer; Stream: TStream);
- procedure ExpandRLE8ScanLine(Row: Integer; Stream: TStream);
- function Monochrome(N: Integer): Integer;
- function Quadrochrome(N: Integer): Integer;
- function Octochrome(N: Integer): Integer;
- public
- procedure LoadFromFile(const filename: string); override;
- procedure SaveToFile(const filename: string); override;
- procedure LoadFromStream(stream: TStream); override;
- procedure SaveToStream(stream: TStream); override;
- class function Capabilities: TDataFileCapabilities; override;
- procedure AssignFromTexture(textureContext: TgxContext;
- const textureHandle: GLuint;
- textureTarget: TglTextureTarget;
- const CurrentFormat: boolean;
- const intFormat: TglInternalFormat); reintroduce;
- end;
- //---------------------------------------------------
- implementation
- //---------------------------------------------------
- const
- BMmagic = 19778; // BMP magic word is always 19778 : 'BM'
- // Values for Compression field
- BI_RGB = 0;
- BI_RLE8 = 1;
- BI_RLE4 = 2;
- BI_BITFIELDS = 3;
- type
- TBitMapFileHeader = packed record
- {00+02 :File type}
- bfType: word;
- {02+04 :File size in bytes}
- bfSize: longint;
- {06+04 : Reserved}
- bfReserved: longint;
- {10+04 : Offset of image data : size if the file hieder + the info header + palette}
- bfOffset: longint;
- end;
- PBitMapFileHeader = ^TBitMapFileHeader;
- TBitMapInfoHeader = packed record
- {14+04 : Size of the bitmap info header : sould be 40=$28}
- Size: longint;
- {18+04 : Image width in pixels}
- Width: longint;
- {22+04 : Image height in pixels}
- Height: longint;
- {26+02 : Number of image planes : should be 1 always}
- Planes: word;
- {28+02 : Color resolution : Number of bits per pixel (1,4,8,16,24,32)}
- BitCount: word;
- {30+04 : Compression Type}
- Compression: longint;
- {34+04 : Size of image data (not headers nor palette): can be 0 if no compression}
- SizeImage: longint;
- {38+04 : Horizontal resolution in pixel/meter}
- XPelsPerMeter: Longint;
- {42+04 : Vertical resolution in pixel/meter}
- YPelsPerMeter: Longint;
- {46+04 : Number of colors used}
- ClrUsed: longint;
- {50+04 : Number of imprtant colors used : usefull for displaying on VGA256}
- ClrImportant: longint;
- end;
- PBitMapInfoHeader = ^TBitMapInfoHeader;
- procedure TgxBMPImage.LoadFromFile(const filename: string);
- var
- fs: TStream;
- begin
- if FileStreamExists(fileName) then
- begin
- fs := TFileStream.Create(fileName, fmOpenRead);
- try
- LoadFromStream(fs);
- finally
- fs.Free;
- ResourceName := filename;
- end;
- end
- else
- raise EInvalidRasterFile.CreateFmt('File %s not found', [filename]);
- end;
- procedure TgxBMPImage.SaveToFile(const filename: string);
- var
- fs: TStream;
- begin
- fs := TFileStream.Create(fileName, fmOpenWrite or fmCreate);
- try
- SaveToStream(fs);
- finally
- fs.Free;
- end;
- ResourceName := filename;
- end;
- function TgxBMPImage.CountBits(Value: byte): shortint;
- var
- i, bits: shortint;
- begin
- bits := 0;
- for i := 0 to 7 do
- begin
- if (value mod 2) <> 0 then
- inc(bits);
- value := value shr 1;
- end;
- Result := bits;
- end;
- function TgxBMPImage.ShiftCount(Mask: longword): shortint;
- var
- tmp: shortint;
- begin
- tmp := 0;
- if Mask = 0 then
- begin
- Result := 0;
- exit;
- end;
- while (Mask mod 2) = 0 do // rightmost bit is 0
- begin
- inc(tmp);
- Mask := Mask shr 1;
- end;
- tmp := tmp - (8 - CountBits(Mask and $FF));
- Result := tmp;
- end;
- function TgxBMPImage.ExpandColor(Value: longword): TgxPixel32;
- var
- tmpr, tmpg, tmpb: longword;
- begin
- tmpr := value and RedMask;
- tmpg := value and GreenMask;
- tmpb := value and BlueMask;
- if RedShift < 0 then
- Result.R := byte(tmpr shl (-RedShift))
- else
- Result.R := byte(tmpr shr RedShift);
- if GreenShift < 0 then
- Result.G := byte(tmpg shl (-GreenShift))
- else
- Result.G := byte(tmpg shr GreenShift);
- if BlueShift < 0 then
- Result.B := byte(tmpb shl (-BlueShift))
- else
- Result.B := byte(tmpb shr BlueShift);
- end;
- function TgxBMPImage.Monochrome(N: Integer): Integer;
- begin
- Result := (FLineBuffer[N div 8] shr (7 - (N and 7))) and 1;
- end;
- function TgxBMPImage.Quadrochrome(N: Integer): Integer;
- begin
- Result := (FLineBuffer[N div 2] shr (((N + 1) and 1) * 4)) and $0F;
- end;
- function TgxBMPImage.Octochrome(N: Integer): Integer;
- begin
- Result := FLineBuffer[N];
- end;
- procedure TgxBMPImage.LoadFromStream(stream: TStream);
- type
- TBitShiftFunc = function(N: Integer): Integer of object;
- var
- LHeader: TBitMapFileHeader;
- LInfo: TBitMapInfoHeader;
- BadCompression: Boolean;
- Ptr: PByte;
- BitCount, LineSize: Integer;
- Row: Integer;
- nPalette: Integer;
- LPalette: array of TgxPixel32;
- BitShiftFunc: TBitShiftFunc;
- procedure ReadScanLine;
- var
- I: Integer;
- begin
- if nPalette > 0 then
- begin
- Stream.Read(FLineBuffer[0], FReadSize);
- for I := LInfo.Width - 1 downto 0 do
- PgxPixel32Array(Ptr)[I] := LPalette[BitShiftFunc(I)];
- end
- else if LInfo.Compression = BI_RLE8 then
- begin
- ExpandRLE8ScanLine(Row, Stream);
- Move(FLineBuffer[0], Ptr^, LineSize);
- end
- else if LInfo.Compression = BI_RLE4 then
- begin
- ExpandRLE4ScanLine(Row, Stream);
- Move(FLineBuffer[0], Ptr^, LineSize);
- end
- else if LInfo.BitCount = 16 then
- begin
- Stream.Read(FLineBuffer[0], FReadSize);
- for I := LInfo.Width - 1 downto 0 do
- PgxPixel32Array(Ptr)[I] := ExpandColor(PWordArray(FLineBuffer)[I]);
- end
- else
- Stream.Read(Ptr^, FReadSize);
- Inc(Ptr, LineSize);
- end;
- begin
- stream.Read(LHeader, SizeOf(TBitMapFileHeader));
- if LHeader.bfType <> BMmagic then
- raise EInvalidRasterFile.Create('Invalid BMP header');
- stream.Read(LInfo, SizeOf(TBitMapInfoHeader));
- stream.Position := stream.Position - SizeOf(TBitMapInfoHeader) + LInfo.Size;
- BadCompression := false;
- if ((LInfo.Compression = BI_RLE4) and (LInfo.BitCount <> 4)) then
- BadCompression := true;
- if ((LInfo.Compression = BI_RLE8) and (LInfo.BitCount <> 8)) then
- BadCompression := true;
- if ((LInfo.Compression = BI_BITFIELDS) and (not (LInfo.BitCount in [16, 32]))) then
- BadCompression := true;
- if not (LInfo.Compression in [BI_RGB..BI_BITFIELDS]) then
- BadCompression := true;
- if BadCompression then
- raise EInvalidRasterFile.Create('Bad BMP compression mode');
- FTopDown := (LInfo.Height < 0);
- LInfo.Height := abs(LInfo.Height);
- if (FTopDown and (not (LInfo.Compression in [BI_RGB, BI_BITFIELDS]))) then
- raise EInvalidRasterFile.Create('Top-down bitmaps cannot be compressed');
- nPalette := 0;
- if ((LInfo.Compression = BI_RGB)
- and (LInfo.BitCount = 16)) then // 5 bits per channel, fixed mask
- begin
- RedMask := $7C00;
- RedShift := 7;
- GreenMask := $03E0;
- GreenShift := 2;
- BlueMask := $001F;
- BlueShift := -3;
- end
- else if ((LInfo.Compression = BI_BITFIELDS)
- and (LInfo.BitCount in [16, 32])) then // arbitrary mask
- begin
- Stream.Read(RedMask, 4);
- Stream.Read(GreenMask, 4);
- Stream.Read(BlueMask, 4);
- RedShift := ShiftCount(RedMask);
- GreenShift := ShiftCount(GreenMask);
- BlueShift := ShiftCount(BlueMask);
- end
- else if LInfo.BitCount in [1, 4, 8] then
- begin
- nPalette := 1 shl LInfo.BitCount;
- SetLength(LPalette, nPalette);
- if LInfo.ClrUsed > 0 then
- Stream.Read(LPalette[0], LInfo.ClrUsed * SizeOf(TgxPixel32))
- else // Seems to me that this is dangerous.
- Stream.Read(LPalette[0], nPalette * SizeOf(TgxPixel32));
- end
- else if LInfo.ClrUsed > 0 then { Skip palette }
- Stream.Position := Stream.Position + LInfo.ClrUsed * 3;
- UnMipmap;
- FLOD[0].Width := LInfo.Width;
- FLOD[0].Height := LInfo.Height;
- FLOD[0].Depth := 0;
- BitCount := 0;
- FColorFormat := GL_BGRA;
- FInternalFormat := tfRGBA8;
- FElementSize := 4;
- case LInfo.BitCount of
- 1:
- begin
- BitCount := 1;
- BitShiftFunc := Monochrome;
- end;
- 4:
- begin
- BitCount := 4;
- BitShiftFunc := Quadrochrome;
- end;
- 8:
- begin
- BitCount := 8;
- BitShiftFunc := Octochrome;
- end;
- 16:
- BitCount := 16;
- 24:
- begin
- BitCount := 24;
- FColorFormat := GL_BGR;
- FInternalFormat := tfRGB8;
- FElementSize := 3;
- end;
- 32:
- BitCount := 32;
- end;
- FDataType := GL_UNSIGNED_BYTE;
- FCubeMap := False;
- FTextureArray := False;
- ReallocMem(FData, DataSize);
- FDeltaX := -1;
- FDeltaY := -1;
- Ptr := PByte(FData);
- LineSize := GetWidth * FElementSize;
- FReadSize := ((LInfo.Width * BitCount + 31) div 32) shl 2;
- GetMem(FLineBuffer, FReadSize);
- try
- if FTopDown then
- for Row := 0 to GetHeight - 1 do // A rare case of top-down bitmap!
- ReadScanLine
- else
- for Row := GetHeight - 1 downto 0 do
- ReadScanLine;
- finally
- FreeMem(FLineBuffer);
- end;
- end;
- procedure TgxBMPImage.ExpandRLE4ScanLine(Row: Integer; Stream: TStream);
- var
- i, j, tmpsize: integer;
- b0, b1: byte;
- nibline: PByteArray;
- even: boolean;
- begin
- tmpsize := FReadSize * 2; { ReadSize is in bytes, while nibline is made of nibbles, so it's 2*readsize long }
- getmem(nibline, tmpsize);
- try
- i := 0;
- while true do
- begin
- { let's see if we must skip pixels because of delta... }
- if FDeltaY <> -1 then
- begin
- if Row = FDeltaY then
- j := FDeltaX { If we are on the same line, skip till DeltaX }
- else
- j := tmpsize; { else skip up to the end of this line }
- while (i < j) do
- begin
- NibLine[i] := 0;
- inc(i);
- end;
- if Row = FDeltaY then { we don't need delta anymore }
- FDeltaY := -1
- else
- break; { skipping must continue on the next line, we are finished here }
- end;
- Stream.Read(b0, 1);
- Stream.Read(b1, 1);
- if b0 <> 0 then { number of repetitions }
- begin
- if b0 + i > tmpsize then
- raise EInvalidRasterFile.Create('Bad BMP RLE chunk at row ' + inttostr(row) + ', col ' + inttostr(i) + ', file offset $' + inttohex(Stream.Position, 16));
- even := true;
- j := i + b0;
- while (i < j) do
- begin
- if even then
- NibLine[i] := (b1 and $F0) shr 4
- else
- NibLine[i] := b1 and $0F;
- inc(i);
- even := not even;
- end;
- end
- else
- case b1 of
- 0: break; { end of line }
- 1: break; { end of file }
- 2:
- begin { Next pixel position. Skipped pixels should be left untouched, but we set them to zero }
- Stream.Read(b0, 1);
- Stream.Read(b1, 1);
- FDeltaX := i + b0;
- FDeltaY := Row + b1;
- end
- else
- begin { absolute mode }
- if b1 + i > tmpsize then
- raise EInvalidRasterFile.Create('Bad BMP RLE chunk at row ' + inttostr(row) + ', col ' + inttostr(i) + ', file offset $' + inttohex(Stream.Position, 16));
- j := i + b1;
- even := true;
- while (i < j) do
- begin
- if even then
- begin
- Stream.Read(b0, 1);
- NibLine[i] := (b0 and $F0) shr 4;
- end
- else
- NibLine[i] := b0 and $0F;
- inc(i);
- even := not even;
- end;
- { aligned on 2 bytes boundary: see rle8 for details }
- b1 := b1 + (b1 mod 2);
- if (b1 mod 4) <> 0 then
- Stream.Seek(1, soFromCurrent);
- end;
- end;
- end;
- { pack the nibline into the linebuf }
- for i := 0 to FReadSize - 1 do
- FLineBuffer[i] := (NibLine[i * 2] shl 4) or NibLine[i * 2 + 1];
- finally
- FreeMem(nibline)
- end;
- end;
- procedure TgxBMPImage.ExpandRLE8ScanLine(Row: Integer; Stream: TStream);
- var
- i, j: integer;
- b0, b1: byte;
- begin
- i := 0;
- while true do
- begin
- { let's see if we must skip pixels because of delta... }
- if FDeltaY <> -1 then
- begin
- if Row = FDeltaY then
- j := FDeltaX { If we are on the same line, skip till DeltaX }
- else
- j := FReadSize; { else skip up to the end of this line }
- while (i < j) do
- begin
- FLineBuffer[i] := 0;
- inc(i);
- end;
- if Row = FDeltaY then { we don't need delta anymore }
- FDeltaY := -1
- else
- break; { skipping must continue on the next line, we are finished here }
- end;
- Stream.Read(b0, 1);
- Stream.Read(b1, 1);
- if b0 <> 0 then { number of repetitions }
- begin
- if b0 + i > FReadSize then
- raise EInvalidRasterFile.Create('Bad BMP RLE chunk at row ' + inttostr(row) + ', col ' + inttostr(i) + ', file offset $' + inttohex(Stream.Position, 16));
- j := i + b0;
- while (i < j) do
- begin
- FLineBuffer[i] := b1;
- inc(i);
- end;
- end
- else
- case b1 of
- 0: break; { end of line }
- 1: break; { end of file }
- 2:
- begin { Next pixel position. Skipped pixels should be left untouched, but we set them to zero }
- Stream.Read(b0, 1);
- Stream.Read(b1, 1);
- FDeltaX := i + b0;
- FDeltaY := Row + b1;
- end
- else
- begin { absolute mode }
- if b1 + i > FReadSize then
- raise EInvalidRasterFile.Create('Bad BMP RLE chunk at row ' + inttostr(row) + ', col ' + inttostr(i) + ', file offset $' + inttohex(Stream.Position, 16));
- Stream.Read(FLineBuffer[i], b1);
- inc(i, b1);
- { aligned on 2 bytes boundary: every group starts on a 2 bytes boundary, but absolute group
- could end on odd address if there is a odd number of elements, so we pad it }
- if (b1 mod 2) <> 0 then
- Stream.Seek(1, soFromCurrent);
- end;
- end;
- end;
- end;
- procedure TgxBMPImage.SaveToStream(stream: TStream);
- begin
- {$Message Hint 'TgxBMPImage.SaveToStream not yet implemented' }
- end;
- procedure TgxBMPImage.AssignFromTexture(textureContext: TgxContext;
- const textureHandle: GLuint; textureTarget: TglTextureTarget;
- const CurrentFormat: boolean; const intFormat: TglInternalFormat);
- begin
- {$Message Hint 'TgxBMPImage.AssignFromTexture not yet implemented' }
- end;
- class function TgxBMPImage.Capabilities: TDataFileCapabilities;
- begin
- Result := [dfcRead {, dfcWrite}];
- end;
- initialization
- RegisterRasterFormat('bmp', 'Bitmap Image File', TgxBMPImage);
- end.
|