2
// This unit is part of the GLScene Engine https://github.com/glscene
5
Graphic engine friendly loading of BMP image.
8
04/04/11 - Yar - Creation
30
TGLBMPImage = class(TGLBaseImage)
34
RedMask, GreenMask, BlueMask: LongWord;
35
RedShift, GreenShift, BlueShift: ShortInt;
36
FLineBuffer: PByteArray;
40
function CountBits(Value: byte): shortint;
41
function ShiftCount(Mask: longword): shortint;
42
function ExpandColor(Value: longword): TGLPixel32;
43
procedure ExpandRLE4ScanLine(Row: Integer; Stream: TStream);
44
procedure ExpandRLE8ScanLine(Row: Integer; Stream: TStream);
45
function Monochrome(N: Integer): Integer;
46
function Quadrochrome(N: Integer): Integer;
47
function Octochrome(N: Integer): Integer;
50
procedure LoadFromFile(const filename: string); override;
51
procedure SaveToFile(const filename: string); override;
52
procedure LoadFromStream(stream: TStream); override;
53
procedure SaveToStream(stream: TStream); override;
54
class function Capabilities: TGLDataFileCapabilities; override;
56
procedure AssignFromTexture(textureContext: TGLContext;
57
const textureHandle: TGLuint;
58
textureTarget: TGLTextureTarget;
59
const CurrentFormat: boolean;
60
const intFormat: TGLInternalFormat); reintroduce;
67
BMmagic = 19778; // BMP magic word is always 19778 : 'BM'
68
// Values for Compression field
76
TBitMapFileHeader = packed record
79
{02+04 :File size in bytes}
83
{10+04 : Offset of image data : size if the file hieder + the info header + palette}
86
PBitMapFileHeader = ^TBitMapFileHeader;
88
TBitMapInfoHeader = packed record
89
{14+04 : Size of the bitmap info header : sould be 40=$28}
91
{18+04 : Image width in pixels}
93
{22+04 : Image height in pixels}
95
{26+02 : Number of image planes : should be 1 always}
97
{28+02 : Color resolution : Number of bits per pixel (1,4,8,16,24,32)}
99
{30+04 : Compression Type}
100
Compression: longint;
101
{34+04 : Size of image data (not headers nor palette): can be 0 if no compression}
103
{38+04 : Horizontal resolution in pixel/meter}
104
XPelsPerMeter: Longint;
105
{42+04 : Vertical resolution in pixel/meter}
106
YPelsPerMeter: Longint;
107
{46+04 : Number of colors used}
109
{50+04 : Number of imprtant colors used : usefull for displaying on VGA256}
110
ClrImportant: longint;
112
PBitMapInfoHeader = ^TBitMapInfoHeader;
117
procedure TGLBMPImage.LoadFromFile(const filename: string);
121
if FileStreamExists(fileName) then
123
fs := CreateFileStream(fileName, fmOpenRead);
128
ResourceName := filename;
132
raise EInvalidRasterFile.CreateFmt('File %s not found', [filename]);
138
procedure TGLBMPImage.SaveToFile(const filename: string);
142
fs := CreateFileStream(fileName, fmOpenWrite or fmCreate);
148
ResourceName := filename;
151
function TGLBMPImage.CountBits(Value: byte): shortint;
158
if (value mod 2) <> 0 then
160
value := value shr 1;
165
function TGLBMPImage.ShiftCount(Mask: longword): shortint;
176
while (Mask mod 2) = 0 do // rightmost bit is 0
181
tmp := tmp - (8 - CountBits(Mask and $FF));
185
function TGLBMPImage.ExpandColor(Value: longword): TGLPixel32;
187
tmpr, tmpg, tmpb: longword;
189
tmpr := value and RedMask;
190
tmpg := value and GreenMask;
191
tmpb := value and BlueMask;
193
Result.R := byte(tmpr shl (-RedShift))
195
Result.R := byte(tmpr shr RedShift);
196
if GreenShift < 0 then
197
Result.G := byte(tmpg shl (-GreenShift))
199
Result.G := byte(tmpg shr GreenShift);
200
if BlueShift < 0 then
201
Result.B := byte(tmpb shl (-BlueShift))
203
Result.B := byte(tmpb shr BlueShift);
206
function TGLBMPImage.Monochrome(N: Integer): Integer;
208
Result := (FLineBuffer[N div 8] shr (7 - (N and 7))) and 1;
211
function TGLBMPImage.Quadrochrome(N: Integer): Integer;
213
Result := (FLineBuffer[N div 2] shr (((N + 1) and 1) * 4)) and $0F;
216
function TGLBMPImage.Octochrome(N: Integer): Integer;
218
Result := FLineBuffer[N];
224
procedure TGLBMPImage.LoadFromStream(stream: TStream);
226
TBitShiftFunc = function(N: Integer): Integer of object;
228
LHeader: TBitMapFileHeader;
229
LInfo: TBitMapInfoHeader;
230
BadCompression: Boolean;
232
BitCount, LineSize: Integer;
235
LPalette: array of TGLPixel32;
236
BitShiftFunc: TBitShiftFunc;
238
procedure ReadScanLine;
244
Stream.Read(FLineBuffer[0], FReadSize);
245
for I := LInfo.Width - 1 downto 0 do
246
PGLPixel32Array(Ptr)[I] := LPalette[BitShiftFunc(I)];
248
else if LInfo.Compression = BI_RLE8 then
250
ExpandRLE8ScanLine(Row, Stream);
251
Move(FLineBuffer[0], Ptr^, LineSize);
253
else if LInfo.Compression = BI_RLE4 then
255
ExpandRLE4ScanLine(Row, Stream);
256
Move(FLineBuffer[0], Ptr^, LineSize);
258
else if LInfo.BitCount = 16 then
260
Stream.Read(FLineBuffer[0], FReadSize);
261
for I := LInfo.Width - 1 downto 0 do
262
PGLPixel32Array(Ptr)[I] := ExpandColor(PWordArray(FLineBuffer)[I]);
265
Stream.Read(Ptr^, FReadSize);
271
stream.Read(LHeader, SizeOf(TBitMapFileHeader));
272
if LHeader.bfType <> BMmagic then
273
raise EInvalidRasterFile.Create('Invalid BMP header');
275
stream.Read(LInfo, SizeOf(TBitMapInfoHeader));
276
stream.Position := stream.Position - SizeOf(TBitMapInfoHeader) + LInfo.Size;
278
BadCompression := false;
279
if ((LInfo.Compression = BI_RLE4) and (LInfo.BitCount <> 4)) then
280
BadCompression := true;
281
if ((LInfo.Compression = BI_RLE8) and (LInfo.BitCount <> 8)) then
282
BadCompression := true;
283
if ((LInfo.Compression = BI_BITFIELDS) and (not (LInfo.BitCount in [16, 32]))) then
284
BadCompression := true;
285
if not (LInfo.Compression in [BI_RGB..BI_BITFIELDS]) then
286
BadCompression := true;
287
if BadCompression then
288
raise EInvalidRasterFile.Create('Bad BMP compression mode');
289
FTopDown := (LInfo.Height < 0);
290
LInfo.Height := abs(LInfo.Height);
291
if (FTopDown and (not (LInfo.Compression in [BI_RGB, BI_BITFIELDS]))) then
292
raise EInvalidRasterFile.Create('Top-down bitmaps cannot be compressed');
295
if ((LInfo.Compression = BI_RGB)
296
and (LInfo.BitCount = 16)) then // 5 bits per channel, fixed mask
305
else if ((LInfo.Compression = BI_BITFIELDS)
306
and (LInfo.BitCount in [16, 32])) then // arbitrary mask
308
Stream.Read(RedMask, 4);
309
Stream.Read(GreenMask, 4);
310
Stream.Read(BlueMask, 4);
311
RedShift := ShiftCount(RedMask);
312
GreenShift := ShiftCount(GreenMask);
313
BlueShift := ShiftCount(BlueMask);
315
else if LInfo.BitCount in [1, 4, 8] then
317
nPalette := 1 shl LInfo.BitCount;
318
SetLength(LPalette, nPalette);
319
if LInfo.ClrUsed > 0 then
320
Stream.Read(LPalette[0], LInfo.ClrUsed * SizeOf(TGLPixel32))
321
else // Seems to me that this is dangerous.
322
Stream.Read(LPalette[0], nPalette * SizeOf(TGLPixel32));
324
else if LInfo.ClrUsed > 0 then { Skip palette }
325
Stream.Position := Stream.Position + LInfo.ClrUsed * 3;
328
FLOD[0].Width := LInfo.Width;
329
FLOD[0].Height := LInfo.Height;
332
FColorFormat := GL_BGRA;
333
FInternalFormat := tfRGBA8;
336
case LInfo.BitCount of
340
BitShiftFunc := Monochrome;
345
BitShiftFunc := Quadrochrome;
350
BitShiftFunc := Octochrome;
357
FColorFormat := GL_BGR;
358
FInternalFormat := tfRGB8;
365
FDataType := GL_UNSIGNED_BYTE;
367
FTextureArray := False;
368
ReallocMem(FData, DataSize);
373
LineSize := GetWidth * FElementSize;
375
FReadSize := ((LInfo.Width * BitCount + 31) div 32) shl 2;
376
GetMem(FLineBuffer, FReadSize);
380
for Row := 0 to GetHeight - 1 do // A rare case of top-down bitmap!
383
for Row := GetHeight - 1 downto 0 do
386
FreeMem(FLineBuffer);
391
procedure TGLBMPImage.ExpandRLE4ScanLine(Row: Integer; Stream: TStream);
393
i, j, tmpsize: integer;
398
tmpsize := FReadSize * 2; { ReadSize is in bytes, while nibline is made of nibbles, so it's 2*readsize long }
399
getmem(nibline, tmpsize);
404
{ let's see if we must skip pixels because of delta... }
405
if FDeltaY <> -1 then
407
if Row = FDeltaY then
408
j := FDeltaX { If we are on the same line, skip till DeltaX }
410
j := tmpsize; { else skip up to the end of this line }
417
if Row = FDeltaY then { we don't need delta anymore }
420
break; { skipping must continue on the next line, we are finished here }
425
if b0 <> 0 then { number of repetitions }
427
if b0 + i > tmpsize then
428
raise EInvalidRasterFile.Create('Bad BMP RLE chunk at row ' + inttostr(row) + ', col ' + inttostr(i) + ', file offset $' + inttohex(Stream.Position, 16));
434
NibLine[i] := (b1 and $F0) shr 4
436
NibLine[i] := b1 and $0F;
443
0: break; { end of line }
444
1: break; { end of file }
446
begin { Next pixel position. Skipped pixels should be left untouched, but we set them to zero }
453
begin { absolute mode }
454
if b1 + i > tmpsize then
455
raise EInvalidRasterFile.Create('Bad BMP RLE chunk at row ' + inttostr(row) + ', col ' + inttostr(i) + ', file offset $' + inttohex(Stream.Position, 16));
463
NibLine[i] := (b0 and $F0) shr 4;
466
NibLine[i] := b0 and $0F;
470
{ aligned on 2 bytes boundary: see rle8 for details }
471
b1 := b1 + (b1 mod 2);
472
if (b1 mod 4) <> 0 then
473
Stream.Seek(1, soFromCurrent);
477
{ pack the nibline into the linebuf }
478
for i := 0 to FReadSize - 1 do
479
FLineBuffer[i] := (NibLine[i * 2] shl 4) or NibLine[i * 2 + 1];
485
procedure TGLBMPImage.ExpandRLE8ScanLine(Row: Integer; Stream: TStream);
493
{ let's see if we must skip pixels because of delta... }
494
if FDeltaY <> -1 then
496
if Row = FDeltaY then
497
j := FDeltaX { If we are on the same line, skip till DeltaX }
499
j := FReadSize; { else skip up to the end of this line }
506
if Row = FDeltaY then { we don't need delta anymore }
509
break; { skipping must continue on the next line, we are finished here }
514
if b0 <> 0 then { number of repetitions }
516
if b0 + i > FReadSize then
517
raise EInvalidRasterFile.Create('Bad BMP RLE chunk at row ' + inttostr(row) + ', col ' + inttostr(i) + ', file offset $' + inttohex(Stream.Position, 16));
521
FLineBuffer[i] := b1;
527
0: break; { end of line }
528
1: break; { end of file }
530
begin { Next pixel position. Skipped pixels should be left untouched, but we set them to zero }
537
begin { absolute mode }
538
if b1 + i > FReadSize then
539
raise EInvalidRasterFile.Create('Bad BMP RLE chunk at row ' + inttostr(row) + ', col ' + inttostr(i) + ', file offset $' + inttohex(Stream.Position, 16));
540
Stream.Read(FLineBuffer[i], b1);
542
{ aligned on 2 bytes boundary: every group starts on a 2 bytes boundary, but absolute group
543
could end on odd address if there is a odd number of elements, so we pad it }
544
if (b1 mod 2) <> 0 then
545
Stream.Seek(1, soFromCurrent);
554
procedure TGLBMPImage.SaveToStream(stream: TStream);
556
{$Message Hint 'TGLBMPImage.SaveToStream not yet implemented' }
562
procedure TGLBMPImage.AssignFromTexture(textureContext: TGLContext;
563
const textureHandle: TGLuint; textureTarget: TGLTextureTarget;
564
const CurrentFormat: boolean; const intFormat: TGLInternalFormat);
566
{$Message Hint 'TGLBMPImage.AssignFromTexture not yet implemented' }
569
class function TGLBMPImage.Capabilities: TGLDataFileCapabilities;
571
Result := [dfcRead {, dfcWrite}];
576
{ Register this Fileformat-Handler with GLScene }
577
RegisterRasterFormat('bmp', 'Bitmap Image File', TGLBMPImage);