GLS.FileBMP.pas 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564
  1. //
  2. // The multimedia graphics platform GLScene https://github.com/glscene
  3. //
  4. unit GLS.FileBMP;
  5. (* Graphic engine friendly loading of BMP image *)
  6. interface
  7. {$I GLScene.inc}
  8. uses
  9. Winapi.OpenGL,
  10. Winapi.OpenGLext,
  11. System.Classes,
  12. System.SysUtils,
  13. GLS.OpenGLTokens,
  14. GLS.Context,
  15. GLS.Graphics,
  16. GLS.ApplicationFileIO,
  17. GLS.TextureFormat;
  18. type
  19. TGLBMPImage = class(TGLBaseImage)
  20. private
  21. FTopDown: Boolean;
  22. RedMask, GreenMask, BlueMask: LongWord;
  23. RedShift, GreenShift, BlueShift: ShortInt;
  24. FLineBuffer: PByteArray;
  25. FReadSize: Integer;
  26. FDeltaX: Integer;
  27. FDeltaY: Integer;
  28. function CountBits(Value: byte): shortint;
  29. function ShiftCount(Mask: longword): shortint;
  30. function ExpandColor(Value: longword): TGLPixel32;
  31. procedure ExpandRLE4ScanLine(Row: Integer; Stream: TStream);
  32. procedure ExpandRLE8ScanLine(Row: Integer; Stream: TStream);
  33. function Monochrome(N: Integer): Integer;
  34. function Quadrochrome(N: Integer): Integer;
  35. function Octochrome(N: Integer): Integer;
  36. public
  37. procedure LoadFromFile(const filename: string); override;
  38. procedure SaveToFile(const filename: string); override;
  39. procedure LoadFromStream(stream: TStream); override;
  40. procedure SaveToStream(stream: TStream); override;
  41. class function Capabilities: TGLDataFileCapabilities; override;
  42. procedure AssignFromTexture(textureContext: TGLContext;
  43. const textureHandle: Cardinal;
  44. textureTarget: TGLTextureTarget;
  45. const CurrentFormat: boolean;
  46. const intFormat: TGLInternalFormat); reintroduce;
  47. end;
  48. //========================================================
  49. implementation
  50. //========================================================
  51. const
  52. BMmagic = 19778; // BMP magic word is always 19778 : 'BM'
  53. // Values for Compression field
  54. BI_RGB = 0;
  55. BI_RLE8 = 1;
  56. BI_RLE4 = 2;
  57. BI_BITFIELDS = 3;
  58. type
  59. TBitMapFileHeader = packed record
  60. {00+02 :File type}
  61. bfType: word;
  62. {02+04 :File size in bytes}
  63. bfSize: longint;
  64. {06+04 : Reserved}
  65. bfReserved: longint;
  66. {10+04 : Offset of image data : size if the file hieder + the info header + palette}
  67. bfOffset: longint;
  68. end;
  69. PBitMapFileHeader = ^TBitMapFileHeader;
  70. TBitMapInfoHeader = packed record
  71. {14+04 : Size of the bitmap info header : sould be 40=$28}
  72. Size: longint;
  73. {18+04 : Image width in pixels}
  74. Width: longint;
  75. {22+04 : Image height in pixels}
  76. Height: longint;
  77. {26+02 : Number of image planes : should be 1 always}
  78. Planes: word;
  79. {28+02 : Color resolution : Number of bits per pixel (1,4,8,16,24,32)}
  80. BitCount: word;
  81. {30+04 : Compression Type}
  82. Compression: longint;
  83. {34+04 : Size of image data (not headers nor palette): can be 0 if no compression}
  84. SizeImage: longint;
  85. {38+04 : Horizontal resolution in pixel/meter}
  86. XPelsPerMeter: Longint;
  87. {42+04 : Vertical resolution in pixel/meter}
  88. YPelsPerMeter: Longint;
  89. {46+04 : Number of colors used}
  90. ClrUsed: longint;
  91. {50+04 : Number of imprtant colors used : usefull for displaying on VGA256}
  92. ClrImportant: longint;
  93. end;
  94. PBitMapInfoHeader = ^TBitMapInfoHeader;
  95. procedure TGLBMPImage.LoadFromFile(const filename: string);
  96. var
  97. fs: TStream;
  98. begin
  99. if FileStreamExists(fileName) then
  100. begin
  101. fs := TFileStream.Create(fileName, fmOpenRead);
  102. try
  103. LoadFromStream(fs);
  104. finally
  105. fs.Free;
  106. ResourceName := filename;
  107. end;
  108. end
  109. else
  110. raise EInvalidRasterFile.CreateFmt('File %s not found', [filename]);
  111. end;
  112. procedure TGLBMPImage.SaveToFile(const filename: string);
  113. var
  114. fs: TStream;
  115. begin
  116. fs := TFileStream.Create(fileName, fmOpenWrite or fmCreate);
  117. try
  118. SaveToStream(fs);
  119. finally
  120. fs.Free;
  121. end;
  122. ResourceName := filename;
  123. end;
  124. function TGLBMPImage.CountBits(Value: byte): shortint;
  125. var
  126. i, bits: shortint;
  127. begin
  128. bits := 0;
  129. for i := 0 to 7 do
  130. begin
  131. if (value mod 2) <> 0 then
  132. inc(bits);
  133. value := value shr 1;
  134. end;
  135. Result := bits;
  136. end;
  137. function TGLBMPImage.ShiftCount(Mask: longword): shortint;
  138. var
  139. tmp: shortint;
  140. begin
  141. tmp := 0;
  142. if Mask = 0 then
  143. begin
  144. Result := 0;
  145. exit;
  146. end;
  147. while (Mask mod 2) = 0 do // rightmost bit is 0
  148. begin
  149. inc(tmp);
  150. Mask := Mask shr 1;
  151. end;
  152. tmp := tmp - (8 - CountBits(Mask and $FF));
  153. Result := tmp;
  154. end;
  155. function TGLBMPImage.ExpandColor(Value: longword): TGLPixel32;
  156. var
  157. tmpr, tmpg, tmpb: longword;
  158. begin
  159. tmpr := value and RedMask;
  160. tmpg := value and GreenMask;
  161. tmpb := value and BlueMask;
  162. if RedShift < 0 then
  163. Result.R := byte(tmpr shl (-RedShift))
  164. else
  165. Result.R := byte(tmpr shr RedShift);
  166. if GreenShift < 0 then
  167. Result.G := byte(tmpg shl (-GreenShift))
  168. else
  169. Result.G := byte(tmpg shr GreenShift);
  170. if BlueShift < 0 then
  171. Result.B := byte(tmpb shl (-BlueShift))
  172. else
  173. Result.B := byte(tmpb shr BlueShift);
  174. end;
  175. function TGLBMPImage.Monochrome(N: Integer): Integer;
  176. begin
  177. Result := (FLineBuffer[N div 8] shr (7 - (N and 7))) and 1;
  178. end;
  179. function TGLBMPImage.Quadrochrome(N: Integer): Integer;
  180. begin
  181. Result := (FLineBuffer[N div 2] shr (((N + 1) and 1) * 4)) and $0F;
  182. end;
  183. function TGLBMPImage.Octochrome(N: Integer): Integer;
  184. begin
  185. Result := FLineBuffer[N];
  186. end;
  187. procedure TGLBMPImage.LoadFromStream(stream: TStream);
  188. type
  189. TBitShiftFunc = function(N: Integer): Integer of object;
  190. var
  191. LHeader: TBitMapFileHeader;
  192. LInfo: TBitMapInfoHeader;
  193. BadCompression: Boolean;
  194. Ptr: PByte;
  195. BitCount, LineSize: Integer;
  196. Row: Integer;
  197. nPalette: Integer;
  198. LPalette: array of TGLPixel32;
  199. BitShiftFunc: TBitShiftFunc;
  200. procedure ReadScanLine;
  201. var
  202. I: Integer;
  203. begin
  204. if nPalette > 0 then
  205. begin
  206. Stream.Read(FLineBuffer[0], FReadSize);
  207. for I := LInfo.Width - 1 downto 0 do
  208. PGLPixel32Array(Ptr)[I] := LPalette[BitShiftFunc(I)];
  209. end
  210. else if LInfo.Compression = BI_RLE8 then
  211. begin
  212. ExpandRLE8ScanLine(Row, Stream);
  213. Move(FLineBuffer[0], Ptr^, LineSize);
  214. end
  215. else if LInfo.Compression = BI_RLE4 then
  216. begin
  217. ExpandRLE4ScanLine(Row, Stream);
  218. Move(FLineBuffer[0], Ptr^, LineSize);
  219. end
  220. else if LInfo.BitCount = 16 then
  221. begin
  222. Stream.Read(FLineBuffer[0], FReadSize);
  223. for I := LInfo.Width - 1 downto 0 do
  224. PGLPixel32Array(Ptr)[I] := ExpandColor(PWordArray(FLineBuffer)[I]);
  225. end
  226. else
  227. Stream.Read(Ptr^, FReadSize);
  228. Inc(Ptr, LineSize);
  229. end;
  230. begin
  231. stream.Read(LHeader, SizeOf(TBitMapFileHeader));
  232. if LHeader.bfType <> BMmagic then
  233. raise EInvalidRasterFile.Create('Invalid BMP header');
  234. stream.Read(LInfo, SizeOf(TBitMapInfoHeader));
  235. stream.Position := stream.Position - SizeOf(TBitMapInfoHeader) + LInfo.Size;
  236. BadCompression := false;
  237. if ((LInfo.Compression = BI_RLE4) and (LInfo.BitCount <> 4)) then
  238. BadCompression := true;
  239. if ((LInfo.Compression = BI_RLE8) and (LInfo.BitCount <> 8)) then
  240. BadCompression := true;
  241. if ((LInfo.Compression = BI_BITFIELDS) and (not (LInfo.BitCount in [16, 32]))) then
  242. BadCompression := true;
  243. if not (LInfo.Compression in [BI_RGB..BI_BITFIELDS]) then
  244. BadCompression := true;
  245. if BadCompression then
  246. raise EInvalidRasterFile.Create('Bad BMP compression mode');
  247. FTopDown := (LInfo.Height < 0);
  248. LInfo.Height := abs(LInfo.Height);
  249. if (FTopDown and (not (LInfo.Compression in [BI_RGB, BI_BITFIELDS]))) then
  250. raise EInvalidRasterFile.Create('Top-down bitmaps cannot be compressed');
  251. nPalette := 0;
  252. if ((LInfo.Compression = BI_RGB)
  253. and (LInfo.BitCount = 16)) then // 5 bits per channel, fixed mask
  254. begin
  255. RedMask := $7C00;
  256. RedShift := 7;
  257. GreenMask := $03E0;
  258. GreenShift := 2;
  259. BlueMask := $001F;
  260. BlueShift := -3;
  261. end
  262. else if ((LInfo.Compression = BI_BITFIELDS)
  263. and (LInfo.BitCount in [16, 32])) then // arbitrary mask
  264. begin
  265. Stream.Read(RedMask, 4);
  266. Stream.Read(GreenMask, 4);
  267. Stream.Read(BlueMask, 4);
  268. RedShift := ShiftCount(RedMask);
  269. GreenShift := ShiftCount(GreenMask);
  270. BlueShift := ShiftCount(BlueMask);
  271. end
  272. else if LInfo.BitCount in [1, 4, 8] then
  273. begin
  274. nPalette := 1 shl LInfo.BitCount;
  275. SetLength(LPalette, nPalette);
  276. if LInfo.ClrUsed > 0 then
  277. Stream.Read(LPalette[0], LInfo.ClrUsed * SizeOf(TGLPixel32))
  278. else // Seems to me that this is dangerous.
  279. Stream.Read(LPalette[0], nPalette * SizeOf(TGLPixel32));
  280. end
  281. else if LInfo.ClrUsed > 0 then { Skip palette }
  282. Stream.Position := Stream.Position + LInfo.ClrUsed * 3;
  283. UnMipmap;
  284. FLOD[0].Width := LInfo.Width;
  285. FLOD[0].Height := LInfo.Height;
  286. FLOD[0].Depth := 0;
  287. BitCount := 0;
  288. FColorFormat := GL_BGRA;
  289. FInternalFormat := tfRGBA8;
  290. FElementSize := 4;
  291. case LInfo.BitCount of
  292. 1:
  293. begin
  294. BitCount := 1;
  295. BitShiftFunc := Monochrome;
  296. end;
  297. 4:
  298. begin
  299. BitCount := 4;
  300. BitShiftFunc := Quadrochrome;
  301. end;
  302. 8:
  303. begin
  304. BitCount := 8;
  305. BitShiftFunc := Octochrome;
  306. end;
  307. 16:
  308. BitCount := 16;
  309. 24:
  310. begin
  311. BitCount := 24;
  312. FColorFormat := GL_BGR;
  313. FInternalFormat := tfRGB8;
  314. FElementSize := 3;
  315. end;
  316. 32:
  317. BitCount := 32;
  318. end;
  319. FDataType := GL_UNSIGNED_BYTE;
  320. FCubeMap := False;
  321. FTextureArray := False;
  322. ReallocMem(FData, DataSize);
  323. FDeltaX := -1;
  324. FDeltaY := -1;
  325. Ptr := PByte(FData);
  326. LineSize := GetWidth * FElementSize;
  327. FReadSize := ((LInfo.Width * BitCount + 31) div 32) shl 2;
  328. GetMem(FLineBuffer, FReadSize);
  329. try
  330. if FTopDown then
  331. for Row := 0 to GetHeight - 1 do // A rare case of top-down bitmap!
  332. ReadScanLine
  333. else
  334. for Row := GetHeight - 1 downto 0 do
  335. ReadScanLine;
  336. finally
  337. FreeMem(FLineBuffer);
  338. end;
  339. end;
  340. procedure TGLBMPImage.ExpandRLE4ScanLine(Row: Integer; Stream: TStream);
  341. var
  342. i, j, tmpsize: integer;
  343. b0, b1: byte;
  344. nibline: PByteArray;
  345. even: boolean;
  346. begin
  347. tmpsize := FReadSize * 2; { ReadSize is in bytes, while nibline is made of nibbles, so it's 2*readsize long }
  348. getmem(nibline, tmpsize);
  349. try
  350. i := 0;
  351. while true do
  352. begin
  353. { let's see if we must skip pixels because of delta... }
  354. if FDeltaY <> -1 then
  355. begin
  356. if Row = FDeltaY then
  357. j := FDeltaX { If we are on the same line, skip till DeltaX }
  358. else
  359. j := tmpsize; { else skip up to the end of this line }
  360. while (i < j) do
  361. begin
  362. NibLine[i] := 0;
  363. inc(i);
  364. end;
  365. if Row = FDeltaY then { we don't need delta anymore }
  366. FDeltaY := -1
  367. else
  368. break; { skipping must continue on the next line, we are finished here }
  369. end;
  370. Stream.Read(b0, 1);
  371. Stream.Read(b1, 1);
  372. if b0 <> 0 then { number of repetitions }
  373. begin
  374. if b0 + i > tmpsize then
  375. raise EInvalidRasterFile.Create('Bad BMP RLE chunk at row ' + inttostr(row) + ', col ' + inttostr(i) + ', file offset $' + inttohex(Stream.Position, 16));
  376. even := true;
  377. j := i + b0;
  378. while (i < j) do
  379. begin
  380. if even then
  381. NibLine[i] := (b1 and $F0) shr 4
  382. else
  383. NibLine[i] := b1 and $0F;
  384. inc(i);
  385. even := not even;
  386. end;
  387. end
  388. else
  389. case b1 of
  390. 0: break; { end of line }
  391. 1: break; { end of file }
  392. 2:
  393. begin { Next pixel position. Skipped pixels should be left untouched, but we set them to zero }
  394. Stream.Read(b0, 1);
  395. Stream.Read(b1, 1);
  396. FDeltaX := i + b0;
  397. FDeltaY := Row + b1;
  398. end
  399. else
  400. begin { absolute mode }
  401. if b1 + i > tmpsize then
  402. raise EInvalidRasterFile.Create('Bad BMP RLE chunk at row ' + inttostr(row) + ', col ' + inttostr(i) + ', file offset $' + inttohex(Stream.Position, 16));
  403. j := i + b1;
  404. even := true;
  405. while (i < j) do
  406. begin
  407. if even then
  408. begin
  409. Stream.Read(b0, 1);
  410. NibLine[i] := (b0 and $F0) shr 4;
  411. end
  412. else
  413. NibLine[i] := b0 and $0F;
  414. inc(i);
  415. even := not even;
  416. end;
  417. { aligned on 2 bytes boundary: see rle8 for details }
  418. b1 := b1 + (b1 mod 2);
  419. if (b1 mod 4) <> 0 then
  420. Stream.Seek(1, soFromCurrent);
  421. end;
  422. end;
  423. end;
  424. { pack the nibline into the linebuf }
  425. for i := 0 to FReadSize - 1 do
  426. FLineBuffer[i] := (NibLine[i * 2] shl 4) or NibLine[i * 2 + 1];
  427. finally
  428. FreeMem(nibline)
  429. end;
  430. end;
  431. procedure TGLBMPImage.ExpandRLE8ScanLine(Row: Integer; Stream: TStream);
  432. var
  433. i, j: integer;
  434. b0, b1: byte;
  435. begin
  436. i := 0;
  437. while true do
  438. begin
  439. { let's see if we must skip pixels because of delta... }
  440. if FDeltaY <> -1 then
  441. begin
  442. if Row = FDeltaY then
  443. j := FDeltaX { If we are on the same line, skip till DeltaX }
  444. else
  445. j := FReadSize; { else skip up to the end of this line }
  446. while (i < j) do
  447. begin
  448. FLineBuffer[i] := 0;
  449. inc(i);
  450. end;
  451. if Row = FDeltaY then { we don't need delta anymore }
  452. FDeltaY := -1
  453. else
  454. break; { skipping must continue on the next line, we are finished here }
  455. end;
  456. Stream.Read(b0, 1);
  457. Stream.Read(b1, 1);
  458. if b0 <> 0 then { number of repetitions }
  459. begin
  460. if b0 + i > FReadSize then
  461. raise EInvalidRasterFile.Create('Bad BMP RLE chunk at row ' + inttostr(row) + ', col ' + inttostr(i) + ', file offset $' + inttohex(Stream.Position, 16));
  462. j := i + b0;
  463. while (i < j) do
  464. begin
  465. FLineBuffer[i] := b1;
  466. inc(i);
  467. end;
  468. end
  469. else
  470. case b1 of
  471. 0: break; { end of line }
  472. 1: break; { end of file }
  473. 2:
  474. begin { Next pixel position. Skipped pixels should be left untouched, but we set them to zero }
  475. Stream.Read(b0, 1);
  476. Stream.Read(b1, 1);
  477. FDeltaX := i + b0;
  478. FDeltaY := Row + b1;
  479. end
  480. else
  481. begin { absolute mode }
  482. if b1 + i > FReadSize then
  483. raise EInvalidRasterFile.Create('Bad BMP RLE chunk at row ' + inttostr(row) + ', col ' + inttostr(i) + ', file offset $' + inttohex(Stream.Position, 16));
  484. Stream.Read(FLineBuffer[i], b1);
  485. inc(i, b1);
  486. { aligned on 2 bytes boundary: every group starts on a 2 bytes boundary, but absolute group
  487. could end on odd address if there is a odd number of elements, so we pad it }
  488. if (b1 mod 2) <> 0 then
  489. Stream.Seek(1, soFromCurrent);
  490. end;
  491. end;
  492. end;
  493. end;
  494. procedure TGLBMPImage.SaveToStream(stream: TStream);
  495. begin
  496. {$Message Hint 'TGLBMPImage.SaveToStream not yet implemented' }
  497. end;
  498. procedure TGLBMPImage.AssignFromTexture(textureContext: TGLContext;
  499. const textureHandle: Cardinal; textureTarget: TGLTextureTarget;
  500. const CurrentFormat: boolean; const intFormat: TGLInternalFormat);
  501. begin
  502. {$Message Hint 'TGLBMPImage.AssignFromTexture not yet implemented' }
  503. end;
  504. class function TGLBMPImage.Capabilities: TGLDataFileCapabilities;
  505. begin
  506. Result := [dfcRead {, dfcWrite}];
  507. end;
  508. //=============================================================
  509. initialization
  510. //=============================================================
  511. { Register this Fileformat-Handler with GLScene }
  512. RegisterRasterFormat('bmp', 'Bitmap Image File', TGLBMPImage);
  513. end.