ImagingXpm.pas 16 KB

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