ImagingPcx.pas 12 KB

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