ImagingPcx.pas 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  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. { This unit contains image format loader for ZSoft Paintbrush images known as PCX.}
  12. unit ImagingPcx;
  13. {$I ImagingOptions.inc}
  14. interface
  15. uses
  16. ImagingTypes, Imaging, ImagingFormats, ImagingUtility, ImagingIO;
  17. type
  18. { Class for loading ZSoft Paintbrush images known as PCX. It is old
  19. format which can store 1bit, 2bit, 4bit, 8bit, and 24bit (and 32bit but is
  20. probably non-standard) images. Only loading is supported (you can still come
  21. across some PCX files) but saving is not (I don't wont this venerable format
  22. to spread).}
  23. TPCXFileFormat = class(TImageFileFormat)
  24. protected
  25. procedure Define; override;
  26. function LoadData(Handle: TImagingHandle; var Images: TDynImageDataArray;
  27. OnlyFirstLevel: Boolean): Boolean; override;
  28. public
  29. function TestFormat(Handle: TImagingHandle): Boolean; override;
  30. end;
  31. implementation
  32. const
  33. SPCXFormatName = 'ZSoft Paintbrush Image';
  34. SPCXMasks = '*.pcx';
  35. type
  36. TPCXHeader = packed record
  37. Id: Byte; // Always $0A
  38. Version: Byte; // 0, 2, 3, 4, 5
  39. Encoding: Byte; // 0, 1
  40. BitsPerPixel: Byte; // 1, 2, 4, 8
  41. X0, Y0: Word; // Image window top-left
  42. X1, Y1: Word; // Image window bottom-right
  43. DpiX: Word;
  44. DpiY: Word;
  45. Palette16: array [0..15] of TColor24Rec;
  46. Reserved1: Byte;
  47. Planes: Byte; // 1, 3, 4
  48. BytesPerLine: Word;
  49. PaletteType: Word; // 1: color or s/w 2: grayscale
  50. Reserved2: array [0..57] of Byte;
  51. end;
  52. { TPCXFileFormat }
  53. procedure TPCXFileFormat.Define;
  54. begin
  55. inherited;
  56. FName := SPCXFormatName;
  57. FFeatures := [ffLoad];
  58. AddMasks(SPCXMasks);
  59. end;
  60. function TPCXFileFormat.LoadData(Handle: TImagingHandle;
  61. var Images: TDynImageDataArray; OnlyFirstLevel: Boolean): Boolean;
  62. const
  63. ifMono: TImageFormat = TImageFormat(250);
  64. ifIndex2: TImageFormat = TImageFormat(251);
  65. ifIndex4: TImageFormat = TImageFormat(252);
  66. var
  67. Hdr: TPCXHeader;
  68. PalID, B: Byte;
  69. PalPCX: TPalette24Size256;
  70. FileDataFormat: TImageFormat;
  71. I, J, UncompSize, BytesPerLine, ByteNum, BitNum: LongInt;
  72. UncompData, RowPointer, PixelIdx: PByte;
  73. Pixel24: PColor24Rec;
  74. Pixel32: PColor32Rec;
  75. AlphaPlane, RedPlane, GreenPlane, BluePlane,
  76. Plane1, Plane2, Plane3, Plane4: PByteArray;
  77. procedure RleDecode(Target: PByte; UnpackedSize: LongInt);
  78. var
  79. Count: LongInt;
  80. Source: Byte;
  81. begin
  82. while UnpackedSize > 0 do
  83. with GetIO do
  84. begin
  85. GetIO.Read(Handle, @Source, SizeOf(Source));
  86. if (Source and $C0) = $C0 then
  87. begin
  88. // RLE data
  89. Count := Source and $3F;
  90. if UnpackedSize < Count then
  91. Count := UnpackedSize;
  92. Read(Handle, @Source, SizeOf(Source));
  93. FillChar(Target^, Count, Source);
  94. //Inc(Source);
  95. Inc(Target, Count);
  96. Dec(UnpackedSize, Count);
  97. end
  98. else
  99. begin
  100. // Uncompressed data
  101. Target^ := Source;
  102. Inc(Target);
  103. Dec(UnpackedSize);
  104. end;
  105. end;
  106. end;
  107. begin
  108. Result := False;
  109. SetLength(Images, 1);
  110. with GetIO, Images[0] do
  111. begin
  112. // Read PCX header and store input position (start of image data)
  113. Read(Handle, @Hdr, SizeOf(Hdr));
  114. FileDataFormat := ifUnknown;
  115. // Determine image's data format and find its Imaging equivalent
  116. // (using some custom TImageFormat constants)
  117. case Hdr.BitsPerPixel of
  118. 1:
  119. case Hdr.Planes of
  120. 1: FileDataFormat := ifMono;
  121. 4: FileDataFormat := ifIndex4;
  122. end;
  123. 2: FileDataFormat := ifIndex2;
  124. 4: FileDataFormat := ifIndex4;
  125. 8:
  126. case Hdr.Planes of
  127. 1: FileDataFormat := ifIndex8;
  128. 3: FileDataFormat := ifR8G8B8;
  129. 4: FileDataFormat := ifA8R8G8B8;
  130. end;
  131. end;
  132. // No compatible Imaging format found, exit
  133. if FileDataFormat = ifUnknown then
  134. Exit;
  135. // Get width, height, and output data format (unsupported formats
  136. // like ifMono are converted later to ifIndex8)
  137. Width := Hdr.X1 - Hdr.X0 + 1;
  138. Height := Hdr.Y1 - Hdr.Y0 + 1;
  139. if FileDataFormat in [ifIndex8, ifR8G8B8] then
  140. Format := FileDataFormat
  141. else
  142. Format := ifIndex8;
  143. NewImage(Width, Height, Format, Images[0]);
  144. if not (FileDataFormat in [ifIndex8, ifR8G8B8]) then
  145. begin
  146. // other formats use palette embedded to file header
  147. for I := Low(Hdr.Palette16) to High(Hdr.Palette16) do
  148. begin
  149. Palette[I].A := $FF;
  150. Palette[I].R := Hdr.Palette16[I].B;
  151. Palette[I].G := Hdr.Palette16[I].G;
  152. Palette[I].B := Hdr.Palette16[I].R;
  153. end;
  154. end;
  155. // Now we determine various data sizes
  156. BytesPerLine := Hdr.BytesPerLine * Hdr.Planes;
  157. UncompSize := BytesPerLine * Height;
  158. GetMem(UncompData, UncompSize);
  159. try
  160. if Hdr.Encoding = 1 then
  161. begin
  162. // Image data is compressed -> read and decompress
  163. RleDecode(UncompData, UncompSize);
  164. end
  165. else
  166. begin
  167. // Just read uncompressed data
  168. Read(Handle, UncompData, UncompSize);
  169. end;
  170. if FileDataFormat in [ifR8G8B8, ifA8R8G8B8] then
  171. begin
  172. // RGB and ARGB images are stored in layout different from
  173. // Imaging's (and most other file formats'). First there is
  174. // Width red values then there is Width green values and so on
  175. RowPointer := UncompData;
  176. if FileDataFormat = ifA8R8G8B8 then
  177. begin
  178. Pixel32 := Bits;
  179. for I := 0 to Height - 1 do
  180. begin
  181. AlphaPlane := PByteArray(RowPointer);
  182. RedPlane := @AlphaPlane[Hdr.BytesPerLine];
  183. GreenPlane := @AlphaPlane[Hdr.BytesPerLine * 2];
  184. BluePlane := @AlphaPlane[Hdr.BytesPerLine * 3];
  185. for J := 0 to Width - 1 do
  186. begin
  187. Pixel32.A := AlphaPlane[J];
  188. Pixel32.R := RedPlane[J];
  189. Pixel32.G := GreenPlane[J];
  190. Pixel32.B := BluePlane[J];
  191. Inc(Pixel32);
  192. end;
  193. Inc(RowPointer, BytesPerLine);
  194. end;
  195. end
  196. else
  197. begin
  198. Pixel24 := Bits;
  199. for I := 0 to Height - 1 do
  200. begin
  201. RedPlane := PByteArray(RowPointer);
  202. GreenPlane := @RedPlane[Hdr.BytesPerLine];
  203. BluePlane := @RedPlane[Hdr.BytesPerLine * 2];
  204. for J := 0 to Width - 1 do
  205. begin
  206. Pixel24.R := RedPlane[J];
  207. Pixel24.G := GreenPlane[J];
  208. Pixel24.B := BluePlane[J];
  209. Inc(Pixel24);
  210. end;
  211. Inc(RowPointer, BytesPerLine);
  212. end;
  213. end;
  214. end
  215. else if FileDataFormat = ifIndex8 then
  216. begin
  217. // Just copy 8bit lines
  218. for I := 0 to Height - 1 do
  219. Move(PByteArray(UncompData)[I * Hdr.BytesPerLine], PByteArray(Bits)[I * Width], Width);
  220. end
  221. else if FileDataFormat = ifMono then
  222. begin
  223. // Convert 1bit images to ifIndex8
  224. Convert1To8(UncompData, Bits, Width, Height, Hdr.BytesPerLine, False);
  225. end
  226. else if FileDataFormat = ifIndex2 then
  227. begin
  228. // Convert 2bit images to ifIndex8. Note that 2bit PCX images
  229. // usually use (from specs, I've never seen one myself) CGA palette
  230. // which is not array of RGB triplets. So 2bit PCXs are loaded but
  231. // their colors would be wrong
  232. Convert2To8(UncompData, Bits, Width, Height, Hdr.BytesPerLine, False);
  233. end
  234. else if FileDataFormat = ifIndex4 then
  235. begin
  236. // 4bit images can be stored similar to RGB images (in four one bit planes)
  237. // or like array of nibbles (which is more common)
  238. if (Hdr.BitsPerPixel = 1) and (Hdr.Planes = 4) then
  239. begin
  240. RowPointer := UncompData;
  241. PixelIdx := Bits;
  242. for I := 0 to Height - 1 do
  243. begin
  244. Plane1 := PByteArray(RowPointer);
  245. Plane2 := @Plane1[Hdr.BytesPerLine];
  246. Plane3 := @Plane1[Hdr.BytesPerLine * 2];
  247. Plane4 := @Plane1[Hdr.BytesPerLine * 3];
  248. for J := 0 to Width - 1 do
  249. begin
  250. B := 0;
  251. ByteNum := J div 8;
  252. BitNum := 7 - (J mod 8);
  253. if (Plane1[ByteNum] shr BitNum) and $1 <> 0 then B := B or $01;
  254. if (Plane2[ByteNum] shr BitNum) and $1 <> 0 then B := B or $02;
  255. if (Plane3[ByteNum] shr BitNum) and $1 <> 0 then B := B or $04;
  256. if (Plane4[ByteNum] shr BitNum) and $1 <> 0 then B := B or $08;
  257. PixelIdx^ := B;
  258. Inc(PixelIdx);
  259. end;
  260. Inc(RowPointer, BytesPerLine);
  261. end;
  262. end
  263. else if (Hdr.BitsPerPixel = 4) and (Hdr.Planes = 1) then
  264. begin
  265. // Convert 4bit images to ifIndex8
  266. Convert4To8(UncompData, Bits, Width, Height, Hdr.BytesPerLine, False);
  267. end
  268. end;
  269. if FileDataFormat = ifIndex8 then
  270. begin
  271. // 8bit palette is appended at the end of the file
  272. // with $0C identifier
  273. //Seek(Handle, -769, smFromEnd);
  274. Read(Handle, @PalID, SizeOf(PalID));
  275. if PalID = $0C then
  276. begin
  277. Read(Handle, @PalPCX, SizeOf(PalPCX));
  278. for I := Low(PalPCX) to High(PalPCX) do
  279. begin
  280. Palette[I].A := $FF;
  281. Palette[I].R := PalPCX[I].B;
  282. Palette[I].G := PalPCX[I].G;
  283. Palette[I].B := PalPCX[I].R;
  284. end;
  285. end
  286. else
  287. Seek(Handle, -SizeOf(PalID), smFromCurrent);
  288. end;
  289. finally
  290. FreeMem(UncompData);
  291. end;
  292. Result := True;
  293. end;
  294. end;
  295. function TPCXFileFormat.TestFormat(Handle: TImagingHandle): Boolean;
  296. var
  297. Hdr: TPCXHeader;
  298. ReadCount: LongInt;
  299. begin
  300. Result := False;
  301. if Handle <> nil then
  302. begin
  303. ReadCount := GetIO.Read(Handle, @Hdr, SizeOf(Hdr));
  304. GetIO.Seek(Handle, -ReadCount, smFromCurrent);
  305. Result := (ReadCount >= SizeOf(Hdr)) and
  306. (Hdr.Id = $0A) and
  307. (Hdr.Version in [0, 2, 3, 4, 5]) and
  308. (Hdr.Encoding in [0..1]) and
  309. (Hdr.BitsPerPixel in [1, 2, 4, 8]) and
  310. (Hdr.Planes in [1, 3, 4]) and
  311. (Hdr.PaletteType in [1..2]);
  312. end;
  313. end;
  314. initialization
  315. RegisterImageFileFormat(TPCXFileFormat);
  316. {
  317. File Notes:
  318. -- TODOS ----------------------------------------------------
  319. - nothing now
  320. -- 0.21 Changes/Bug Fixes -----------------------------------
  321. - Made loader stream-safe - stream position is exactly at the end of the
  322. image after loading and file size doesn't need to be know during the process.
  323. - Initial TPCXFileFormat class implemented.
  324. }
  325. end.