fpreadbmp.pp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519
  1. {*****************************************************************************}
  2. {
  3. This file is part of the Free Pascal's "Free Components Library".
  4. Copyright (c) 2003 by Mazen NEIFER of the Free Pascal development team
  5. BMP reader implementation.
  6. See the file COPYING.FPC, included in this distribution,
  7. for details about the copyright.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  11. }
  12. {*****************************************************************************}
  13. { 08/2005 by Giulio Bernardi:
  14. - Added support for 16 and 15 bpp bitmaps.
  15. - If we have bpp <= 8 make an indexed image instead of converting it to RGB
  16. - Support for RLE4 and RLE8 decoding
  17. - Support for top-down bitmaps
  18. }
  19. {$mode objfpc}
  20. {$h+}
  21. unit FPReadBMP;
  22. interface
  23. uses FPImage, classes, sysutils, BMPcomn;
  24. type
  25. TFPReaderBMP = class (TFPCustomImageReader)
  26. Private
  27. DeltaX, DeltaY : integer; // Used for the never-used delta option in RLE
  28. TopDown : boolean; // If set, bitmap is stored top down instead of bottom up
  29. continue : boolean; // needed for onprogress event
  30. percent : byte;
  31. percentinterval : longword;
  32. percentacc : longword;
  33. Rect : TRect;
  34. Procedure FreeBufs; // Free (and nil) buffers.
  35. protected
  36. ReadSize : Integer; // Size (in bytes) of 1 scanline.
  37. BFI : TBitMapInfoHeader; // The header as read from the stream.
  38. FPalette : PFPcolor; // Buffer with Palette entries. (useless now)
  39. LineBuf : PByte; // Buffer for 1 scanline. Can be Byte, Word, TColorRGB or TColorRGBA
  40. RedMask, GreenMask, BlueMask : longword; //Used if Compression=bi_bitfields
  41. RedShift, GreenShift, BlueShift : shortint;
  42. // SetupRead will allocate the needed buffers, and read the colormap if needed.
  43. procedure SetupRead(nPalette, nRowBits: Integer; Stream : TStream); virtual;
  44. function CountBits(Value : byte) : shortint;
  45. function ShiftCount(Mask : longword) : shortint;
  46. function ExpandColor(value : longword) : TFPColor;
  47. procedure ExpandRLE8ScanLine(Row : Integer; Stream : TStream);
  48. procedure ExpandRLE4ScanLine(Row : Integer; Stream : TStream);
  49. procedure ReadScanLine(Row : Integer; Stream : TStream); virtual;
  50. procedure WriteScanLine(Row : Integer; Img : TFPCustomImage); virtual;
  51. // required by TFPCustomImageReader
  52. procedure InternalRead (Stream:TStream; Img:TFPCustomImage); override;
  53. function InternalCheck (Stream:TStream) : boolean; override;
  54. public
  55. constructor Create; override;
  56. destructor Destroy; override;
  57. end;
  58. implementation
  59. function RGBAToFPColor(Const RGBA: TColorRGBA) : TFPcolor;
  60. begin
  61. with Result, RGBA do
  62. begin
  63. Red :=(R shl 8) or R;
  64. Green :=(G shl 8) or G;
  65. Blue :=(B shl 8) or B;
  66. Alpha :=255-A;
  67. Alpha :=(Alpha shl 8) or Alpha
  68. end;
  69. end;
  70. Function RGBToFPColor(Const RGB : TColorRGB) : TFPColor;
  71. begin
  72. with Result,RGB do
  73. begin {Use only the high byte to convert the color}
  74. Red := (R shl 8) + R;
  75. Green := (G shl 8) + G;
  76. Blue := (B shl 8) + B;
  77. Alpha := AlphaOpaque;
  78. end;
  79. end;
  80. Constructor TFPReaderBMP.create;
  81. begin
  82. inherited create;
  83. end;
  84. Destructor TFPReaderBMP.Destroy;
  85. begin
  86. FreeBufs;
  87. inherited destroy;
  88. end;
  89. Procedure TFPReaderBMP.FreeBufs;
  90. begin
  91. If (LineBuf<>Nil) then
  92. begin
  93. FreeMem(LineBuf);
  94. LineBuf:=Nil;
  95. end;
  96. If (FPalette<>Nil) then
  97. begin
  98. FreeMem(FPalette);
  99. FPalette:=Nil;
  100. end;
  101. end;
  102. { Counts how many bits are set }
  103. function TFPReaderBMP.CountBits(Value : byte) : shortint;
  104. var i,bits : shortint;
  105. begin
  106. bits:=0;
  107. for i:=0 to 7 do
  108. begin
  109. if (value mod 2)<>0 then inc(bits);
  110. value:=value shr 1;
  111. end;
  112. Result:=bits;
  113. end;
  114. { If compression is bi_bitfields, there could be arbitrary masks for colors.
  115. Although this is not compatible with windows9x it's better to know how to read these bitmaps
  116. We must determine how to switch the value once masked
  117. Example: 0000 0111 1110 0000, if we shr 5 we have 00XX XXXX for the color, but these bits must be the
  118. highest in the color, so we must shr (5-(8-6))=3, and we have XXXX XX00.
  119. A negative value means "shift left" }
  120. function TFPReaderBMP.ShiftCount(Mask : longword) : shortint;
  121. var tmp : shortint;
  122. begin
  123. tmp:=0;
  124. if Mask=0 then
  125. begin
  126. Result:=0;
  127. exit;
  128. end;
  129. while (Mask mod 2)=0 do { rightmost bit is 0 }
  130. begin
  131. inc(tmp);
  132. Mask:= Mask shr 1;
  133. end;
  134. tmp:=tmp-(8-CountBits(Mask and $FF));
  135. Result:=tmp;
  136. end;
  137. function TFPReaderBMP.ExpandColor(value : longword) : TFPColor;
  138. var tmpr, tmpg, tmpb : longword;
  139. col : TColorRGB;
  140. begin
  141. {$IFDEF ENDIAN_BIG}
  142. value:=swap(value);
  143. {$ENDIF}
  144. tmpr:=value and RedMask;
  145. tmpg:=value and GreenMask;
  146. tmpb:=value and BlueMask;
  147. if RedShift < 0 then col.R:=byte(tmpr shl (-RedShift))
  148. else col.R:=byte(tmpr shr RedShift);
  149. if GreenShift < 0 then col.G:=byte(tmpg shl (-GreenShift))
  150. else col.G:=byte(tmpg shr GreenShift);
  151. if BlueShift < 0 then col.B:=byte(tmpb shl (-BlueShift))
  152. else col.B:=byte(tmpb shr BlueShift);
  153. Result:=RGBToFPColor(col);
  154. end;
  155. procedure TFPReaderBMP.SetupRead(nPalette, nRowBits: Integer; Stream : TStream);
  156. var
  157. ColInfo: ARRAY OF TColorRGBA;
  158. i: Integer;
  159. begin
  160. if ((BFI.Compression=BI_RGB) and (BFI.BitCount=16)) then { 5 bits per channel, fixed mask }
  161. begin
  162. RedMask:=$7C00; RedShift:=7;
  163. GreenMask:=$03E0; GreenShift:=2;
  164. BlueMask:=$001F; BlueShift:=-3;
  165. end
  166. else if ((BFI.Compression=BI_BITFIELDS) and (BFI.BitCount in [16,32])) then { arbitrary mask }
  167. begin
  168. Stream.Read(RedMask,4);
  169. Stream.Read(GreenMask,4);
  170. Stream.Read(BlueMask,4);
  171. {$IFDEF ENDIAN_BIG}
  172. RedMask:=swap(RedMask);
  173. GreenMask:=swap(GreenMask);
  174. BlueMask:=swap(BlueMask);
  175. {$ENDIF}
  176. RedShift:=ShiftCount(RedMask);
  177. GreenShift:=ShiftCount(GreenMask);
  178. BlueShift:=ShiftCount(BlueMask);
  179. end
  180. else if nPalette>0 then
  181. begin
  182. GetMem(FPalette, nPalette*SizeOf(TFPColor));
  183. SetLength(ColInfo, nPalette);
  184. if BFI.ClrUsed>0 then
  185. Stream.Read(ColInfo[0],BFI.ClrUsed*SizeOf(TColorRGBA))
  186. else // Seems to me that this is dangerous.
  187. Stream.Read(ColInfo[0],nPalette*SizeOf(TColorRGBA));
  188. for i := 0 to High(ColInfo) do
  189. FPalette[i] := RGBAToFPColor(ColInfo[i]);
  190. end
  191. else if BFI.ClrUsed>0 then { Skip palette }
  192. Stream.Position := Stream.Position + BFI.ClrUsed*SizeOf(TColorRGBA);
  193. ReadSize:=((nRowBits + 31) div 32) shl 2;
  194. GetMem(LineBuf,ReadSize);
  195. end;
  196. procedure TFPReaderBMP.InternalRead(Stream:TStream; Img:TFPCustomImage);
  197. Var
  198. Row, i, pallen : Integer;
  199. BadCompression : boolean;
  200. begin
  201. Rect.Left:=0; Rect.Top:=0; Rect.Right:=0; Rect.Bottom:=0;
  202. continue:=true;
  203. Progress(psStarting,0,false,Rect,'',continue);
  204. if not continue then exit;
  205. Stream.Read(BFI,SizeOf(BFI));
  206. {$IFDEF ENDIAN_BIG}
  207. SwapBMPInfoHeader(BFI);
  208. {$ENDIF}
  209. { This will move past any junk after the BFI header }
  210. Stream.Position:=Stream.Position-SizeOf(BFI)+BFI.Size;
  211. with BFI do
  212. begin
  213. BadCompression:=false;
  214. if ((Compression=BI_RLE4) and (BitCount<>4)) then BadCompression:=true;
  215. if ((Compression=BI_RLE8) and (BitCount<>8)) then BadCompression:=true;
  216. if ((Compression=BI_BITFIELDS) and (not (BitCount in [16,32]))) then BadCompression:=true;
  217. if not (Compression in [BI_RGB..BI_BITFIELDS]) then BadCompression:=true;
  218. if BadCompression then
  219. raise FPImageException.Create('Bad BMP compression mode');
  220. TopDown:=(Height<0);
  221. Height:=abs(Height);
  222. if (TopDown and (not (Compression in [BI_RGB,BI_BITFIELDS]))) then
  223. raise FPImageException.Create('Top-down bitmaps cannot be compressed');
  224. Img.SetSize(0,0);
  225. if BitCount<=8 then
  226. begin
  227. Img.UsePalette:=true;
  228. Img.Palette.Clear;
  229. end
  230. else Img.UsePalette:=false;
  231. Case BFI.BitCount of
  232. 1 : { Monochrome }
  233. SetupRead(2,Width,Stream);
  234. 4 :
  235. SetupRead(16,Width*4,Stream);
  236. 8 :
  237. SetupRead(256,Width*8,Stream);
  238. 16 :
  239. SetupRead(0,Width*8*2,Stream);
  240. 24:
  241. SetupRead(0,Width*8*3,Stream);
  242. 32:
  243. SetupRead(0,Width*8*4,Stream);
  244. end;
  245. end;
  246. Try
  247. { Note: it would be better to Fill the image palette in setupread instead of creating FPalette.
  248. FPalette is indeed useless but we cannot remove it since it's not private :\ }
  249. pallen:=0;
  250. if BFI.BitCount<=8 then
  251. if BFI.ClrUsed>0 then pallen:=BFI.ClrUsed
  252. else pallen:=(1 shl BFI.BitCount);
  253. if pallen>0 then
  254. begin
  255. Img.Palette.Count:=pallen;
  256. for i:=0 to pallen-1 do
  257. Img.Palette.Color[i]:=FPalette[i];
  258. end;
  259. Img.SetSize(BFI.Width,BFI.Height);
  260. percent:=0;
  261. percentinterval:=(Img.Height*4) div 100;
  262. if percentinterval=0 then percentinterval:=$FFFFFFFF;
  263. percentacc:=0;
  264. DeltaX:=-1; DeltaY:=-1;
  265. if TopDown then
  266. for Row:=0 to Img.Height-1 do { A rare case of top-down bitmap! }
  267. begin
  268. ReadScanLine(Row,Stream); // Scanline in LineBuf with Size ReadSize.
  269. WriteScanLine(Row,Img);
  270. if not continue then exit;
  271. end
  272. else
  273. for Row:=Img.Height-1 downto 0 do
  274. begin
  275. ReadScanLine(Row,Stream); // Scanline in LineBuf with Size ReadSize.
  276. WriteScanLine(Row,Img);
  277. if not continue then exit;
  278. end;
  279. Progress(psEnding,100,false,Rect,'',continue);
  280. finally
  281. FreeBufs;
  282. end;
  283. end;
  284. procedure TFPReaderBMP.ExpandRLE8ScanLine(Row : Integer; Stream : TStream);
  285. var i,j : integer;
  286. b0, b1 : byte;
  287. begin
  288. i:=0;
  289. while true do
  290. begin
  291. { let's see if we must skip pixels because of delta... }
  292. if DeltaY<>-1 then
  293. begin
  294. if Row=DeltaY then j:=DeltaX { If we are on the same line, skip till DeltaX }
  295. else j:=ReadSize; { else skip up to the end of this line }
  296. while (i<j) do
  297. begin
  298. LineBuf[i]:=0;
  299. inc(i);
  300. end;
  301. if Row=DeltaY then { we don't need delta anymore }
  302. DeltaY:=-1
  303. else break; { skipping must continue on the next line, we are finished here }
  304. end;
  305. Stream.Read(b0,1); Stream.Read(b1,1);
  306. if b0<>0 then { number of repetitions }
  307. begin
  308. if b0+i>ReadSize then
  309. raise FPImageException.Create('Bad BMP RLE chunk at row '+inttostr(row)+', col '+inttostr(i)+', file offset $'+inttohex(Stream.Position,16) );
  310. j:=i+b0;
  311. while (i<j) do
  312. begin
  313. LineBuf[i]:=b1;
  314. inc(i);
  315. end;
  316. end
  317. else
  318. case b1 of
  319. 0: break; { end of line }
  320. 1: break; { end of file }
  321. 2: begin { Next pixel position. Skipped pixels should be left untouched, but we set them to zero }
  322. Stream.Read(b0,1); Stream.Read(b1,1);
  323. DeltaX:=i+b0; DeltaY:=Row+b1;
  324. end
  325. else begin { absolute mode }
  326. if b1+i>ReadSize then
  327. raise FPImageException.Create('Bad BMP RLE chunk at row '+inttostr(row)+', col '+inttostr(i)+', file offset $'+inttohex(Stream.Position,16) );
  328. Stream.Read(LineBuf[i],b1);
  329. inc(i,b1);
  330. { aligned on 2 bytes boundary: every group starts on a 2 bytes boundary, but absolute group
  331. could end on odd address if there is a odd number of elements, so we pad it }
  332. if (b1 mod 2)<>0 then Stream.Seek(1,soFromCurrent);
  333. end;
  334. end;
  335. end;
  336. end;
  337. procedure TFPReaderBMP.ExpandRLE4ScanLine(Row : Integer; Stream : TStream);
  338. var i,j,tmpsize : integer;
  339. b0, b1 : byte;
  340. nibline : pbyte; { temporary array of nibbles }
  341. even : boolean;
  342. begin
  343. tmpsize:=ReadSize*2; { ReadSize is in bytes, while nibline is made of nibbles, so it's 2*readsize long }
  344. getmem(nibline,tmpsize);
  345. if nibline=nil then
  346. raise FPImageException.Create('Out of memory');
  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 DeltaY<>-1 then
  353. begin
  354. if Row=DeltaY then j:=DeltaX { If we are on the same line, skip till DeltaX }
  355. else j:=tmpsize; { else skip up to the end of this line }
  356. while (i<j) do
  357. begin
  358. NibLine[i]:=0;
  359. inc(i);
  360. end;
  361. if Row=DeltaY then { we don't need delta anymore }
  362. DeltaY:=-1
  363. else break; { skipping must continue on the next line, we are finished here }
  364. end;
  365. Stream.Read(b0,1); Stream.Read(b1,1);
  366. if b0<>0 then { number of repetitions }
  367. begin
  368. if b0+i>tmpsize then
  369. raise FPImageException.Create('Bad BMP RLE chunk at row '+inttostr(row)+', col '+inttostr(i)+', file offset $'+inttohex(Stream.Position,16) );
  370. even:=true;
  371. j:=i+b0;
  372. while (i<j) do
  373. begin
  374. if even then NibLine[i]:=(b1 and $F0) shr 4
  375. else NibLine[i]:=b1 and $0F;
  376. inc(i);
  377. even:=not even;
  378. end;
  379. end
  380. else
  381. case b1 of
  382. 0: break; { end of line }
  383. 1: break; { end of file }
  384. 2: begin { Next pixel position. Skipped pixels should be left untouched, but we set them to zero }
  385. Stream.Read(b0,1); Stream.Read(b1,1);
  386. DeltaX:=i+b0; DeltaY:=Row+b1;
  387. end
  388. else begin { absolute mode }
  389. if b1+i>tmpsize then
  390. raise FPImageException.Create('Bad BMP RLE chunk at row '+inttostr(row)+', col '+inttostr(i)+', file offset $'+inttohex(Stream.Position,16) );
  391. j:=i+b1;
  392. even:=true;
  393. while (i<j) do
  394. begin
  395. if even then
  396. begin
  397. Stream.Read(b0,1);
  398. NibLine[i]:=(b0 and $F0) shr 4;
  399. end
  400. else NibLine[i]:=b0 and $0F;
  401. inc(i);
  402. even:=not even;
  403. end;
  404. { aligned on 2 bytes boundary: see rle8 for details }
  405. b1:=b1+(b1 mod 2);
  406. if (b1 mod 4)<>0 then Stream.Seek(1,soFromCurrent);
  407. end;
  408. end;
  409. end;
  410. { pack the nibline into the linebuf }
  411. for i:=0 to ReadSize-1 do
  412. LineBuf[i]:=(NibLine[i*2] shl 4) or NibLine[i*2+1];
  413. finally
  414. FreeMem(nibline)
  415. end;
  416. end;
  417. procedure TFPReaderBMP.ReadScanLine(Row : Integer; Stream : TStream);
  418. begin
  419. if BFI.Compression=BI_RLE8 then ExpandRLE8ScanLine(Row,Stream)
  420. else if BFI.Compression=BI_RLE4 then ExpandRLE4ScanLine(Row,Stream)
  421. else Stream.Read(LineBuf[0],ReadSize);
  422. end;
  423. procedure TFPReaderBMP.WriteScanLine(Row : Integer; Img : TFPCustomImage);
  424. Var
  425. Column : Integer;
  426. begin
  427. Case BFI.BitCount of
  428. 1 :
  429. for Column:=0 to Img.Width-1 do
  430. if ((LineBuf[Column div 8] shr (7-(Column and 7)) ) and 1) <> 0 then
  431. img.Pixels[Column,Row]:=1
  432. else
  433. img.Pixels[Column,Row]:=0;
  434. 4 :
  435. for Column:=0 to img.Width-1 do
  436. img.Pixels[Column,Row]:=(LineBuf[Column div 2] shr (((Column+1) and 1)*4)) and $0f;
  437. 8 :
  438. for Column:=0 to img.Width-1 do
  439. img.Pixels[Column,Row]:=LineBuf[Column];
  440. 16 :
  441. for Column:=0 to img.Width-1 do
  442. img.colors[Column,Row]:=ExpandColor(PWord(LineBuf)[Column]);
  443. 24 :
  444. for Column:=0 to img.Width-1 do
  445. img.colors[Column,Row]:=RGBToFPColor(PColorRGB(LineBuf)[Column]);
  446. 32 :
  447. for Column:=0 to img.Width-1 do
  448. if BFI.Compression=BI_BITFIELDS then
  449. img.colors[Column,Row]:=ExpandColor(PLongWord(LineBuf)[Column])
  450. else
  451. img.colors[Column,Row]:=RGBAToFPColor(PColorRGBA(LineBuf)[Column]);
  452. end;
  453. inc(percentacc,4);
  454. if percentacc>=percentinterval then
  455. begin
  456. percent:=percent+(percentacc div percentinterval);
  457. percentacc:=percentacc mod percentinterval;
  458. Progress(psRunning,percent,false,Rect,'',continue);
  459. end;
  460. end;
  461. function TFPReaderBMP.InternalCheck (Stream:TStream) : boolean;
  462. var
  463. BFH:TBitMapFileHeader;
  464. begin
  465. stream.Read(BFH,SizeOf(BFH));
  466. {$IFDEF ENDIAN_BIG}
  467. SwapBMPFileHeader(BFH);
  468. {$ENDIF}
  469. With BFH do
  470. Result:=(bfType=BMmagic); // Just check magic number
  471. end;
  472. initialization
  473. ImageHandlers.RegisterImageReader ('BMP Format', 'bmp', TFPReaderBMP);
  474. end.