ImagingXpm.pas 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585
  1. {
  2. $Id$
  3. Vampyre Imaging Library
  4. by Marek Mauder
  5. http://imaginglib.sourceforge.net
  6. The contents of this file are used with permission, subject to the Mozilla
  7. Public License Version 1.1 (the "License"); you may not use this file except
  8. in compliance with the License. You may obtain a copy of the License at
  9. http://www.mozilla.org/MPL/MPL-1.1.html
  10. Software distributed under the License is distributed on an "AS IS" basis,
  11. WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
  12. the specific language governing rights and limitations under the License.
  13. Alternatively, the contents of this file may be used under the terms of the
  14. GNU Lesser General Public License (the "LGPL License"), in which case the
  15. provisions of the LGPL License are applicable instead of those above.
  16. If you wish to allow use of your version of this file only under the terms
  17. of the LGPL License and not to allow others to use your version of this file
  18. under the MPL, indicate your decision by deleting the provisions above and
  19. replace them with the notice and other provisions required by the LGPL
  20. License. If you do not delete the provisions above, a recipient may use
  21. your version of this file under either the MPL or the LGPL License.
  22. For more information about the LGPL: http://www.gnu.org/copyleft/lesser.html
  23. }
  24. { This unit contains image format loader for X Window Pixmap images. }
  25. unit ImagingXpm;
  26. {$I ImagingOptions.inc}
  27. interface
  28. uses
  29. SysUtils, Classes, Contnrs, ImagingTypes, Imaging, ImagingUtility,
  30. ImagingFormats, ImagingIO, ImagingCanvases;
  31. type
  32. { Class for loading X Window Pixmap images known as XPM.
  33. It is ASCII-text-based format, basicaly a fragment of C code
  34. declaring static array. Loaded image is in ifA8R8G8B8 data format.
  35. Loading as well as saving is supported now. }
  36. TXPMFileFormat = class(TImageFileFormat)
  37. protected
  38. function LoadData(Handle: TImagingHandle; var Images: TDynImageDataArray;
  39. OnlyFirstLevel: Boolean): Boolean; override;
  40. function SaveData(Handle: TImagingHandle; const Images: TDynImageDataArray;
  41. Index: LongInt): Boolean; override;
  42. procedure ConvertToSupported(var Image: TImageData;
  43. const Info: TImageFormatInfo); override;
  44. public
  45. constructor Create; override;
  46. function TestFormat(Handle: TImagingHandle): Boolean; override;
  47. end;
  48. implementation
  49. const
  50. SXPMFormatName = 'X Window Pixmap';
  51. SXPMMasks = '*.xpm';
  52. XPMSupportedFormats: TImageFormats = [ifA8R8G8B8];
  53. const
  54. SXPMId = '/* XPM */';
  55. WhiteSpaces = [#9, #10, #13, #32];
  56. const
  57. BucketCount = 257;
  58. type
  59. TColorHolder = class
  60. public
  61. Color: TColor32;
  62. end;
  63. TBucketItem = record
  64. Key: TColor32;
  65. Data: string[8];
  66. end;
  67. TBucketItemArray = array of TBucketItem;
  68. TBucket = record
  69. Count: Integer;
  70. ItemIdxStart: Integer;
  71. Items: TBucketItemArray;
  72. end;
  73. TBucketArray = array of TBucket;
  74. { Simple color-string hash table for faster than linear searches
  75. during XPM saving. }
  76. TSimpleBucketList = class
  77. private
  78. FBuckets: TBucketArray;
  79. FItemCount: Integer;
  80. FABucket, FAIndex: Integer;
  81. function GetData(AKey: TColor32): string;
  82. procedure SetData(AKey: TColor32; const AData: string);
  83. function FindItem(AKey: TColor32; out ABucket, AIndex: Integer): Boolean;
  84. public
  85. constructor Create;
  86. procedure Add(AKey: TColor32; const AData: string);
  87. function Exists(AKey: TColor32): Boolean;
  88. function EnumNext(out AData: string): TColor32;
  89. property Data[AKey: TColor32]: string read GetData write SetData; default;
  90. property ItemCount: Integer read FItemCount;
  91. end;
  92. { TSimpleBucketList }
  93. constructor TSimpleBucketList.Create;
  94. begin
  95. SetLength(FBuckets, BucketCount);
  96. end;
  97. function TSimpleBucketList.GetData(AKey: TColor32): string;
  98. var
  99. Bucket, Index: Integer;
  100. begin
  101. Result := '';
  102. if FindItem(AKey, Bucket, Index) then
  103. Result := FBuckets[Bucket].Items[Index].Data;
  104. end;
  105. procedure TSimpleBucketList.SetData(AKey: TColor32; const AData: string);
  106. var
  107. Bucket, Index: Integer;
  108. begin
  109. if FindItem(AKey, Bucket, Index) then
  110. FBuckets[Bucket].Items[Index].Data := AData;
  111. end;
  112. function TSimpleBucketList.EnumNext(out AData: string): TColor32;
  113. begin
  114. // Skip empty buckets
  115. while FAIndex >= FBuckets[FABucket].Count do
  116. begin
  117. Inc(FABucket);
  118. if FABucket >= Length(FBuckets) then
  119. FABucket := 0;
  120. FAIndex := 0;
  121. end;
  122. Result := FBuckets[FABucket].Items[FAIndex].Key;
  123. AData := FBuckets[FABucket].Items[FAIndex].Data;
  124. Inc(FAIndex);
  125. end;
  126. function TSimpleBucketList.FindItem(AKey: TColor32; out ABucket,
  127. AIndex: Integer): Boolean;
  128. var
  129. I: Integer;
  130. Col: TColor32Rec;
  131. begin
  132. Result := False;
  133. Col := TColor32Rec(AKey);
  134. ABucket := (Col.A + 11 * Col.B + 59 * Col.R + 119 * Col.G) mod BucketCount;
  135. with FBuckets[ABucket] do
  136. for I := 0 to Count - 1 do
  137. if Items[I].Key = AKey then
  138. begin
  139. AIndex := I;
  140. Result := True;
  141. Break;
  142. end;
  143. end;
  144. procedure TSimpleBucketList.Add(AKey: TColor32; const AData: string);
  145. var
  146. Bucket, Index, Delta, Size: Integer;
  147. begin
  148. if not FindItem(AKey, Bucket, Index) then
  149. with FBuckets[Bucket] do
  150. begin
  151. Size := Length(Items);
  152. if Count = Size then
  153. begin
  154. if Size > 64 then
  155. Delta := Size div 4
  156. else
  157. Delta := 16;
  158. SetLength(Items, Size + Delta);
  159. end;
  160. with Items[Count] do
  161. begin
  162. Key := AKey;
  163. Data := AData;
  164. end;
  165. Inc(Count);
  166. Inc(FItemCount);
  167. end;
  168. end;
  169. function TSimpleBucketList.Exists(AKey: TColor32): Boolean;
  170. var
  171. Bucket, Index: Integer;
  172. begin
  173. Result := FindItem(AKey, Bucket, Index);
  174. end;
  175. {
  176. TXPMFileFormat implementation
  177. }
  178. constructor TXPMFileFormat.Create;
  179. begin
  180. inherited Create;
  181. FName := SXPMFormatName;
  182. FCanLoad := True;
  183. FCanSave := True;
  184. FIsMultiImageFormat := False;
  185. FSupportedFormats := XPMSupportedFormats;
  186. AddMasks(SXPMMasks);
  187. end;
  188. function TXPMFileFormat.LoadData(Handle: TImagingHandle;
  189. var Images: TDynImageDataArray; OnlyFirstLevel: Boolean): Boolean;
  190. var
  191. Contents, PalLookup: TStringList;
  192. S: AnsiString;
  193. I, J, NumColors, Cpp, Line: Integer;
  194. procedure SkipWhiteSpace(var Line: string);
  195. begin
  196. while (Length(Line) > 0) and (Line[1] in WhiteSpaces) do
  197. Delete(Line, 1, 1);
  198. end;
  199. function ReadString(var Line: string): string;
  200. begin
  201. Result := '';
  202. SkipWhiteSpace(Line);
  203. while (Length(Line) > 0) and not(Line[1] in WhiteSpaces) do
  204. begin
  205. SetLength(Result, Length(Result) + 1);
  206. Result[Length(Result)] := Line[1];
  207. Delete(Line, 1, 1);
  208. end;
  209. end;
  210. function ReadInt(var Line: string): Integer;
  211. begin
  212. Result := StrToInt(ReadString(Line));
  213. end;
  214. function ParseHeader: Boolean;
  215. var
  216. S: string;
  217. begin
  218. S := Contents[0];
  219. try
  220. Images[0].Width := ReadInt(S);
  221. Images[0].Height := ReadInt(S);
  222. NumColors := ReadInt(S);
  223. Cpp := ReadInt(S);
  224. Line := 1;
  225. Result := True;
  226. except
  227. Result := False;
  228. end;
  229. end;
  230. function NamedToColor(const ColStr: string): TColor32;
  231. var
  232. S: string;
  233. begin
  234. S := LowerCase(ColStr);
  235. if (S = 'transparent') or (S = 'none') then
  236. Result := pcClear
  237. else if S = 'black' then
  238. Result := pcBlack
  239. else if S = 'blue' then
  240. Result := pcBlue
  241. else if S = 'green' then
  242. Result := pcGreen
  243. else if S = 'cyan' then
  244. Result := pcAqua
  245. else if S = 'red' then
  246. Result := pcRed
  247. else if S = 'magenta' then
  248. Result := pcFuchsia
  249. else if S = 'yellow' then
  250. Result := pcYellow
  251. else if S = 'white' then
  252. Result := pcWhite
  253. else if S = 'gray' then
  254. Result := pcLtGray
  255. else if S = 'dkblue' then
  256. Result := pcNavy
  257. else if S = 'dkgreen' then
  258. Result := pcGreen
  259. else if S = 'dkcyan' then
  260. Result := pcTeal
  261. else if S = 'dkred' then
  262. Result := pcMaroon
  263. else if S = 'dkmagenta' then
  264. Result := pcPurple
  265. else if S = 'dkyellow' then
  266. Result := pcOlive
  267. else if S = 'maroon' then
  268. Result := pcMaroon
  269. else if S = 'olive' then
  270. Result := pcOlive
  271. else if S = 'navy' then
  272. Result := pcNavy
  273. else if S = 'purple' then
  274. Result := pcPurple
  275. else if S = 'teal' then
  276. Result := pcTeal
  277. else if S = 'silver' then
  278. Result := pcSilver
  279. else if S = 'lime' then
  280. Result := pcLime
  281. else if S = 'fuchsia' then
  282. Result := pcFuchsia
  283. else if S = 'aqua' then
  284. Result := pcAqua
  285. else
  286. Result := pcClear;
  287. end;
  288. procedure ParsePalette;
  289. var
  290. I: Integer;
  291. S, ColType, ColStr, Code: string;
  292. Color: TColor32;
  293. Holder: TColorHolder;
  294. begin
  295. for I := 0 to NumColors - 1 do
  296. begin
  297. Holder := TColorHolder.Create;
  298. // Parse pixel code and color
  299. S := Contents[Line + I];
  300. Code := Copy(S, 1, Cpp);
  301. Delete(S, 1, Cpp);
  302. ColType := ReadString(S);
  303. ColStr := ReadString(S);
  304. // Convert color from hex number or named constant
  305. if ColStr[1] = '#' then
  306. begin
  307. Delete(ColStr, 1, 1);
  308. Color := LongWord(StrToInt('$' + Trim(ColStr))) or $FF000000;
  309. end
  310. else
  311. Color := NamedToColor(ColStr);
  312. // Store code and color in table for later lookup
  313. Holder.Color := Color;
  314. PalLookup.AddObject(Code, Holder);
  315. end;
  316. Inc(Line, NumColors);
  317. end;
  318. procedure ParsePixels;
  319. var
  320. X, Y, Idx: Integer;
  321. S, Code: string;
  322. Pix: PColor32;
  323. begin
  324. Pix := Images[0].Bits;
  325. for Y := 0 to Images[0].Height - 1 do
  326. begin
  327. S := Contents[Line + Y];
  328. for X := 0 to Images[0].Width - 1 do
  329. begin
  330. // Read code and look up color in the palette
  331. Code := Copy(S, X * Cpp + 1, Cpp);
  332. if PalLookup.Find(Code, Idx) then
  333. Pix^ := TColorHolder(PalLookup.Objects[Idx]).Color
  334. else
  335. Pix^ := pcClear;
  336. Inc(Pix);
  337. end;
  338. end;
  339. end;
  340. begin
  341. Result := False;
  342. SetLength(Images, 1);
  343. with GetIO, Images[0] do
  344. begin
  345. // Look up table for XPM palette entries
  346. PalLookup := TStringList.Create;
  347. PalLookup.Sorted := True;
  348. PalLookup.CaseSensitive := True;
  349. // Read whole file and assign it to string list
  350. Contents := TStringList.Create;
  351. SetLength(S, GetInputSize(GetIO, Handle));
  352. Read(Handle, @S[1], Length(S));
  353. Contents.Text := S;
  354. // Remove quotes and other stuff
  355. for I := Contents.Count - 1 downto 0 do
  356. begin
  357. J := Pos('"', Contents[I]);
  358. if J > 0 then
  359. Contents[I] := Copy(Contents[I], J + 1, LastDelimiter('"', Contents[I]) - J - 1)
  360. else
  361. Contents.Delete(I);
  362. end;
  363. // Parse header and create new image
  364. if not ParseHeader then
  365. Exit;
  366. NewImage(Width, Height, ifA8R8G8B8, Images[0]);
  367. // Read palette entries and assign colors to pixels
  368. ParsePalette;
  369. ParsePixels;
  370. Contents.Free;
  371. for I := 0 to PalLookup.Count - 1 do
  372. PalLookup.Objects[I].Free;
  373. PalLookup.Free;
  374. Result := True;
  375. end;
  376. end;
  377. function TXPMFileFormat.SaveData(Handle: TImagingHandle;
  378. const Images: TDynImageDataArray; Index: LongInt): Boolean;
  379. const
  380. ColorCharsCount = 92;
  381. ColorChars = ' .XoO+@#$%&*=-;:>,<1234567890qwertyuipasdfghjklzxcvbnmMNBVCZASDFGHJKLPIUYTREWQ!~^/()_`''][{}|';
  382. var
  383. X, Y: Integer;
  384. ImageToSave: TImageData;
  385. MustBeFreed: Boolean;
  386. StrFile: TStringList;
  387. ColTable: TSimpleBucketList;
  388. Stream: TMemoryStream;
  389. Line, Id: string;
  390. CharsPerPixel: Integer;
  391. Ptr: PColor32Rec;
  392. ColRec: TColor32Rec;
  393. procedure BuildColorTables(const Img: TImageData);
  394. var
  395. I: Integer;
  396. begin
  397. Ptr := Img.Bits;
  398. for I := 0 to Img.Width * Img.Height - 1 do
  399. begin
  400. if not ColTable.Exists(Ptr.Color) then
  401. ColTable.Add(Ptr.Color, '');
  402. Inc(Ptr);
  403. end;
  404. end;
  405. procedure MakeStrIdsForColors;
  406. var
  407. I, J, K: Integer;
  408. Id, Data: string;
  409. begin
  410. SetLength(Id, CharsPerPixel);
  411. for I := 0 to ColTable.ItemCount - 1 do
  412. begin
  413. ColRec.Color := ColTable.EnumNext(Data);
  414. K := I;
  415. for J := 0 to CharsPerPixel - 1 do
  416. begin
  417. Id[J + 1] := ColorChars[K mod ColorCharsCount + 1];
  418. K := K div ColorCharsCount;
  419. end;
  420. ColTable.Data[ColRec.Color] := Id;
  421. end;
  422. end;
  423. begin
  424. Result := False;
  425. StrFile := TStringList.Create;
  426. ColTable := TSimpleBucketList.Create;
  427. Stream := TMemoryStream.Create;
  428. if MakeCompatible(Images[Index], ImageToSave, MustBeFreed) then
  429. try
  430. // Put all unique colors of image to table
  431. BuildColorTables(ImageToSave);
  432. // Compute the character per pixel
  433. CharsPerPixel := 1;
  434. X := ColorCharsCount;
  435. while ColTable.ItemCount > X do
  436. begin
  437. X := X * ColorCharsCount;
  438. Inc(CharsPerPixel);
  439. end;
  440. // Assign char id to each color
  441. MakeStrIdsForColors;
  442. // Start writing XPM file
  443. StrFile.Add(SXPMId);
  444. StrFile.Add('static char *graphic[] = {');
  445. StrFile.Add('/* width height num_colors chars_per_pixel */');
  446. StrFile.Add(SysUtils.Format('"%d %d %d %d", ', [ImageToSave.Width,
  447. ImageToSave.Height, ColTable.ItemCount, CharsPerPixel]));
  448. StrFile.Add('/* colors */');
  449. // Write 'colors' part of XPM file
  450. for X := 0 to ColTable.ItemCount - 1 do
  451. begin
  452. ColRec.Color := ColTable.EnumNext(Id);
  453. if ColRec.A >= 128 then
  454. StrFile.Add(Format('"%s c #%.2x%.2x%.2x",', [Id, ColRec.R, ColRec.G, ColRec.B]))
  455. else
  456. StrFile.Add(Format('"%s c None",', [Id]));
  457. end;
  458. StrFile.Add('/* pixels */');
  459. // Write pixels - for aech pixel of image find its char id
  460. // and append it to line
  461. Ptr := ImageToSave.Bits;
  462. for Y := 0 to ImageToSave.Height - 1 do
  463. begin
  464. Line := '';
  465. for X := 0 to ImageToSave.Width - 1 do
  466. begin
  467. Line := Line + ColTable.Data[Ptr.Color];
  468. Inc(Ptr);
  469. end;
  470. Line := '"' + Line + '"';
  471. if Y < ImageToSave.Height - 1 then
  472. Line := Line + ',';
  473. StrFile.Add(Line);
  474. end;
  475. StrFile.Add('};');
  476. // Finally save strings to stream and write stream's data to output
  477. // (we could directly write lines from list to output, but stream method
  478. // takes care of D2009+ Unicode strings).
  479. StrFile.SaveToStream(Stream);
  480. GetIO.Write(Handle, Stream.Memory, Stream.Size);
  481. Result := True;
  482. finally
  483. StrFile.Free;
  484. ColTable.Free;
  485. Stream.Free;
  486. if MustBeFreed then
  487. FreeImage(ImageToSave);
  488. end;
  489. end;
  490. procedure TXPMFileFormat.ConvertToSupported(var Image: TImageData;
  491. const Info: TImageFormatInfo);
  492. begin
  493. ConvertImage(Image, ifA8R8G8B8)
  494. end;
  495. function TXPMFileFormat.TestFormat(Handle: TImagingHandle): Boolean;
  496. var
  497. Id: array[0 .. 8] of AnsiChar;
  498. ReadCount: Integer;
  499. begin
  500. Result := False;
  501. if Handle <> nil then
  502. begin
  503. ReadCount := GetIO.Read(Handle, @Id, SizeOf(Id));
  504. GetIO.Seek(Handle, -ReadCount, smFromCurrent);
  505. Result := (Id = SXPMId) and (ReadCount = SizeOf(Id));
  506. end;
  507. end;
  508. initialization
  509. RegisterImageFileFormat(TXPMFileFormat);
  510. {
  511. File Notes:
  512. -- TODOS ----------------------------------------------------
  513. - nothing now
  514. -- 0.26.3 Changes/Bug Fixes -----------------------------------
  515. - Added XPM saving.
  516. -- 0.25.0 Changes/Bug Fixes -----------------------------------
  517. - Added XPM loading.
  518. - Unit created.
  519. }
  520. end.