ImagingBitmap.pas 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840
  1. {
  2. Vampyre Imaging Library
  3. by Marek Mauder
  4. https://github.com/galfar/imaginglib
  5. https://imaginglib.sourceforge.io
  6. - - - - -
  7. This Source Code Form is subject to the terms of the Mozilla Public
  8. License, v. 2.0. If a copy of the MPL was not distributed with this
  9. file, You can obtain one at https://mozilla.org/MPL/2.0.
  10. }
  11. {
  12. This unit contains image format loader/saver for Windows Bitmap images.
  13. }
  14. unit ImagingBitmap;
  15. {$I ImagingOptions.inc}
  16. interface
  17. uses
  18. ImagingTypes, Imaging, ImagingUtility, ImagingFormats, ImagingIO;
  19. type
  20. { Class for loading and saving Windows Bitmap images.
  21. It can load/save 8bit indexed, 16, 24, 32 bit RGB or ARGB
  22. images with or without RLE compression. It can also load 1/4 bit
  23. indexed images and OS2 bitmaps.}
  24. TBitmapFileFormat = class(TImageFileFormat)
  25. protected
  26. FUseRLE: LongBool;
  27. procedure Define; override;
  28. function LoadData(Handle: TImagingHandle; var Images: TDynImageDataArray;
  29. OnlyFirstLevel: Boolean): Boolean; override;
  30. function SaveData(Handle: TImagingHandle; const Images: TDynImageDataArray;
  31. Index: LongInt): Boolean; override;
  32. procedure ConvertToSupported(var Image: TImageData;
  33. const Info: TImageFormatInfo); override;
  34. public
  35. function TestFormat(Handle: TImagingHandle): Boolean; override;
  36. published
  37. { Controls that RLE compression is used during saving. Accessible trough
  38. ImagingBitmapRLE option.}
  39. property UseRLE: LongBool read FUseRLE write FUseRLE;
  40. end;
  41. implementation
  42. const
  43. SBitmapFormatName = 'Windows Bitmap Image';
  44. SBitmapMasks = '*.bmp,*.dib';
  45. BitmapSupportedFormats: TImageFormats = [ifIndex8, ifA1R5G5B5, ifA4R4G4B4,
  46. ifR5G6B5, ifR8G8B8, ifA8R8G8B8, ifX1R5G5B5, ifX4R4G4B4, ifX8R8G8B8];
  47. BitmapDefaultRLE = True;
  48. const
  49. { Bitmap file identifier 'BM'.}
  50. BMMagic: Word = 19778;
  51. { Constants for the TBitmapInfoHeader.Compression field.}
  52. BI_RGB = 0;
  53. BI_RLE8 = 1;
  54. BI_RLE4 = 2;
  55. BI_BITFIELDS = 3;
  56. V3InfoHeaderSize = 40;
  57. V4InfoHeaderSize = 108;
  58. type
  59. { File Header for Windows/OS2 bitmap file.}
  60. TBitmapFileHeader = packed record
  61. ID: Word; // Is always 19778 : 'BM'
  62. Size: UInt32; // File size
  63. Reserved1: Word;
  64. Reserved2: Word;
  65. Offset: UInt32; // Offset from start pos to beginning of image bits
  66. end;
  67. { Info Header for Windows bitmap file version 4.}
  68. TBitmapInfoHeader = packed record
  69. Size: UInt32;
  70. Width: Int32;
  71. Height: Int32;
  72. Planes: Word;
  73. BitCount: Word;
  74. Compression: UInt32;
  75. SizeImage: UInt32;
  76. XPelsPerMeter: Int32;
  77. YPelsPerMeter: Int32;
  78. ClrUsed: UInt32;
  79. ClrImportant: UInt32;
  80. RedMask: UInt32;
  81. GreenMask: UInt32;
  82. BlueMask: UInt32;
  83. AlphaMask: UInt32;
  84. CSType: UInt32;
  85. EndPoints: array[0..8] of UInt32;
  86. GammaRed: UInt32;
  87. GammaGreen: UInt32;
  88. GammaBlue: UInt32;
  89. end;
  90. { Info Header for OS2 bitmaps.}
  91. TBitmapCoreHeader = packed record
  92. Size: UInt32;
  93. Width: Word;
  94. Height: Word;
  95. Planes: Word;
  96. BitCount: Word;
  97. end;
  98. { Used in RLE encoding and decoding.}
  99. TRLEOpcode = packed record
  100. Count: Byte;
  101. Command: Byte;
  102. end;
  103. PRLEOpcode = ^TRLEOpcode;
  104. { TBitmapFileFormat class implementation }
  105. procedure TBitmapFileFormat.Define;
  106. begin
  107. inherited;
  108. FName := SBitmapFormatName;
  109. FFeatures := [ffLoad, ffSave];
  110. FSupportedFormats := BitmapSupportedFormats;
  111. FUseRLE := BitmapDefaultRLE;
  112. AddMasks(SBitmapMasks);
  113. RegisterOption(ImagingBitmapRLE, @FUseRLE);
  114. end;
  115. function TBitmapFileFormat.LoadData(Handle: TImagingHandle;
  116. var Images: TDynImageDataArray; OnlyFirstLevel: Boolean): Boolean;
  117. var
  118. BF: TBitmapFileHeader;
  119. BI: TBitmapInfoHeader;
  120. BC: TBitmapCoreHeader;
  121. IsOS2: Boolean;
  122. PalRGB: PPalette24;
  123. I, FPalSize, AlignedSize, StartPos, HeaderSize, AlignedWidthBytes, WidthBytes: LongInt;
  124. Info: TImageFormatInfo;
  125. Data: Pointer;
  126. procedure LoadRGB;
  127. var
  128. I: LongInt;
  129. LineBuffer: PByte;
  130. begin
  131. with Images[0], GetIO do
  132. begin
  133. // If BI.Height is < 0 then image data are stored non-flipped
  134. // but default in windows is flipped so if Height is positive we must
  135. // flip it
  136. if BI.BitCount < 8 then
  137. begin
  138. // For 1 and 4 bit images load aligned data, they will be converted to
  139. // 8 bit and unaligned later
  140. GetMem(Data, AlignedSize);
  141. if BI.Height < 0 then
  142. Read(Handle, Data, AlignedSize)
  143. else
  144. for I := Height - 1 downto 0 do
  145. Read(Handle, @PByteArray(Data)[I * AlignedWidthBytes], AlignedWidthBytes);
  146. end
  147. else
  148. begin
  149. // Images with pixels of size >= 1 Byte are read line by line and
  150. // copied to image bits without padding bytes
  151. GetMem(LineBuffer, AlignedWidthBytes);
  152. try
  153. if BI.Height < 0 then
  154. for I := 0 to Height - 1 do
  155. begin
  156. Read(Handle, LineBuffer, AlignedWidthBytes);
  157. Move(LineBuffer^, PByteArray(Bits)[I * WidthBytes], WidthBytes);
  158. end
  159. else
  160. for I := Height - 1 downto 0 do
  161. begin
  162. Read(Handle, LineBuffer, AlignedWidthBytes);
  163. Move(LineBuffer^, PByteArray(Bits)[I * WidthBytes], WidthBytes);
  164. end;
  165. finally
  166. FreeMemNil(LineBuffer);
  167. end;
  168. end;
  169. end;
  170. end;
  171. procedure LoadRLE4;
  172. var
  173. RLESrc: PByteArray;
  174. Row, Col, WriteRow, I: Integer;
  175. SrcPos: UInt32;
  176. DeltaX, DeltaY, Low, High: Byte;
  177. Pixels: PByteArray;
  178. OpCode: TRLEOpcode;
  179. NegHeightBitmap: Boolean;
  180. begin
  181. GetMem(RLESrc, BI.SizeImage);
  182. GetIO.Read(Handle, RLESrc, BI.SizeImage);
  183. with Images[0] do
  184. try
  185. Low := 0;
  186. Pixels := Bits;
  187. SrcPos := 0;
  188. NegHeightBitmap := BI.Height < 0;
  189. Row := 0; // Current row in dest image
  190. Col := 0; // Current column in dest image
  191. // Row in dest image where actual writing will be done
  192. WriteRow := Iff(NegHeightBitmap, Row, Height - 1 - Row);
  193. while (Row < Height) and (SrcPos < BI.SizeImage) do
  194. begin
  195. // Read RLE op-code
  196. OpCode := PRLEOpcode(@RLESrc[SrcPos])^;
  197. Inc(SrcPos, SizeOf(OpCode));
  198. if OpCode.Count = 0 then
  199. begin
  200. // A byte Count of zero means that this is a special
  201. // instruction.
  202. case OpCode.Command of
  203. 0:
  204. begin
  205. // Move to next row
  206. Inc(Row);
  207. WriteRow := Iff(NegHeightBitmap, Row, Height - 1 - Row);
  208. Col := 0;
  209. end ;
  210. 1: Break; // Image is finished
  211. 2:
  212. begin
  213. // Move to a new relative position
  214. DeltaX := RLESrc[SrcPos];
  215. DeltaY := RLESrc[SrcPos + 1];
  216. Inc(SrcPos, 2);
  217. Inc(Col, DeltaX);
  218. Inc(Row, DeltaY);
  219. end
  220. else
  221. // Do not read data after EOF
  222. if SrcPos + OpCode.Command > BI.SizeImage then
  223. OpCode.Command := BI.SizeImage - SrcPos;
  224. // Take padding bytes and nibbles into account
  225. if Col + OpCode.Command > Width then
  226. OpCode.Command := Width - Col;
  227. // Store absolute data. Command code is the
  228. // number of absolute bytes to store
  229. for I := 0 to OpCode.Command - 1 do
  230. begin
  231. if (I and 1) = 0 then
  232. begin
  233. High := RLESrc[SrcPos] shr 4;
  234. Low := RLESrc[SrcPos] and $F;
  235. Pixels[WriteRow * Width + Col] := High;
  236. Inc(SrcPos);
  237. end
  238. else
  239. Pixels[WriteRow * Width + Col] := Low;
  240. Inc(Col);
  241. end;
  242. // Odd number of bytes is followed by a pad byte
  243. if (OpCode.Command mod 4) in [1, 2] then
  244. Inc(SrcPos);
  245. end;
  246. end
  247. else
  248. begin
  249. // Take padding bytes and nibbles into account
  250. if Col + OpCode.Count > Width then
  251. OpCode.Count := Width - Col;
  252. // Store a run of the same color value
  253. for I := 0 to OpCode.Count - 1 do
  254. begin
  255. if (I and 1) = 0 then
  256. Pixels[WriteRow * Width + Col] := OpCode.Command shr 4
  257. else
  258. Pixels[WriteRow * Width + Col] := OpCode.Command and $F;
  259. Inc(Col);
  260. end;
  261. end;
  262. end;
  263. finally
  264. FreeMem(RLESrc);
  265. end;
  266. end;
  267. procedure LoadRLE8;
  268. var
  269. RLESrc: PByteArray;
  270. SrcCount, Row, Col, WriteRow: Integer;
  271. SrcPos: UInt32;
  272. DeltaX, DeltaY: Byte;
  273. Pixels: PByteArray;
  274. OpCode: TRLEOpcode;
  275. NegHeightBitmap: Boolean;
  276. begin
  277. GetMem(RLESrc, BI.SizeImage);
  278. GetIO.Read(Handle, RLESrc, BI.SizeImage);
  279. with Images[0] do
  280. try
  281. Pixels := Bits;
  282. SrcPos := 0;
  283. NegHeightBitmap := BI.Height < 0;
  284. Row := 0; // Current row in dest image
  285. Col := 0; // Current column in dest image
  286. // Row in dest image where actual writing will be done
  287. WriteRow := Iff(NegHeightBitmap, Row, Height - 1 - Row);
  288. while (Row < Height) and (SrcPos < BI.SizeImage) do
  289. begin
  290. // Read RLE op-code
  291. OpCode := PRLEOpcode(@RLESrc[SrcPos])^;
  292. Inc(SrcPos, SizeOf(OpCode));
  293. if OpCode.Count = 0 then
  294. begin
  295. // A byte Count of zero means that this is a special
  296. // instruction.
  297. case OpCode.Command of
  298. 0:
  299. begin
  300. // Move to next row
  301. Inc(Row);
  302. WriteRow := Iff(NegHeightBitmap, Row, Height - 1 - Row);
  303. Col := 0;
  304. end ;
  305. 1: Break; // Image is finished
  306. 2:
  307. begin
  308. // Move to a new relative position
  309. DeltaX := RLESrc[SrcPos];
  310. DeltaY := RLESrc[SrcPos + 1];
  311. Inc(SrcPos, 2);
  312. Inc(Col, DeltaX);
  313. Inc(Row, DeltaY);
  314. end
  315. else
  316. SrcCount := OpCode.Command;
  317. // Do not read data after EOF
  318. if SrcPos + OpCode.Command > BI.SizeImage then
  319. OpCode.Command := BI.SizeImage - SrcPos;
  320. // Take padding bytes into account
  321. if Col + OpCode.Command > Width then
  322. OpCode.Command := Width - Col;
  323. // Store absolute data. Command code is the
  324. // number of absolute bytes to store
  325. Move(RLESrc[SrcPos], Pixels[WriteRow * Width + Col], OpCode.Command);
  326. Inc(SrcPos, SrcCount);
  327. Inc(Col, OpCode.Command);
  328. // Odd number of bytes is followed by a pad byte
  329. if (SrcCount mod 2) = 1 then
  330. Inc(SrcPos);
  331. end;
  332. end
  333. else
  334. begin
  335. // Take padding bytes into account
  336. if Col + OpCode.Count > Width then
  337. OpCode.Count := Width - Col;
  338. // Store a run of the same color value. Count is number of bytes to store
  339. FillChar(Pixels [WriteRow * Width + Col], OpCode.Count, OpCode.Command);
  340. Inc(Col, OpCode.Count);
  341. end;
  342. end;
  343. finally
  344. FreeMem(RLESrc);
  345. end;
  346. end;
  347. begin
  348. Data := nil;
  349. SetLength(Images, 1);
  350. with GetIO, Images[0] do
  351. try
  352. FillChar(BI, SizeOf(BI), 0);
  353. StartPos := Tell(Handle);
  354. Read(Handle, @BF, SizeOf(BF));
  355. Read(Handle, @BI.Size, SizeOf(BI.Size));
  356. IsOS2 := BI.Size = SizeOf(TBitmapCoreHeader);
  357. // Bitmap Info reading
  358. if IsOS2 then
  359. begin
  360. // OS/2 type bitmap, reads info header without 4 already read bytes
  361. Read(Handle, @PByteArray(@BC)[SizeOf(BI.Size)],
  362. SizeOf(TBitmapCoreHeader) - SizeOf(BI.Size));
  363. with BI do
  364. begin
  365. ClrUsed := 0;
  366. Compression := BI_RGB;
  367. BitCount := BC.BitCount;
  368. Height := BC.Height;
  369. Width := BC.Width;
  370. end;
  371. end
  372. else
  373. begin
  374. // Windows type bitmap
  375. HeaderSize := Min(BI.Size - SizeOf(BI.Size), SizeOf(BI) - SizeOf(BI.Size)); // do not read more than size of BI!
  376. Read(Handle, @PByteArray(@BI)[SizeOf(BI.Size)], HeaderSize);
  377. // SizeImage can be 0 for BI_RGB images, but it is here because of:
  378. // I saved 8bit bitmap in Paint Shop Pro 8 as OS2 RLE compressed.
  379. // It wrote strange 64 Byte Info header with SizeImage set to 0
  380. // Some progs were able to open it, some were not.
  381. if BI.SizeImage = 0 then
  382. BI.SizeImage := BF.Size - BF.Offset;
  383. end;
  384. // Bit mask reading. Only read it if there is V3 header, V4 header has
  385. // masks loaded already (only masks for RGB in V3).
  386. if (BI.Compression = BI_BITFIELDS) and (BI.Size = V3InfoHeaderSize) then
  387. Read(Handle, @BI.RedMask, SizeOf(BI.RedMask) * 3);
  388. case BI.BitCount of
  389. 1, 4, 8: Format := ifIndex8;
  390. 16:
  391. if BI.RedMask = $0F00 then
  392. // Set XRGB4 or ARGB4 according to value of alpha mask
  393. Format := IffFormat(BI.AlphaMask = 0, ifX4R4G4B4, ifA4R4G4B4)
  394. else if BI.RedMask = $F800 then
  395. Format := ifR5G6B5
  396. else
  397. // R5G5B5 is default 16bit format (with Compression = BI_RGB or masks).
  398. // We set it to A1.. and later there is a check if there are any alpha values
  399. // and if not it is changed to X1R5G5B5
  400. Format := ifA1R5G5B5;
  401. 24: Format := ifR8G8B8;
  402. 32: Format := ifA8R8G8B8; // As with R5G5B5 there is alpha check later
  403. end;
  404. NewImage(BI.Width, Abs(BI.Height), Format, Images[0]);
  405. Info := GetFormatInfo(Format);
  406. WidthBytes := Width * Info.BytesPerPixel;
  407. AlignedWidthBytes := (((Width * BI.BitCount) + 31) shr 5) * 4;
  408. AlignedSize := Height * LongInt(AlignedWidthBytes);
  409. // Palette settings and reading
  410. if BI.BitCount <= 8 then
  411. begin
  412. // Seek to the beginning of palette
  413. Seek(Handle, StartPos + SizeOf(TBitmapFileHeader) + LongInt(BI.Size),
  414. smFromBeginning);
  415. if IsOS2 then
  416. begin
  417. // OS/2 type
  418. FPalSize := 1 shl BI.BitCount;
  419. GetMem(PalRGB, FPalSize * SizeOf(TColor24Rec));
  420. try
  421. Read(Handle, PalRGB, FPalSize * SizeOf(TColor24Rec));
  422. for I := 0 to FPalSize - 1 do
  423. with PalRGB[I] do
  424. begin
  425. Palette[I].R := R;
  426. Palette[I].G := G;
  427. Palette[I].B := B;
  428. end;
  429. finally
  430. FreeMemNil(PalRGB);
  431. end;
  432. end
  433. else
  434. begin
  435. // Windows type
  436. FPalSize := BI.ClrUsed;
  437. if FPalSize = 0 then
  438. FPalSize := 1 shl BI.BitCount;
  439. Read(Handle, Palette, FPalSize * SizeOf(TColor32Rec));
  440. end;
  441. for I := 0 to Info.PaletteEntries - 1 do
  442. Palette[I].A := $FF;
  443. end;
  444. // Seek to the beginning of image bits
  445. Seek(Handle, StartPos + LongInt(BF.Offset), smFromBeginning);
  446. case BI.Compression of
  447. BI_RGB: LoadRGB;
  448. BI_RLE4: LoadRLE4;
  449. BI_RLE8: LoadRLE8;
  450. BI_BITFIELDS: LoadRGB;
  451. end;
  452. if BI.AlphaMask = 0 then
  453. begin
  454. // Alpha mask is not stored in file (V3) or not defined.
  455. // Check alpha channels of loaded images if they might contain them.
  456. if Format = ifA1R5G5B5 then
  457. begin
  458. // Check if there is alpha channel present in A1R5GB5 images, if it is not
  459. // change format to X1R5G5B5
  460. if not Has16BitImageAlpha(Width * Height, Bits) then
  461. Format := ifX1R5G5B5;
  462. end
  463. else if Format = ifA8R8G8B8 then
  464. begin
  465. // Check if there is alpha channel present in A8R8G8B8 images, if it is not
  466. // change format to X8R8G8B8
  467. if not Has32BitImageAlpha(Width * Height, Bits) then
  468. Format := ifX8R8G8B8;
  469. end;
  470. end;
  471. if BI.BitCount < 8 then
  472. begin
  473. // 1 and 4 bpp images are supported only for loading which is now
  474. // so we now convert them to 8bpp (and unalign scanlines).
  475. case BI.BitCount of
  476. 1: Convert1To8(Data, Bits, Width, Height, AlignedWidthBytes, False);
  477. 4:
  478. begin
  479. // RLE4 bitmaps are translated to 8bit during RLE decoding
  480. if BI.Compression <> BI_RLE4 then
  481. Convert4To8(Data, Bits, Width, Height, AlignedWidthBytes, False);
  482. end;
  483. end;
  484. // Enlarge palette
  485. ReallocMem(Palette, Info.PaletteEntries * SizeOf(TColor32Rec));
  486. end;
  487. Result := True;
  488. finally
  489. FreeMemNil(Data);
  490. end;
  491. end;
  492. function TBitmapFileFormat.SaveData(Handle: TImagingHandle;
  493. const Images: TDynImageDataArray; Index: LongInt): Boolean;
  494. var
  495. StartPos, EndPos, I, Pad, PadSize, WidthBytes: LongInt;
  496. BF: TBitmapFileHeader;
  497. BI: TBitmapInfoHeader;
  498. Info: TImageFormatInfo;
  499. ImageToSave: TImageData;
  500. MustBeFreed: Boolean;
  501. procedure SaveRLE8;
  502. const
  503. BufferSize = 8 * 1024;
  504. var
  505. X, Y, I, SrcPos: LongInt;
  506. DiffCount, SameCount: Byte;
  507. Pixels: PByteArray;
  508. Buffer: array[0..BufferSize - 1] of Byte;
  509. BufferPos: LongInt;
  510. procedure WriteByte(ByteToWrite: Byte);
  511. begin
  512. if BufferPos = BufferSize then
  513. begin
  514. // Flush buffer if necessary
  515. GetIO.Write(Handle, @Buffer, BufferPos);
  516. BufferPos := 0;
  517. end;
  518. Buffer[BufferPos] := ByteToWrite;
  519. Inc(BufferPos);
  520. end;
  521. begin
  522. BufferPos := 0;
  523. with GetIO, ImageToSave do
  524. begin
  525. for Y := Height - 1 downto 0 do
  526. begin
  527. X := 0;
  528. SrcPos := 0;
  529. Pixels := @PByteArray(Bits)[Y * Width];
  530. while X < Width do
  531. begin
  532. SameCount := 1;
  533. DiffCount := 0;
  534. // Determine run length
  535. while X + SameCount < Width do
  536. begin
  537. // If we reach max run length or byte with different value
  538. // we end this run
  539. if (SameCount = 255) or (Pixels[SrcPos + SameCount] <> Pixels[SrcPos]) then
  540. Break;
  541. Inc(SameCount);
  542. end;
  543. if SameCount = 1 then
  544. begin
  545. // If there are not some bytes with the same value we
  546. // compute how many different bytes are there
  547. while X + DiffCount < Width do
  548. begin
  549. // Stop diff byte counting if there two bytes with the same value
  550. // or DiffCount is too big
  551. if (DiffCount = 255) or (Pixels[SrcPos + DiffCount + 1] =
  552. Pixels[SrcPos + DiffCount]) then
  553. Break;
  554. Inc(DiffCount);
  555. end;
  556. end;
  557. // Now store absolute data (direct copy image->file) or
  558. // store RLE code only (number of repeats + byte to be repeated)
  559. if DiffCount > 2 then
  560. begin
  561. // Save 'Absolute Data' (0 + number of bytes) but only
  562. // if number is >2 because (0+1) and (0+2) are other special commands
  563. WriteByte(0);
  564. WriteByte(DiffCount);
  565. // Write absolute data to buffer
  566. for I := 0 to DiffCount - 1 do
  567. WriteByte(Pixels[SrcPos + I]);
  568. Inc(X, DiffCount);
  569. Inc(SrcPos, DiffCount);
  570. // Odd number of bytes must be padded
  571. if (DiffCount mod 2) = 1 then
  572. WriteByte(0);
  573. end
  574. else
  575. begin
  576. // Save number of repeats and byte that should be repeated
  577. WriteByte(SameCount);
  578. WriteByte(Pixels[SrcPos]);
  579. Inc(X, SameCount);
  580. Inc(SrcPos, SameCount);
  581. end;
  582. end;
  583. // Save 'End Of Line' command
  584. WriteByte(0);
  585. WriteByte(0);
  586. end;
  587. // Save 'End Of Bitmap' command
  588. WriteByte(0);
  589. WriteByte(1);
  590. // Flush buffer
  591. GetIO.Write(Handle, @Buffer, BufferPos);
  592. end;
  593. end;
  594. begin
  595. Result := False;
  596. if MakeCompatible(Images[Index], ImageToSave, MustBeFreed) then
  597. with GetIO, ImageToSave do
  598. try
  599. Info := GetFormatInfo(Format);
  600. StartPos := Tell(Handle);
  601. FillChar(BF, SizeOf(BF), 0);
  602. FillChar(BI, SizeOf(BI), 0);
  603. // Other fields will be filled later - we don't know all values now
  604. BF.ID := BMMagic;
  605. Write(Handle, @BF, SizeOf(BF));
  606. if Info.HasAlphaChannel and (Info.BytesPerPixel = 2){V4 temp hack} then
  607. // Save images with alpha in V4 format
  608. BI.Size := V4InfoHeaderSize
  609. else
  610. // Save images without alpha in V3 format - for better compatibility
  611. BI.Size := V3InfoHeaderSize;
  612. BI.Width := Width;
  613. BI.Height := Height;
  614. BI.Planes := 1;
  615. BI.BitCount := Info.BytesPerPixel * 8;
  616. BI.XPelsPerMeter := 2835; // 72 dpi
  617. BI.YPelsPerMeter := 2835; // 72 dpi
  618. // Set compression
  619. if (Info.BytesPerPixel = 1) and FUseRLE then
  620. BI.Compression := BI_RLE8
  621. else if (Info.HasAlphaChannel or
  622. ((BI.BitCount = 16) and (Format <> ifX1R5G5B5))) and (Info.BytesPerPixel = 2){V4 temp hack} then
  623. BI.Compression := BI_BITFIELDS
  624. else
  625. BI.Compression := BI_RGB;
  626. // Write header (first time)
  627. Write(Handle, @BI, BI.Size);
  628. // Write mask info
  629. if BI.Compression = BI_BITFIELDS then
  630. begin
  631. if BI.BitCount = 16 then
  632. with Info.PixelFormat^ do
  633. begin
  634. BI.RedMask := RBitMask;
  635. BI.GreenMask := GBitMask;
  636. BI.BlueMask := BBitMask;
  637. BI.AlphaMask := ABitMask;
  638. end
  639. else
  640. begin
  641. // Set masks for A8R8G8B8
  642. BI.RedMask := $00FF0000;
  643. BI.GreenMask := $0000FF00;
  644. BI.BlueMask := $000000FF;
  645. BI.AlphaMask := $FF000000;
  646. end;
  647. // If V3 header is used RGB masks must be written to file separately.
  648. // V4 header has embedded masks (V4 is default for formats with alpha).
  649. if BI.Size = V3InfoHeaderSize then
  650. Write(Handle, @BI.RedMask, SizeOf(BI.RedMask) * 3);
  651. end;
  652. // Write palette
  653. if Palette <> nil then
  654. Write(Handle, Palette, Info.PaletteEntries * SizeOf(TColor32Rec));
  655. BF.Offset := Tell(Handle) - StartPos;
  656. if BI.Compression <> BI_RLE8 then
  657. begin
  658. // Save uncompressed data, scanlines must be filled with pad bytes
  659. // to be multiples of 4, save as bottom-up (Windows native) bitmap
  660. Pad := 0;
  661. WidthBytes := Width * Info.BytesPerPixel;
  662. PadSize := ((Width * BI.BitCount + 31) div 32) * 4 - WidthBytes;
  663. for I := Height - 1 downto 0 do
  664. begin
  665. Write(Handle, @PByteArray(Bits)[I * WidthBytes], WidthBytes);
  666. if PadSize > 0 then
  667. Write(Handle, @Pad, PadSize);
  668. end;
  669. end
  670. else
  671. begin
  672. // Save data with RLE8 compression
  673. SaveRLE8;
  674. end;
  675. EndPos := Tell(Handle);
  676. Seek(Handle, StartPos, smFromBeginning);
  677. // Rewrite header with new values
  678. BF.Size := EndPos - StartPos;
  679. BI.SizeImage := BF.Size - BF.Offset;
  680. Write(Handle, @BF, SizeOf(BF));
  681. Write(Handle, @BI, BI.Size);
  682. Seek(Handle, EndPos, smFromBeginning);
  683. Result := True;
  684. finally
  685. if MustBeFreed then
  686. FreeImage(ImageToSave);
  687. end;
  688. end;
  689. procedure TBitmapFileFormat.ConvertToSupported(var Image: TImageData;
  690. const Info: TImageFormatInfo);
  691. var
  692. ConvFormat: TImageFormat;
  693. begin
  694. if Info.IsFloatingPoint then
  695. // Convert FP image to RGB/ARGB according to presence of alpha channel
  696. ConvFormat := IffFormat(Info.HasAlphaChannel, ifA8R8G8B8, ifR8G8B8)
  697. else if Info.HasGrayChannel or Info.IsIndexed then
  698. // Convert all grayscale and indexed images to Index8 unless they have alpha
  699. // (preserve it)
  700. ConvFormat := IffFormat(Info.HasAlphaChannel, ifA8R8G8B8, ifIndex8)
  701. else if Info.HasAlphaChannel then
  702. // Convert images with alpha channel to A8R8G8B8
  703. ConvFormat := ifA8R8G8B8
  704. else if Info.UsePixelFormat then
  705. // Convert 16bit RGB images (no alpha) to X1R5G5B5
  706. ConvFormat := ifX1R5G5B5
  707. else
  708. // Convert all other formats to R8G8B8
  709. ConvFormat := ifR8G8B8;
  710. ConvertImage(Image, ConvFormat);
  711. end;
  712. function TBitmapFileFormat.TestFormat(Handle: TImagingHandle): Boolean;
  713. var
  714. Hdr: TBitmapFileHeader;
  715. ReadCount: LongInt;
  716. begin
  717. Result := False;
  718. if Handle <> nil then
  719. with GetIO do
  720. begin
  721. ReadCount := Read(Handle, @Hdr, SizeOf(Hdr));
  722. Seek(Handle, -ReadCount, smFromCurrent);
  723. Result := (Hdr.ID = BMMagic) and (ReadCount = SizeOf(Hdr));
  724. end;
  725. end;
  726. initialization
  727. RegisterImageFileFormat(TBitmapFileFormat);
  728. {
  729. File Notes:
  730. -- TODOS ----------------------------------------------------
  731. - nothing now
  732. - Add option to choose to save V3 or V4 headers.
  733. -- 0.25.0 Changes/Bug Fixes ---------------------------------
  734. - Fixed problem with indexed BMP loading - some pal entries
  735. could end up with alpha=0.
  736. -- 0.23 Changes/Bug Fixes -----------------------------------
  737. - Now saves bitmaps as bottom-up for better compatibility
  738. (mainly Lazarus' TImage!).
  739. - Fixed crash when loading bitmaps with headers larger than V4.
  740. - Temp hacks to disable V4 headers for 32bit images (compatibility with
  741. other soft).
  742. -- 0.21 Changes/Bug Fixes -----------------------------------
  743. - Removed temporary data allocation for image with aligned scanlines.
  744. They are now directly written to output so memory requirements are
  745. much lower now.
  746. - Now uses and recognizes BITMAPINFOHEADERV4 when loading/saving.
  747. Mainly for formats with alpha channels.
  748. - Added ifR5G6B5 to supported formats, changed converting to supported
  749. formats little bit.
  750. - Rewritten SaveRLE8 nested procedure. Old code was long and
  751. mysterious - new is short and much more readable.
  752. - MakeCompatible method moved to base class, put ConvertToSupported here.
  753. GetSupportedFormats removed, it is now set in constructor.
  754. - Rewritten LoadRLE4 and LoadRLE8 nested procedures.
  755. Should be less buggy an more readable (load inspired by Colosseum Builders' code).
  756. - Made public properties for options registered to SetOption/GetOption
  757. functions.
  758. - Added alpha check to 32b bitmap loading too (teh same as in 16b
  759. bitmap loading).
  760. - Moved Convert1To8 and Convert4To8 to ImagingFormats
  761. - Changed extensions to filename masks.
  762. - Changed SaveData, LoadData, and MakeCompatible methods according
  763. to changes in base class in Imaging unit.
  764. -- 0.19 Changes/Bug Fixes -----------------------------------
  765. - fixed wrong const that caused A4R4G4B4 BMPs to load as A1R5G5B5
  766. - fixed the bug that caused 8bit RLE compressed bitmaps to load as
  767. whole black
  768. -- 0.17 Changes/Bug Fixes -----------------------------------
  769. - 16 bit images are usually without alpha but some has alpha
  770. channel and there is no indication of it - so I have added
  771. a check: if all pixels of image are with alpha = 0 image is treated
  772. as X1R5G5B5 otherwise as A1R5G5B5
  773. -- 0.13 Changes/Bug Fixes -----------------------------------
  774. - when loading 1/4 bit images with dword aligned dimensions
  775. there was ugly memory rewriting bug causing image corruption
  776. }
  777. end.