2 $Id: ImagingXpm.pas 171 2009-09-02 01:34:19Z galfar $
3 Vampyre Imaging Library
5 http://imaginglib.sourceforge.net
7 The contents of this file are used with permission, subject to the Mozilla
8 Public License Version 1.1 (the "License"); you may not use this file except
9 in compliance with the License. You may obtain a copy of the License at
10 http://www.mozilla.org/MPL/MPL-1.1.html
12 Software distributed under the License is distributed on an "AS IS" basis,
13 WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
14 the specific language governing rights and limitations under the License.
16 Alternatively, the contents of this file may be used under the terms of the
17 GNU Lesser General Public License (the "LGPL License"), in which case the
18 provisions of the LGPL License are applicable instead of those above.
19 If you wish to allow use of your version of this file only under the terms
20 of the LGPL License and not to allow others to use your version of this file
21 under the MPL, indicate your decision by deleting the provisions above and
22 replace them with the notice and other provisions required by the LGPL
23 License. If you do not delete the provisions above, a recipient may use
24 your version of this file under either the MPL or the LGPL License.
26 For more information about the LGPL: http://www.gnu.org/copyleft/lesser.html
29 { This unit contains image format loader for X Window Pixmap images. }
31 {$I ImagingOptions.inc}
36 SysUtils
, Classes
, Contnrs
, ImagingTypes
, Imaging
, ImagingUtility
,
37 ImagingFormats
, ImagingIO
, ImagingCanvases
;
40 { Class for loading X Window Pixmap images known as XPM.
41 It is ASCII-text-based format, basicaly a fragment of C code
42 declaring static array. Loaded image is in ifA8R8G8B8 data format.
43 Loading as well as saving is supported now. }
44 TXPMFileFormat
= class(TImageFileFormat
)
46 function LoadData(Handle
: TImagingHandle
; var Images
: TDynImageDataArray
;
47 OnlyFirstLevel
: Boolean): Boolean; override;
48 function SaveData(Handle
: TImagingHandle
; const Images
: TDynImageDataArray
;
49 Index
: LongInt): Boolean; override;
50 procedure ConvertToSupported(var Image
: TImageData
;
51 const Info
: TImageFormatInfo
); override;
53 constructor Create
; override;
54 function TestFormat(Handle
: TImagingHandle
): Boolean; override;
60 SXPMFormatName
= 'X Window Pixmap';
62 XPMSupportedFormats
: TImageFormats
= [ifA8R8G8B8
];
66 WhiteSpaces
= [#9, #10, #13, #32];
82 TBucketItemArray
= array of TBucketItem
;
86 ItemIdxStart
: Integer;
87 Items
: TBucketItemArray
;
90 TBucketArray
= array of TBucket
;
92 { Simple color-string hash table for faster than linear searches
94 TSimpleBucketList
= class
96 FBuckets
: TBucketArray
;
98 FABucket
, FAIndex
: Integer;
99 function GetData(AKey
: TColor32
): string;
100 procedure SetData(AKey
: TColor32
; const AData
: string);
101 function FindItem(AKey
: TColor32
; out ABucket
, AIndex
: Integer): Boolean;
104 procedure Add(AKey
: TColor32
; const AData
: string);
105 function Exists(AKey
: TColor32
): Boolean;
106 function EnumNext(out AData
: string): TColor32
;
107 property Data
[AKey
: TColor32
]: string read GetData write SetData
; default
;
108 property ItemCount
: Integer read FItemCount
;
111 { TSimpleBucketList }
113 constructor TSimpleBucketList
.Create
;
115 SetLength(FBuckets
, BucketCount
);
118 function TSimpleBucketList
.GetData(AKey
: TColor32
): string;
120 Bucket
, Index
: Integer;
123 if FindItem(AKey
, Bucket
, Index
) then
124 Result
:= FBuckets
[Bucket
].Items
[Index
].Data
;
127 procedure TSimpleBucketList
.SetData(AKey
: TColor32
; const AData
: string);
129 Bucket
, Index
: Integer;
131 if FindItem(AKey
, Bucket
, Index
) then
132 FBuckets
[Bucket
].Items
[Index
].Data
:= AData
;
135 function TSimpleBucketList
.EnumNext(out AData
: string): TColor32
;
137 // Skip empty buckets
138 while FAIndex
>= FBuckets
[FABucket
].Count
do
141 if FABucket
>= Length(FBuckets
) then
146 Result
:= FBuckets
[FABucket
].Items
[FAIndex
].Key
;
147 AData
:= FBuckets
[FABucket
].Items
[FAIndex
].Data
;
151 function TSimpleBucketList
.FindItem(AKey
: TColor32
; out ABucket
,
152 AIndex
: Integer): Boolean;
158 Col
:= TColor32Rec(AKey
);
159 ABucket
:= (Col
.A
+ 11 * Col
.B
+ 59 * Col
.R
+ 119 * Col
.G
) mod BucketCount
;
160 with FBuckets
[ABucket
] do
161 for I
:= 0 to Count
- 1 do
162 if Items
[I
].Key
= AKey
then
170 procedure TSimpleBucketList
.Add(AKey
: TColor32
; const AData
: string);
172 Bucket
, Index
, Delta
, Size
: Integer;
174 if not FindItem(AKey
, Bucket
, Index
) then
175 with FBuckets
[Bucket
] do
177 Size
:= Length(Items
);
184 SetLength(Items
, Size
+ Delta
);
197 function TSimpleBucketList
.Exists(AKey
: TColor32
): Boolean;
199 Bucket
, Index
: Integer;
201 Result
:= FindItem(AKey
, Bucket
, Index
);
205 TXPMFileFormat implementation
208 constructor TXPMFileFormat
.Create
;
211 FName
:= SXPMFormatName
;
214 FIsMultiImageFormat
:= False;
215 FSupportedFormats
:= XPMSupportedFormats
;
220 function TXPMFileFormat
.LoadData(Handle
: TImagingHandle
;
221 var Images
: TDynImageDataArray
; OnlyFirstLevel
: Boolean): Boolean;
223 Contents
, PalLookup
: TStringList
;
225 I
, J
, NumColors
, Cpp
, Line
: Integer;
227 procedure SkipWhiteSpace(var Line
: string);
229 while (Length(Line
) > 0) and (Line
[1] in WhiteSpaces
) do
233 function ReadString(var Line
: string): string;
236 SkipWhiteSpace(Line
);
237 while (Length(Line
) > 0) and not(Line
[1] in WhiteSpaces
) do
239 SetLength(Result
, Length(Result
) + 1);
240 Result
[Length(Result
)] := Line
[1];
245 function ReadInt(var Line
: string): Integer;
247 Result
:= StrToInt(ReadString(Line
));
250 function ParseHeader
: Boolean;
256 Images
[0].Width
:= ReadInt(S
);
257 Images
[0].Height
:= ReadInt(S
);
258 NumColors
:= ReadInt(S
);
267 function NamedToColor(const ColStr
: string): TColor32
;
271 S
:= LowerCase(ColStr
);
272 if (S
= 'transparent') or (S
= 'none') then
274 else if S
= 'black' then
276 else if S
= 'blue' then
278 else if S
= 'green' then
280 else if S
= 'cyan' then
282 else if S
= 'red' then
284 else if S
= 'magenta' then
286 else if S
= 'yellow' then
288 else if S
= 'white' then
290 else if S
= 'gray' then
292 else if S
= 'dkblue' then
294 else if S
= 'dkgreen' then
296 else if S
= 'dkcyan' then
298 else if S
= 'dkred' then
300 else if S
= 'dkmagenta' then
302 else if S
= 'dkyellow' then
304 else if S
= 'maroon' then
306 else if S
= 'olive' then
308 else if S
= 'navy' then
310 else if S
= 'purple' then
312 else if S
= 'teal' then
314 else if S
= 'silver' then
316 else if S
= 'lime' then
318 else if S
= 'fuchsia' then
320 else if S
= 'aqua' then
326 procedure ParsePalette
;
329 S
, ColType
, ColStr
, Code
: string;
331 Holder
: TColorHolder
;
333 for I
:= 0 to NumColors
- 1 do
335 Holder
:= TColorHolder
.Create
;
336 // Parse pixel code and color
337 S
:= Contents
[Line
+ I
];
338 Code
:= Copy(S
, 1, Cpp
);
340 ColType
:= ReadString(S
);
341 ColStr
:= ReadString(S
);
342 // Convert color from hex number or named constant
343 if ColStr
[1] = '#' then
345 Delete(ColStr
, 1, 1);
346 Color
:= LongWord(StrToInt('$' + Trim(ColStr
))) or $FF000000;
349 Color
:= NamedToColor(ColStr
);
350 // Store code and color in table for later lookup
351 Holder
.Color
:= Color
;
352 PalLookup
.AddObject(Code
, Holder
);
354 Inc(Line
, NumColors
);
357 procedure ParsePixels
;
363 Pix
:= Images
[0].Bits
;
364 for Y
:= 0 to Images
[0].Height
- 1 do
366 S
:= Contents
[Line
+ Y
];
367 for X
:= 0 to Images
[0].Width
- 1 do
369 // Read code and look up color in the palette
370 Code
:= Copy(S
, X
* Cpp
+ 1, Cpp
);
371 if PalLookup
.Find(Code
, Idx
) then
372 Pix
^ := TColorHolder(PalLookup
.Objects
[Idx
]).Color
382 SetLength(Images
, 1);
383 with GetIO
, Images
[0] do
385 // Look up table for XPM palette entries
386 PalLookup
:= TStringList
.Create
;
387 PalLookup
.Sorted
:= True;
388 PalLookup
.CaseSensitive
:= True;
389 // Read whole file and assign it to string list
390 Contents
:= TStringList
.Create
;
391 SetLength(S
, GetInputSize(GetIO
, Handle
));
392 Read(Handle
, @S
[1], Length(S
));
394 // Remove quotes and other stuff
395 for I
:= Contents
.Count
- 1 downto 0 do
397 J
:= Pos('"', Contents
[I
]);
399 Contents
[I
] := Copy(Contents
[I
], J
+ 1, LastDelimiter('"', Contents
[I
]) - J
- 1)
403 // Parse header and create new image
404 if not ParseHeader
then
406 NewImage(Width
, Height
, ifA8R8G8B8
, Images
[0]);
407 // Read palette entries and assign colors to pixels
412 for I
:= 0 to PalLookup
.Count
- 1 do
413 PalLookup
.Objects
[I
].Free
;
419 function TXPMFileFormat
.SaveData(Handle
: TImagingHandle
;
420 const Images
: TDynImageDataArray
; Index
: LongInt): Boolean;
422 ColorCharsCount
= 92;
423 ColorChars
= ' .XoO+@#$%&*=-;:>,<1234567890qwertyuipasdfghjklzxcvbnmMNBVCZASDFGHJKLPIUYTREWQ!~^/()_`''][{}|';
426 ImageToSave
: TImageData
;
427 MustBeFreed
: Boolean;
428 StrFile
: TStringList
;
429 ColTable
: TSimpleBucketList
;
430 Stream
: TMemoryStream
;
432 CharsPerPixel
: Integer;
436 procedure BuildColorTables(const Img
: TImageData
);
441 for I
:= 0 to Img
.Width
* Img
.Height
- 1 do
443 if not ColTable
.Exists(Ptr
.Color
) then
444 ColTable
.Add(Ptr
.Color
, '');
449 procedure MakeStrIdsForColors
;
454 SetLength(Id
, CharsPerPixel
);
455 for I
:= 0 to ColTable
.ItemCount
- 1 do
457 ColRec
.Color
:= ColTable
.EnumNext(Data
);
459 for J
:= 0 to CharsPerPixel
- 1 do
461 Id
[J
+ 1] := ColorChars
[K
mod ColorCharsCount
+ 1];
462 K
:= K
div ColorCharsCount
;
464 ColTable
.Data
[ColRec
.Color
] := Id
;
471 StrFile
:= TStringList
.Create
;
472 ColTable
:= TSimpleBucketList
.Create
;
473 Stream
:= TMemoryStream
.Create
;
475 if MakeCompatible(Images
[Index
], ImageToSave
, MustBeFreed
) then
477 // Put all unique colors of image to table
478 BuildColorTables(ImageToSave
);
479 // Compute the character per pixel
481 X
:= ColorCharsCount
;
482 while ColTable
.ItemCount
> X
do
484 X
:= X
* ColorCharsCount
;
487 // Assign char id to each color
490 // Start writing XPM file
492 StrFile
.Add('static char *graphic[] = {');
493 StrFile
.Add('/* width height num_colors chars_per_pixel */');
494 StrFile
.Add(SysUtils
.Format('"%d %d %d %d", ', [ImageToSave
.Width
,
495 ImageToSave
.Height
, ColTable
.ItemCount
, CharsPerPixel
]));
496 StrFile
.Add('/* colors */');
498 // Write 'colors' part of XPM file
499 for X
:= 0 to ColTable
.ItemCount
- 1 do
501 ColRec
.Color
:= ColTable
.EnumNext(Id
);
502 if ColRec
.A
>= 128 then
503 StrFile
.Add(Format('"%s c #%.2x%.2x%.2x",', [Id
, ColRec
.R
, ColRec
.G
, ColRec
.B
]))
505 StrFile
.Add(Format('"%s c None",', [Id
]));
508 StrFile
.Add('/* pixels */');
510 // Write pixels - for aech pixel of image find its char id
511 // and append it to line
512 Ptr
:= ImageToSave
.Bits
;
513 for Y
:= 0 to ImageToSave
.Height
- 1 do
516 for X
:= 0 to ImageToSave
.Width
- 1 do
518 Line
:= Line
+ ColTable
.Data
[Ptr
.Color
];
521 Line
:= '"' + Line
+ '"';
522 if Y
< ImageToSave
.Height
- 1 then
529 // Finally save strings to stream and write stream's data to output
530 // (we could directly write lines from list to output, but stream method
531 // takes care of D2009+ Unicode strings).
532 StrFile
.SaveToStream(Stream
);
533 GetIO
.Write(Handle
, Stream
.Memory
, Stream
.Size
);
541 FreeImage(ImageToSave
);
545 procedure TXPMFileFormat
.ConvertToSupported(var Image
: TImageData
;
546 const Info
: TImageFormatInfo
);
548 ConvertImage(Image
, ifA8R8G8B8
)
551 function TXPMFileFormat
.TestFormat(Handle
: TImagingHandle
): Boolean;
553 Id
: array[0 .. 8] of AnsiChar;
557 if Handle
<> nil then
559 ReadCount
:= GetIO
.Read(Handle
, @Id
, SizeOf(Id
));
560 GetIO
.Seek(Handle
, -ReadCount
, smFromCurrent
);
561 Result
:= (Id
= SXPMId
) and (ReadCount
= SizeOf(Id
));
567 RegisterImageFileFormat(TXPMFileFormat
);
572 -- TODOS ----------------------------------------------------
575 -- 0.26.3 Changes/Bug Fixes -----------------------------------
578 -- 0.25.0 Changes/Bug Fixes -----------------------------------