GLS.FileBMP.pas 15 KB

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