2 Vampyre Imaging Library
4 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
11 Software distributed under the License is distributed on an "AS IS" basis,
12 WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
13 the specific language governing rights and limitations under the License.
15 Alternatively, the contents of this file may be used under the terms of the
16 GNU Lesser General Public License (the "LGPL License"), in which case the
17 provisions of the LGPL License are applicable instead of those above.
18 If you wish to allow use of your version of this file only under the terms
19 of the LGPL License and not to allow others to use your version of this file
20 under the MPL, indicate your decision by deleting the provisions above and
21 replace them with the notice and other provisions required by the LGPL
22 License. If you do not delete the provisions above, a recipient may use
23 your version of this file under either the MPL or the LGPL License.
25 For more information about the LGPL: http://www.gnu.org/copyleft/lesser.html
28 { This unit contains loader/saver for Portable Maps file format family (or PNM).
29 That includes PBM, PGM, PPM, PAM, and PFM formats.}
30 unit ImagingPortableMaps
;
32 {$I ImagingOptions.inc}
37 SysUtils
, ImagingTypes
, Imaging
, ImagingFormats
, ImagingUtility
;
40 { Types of pixels of PNM images.}
41 TTupleType
= (ttInvalid
, ttBlackAndWhite
, ttGrayScale
, ttRGB
, ttBlackAndWhiteAlpha
,
42 ttGrayScaleAlpha
, ttRGBAlpha
, ttGrayScaleFP
, ttRGBFP
);
44 { Record with info about PNM image used in both loading and saving functions.}
45 TPortableMapInfo
= record
52 TupleType
: TTupleType
;
54 HasPAMHeader
: Boolean;
58 { Base class for Portable Map file formats (or Portable AnyMaps or PNM).
59 There are several types of PNM file formats that share common
60 (simple) structure. This class can actually load all supported PNM formats.
61 Saving is also done by this class but descendants (each for different PNM
63 TPortableMapFileFormat
= class(TImageFileFormat
)
66 FSaveBinary
: LongBool;
67 FUSFormat
: TFormatSettings
;
68 procedure Define
; override;
69 function LoadData(Handle
: TImagingHandle
; var Images
: TDynImageDataArray
;
70 OnlyFirstLevel
: Boolean): Boolean; override;
71 function SaveDataInternal(Handle
: TImagingHandle
; const Images
: TDynImageDataArray
;
72 Index
: LongInt; var MapInfo
: TPortableMapInfo
): Boolean;
74 function TestFormat(Handle
: TImagingHandle
): Boolean; override;
76 { If set to True images will be saved in binary format. If it is False
77 they will be saved in text format (which could result in 5-10x bigger file).
78 Default is value True. Note that PAM and PFM files are always saved in binary.}
79 property SaveBinary
: LongBool read FSaveBinary write FSaveBinary
;
82 { Portable Bit Map is used to store monochrome 1bit images. Raster data
83 can be saved as text or binary data. Either way value of 0 represents white
84 and 1 is black. As Imaging does not have support for 1bit data formats
85 PBM images can be loaded but not saved. Loaded images are returned in
86 ifGray8 format (witch pixel values scaled from 1bit to 8bit).}
87 TPBMFileFormat
= class(TPortableMapFileFormat
)
89 procedure Define
; override;
92 { Portable Gray Map is used to store grayscale 8bit or 16bit images.
93 Raster data can be saved as text or binary data.}
94 TPGMFileFormat
= class(TPortableMapFileFormat
)
96 procedure Define
; override;
97 function SaveData(Handle
: TImagingHandle
; const Images
: TDynImageDataArray
;
98 Index
: LongInt): Boolean; override;
99 procedure ConvertToSupported(var Image
: TImageData
;
100 const Info
: TImageFormatInfo
); override;
103 { Portable Pixel Map is used to store RGB images with 8bit or 16bit channels.
104 Raster data can be saved as text or binary data.}
105 TPPMFileFormat
= class(TPortableMapFileFormat
)
107 procedure Define
; override;
108 function SaveData(Handle
: TImagingHandle
; const Images
: TDynImageDataArray
;
109 Index
: LongInt): Boolean; override;
110 procedure ConvertToSupported(var Image
: TImageData
;
111 const Info
: TImageFormatInfo
); override;
114 { Portable Arbitrary Map is format that can store image data formats
115 of PBM, PGM, and PPM formats with optional alpha channel. Raster data
116 can be stored only in binary format. All data formats supported
117 by this format are ifGray8, ifGray16, ifA8Gray8, ifA16Gray16,
118 ifR8G8B8, ifR16G16R16, ifA8R8G8B8, and ifA16R16G16B16.}
119 TPAMFileFormat
= class(TPortableMapFileFormat
)
121 procedure Define
; override;
122 function SaveData(Handle
: TImagingHandle
; const Images
: TDynImageDataArray
;
123 Index
: LongInt): Boolean; override;
124 procedure ConvertToSupported(var Image
: TImageData
;
125 const Info
: TImageFormatInfo
); override;
128 { Portable Float Map is unofficial extension of PNM format family which
129 can store images with floating point pixels. Raster data is saved in
130 binary format as array of IEEE 32 bit floating point numbers. One channel
131 or RGB images are supported by PFM format (so no alpha).}
132 TPFMFileFormat
= class(TPortableMapFileFormat
)
134 procedure Define
; override;
135 function SaveData(Handle
: TImagingHandle
; const Images
: TDynImageDataArray
;
136 Index
: LongInt): Boolean; override;
137 procedure ConvertToSupported(var Image
: TImageData
;
138 const Info
: TImageFormatInfo
); override;
144 PortableMapDefaultBinary
= True;
146 SPBMFormatName
= 'Portable Bit Map';
148 SPGMFormatName
= 'Portable Gray Map';
150 PGMSupportedFormats
= [ifGray8
, ifGray16
];
151 SPPMFormatName
= 'Portable Pixel Map';
153 PPMSupportedFormats
= [ifR8G8B8
, ifR16G16B16
];
154 SPAMFormatName
= 'Portable Arbitrary Map';
156 PAMSupportedFormats
= [ifGray8
, ifGray16
, ifA8Gray8
, ifA16Gray16
,
157 ifR8G8B8
, ifR16G16B16
, ifA8R8G8B8
, ifA16R16G16B16
];
158 SPFMFormatName
= 'Portable Float Map';
160 PFMSupportedFormats
= [ifR32F
, ifB32G32R32F
];
163 { TAB, CR, LF, and Space are used as seperators in Portable map headers and data.}
164 WhiteSpaces
= [#9, #10, #13, #32];
166 SPAMHeight
= 'HEIGHT';
168 SPAMMaxVal
= 'MAXVAL';
169 SPAMTupleType
= 'TUPLTYPE';
170 SPAMEndHdr
= 'ENDHDR';
172 { Size of buffer used to speed up text PNM loading/saving.}
173 LineBufferCapacity
= 16 * 1024;
175 TupleTypeNames
: array[TTupleType
] of string = (
176 'INVALID', 'BLACKANDWHITE', 'GRAYSCALE', 'RGB',
177 'BLACKANDWHITE_ALPHA', 'GRAYSCALE_ALPHA', 'RGB_ALPHA', 'GRAYSCALEFP',
180 { TPortableMapFileFormat }
182 procedure TPortableMapFileFormat
.Define
;
185 FFeatures
:= [ffLoad
, ffSave
];
186 FSaveBinary
:= PortableMapDefaultBinary
;
187 FUSFormat
:= GetFormatSettingsForFloats
;
190 function TPortableMapFileFormat
.LoadData(Handle
: TImagingHandle
;
191 var Images
: TDynImageDataArray
; OnlyFirstLevel
: Boolean): Boolean;
193 I
, ScanLineSize
, MonoSize
: LongInt;
196 Info
: TImageFormatInfo
;
197 LineBuffer
: array[0..LineBufferCapacity
- 1] of AnsiChar;
198 LineEnd
, LinePos
: LongInt;
199 MapInfo
: TPortableMapInfo
;
202 procedure CheckBuffer
;
204 if (LineEnd
= 0) or (LinePos
= LineEnd
) then
206 // Reload buffer if its is empty or its end was reached
207 LineEnd
:= GetIO
.Read(Handle
, @LineBuffer
[0], LineBufferCapacity
);
212 procedure FixInputPos
;
214 // Sets input's position to its real pos as it would be without buffering
217 GetIO
.Seek(Handle
, -LineEnd
+ LinePos
, smFromCurrent
);
222 function ReadString
: string;
227 // First skip all whitespace chars
231 S
[1] := LineBuffer
[LinePos
];
235 // Comment detected, skip everything until next line is reached
237 S
[1] := LineBuffer
[LinePos
];
240 until not(S
[1] in WhiteSpaces
);
241 // Now we have reached some chars other than white space, read them until
242 // there is whitespace again
244 SetLength(S
, Length(S
) + 1);
246 S
[Length(S
)] := LineBuffer
[LinePos
];
248 // Repeat until current char is whitespace or end of file is reached
249 // (Line buffer has 0 bytes which happens only on EOF)
250 until (S
[Length(S
)] in WhiteSpaces
) or (LineEnd
= 0);
251 // Get rid of last char - whitespace or null
252 SetLength(S
, Length(S
) - 1);
253 // Move position to the beginning of next string (skip white space - needed
254 // to make the loader stop at the right input position)
257 C
:= LineBuffer
[LinePos
];
259 until not (C
in WhiteSpaces
) or (LineEnd
= 0);
260 // Dec pos, current is the begining of the the string
266 function ReadIntValue
: LongInt; {$IFDEF USE_INLINE}inline;{$ENDIF}
268 Result
:= StrToInt(ReadString
);
271 procedure FindLineBreak
;
278 C
:= LineBuffer
[LinePos
];
287 function ParseHeader
: Boolean;
291 TupleTypeName
: string;
297 FillChar(MapInfo
, SizeOf(MapInfo
), 0);
298 Read(Handle
, @Id
, SizeOf(Id
));
301 if Id
[1] in ['1'..'6'] then
303 // Read header for PBM, PGM, and PPM files
304 MapInfo
.Width
:= ReadIntValue
;
305 MapInfo
.Height
:= ReadIntValue
;
307 if Id
[1] in ['1', '4'] then
310 MapInfo
.BitCount
:= 1
314 // Read channel max value, <=255 for 8bit images, >255 for 16bit images
315 // but some programs think its max colors so put <=256 here
316 MapInfo
.MaxVal
:= ReadIntValue
;
317 MapInfo
.BitCount
:= Iff(MapInfo
.MaxVal
<= 256, 8, 16);
322 '1', '4': MapInfo
.TupleType
:= ttBlackAndWhite
;
323 '2', '5': MapInfo
.TupleType
:= ttGrayScale
;
326 MapInfo
.TupleType
:= ttRGB
;
331 else if Id
[1] = '7' then
333 // Read values from PAM header
335 if (ReadString
<> SPAMWidth
) then Exit
;
336 MapInfo
.Width
:= ReadIntValue
;
338 if (ReadString
<> SPAMheight
) then Exit
;
339 MapInfo
.Height
:= ReadIntValue
;
341 if (ReadString
<> SPAMDepth
) then Exit
;
342 MapInfo
.Depth
:= ReadIntValue
;
344 if (ReadString
<> SPAMMaxVal
) then Exit
;
345 MapInfo
.MaxVal
:= ReadIntValue
;
346 MapInfo
.BitCount
:= Iff(MapInfo
.MaxVal
<= 256, 8, 16);
348 if (ReadString
<> SPAMTupleType
) then Exit
;
349 TupleTypeName
:= ReadString
;
350 for I
:= Low(TTupleType
) to High(TTupleType
) do
351 if SameText(TupleTypeName
, TupleTypeNames
[I
]) then
353 MapInfo
.TupleType
:= I
;
357 if (ReadString
<> SPAMEndHdr
) then Exit
;
359 else if Id
[1] in ['F', 'f'] then
361 // Read header of PFM file
362 MapInfo
.Width
:= ReadIntValue
;
363 MapInfo
.Height
:= ReadIntValue
;
364 Scale
:= StrToFloatDef(ReadString
, 0, FUSFormat
);
365 MapInfo
.IsBigEndian
:= Scale
> 0.0;
367 MapInfo
.TupleType
:= ttRGBFP
369 MapInfo
.TupleType
:= ttGrayScaleFP
;
370 MapInfo
.Depth
:= Iff(MapInfo
.TupleType
= ttRGBFP
, 3, 1);
371 MapInfo
.BitCount
:= Iff(MapInfo
.TupleType
= ttRGBFP
, 96, 32);
375 MapInfo
.Binary
:= (Id
[1] in ['4', '5', '6', '7', 'F', 'f']);
377 if MapInfo
.Binary
and not (Id
[1] in ['F', 'f']) then
379 // Mimic the behaviour of Photoshop and other editors/viewers:
380 // If linenreaks in file are DOS CR/LF 16bit binary values are
381 // little endian, Unix LF only linebreak indicates big endian.
382 MapInfo
.IsBigEndian
:= LineBreak
= #10;
385 // Check if values found in header are valid
386 Result
:= (MapInfo
.Width
> 0) and (MapInfo
.Height
> 0) and
387 (MapInfo
.BitCount
in [1, 8, 16, 32, 96]) and (MapInfo
.TupleType
<> ttInvalid
);
388 // Now check if image has proper number of channels (PAM)
390 case MapInfo
.TupleType
of
391 ttBlackAndWhite
, ttGrayScale
: Result
:= MapInfo
.Depth
= 1;
392 ttBlackAndWhiteAlpha
, ttGrayScaleAlpha
: Result
:= MapInfo
.Depth
= 2;
393 ttRGB
: Result
:= MapInfo
.Depth
= 3;
394 ttRGBAlpha
: Result
:= MapInfo
.Depth
= 4;
403 SetLength(Images
, 1);
405 with GetIO
, Images
[0] do
408 // Try to parse file header
409 if not ParseHeader
then Exit
;
410 // Select appropriate data format based on values read from file header
411 case MapInfo
.TupleType
of
412 ttBlackAndWhite
: Format
:= ifGray8
;
413 ttBlackAndWhiteAlpha
: Format
:= ifA8Gray8
;
414 ttGrayScale
: Format
:= IffFormat(MapInfo
.BitCount
= 8, ifGray8
, ifGray16
);
415 ttGrayScaleAlpha
: Format
:= IffFormat(MapInfo
.BitCount
= 8, ifA8Gray8
, ifA16Gray16
);
416 ttRGB
: Format
:= IffFormat(MapInfo
.BitCount
= 8, ifR8G8B8
, ifR16G16B16
);
417 ttRGBAlpha
: Format
:= IffFormat(MapInfo
.BitCount
= 8, ifA8R8G8B8
, ifA16R16G16B16
);
418 ttGrayScaleFP
: Format
:= ifR32F
;
419 ttRGBFP
: Format
:= ifB32G32R32F
;
421 // Exit if no matching data format was found
422 if Format
= ifUnknown
then Exit
;
424 NewImage(MapInfo
.Width
, MapInfo
.Height
, Format
, Images
[0]);
425 Info
:= GetFormatInfo(Format
);
427 // Now read pixels from file to dest image
428 if not MapInfo
.Binary
then
431 for I
:= 0 to Width
* Height
- 1 do
436 Dest
^ := ReadIntValue
;
437 if MapInfo
.BitCount
= 1 then
438 // If source is 1bit mono image (where 0=white, 1=black)
439 // we must scale it to 8bits
440 Dest
^ := 255 - Dest
^ * 255;
442 ifGray16
: PWord(Dest
)^ := ReadIntValue
;
444 with PColor24Rec(Dest
)^ do
451 with PColor48Rec(Dest
)^ do
458 Inc(Dest
, Info
.BytesPerPixel
);
463 if MapInfo
.BitCount
> 1 then
465 if not (MapInfo
.TupleType
in [ttGrayScaleFP
, ttRGBFP
]) then
467 // Just copy bytes from binary Portable Maps (non 1bit, non FP)
468 Read(Handle
, Bits
, Size
);
473 // FP images are in BGR order and endian swap maybe needed.
474 // Some programs store scanlines in bottom-up order but
475 // I will stick with Photoshops behaviour here
476 Read(Handle
, Bits
, Size
);
477 if MapInfo
.IsBigEndian
then
478 SwapEndianLongWord(PLongWord(Dest
), Size
div SizeOf(LongWord));
481 if MapInfo
.TupleType
in [ttBlackAndWhite
, ttBlackAndWhiteAlpha
] then
483 // Black and white PAM files must be scaled to 8bits. Note that
484 // in PAM files 1=white, 0=black (reverse of PBM)
485 for I
:= 0 to Width
* Height
* Iff(MapInfo
.TupleType
= ttBlackAndWhiteAlpha
, 2, 1) - 1 do
486 PByteArray(Bits
)[I
] := PByteArray(Bits
)[I
] * 255;
488 else if MapInfo
.TupleType
in [ttRGB
, ttRGBAlpha
] then
490 // Swap channels of RGB/ARGB images. Binary RGB image files use BGR order.
491 SwapChannels(Images
[0], ChannelBlue
, ChannelRed
);
494 // Swap byte order if needed
495 if (MapInfo
.BitCount
= 16) and MapInfo
.IsBigEndian
then
496 SwapEndianWord(Bits
, Width
* Height
* Info
.BytesPerPixel
div SizeOf(Word));
500 // Handle binary PBM files (ttBlackAndWhite 1bit)
501 ScanLineSize
:= (Width
+ 7) div 8;
502 // Get total binary data size, read it from file to temp
503 // buffer and convert the data to Gray8
504 MonoSize
:= ScanLineSize
* Height
;
505 GetMem(MonoData
, MonoSize
);
507 Read(Handle
, MonoData
, MonoSize
);
508 Convert1To8(MonoData
, Bits
, Width
, Height
, ScanLineSize
, False);
509 // 1bit mono images must be scaled to 8bit, but inverted (where 0=white, 1=black)
510 for I
:= 0 to Width
* Height
- 1 do
511 PByteArray(Bits
)[I
] := 255 - PByteArray(Bits
)[I
] * 255;
520 if (MapInfo
.MaxVal
<> Pow2Int(MapInfo
.BitCount
) - 1) and
521 (MapInfo
.TupleType
in [ttGrayScale
, ttGrayScaleAlpha
, ttRGB
, ttRGBAlpha
]) then
524 // Scale color values according to MaxVal we got from header
526 for I
:= 0 to Width
* Height
* Info
.BytesPerPixel
div (MapInfo
.BitCount
shr 3) - 1 do
528 if MapInfo
.BitCount
= 8 then
529 Dest
^ := Dest
^ * 255 div MapInfo
.MaxVal
531 PWord(Dest
)^ := PWord(Dest
)^ * 65535 div MapInfo
.MaxVal
;
532 Inc(Dest
, MapInfo
.BitCount
shr 3);
540 function TPortableMapFileFormat
.SaveDataInternal(Handle
: TImagingHandle
;
541 const Images
: TDynImageDataArray
; Index
: Integer; var MapInfo
: TPortableMapInfo
): Boolean;
543 // Use Unix linebreak, for many viewers/editors it means that
544 // 16bit samples are stored as big endian - so we need to swap byte order
547 PixelDelimiter
= #32;
549 ImageToSave
: TImageData
;
550 MustBeFreed
: Boolean;
551 Info
: TImageFormatInfo
;
552 I
, LineLength
: LongInt;
554 Pixel32
: TColor32Rec
;
555 Pixel64
: TColor64Rec
;
558 procedure WriteString(S
: string; Delimiter
: Char = LineDelimiter
);
560 SetLength(S
, Length(S
) + 1);
561 S
[Length(S
)] := Delimiter
;
562 {$IF Defined(DCC) and Defined(UNICODE)}
563 GetIO
.Write(Handle
, @AnsiString(S
)[1], Length(S
));
565 GetIO
.Write(Handle
, @S
[1], Length(S
));
567 Inc(LineLength
, Length(S
));
570 procedure WriteHeader
;
572 WriteString('P' + MapInfo
.FormatId
);
573 if not MapInfo
.HasPAMHeader
then
575 // Write header of PGM, PPM, and PFM files
576 WriteString(IntToStr(ImageToSave
.Width
));
577 WriteString(IntToStr(ImageToSave
.Height
));
578 case MapInfo
.TupleType
of
579 ttGrayScale
, ttRGB
: WriteString(IntToStr(Pow2Int(MapInfo
.BitCount
) - 1));
580 ttGrayScaleFP
, ttRGBFP
:
582 // Negative value indicates that raster data is saved in little endian
583 WriteString(FloatToStr(-1.0, FUSFormat
));
589 // Write PAM file header
590 WriteString(Format('%s %d', [SPAMWidth
, ImageToSave
.Width
]));
591 WriteString(Format('%s %d', [SPAMHeight
, ImageToSave
.Height
]));
592 WriteString(Format('%s %d', [SPAMDepth
, MapInfo
.Depth
]));
593 WriteString(Format('%s %d', [SPAMMaxVal
, Pow2Int(MapInfo
.BitCount
) - 1]));
594 WriteString(Format('%s %s', [SPAMTupleType
, TupleTypeNames
[MapInfo
.TupleType
]]));
595 WriteString(SPAMEndHdr
);
601 if MakeCompatible(Images
[Index
], ImageToSave
, MustBeFreed
) then
602 with GetIO
, ImageToSave
do
604 Info
:= GetFormatInfo(Format
);
605 // Fill values of MapInfo record that were not filled by
606 // descendants in their SaveData methods
607 MapInfo
.BitCount
:= (Info
.BytesPerPixel
div Info
.ChannelCount
) * 8;
608 MapInfo
.Depth
:= Info
.ChannelCount
;
609 if MapInfo
.TupleType
= ttInvalid
then
611 if Info
.HasGrayChannel
then
613 if Info
.HasAlphaChannel
then
614 MapInfo
.TupleType
:= ttGrayScaleAlpha
616 MapInfo
.TupleType
:= ttGrayScale
;
620 if Info
.HasAlphaChannel
then
621 MapInfo
.TupleType
:= ttRGBAlpha
623 MapInfo
.TupleType
:= ttRGB
;
629 if not MapInfo
.Binary
then
633 // For each pixel find its text representation and write it to file
634 for I
:= 0 to Width
* Height
- 1 do
637 ifGray8
: WriteString(IntToStr(Src
^), PixelDelimiter
);
638 ifGray16
: WriteString(IntToStr(PWord(Src
)^), PixelDelimiter
);
640 with PColor24Rec(Src
)^ do
641 WriteString(SysUtils
.Format('%d %d %d', [R
, G
, B
]), PixelDelimiter
);
643 with PColor48Rec(Src
)^ do
644 WriteString(SysUtils
.Format('%d %d %d', [R
, G
, B
]), PixelDelimiter
);
646 // Lines in text PNM images should have length <70
647 if LineLength
> 65 then
650 WriteString('', LineDelimiter
);
652 Inc(Src
, Info
.BytesPerPixel
);
657 // Write binary images
658 if not (MapInfo
.TupleType
in [ttGrayScaleFP
, ttRGBFP
]) then
660 // Save integer binary images
661 if MapInfo
.BitCount
= 8 then
663 if MapInfo
.TupleType
in [ttGrayScale
, ttGrayScaleAlpha
] then
665 // 8bit grayscale images can be written in one Write call
666 Write(Handle
, Bits
, Size
);
670 // 8bit RGB/ARGB images: red and blue must be swapped and
671 // 3 or 4 bytes must be written
673 for I
:= 0 to Width
* Height
- 1 do
674 with PColor32Rec(Src
)^ do
676 if MapInfo
.TupleType
= ttRGBAlpha
then
681 Write(Handle
, @Pixel32
, Info
.BytesPerPixel
);
682 Inc(Src
, Info
.BytesPerPixel
);
688 // Images with 16bit channels: make sure that channel values are saved in big endian
690 if MapInfo
.TupleType
in [ttGrayScale
, ttGrayScaleAlpha
] then
692 // 16bit grayscale image
693 for I
:= 0 to Width
* Height
* Info
.BytesPerPixel
div SizeOf(Word) - 1 do
695 W
:= SwapEndianWord(PWord(Src
)^);
696 Write(Handle
, @W
, SizeOf(Word));
697 Inc(Src
, SizeOf(Word));
702 // RGB images with 16bit channels: swap RB and endian too
703 for I
:= 0 to Width
* Height
- 1 do
704 with PColor64Rec(Src
)^ do
706 if MapInfo
.TupleType
= ttRGBAlpha
then
707 Pixel64
.A
:= SwapEndianWord(A
);
708 Pixel64
.R
:= SwapEndianWord(B
);
709 Pixel64
.G
:= SwapEndianWord(G
);
710 Pixel64
.B
:= SwapEndianWord(R
);
711 Write(Handle
, @Pixel64
, Info
.BytesPerPixel
);
712 Inc(Src
, Info
.BytesPerPixel
);
719 // Floating point images (no need to swap endian here - little
720 // endian is specified in file header)
721 Write(Handle
, Bits
, Size
);
727 FreeImage(ImageToSave
);
731 function TPortableMapFileFormat
.TestFormat(Handle
: TImagingHandle
): Boolean;
737 if Handle
<> nil then
740 ReadCount
:= Read(Handle
, @Id
, SizeOf(Id
));
741 Seek(Handle
, -ReadCount
, smFromCurrent
);
742 Result
:= (Id
[0] = 'P') and (Id
[1] in [FIdNumbers
[0], FIdNumbers
[1]]) and
743 (Id
[2] in WhiteSpaces
);
749 procedure TPBMFileFormat
.Define
;
752 FName
:= SPBMFormatName
;
753 FFeatures
:= [ffLoad
];
760 procedure TPGMFileFormat
.Define
;
763 FName
:= SPGMFormatName
;
764 FSupportedFormats
:= PGMSupportedFormats
;
766 RegisterOption(ImagingPGMSaveBinary
, @FSaveBinary
);
770 function TPGMFileFormat
.SaveData(Handle
: TImagingHandle
;
771 const Images
: TDynImageDataArray
; Index
: Integer): Boolean;
773 MapInfo
: TPortableMapInfo
;
775 FillChar(MapInfo
, SizeOf(MapInfo
), 0);
777 MapInfo
.FormatId
:= FIdNumbers
[1]
779 MapInfo
.FormatId
:= FIdNumbers
[0];
780 MapInfo
.Binary
:= FSaveBinary
;
781 Result
:= SaveDataInternal(Handle
, Images
, Index
, MapInfo
);
784 procedure TPGMFileFormat
.ConvertToSupported(var Image
: TImageData
;
785 const Info
: TImageFormatInfo
);
787 ConvFormat
: TImageFormat
;
789 if Info
.IsFloatingPoint
then
790 // All FP images go to 16bit
791 ConvFormat
:= ifGray16
792 else if Info
.HasGrayChannel
then
793 // Grayscale will be 8 or 16 bit - depends on input's bitcount
794 ConvFormat
:= IffFormat(Info
.BytesPerPixel
div Info
.ChannelCount
> 1,
796 else if Info
.BytesPerPixel
> 4 then
797 // Large bitcounts -> 16bit
798 ConvFormat
:= ifGray16
800 // Rest of the formats -> 8bit
801 ConvFormat
:= ifGray8
;
803 ConvertImage(Image
, ConvFormat
);
808 procedure TPPMFileFormat
.Define
;
811 FName
:= SPPMFormatName
;
812 FSupportedFormats
:= PPMSupportedFormats
;
814 RegisterOption(ImagingPPMSaveBinary
, @FSaveBinary
);
818 function TPPMFileFormat
.SaveData(Handle
: TImagingHandle
;
819 const Images
: TDynImageDataArray
; Index
: Integer): Boolean;
821 MapInfo
: TPortableMapInfo
;
823 FillChar(MapInfo
, SizeOf(MapInfo
), 0);
825 MapInfo
.FormatId
:= FIdNumbers
[1]
827 MapInfo
.FormatId
:= FIdNumbers
[0];
828 MapInfo
.Binary
:= FSaveBinary
;
829 Result
:= SaveDataInternal(Handle
, Images
, Index
, MapInfo
);
832 procedure TPPMFileFormat
.ConvertToSupported(var Image
: TImageData
;
833 const Info
: TImageFormatInfo
);
835 ConvFormat
: TImageFormat
;
837 if Info
.IsFloatingPoint
then
838 // All FP images go to 48bit RGB
839 ConvFormat
:= ifR16G16B16
840 else if Info
.HasGrayChannel
then
841 // Grayscale will be 24 or 48 bit RGB - depends on input's bitcount
842 ConvFormat
:= IffFormat(Info
.BytesPerPixel
div Info
.ChannelCount
> 1,
843 ifR16G16B16
, ifR8G8B8
)
844 else if Info
.BytesPerPixel
> 4 then
845 // Large bitcounts -> 48bit RGB
846 ConvFormat
:= ifR16G16B16
848 // Rest of the formats -> 24bit RGB
849 ConvFormat
:= ifR8G8B8
;
851 ConvertImage(Image
, ConvFormat
);
856 procedure TPAMFileFormat
.Define
;
859 FName
:= SPAMFormatName
;
860 FSupportedFormats
:= PAMSupportedFormats
;
865 function TPAMFileFormat
.SaveData(Handle
: TImagingHandle
;
866 const Images
: TDynImageDataArray
; Index
: Integer): Boolean;
868 MapInfo
: TPortableMapInfo
;
870 FillChar(MapInfo
, SizeOf(MapInfo
), 0);
871 MapInfo
.FormatId
:= FIdNumbers
[0];
872 MapInfo
.Binary
:= True;
873 MapInfo
.HasPAMHeader
:= True;
874 Result
:= SaveDataInternal(Handle
, Images
, Index
, MapInfo
);
877 procedure TPAMFileFormat
.ConvertToSupported(var Image
: TImageData
;
878 const Info
: TImageFormatInfo
);
880 ConvFormat
: TImageFormat
;
882 if Info
.IsFloatingPoint
then
883 ConvFormat
:= IffFormat(Info
.HasAlphaChannel
, ifA16R16G16B16
, ifR16G16B16
)
884 else if Info
.HasGrayChannel
then
885 ConvFormat
:= IffFormat(Info
.HasAlphaChannel
, ifA16Gray16
, ifGray16
)
888 if Info
.BytesPerPixel
<= 4 then
889 ConvFormat
:= IffFormat(Info
.HasAlphaChannel
, ifA8R8G8B8
, ifR8G8B8
)
891 ConvFormat
:= IffFormat(Info
.HasAlphaChannel
, ifA16R16G16B16
, ifR16G16B16
);
893 ConvertImage(Image
, ConvFormat
);
898 procedure TPFMFileFormat
.Define
;
901 FName
:= SPFMFormatName
;
904 FSupportedFormats
:= PFMSupportedFormats
;
907 function TPFMFileFormat
.SaveData(Handle
: TImagingHandle
;
908 const Images
: TDynImageDataArray
; Index
: Integer): Boolean;
910 Info
: TImageFormatInfo
;
911 MapInfo
: TPortableMapInfo
;
913 FillChar(MapInfo
, SizeOf(MapInfo
), 0);
914 Info
:= GetFormatInfo(Images
[Index
].Format
);
916 if (Info
.ChannelCount
> 1) or Info
.IsIndexed
then
917 MapInfo
.TupleType
:= ttRGBFP
919 MapInfo
.TupleType
:= ttGrayScaleFP
;
921 if MapInfo
.TupleType
= ttGrayScaleFP
then
922 MapInfo
.FormatId
:= FIdNumbers
[1]
924 MapInfo
.FormatId
:= FIdNumbers
[0];
926 MapInfo
.Binary
:= True;
927 Result
:= SaveDataInternal(Handle
, Images
, Index
, MapInfo
);
930 procedure TPFMFileFormat
.ConvertToSupported(var Image
: TImageData
;
931 const Info
: TImageFormatInfo
);
933 if (Info
.ChannelCount
> 1) or Info
.IsIndexed
then
934 ConvertImage(Image
, ifB32G32R32F
)
936 ConvertImage(Image
, ifR32F
);
940 RegisterImageFileFormat(TPBMFileFormat
);
941 RegisterImageFileFormat(TPGMFileFormat
);
942 RegisterImageFileFormat(TPPMFileFormat
);
943 RegisterImageFileFormat(TPAMFileFormat
);
944 RegisterImageFileFormat(TPFMFileFormat
);
949 -- TODOS ----------------------------------------------------
952 -- 0.77.1 Changes/Bug Fixes -----------------------------------
953 - Native RGB floating point format of PFM is now supported by Imaging
954 so we use it now for saving instead of A32B32G32B32.
955 - String to float formatting changes (don't change global settings).
957 -- 0.26.3 Changes/Bug Fixes -----------------------------------
958 - Fixed D2009 Unicode related bug in PNM saving.
960 -- 0.24.3 Changes/Bug Fixes -----------------------------------
961 - Improved compatibility of 16bit/component image loading.
962 - Changes for better thread safety.
964 -- 0.21 Changes/Bug Fixes -----------------------------------
965 - Made modifications to ASCII PNM loading to be more "stream-safe".
966 - Fixed bug: indexed images saved as grayscale in PFM.
967 - Changed converting to supported formats little bit.
968 - Added scaling of channel values (non-FP and non-mono images) according
970 - Added buffering to loading of PNM files. More than 10x faster now
972 - Added saving support to PGM, PPM, PAM, and PFM format.
973 - Added PFM file format.
974 - Initial version created.