GXS.FileBMP.pas 15 KB

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