| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015 |
- {
- $Id$
- Vampyre Imaging Library
- by Marek Mauder
- http://imaginglib.sourceforge.net
- The contents of this file are used with permission, subject to the Mozilla
- Public License Version 1.1 (the "License"); you may not use this file except
- in compliance with the License. You may obtain a copy of the License at
- http://www.mozilla.org/MPL/MPL-1.1.html
- Software distributed under the License is distributed on an "AS IS" basis,
- WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
- the specific language governing rights and limitations under the License.
- Alternatively, the contents of this file may be used under the terms of the
- GNU Lesser General Public License (the "LGPL License"), in which case the
- provisions of the LGPL License are applicable instead of those above.
- If you wish to allow use of your version of this file only under the terms
- of the LGPL License and not to allow others to use your version of this file
- under the MPL, indicate your decision by deleting the provisions above and
- replace them with the notice and other provisions required by the LGPL
- License. If you do not delete the provisions above, a recipient may use
- your version of this file under either the MPL or the LGPL License.
- For more information about the LGPL: http://www.gnu.org/copyleft/lesser.html
- }
- { This unit contains loader/saver for Portable Maps file format family (or PNM).
- That includes PBM, PGM, PPM, PAM, and PFM formats.}
- unit ImagingPortableMaps;
- {$I ImagingOptions.inc}
- interface
- uses
- SysUtils, ImagingTypes, Imaging, ImagingFormats, ImagingUtility;
- type
- { Types of pixels of PNM images.}
- TTupleType = (ttInvalid, ttBlackAndWhite, ttGrayScale, ttRGB, ttBlackAndWhiteAlpha,
- ttGrayScaleAlpha, ttRGBAlpha, ttGrayScaleFP, ttRGBFP);
- { Record with info about PNM image used in both loading and saving functions.}
- TPortableMapInfo = record
- Width: LongInt;
- Height: LongInt;
- FormatId: AnsiChar;
- MaxVal: LongInt;
- BitCount: LongInt;
- Depth: LongInt;
- TupleType: TTupleType;
- Binary: Boolean;
- HasPAMHeader: Boolean;
- IsBigEndian: Boolean;
- end;
- { Base class for Portable Map file formats (or Portable AnyMaps or PNM).
- There are several types of PNM file formats that share common
- (simple) structure. This class can actually load all supported PNM formats.
- Saving is also done by this class but descendants (each for different PNM
- format) control it.}
- TPortableMapFileFormat = class(TImageFileFormat)
- protected
- FIdNumbers: TChar2;
- FSaveBinary: LongBool;
- function LoadData(Handle: TImagingHandle; var Images: TDynImageDataArray;
- OnlyFirstLevel: Boolean): Boolean; override;
- function SaveDataInternal(Handle: TImagingHandle; const Images: TDynImageDataArray;
- Index: LongInt; var MapInfo: TPortableMapInfo): Boolean;
- public
- constructor Create; override;
- function TestFormat(Handle: TImagingHandle): Boolean; override;
- published
- { If set to True images will be saved in binary format. If it is False
- they will be saved in text format (which could result in 5-10x bigger file).
- Default is value True. Note that PAM and PFM files are always saved in binary.}
- property SaveBinary: LongBool read FSaveBinary write FSaveBinary;
- end;
- { Portable Bit Map is used to store monochrome 1bit images. Raster data
- can be saved as text or binary data. Either way value of 0 represents white
- and 1 is black. As Imaging does not have support for 1bit data formats
- PBM images can be loaded but not saved. Loaded images are returned in
- ifGray8 format (witch pixel values scaled from 1bit to 8bit).}
- TPBMFileFormat = class(TPortableMapFileFormat)
- public
- constructor Create; override;
- end;
- { Portable Gray Map is used to store grayscale 8bit or 16bit images.
- Raster data can be saved as text or binary data.}
- TPGMFileFormat = class(TPortableMapFileFormat)
- protected
- function SaveData(Handle: TImagingHandle; const Images: TDynImageDataArray;
- Index: LongInt): Boolean; override;
- procedure ConvertToSupported(var Image: TImageData;
- const Info: TImageFormatInfo); override;
- public
- constructor Create; override;
- end;
- { Portable Pixel Map is used to store RGB images with 8bit or 16bit channels.
- Raster data can be saved as text or binary data.}
- TPPMFileFormat = class(TPortableMapFileFormat)
- protected
- function SaveData(Handle: TImagingHandle; const Images: TDynImageDataArray;
- Index: LongInt): Boolean; override;
- procedure ConvertToSupported(var Image: TImageData;
- const Info: TImageFormatInfo); override;
- public
- constructor Create; override;
- end;
- { Portable Arbitrary Map is format that can store image data formats
- of PBM, PGM, and PPM formats with optional alpha channel. Raster data
- can be stored only in binary format. All data formats supported
- by this format are ifGray8, ifGray16, ifA8Gray8, ifA16Gray16,
- ifR8G8B8, ifR16G16R16, ifA8R8G8B8, and ifA16R16G16B16.}
- TPAMFileFormat = class(TPortableMapFileFormat)
- protected
- function SaveData(Handle: TImagingHandle; const Images: TDynImageDataArray;
- Index: LongInt): Boolean; override;
- procedure ConvertToSupported(var Image: TImageData;
- const Info: TImageFormatInfo); override;
- public
- constructor Create; override;
- end;
- { Portable Float Map is unofficial extension of PNM format family which
- can store images with floating point pixels. Raster data is saved in
- binary format as array of IEEE 32 bit floating point numbers. One channel
- or RGB images are supported by PFM format (so no alpha).}
- TPFMFileFormat = class(TPortableMapFileFormat)
- protected
- function SaveData(Handle: TImagingHandle; const Images: TDynImageDataArray;
- Index: LongInt): Boolean; override;
- procedure ConvertToSupported(var Image: TImageData;
- const Info: TImageFormatInfo); override;
- public
- constructor Create; override;
- end;
- implementation
- const
- PortableMapDefaultBinary = True;
- SPBMFormatName = 'Portable Bit Map';
- SPBMMasks = '*.pbm';
- SPGMFormatName = 'Portable Gray Map';
- SPGMMasks = '*.pgm';
- PGMSupportedFormats = [ifGray8, ifGray16];
- SPPMFormatName = 'Portable Pixel Map';
- SPPMMasks = '*.ppm';
- PPMSupportedFormats = [ifR8G8B8, ifR16G16B16];
- SPAMFormatName = 'Portable Arbitrary Map';
- SPAMMasks = '*.pam';
- PAMSupportedFormats = [ifGray8, ifGray16, ifA8Gray8, ifA16Gray16,
- ifR8G8B8, ifR16G16B16, ifA8R8G8B8, ifA16R16G16B16];
- SPFMFormatName = 'Portable Float Map';
- SPFMMasks = '*.pfm';
- PFMSupportedFormats = [ifR32F, ifA32B32G32R32F];
- const
- { TAB, CR, LF, and Space are used as seperators in Portable map headers and data.}
- WhiteSpaces = [#9, #10, #13, #32];
- SPAMWidth = 'WIDTH';
- SPAMHeight = 'HEIGHT';
- SPAMDepth = 'DEPTH';
- SPAMMaxVal = 'MAXVAL';
- SPAMTupleType = 'TUPLTYPE';
- SPAMEndHdr = 'ENDHDR';
- { Size of buffer used to speed up text PNM loading/saving.}
- LineBufferCapacity = 16 * 1024;
- TupleTypeNames: array[TTupleType] of string = (
- 'INVALID', 'BLACKANDWHITE', 'GRAYSCALE', 'RGB',
- 'BLACKANDWHITE_ALPHA', 'GRAYSCALE_ALPHA', 'RGB_ALPHA', 'GRAYSCALEFP',
- 'RGBFP');
- { TPortableMapFileFormat }
- constructor TPortableMapFileFormat.Create;
- begin
- inherited Create;
- FCanLoad := True;
- FCanSave := True;
- FIsMultiImageFormat := False;
- FSaveBinary := PortableMapDefaultBinary;
- end;
- function TPortableMapFileFormat.LoadData(Handle: TImagingHandle;
- var Images: TDynImageDataArray; OnlyFirstLevel: Boolean): Boolean;
- var
- I, ScanLineSize, MonoSize: LongInt;
- Dest: PByte;
- MonoData: Pointer;
- Info: TImageFormatInfo;
- PixelFP: TColorFPRec;
- LineBuffer: array[0..LineBufferCapacity - 1] of AnsiChar;
- LineEnd, LinePos: LongInt;
- MapInfo: TPortableMapInfo;
- LineBreak: string;
- procedure CheckBuffer;
- begin
- if (LineEnd = 0) or (LinePos = LineEnd) then
- begin
- // Reload buffer if its is empty or its end was reached
- LineEnd := GetIO.Read(Handle, @LineBuffer[0], LineBufferCapacity);
- LinePos := 0;
- end;
- end;
- procedure FixInputPos;
- begin
- // Sets input's position to its real pos as it would be without buffering
- if LineEnd > 0 then
- begin
- GetIO.Seek(Handle, -LineEnd + LinePos, smFromCurrent);
- LineEnd := 0;
- end;
- end;
- function ReadString: string;
- var
- S: AnsiString;
- C: AnsiChar;
- begin
- // First skip all whitespace chars
- SetLength(S, 1);
- repeat
- CheckBuffer;
- S[1] := LineBuffer[LinePos];
- Inc(LinePos);
- if S[1] = '#' then
- repeat
- // Comment detected, skip everything until next line is reached
- CheckBuffer;
- S[1] := LineBuffer[LinePos];
- Inc(LinePos);
- until S[1] = #10;
- until not(S[1] in WhiteSpaces);
- // Now we have reached some chars other than white space, read them until
- // there is whitespace again
- repeat
- SetLength(S, Length(S) + 1);
- CheckBuffer;
- S[Length(S)] := LineBuffer[LinePos];
- Inc(LinePos);
- // Repeat until current char is whitespace or end of file is reached
- // (Line buffer has 0 bytes which happens only on EOF)
- until (S[Length(S)] in WhiteSpaces) or (LineEnd = 0);
- // Get rid of last char - whitespace or null
- SetLength(S, Length(S) - 1);
- // Move position to the beginning of next string (skip white space - needed
- // to make the loader stop at the right input position)
- repeat
- CheckBuffer;
- C := LineBuffer[LinePos];
- Inc(LinePos);
- until not (C in WhiteSpaces) or (LineEnd = 0);
- // Dec pos, current is the begining of the the string
- Dec(LinePos);
- Result := string(S);
- end;
- function ReadIntValue: LongInt; {$IFDEF USE_INLINE}inline;{$ENDIF}
- begin
- Result := StrToInt(ReadString);
- end;
- procedure FindLineBreak;
- var
- C: AnsiChar;
- begin
- LineBreak := #10;
- repeat
- CheckBuffer;
- C := LineBuffer[LinePos];
- Inc(LinePos);
- if C = #13 then
- LineBreak := #13#10;
- until C = #10;
- end;
- function ParseHeader: Boolean;
- var
- Id: TChar2;
- I: TTupleType;
- TupleTypeName: string;
- Scale: Single;
- OldSeparator: Char;
- begin
- Result := False;
- with GetIO do
- begin
- FillChar(MapInfo, SizeOf(MapInfo), 0);
- Read(Handle, @Id, SizeOf(Id));
- FindLineBreak;
- if Id[1] in ['1'..'6'] then
- begin
- // Read header for PBM, PGM, and PPM files
- MapInfo.Width := ReadIntValue;
- MapInfo.Height := ReadIntValue;
- if Id[1] in ['1', '4'] then
- begin
- MapInfo.MaxVal := 1;
- MapInfo.BitCount := 1
- end
- else
- begin
- // Read channel max value, <=255 for 8bit images, >255 for 16bit images
- // but some programs think its max colors so put <=256 here
- MapInfo.MaxVal := ReadIntValue;
- MapInfo.BitCount := Iff(MapInfo.MaxVal <= 256, 8, 16);
- end;
- MapInfo.Depth := 1;
- case Id[1] of
- '1', '4': MapInfo.TupleType := ttBlackAndWhite;
- '2', '5': MapInfo.TupleType := ttGrayScale;
- '3', '6':
- begin
- MapInfo.TupleType := ttRGB;
- MapInfo.Depth := 3;
- end;
- end;
- end
- else if Id[1] = '7' then
- begin
- // Read values from PAM header
- // WIDTH
- if (ReadString <> SPAMWidth) then Exit;
- MapInfo.Width := ReadIntValue;
- // HEIGHT
- if (ReadString <> SPAMheight) then Exit;
- MapInfo.Height := ReadIntValue;
- // DEPTH
- if (ReadString <> SPAMDepth) then Exit;
- MapInfo.Depth := ReadIntValue;
- // MAXVAL
- if (ReadString <> SPAMMaxVal) then Exit;
- MapInfo.MaxVal := ReadIntValue;
- MapInfo.BitCount := Iff(MapInfo.MaxVal <= 256, 8, 16);
- // TUPLETYPE
- if (ReadString <> SPAMTupleType) then Exit;
- TupleTypeName := ReadString;
- for I := Low(TTupleType) to High(TTupleType) do
- if SameText(TupleTypeName, TupleTypeNames[I]) then
- begin
- MapInfo.TupleType := I;
- Break;
- end;
- // ENDHDR
- if (ReadString <> SPAMEndHdr) then Exit;
- end
- else if Id[1] in ['F', 'f'] then
- begin
- // Read header of PFM file
- MapInfo.Width := ReadIntValue;
- MapInfo.Height := ReadIntValue;
- OldSeparator := DecimalSeparator;
- DecimalSeparator := '.';
- Scale := StrToFloatDef(ReadString, 0);
- DecimalSeparator := OldSeparator;
- MapInfo.IsBigEndian := Scale > 0.0;
- if Id[1] = 'F' then
- MapInfo.TupleType := ttRGBFP
- else
- MapInfo.TupleType := ttGrayScaleFP;
- MapInfo.Depth := Iff(MapInfo.TupleType = ttRGBFP, 3, 1);
- MapInfo.BitCount := Iff(MapInfo.TupleType = ttRGBFP, 96, 32);
- end;
- FixInputPos;
- MapInfo.Binary := (Id[1] in ['4', '5', '6', '7', 'F', 'f']);
- if MapInfo.Binary and not (Id[1] in ['F', 'f']) then
- begin
- // Mimic the behaviour of Photoshop and other editors/viewers:
- // If linenreaks in file are DOS CR/LF 16bit binary values are
- // little endian, Unix LF only linebreak indicates big endian.
- MapInfo.IsBigEndian := LineBreak = #10;
- end;
- // Check if values found in header are valid
- Result := (MapInfo.Width > 0) and (MapInfo.Height > 0) and
- (MapInfo.BitCount in [1, 8, 16, 32, 96]) and (MapInfo.TupleType <> ttInvalid);
- // Now check if image has proper number of channels (PAM)
- if Result then
- case MapInfo.TupleType of
- ttBlackAndWhite, ttGrayScale: Result := MapInfo.Depth = 1;
- ttBlackAndWhiteAlpha, ttGrayScaleAlpha: Result := MapInfo.Depth = 2;
- ttRGB: Result := MapInfo.Depth = 3;
- ttRGBAlpha: Result := MapInfo.Depth = 4;
- end;
- end;
- end;
- begin
- Result := False;
- LineEnd := 0;
- LinePos := 0;
- SetLength(Images, 1);
- with GetIO, Images[0] do
- begin
- Format := ifUnknown;
- // Try to parse file header
- if not ParseHeader then Exit;
- // Select appropriate data format based on values read from file header
- case MapInfo.TupleType of
- ttBlackAndWhite: Format := ifGray8;
- ttBlackAndWhiteAlpha: Format := ifA8Gray8;
- ttGrayScale: Format := IffFormat(MapInfo.BitCount = 8, ifGray8, ifGray16);
- ttGrayScaleAlpha: Format := IffFormat(MapInfo.BitCount = 8, ifA8Gray8, ifA16Gray16);
- ttRGB: Format := IffFormat(MapInfo.BitCount = 8, ifR8G8B8, ifR16G16B16);
- ttRGBAlpha: Format := IffFormat(MapInfo.BitCount = 8, ifA8R8G8B8, ifA16R16G16B16);
- ttGrayScaleFP: Format := ifR32F;
- ttRGBFP: Format := ifA32B32G32R32F;
- end;
- // Exit if no matching data format was found
- if Format = ifUnknown then Exit;
- NewImage(MapInfo.Width, MapInfo.Height, Format, Images[0]);
- Info := GetFormatInfo(Format);
- // Now read pixels from file to dest image
- if not MapInfo.Binary then
- begin
- Dest := Bits;
- for I := 0 to Width * Height - 1 do
- begin
- case Format of
- ifGray8:
- begin
- Dest^ := ReadIntValue;
- if MapInfo.BitCount = 1 then
- // If source is 1bit mono image (where 0=white, 1=black)
- // we must scale it to 8bits
- Dest^ := 255 - Dest^ * 255;
- end;
- ifGray16: PWord(Dest)^ := ReadIntValue;
- ifR8G8B8:
- with PColor24Rec(Dest)^ do
- begin
- R := ReadIntValue;
- G := ReadIntValue;
- B := ReadIntValue;
- end;
- ifR16G16B16:
- with PColor48Rec(Dest)^ do
- begin
- R := ReadIntValue;
- G := ReadIntValue;
- B := ReadIntValue;
- end;
- end;
- Inc(Dest, Info.BytesPerPixel);
- end;
- end
- else
- begin
- if MapInfo.BitCount > 1 then
- begin
- if not (MapInfo.TupleType in [ttGrayScaleFP, ttRGBFP]) then
- begin
- // Just copy bytes from binary Portable Maps (non 1bit, non FP)
- Read(Handle, Bits, Size);
- end
- else
- begin
- Dest := Bits;
- // FP images are in BGR order and endian swap maybe needed.
- // Some programs store scanlines in bottom-up order but
- // I will stick with Photoshops behaviour here
- for I := 0 to Width * Height - 1 do
- begin
- Read(Handle, @PixelFP, MapInfo.BitCount div 8);
- if MapInfo.TupleType = ttRGBFP then
- with PColorFPRec(Dest)^ do
- begin
- A := 1.0;
- R := PixelFP.R;
- G := PixelFP.G;
- B := PixelFP.B;
- if MapInfo.IsBigEndian then
- SwapEndianLongWord(PLongWord(Dest), 3);
- end
- else
- begin
- PSingle(Dest)^ := PixelFP.B;
- if MapInfo.IsBigEndian then
- SwapEndianLongWord(PLongWord(Dest), 1);
- end;
- Inc(Dest, Info.BytesPerPixel);
- end;
- end;
- if MapInfo.TupleType in [ttBlackAndWhite, ttBlackAndWhiteAlpha] then
- begin
- // Black and white PAM files must be scaled to 8bits. Note that
- // in PAM files 1=white, 0=black (reverse of PBM)
- for I := 0 to Width * Height * Iff(MapInfo.TupleType = ttBlackAndWhiteAlpha, 2, 1) - 1 do
- PByteArray(Bits)[I] := PByteArray(Bits)[I] * 255;
- end
- else if MapInfo.TupleType in [ttRGB, ttRGBAlpha] then
- begin
- // Swap channels of RGB/ARGB images. Binary RGB image files use BGR order.
- SwapChannels(Images[0], ChannelBlue, ChannelRed);
- end;
- // Swap byte order if needed
- if (MapInfo.BitCount = 16) and MapInfo.IsBigEndian then
- SwapEndianWord(Bits, Width * Height * Info.BytesPerPixel div SizeOf(Word));
- end
- else
- begin
- // Handle binary PBM files (ttBlackAndWhite 1bit)
- ScanLineSize := (Width + 7) div 8;
- // Get total binary data size, read it from file to temp
- // buffer and convert the data to Gray8
- MonoSize := ScanLineSize * Height;
- GetMem(MonoData, MonoSize);
- try
- Read(Handle, MonoData, MonoSize);
- Convert1To8(MonoData, Bits, Width, Height, ScanLineSize);
- // 1bit mono images must be scaled to 8bit (where 0=white, 1=black)
- for I := 0 to Width * Height - 1 do
- PByteArray(Bits)[I] := 255 - PByteArray(Bits)[I] * 255;
- finally
- FreeMem(MonoData);
- end;
- end;
- end;
- FixInputPos;
- if (MapInfo.MaxVal <> Pow2Int(MapInfo.BitCount) - 1) and
- (MapInfo.TupleType in [ttGrayScale, ttGrayScaleAlpha, ttRGB, ttRGBAlpha]) then
- begin
- Dest := Bits;
- // Scale color values according to MaxVal we got from header
- // if necessary.
- for I := 0 to Width * Height * Info.BytesPerPixel div (MapInfo.BitCount shr 3) - 1 do
- begin
- if MapInfo.BitCount = 8 then
- Dest^ := Dest^ * 255 div MapInfo.MaxVal
- else
- PWord(Dest)^ := PWord(Dest)^ * 65535 div MapInfo.MaxVal;
- Inc(Dest, MapInfo.BitCount shr 3);
- end;
- end;
- Result := True;
- end;
- end;
- function TPortableMapFileFormat.SaveDataInternal(Handle: TImagingHandle;
- const Images: TDynImageDataArray; Index: Integer; var MapInfo: TPortableMapInfo): Boolean;
- const
- // Use Unix linebreak, for many viewers/editors it means that
- // 16bit samples are stored as big endian - so we need to swap byte order
- // before saving
- LineDelimiter = #10;
- PixelDelimiter = #32;
- var
- ImageToSave: TImageData;
- MustBeFreed: Boolean;
- Info: TImageFormatInfo;
- I, LineLength: LongInt;
- Src: PByte;
- Pixel32: TColor32Rec;
- Pixel64: TColor64Rec;
- W: Word;
- procedure WriteString(S: string; Delimiter: Char = LineDelimiter);
- begin
- SetLength(S, Length(S) + 1);
- S[Length(S)] := Delimiter;
- GetIO.Write(Handle, @S[1], Length(S));
- Inc(LineLength, Length(S));
- end;
- procedure WriteHeader;
- var
- OldSeparator: Char;
- begin
- WriteString('P' + MapInfo.FormatId);
- if not MapInfo.HasPAMHeader then
- begin
- // Write header of PGM, PPM, and PFM files
- WriteString(IntToStr(ImageToSave.Width));
- WriteString(IntToStr(ImageToSave.Height));
- case MapInfo.TupleType of
- ttGrayScale, ttRGB: WriteString(IntToStr(Pow2Int(MapInfo.BitCount) - 1));
- ttGrayScaleFP, ttRGBFP:
- begin
- OldSeparator := DecimalSeparator;
- DecimalSeparator := '.';
- // Negative value indicates that raster data is saved in little endian
- WriteString(FloatToStr(-1.0));
- DecimalSeparator := OldSeparator;
- end;
- end;
- end
- else
- begin
- // Write PAM file header
- WriteString(Format('%s %d', [SPAMWidth, ImageToSave.Width]));
- WriteString(Format('%s %d', [SPAMHeight, ImageToSave.Height]));
- WriteString(Format('%s %d', [SPAMDepth, MapInfo.Depth]));
- WriteString(Format('%s %d', [SPAMMaxVal, Pow2Int(MapInfo.BitCount) - 1]));
- WriteString(Format('%s %s', [SPAMTupleType, TupleTypeNames[MapInfo.TupleType]]));
- WriteString(SPAMEndHdr);
- end;
- end;
- begin
- Result := False;
- if MakeCompatible(Images[Index], ImageToSave, MustBeFreed) then
- with GetIO, ImageToSave do
- try
- Info := GetFormatInfo(Format);
- // Fill values of MapInfo record that were not filled by
- // descendants in their SaveData methods
- MapInfo.BitCount := (Info.BytesPerPixel div Info.ChannelCount) * 8;
- MapInfo.Depth := Info.ChannelCount;
- if MapInfo.TupleType = ttInvalid then
- begin
- if Info.HasGrayChannel then
- begin
- if Info.HasAlphaChannel then
- MapInfo.TupleType := ttGrayScaleAlpha
- else
- MapInfo.TupleType := ttGrayScale;
- end
- else
- begin
- if Info.HasAlphaChannel then
- MapInfo.TupleType := ttRGBAlpha
- else
- MapInfo.TupleType := ttRGB;
- end;
- end;
- // Write file header
- WriteHeader;
- if not MapInfo.Binary then
- begin
- Src := Bits;
- LineLength := 0;
- // For each pixel find its text representation and write it to file
- for I := 0 to Width * Height - 1 do
- begin
- case Format of
- ifGray8: WriteString(IntToStr(Src^), PixelDelimiter);
- ifGray16: WriteString(IntToStr(PWord(Src)^), PixelDelimiter);
- ifR8G8B8:
- with PColor24Rec(Src)^ do
- WriteString(SysUtils.Format('%d %d %d', [R, G, B]), PixelDelimiter);
- ifR16G16B16:
- with PColor48Rec(Src)^ do
- WriteString(SysUtils.Format('%d %d %d', [R, G, B]), PixelDelimiter);
- end;
- // Lines in text PNM images should have length <70
- if LineLength > 65 then
- begin
- LineLength := 0;
- WriteString('', LineDelimiter);
- end;
- Inc(Src, Info.BytesPerPixel);
- end;
- end
- else
- begin
- // Write binary images
- if not (MapInfo.TupleType in [ttGrayScaleFP, ttRGBFP]) then
- begin
- // Save integer binary images
- if MapInfo.BitCount = 8 then
- begin
- if MapInfo.TupleType in [ttGrayScale, ttGrayScaleAlpha] then
- begin
- // 8bit grayscale images can be written in one Write call
- Write(Handle, Bits, Size);
- end
- else
- begin
- // 8bit RGB/ARGB images: read and blue must be swapped and
- // 3 or 4 bytes must be written
- Src := Bits;
- for I := 0 to Width * Height - 1 do
- with PColor32Rec(Src)^ do
- begin
- if MapInfo.TupleType = ttRGBAlpha then
- Pixel32.A := A;
- Pixel32.R := B;
- Pixel32.G := G;
- Pixel32.B := R;
- Write(Handle, @Pixel32, Info.BytesPerPixel);
- Inc(Src, Info.BytesPerPixel);
- end;
- end;
- end
- else
- begin
- // Images with 16bit channels: make sure that channel values are saved in big endian
- Src := Bits;
- if MapInfo.TupleType in [ttGrayScale, ttGrayScaleAlpha] then
- begin
- // 16bit grayscale image
- for I := 0 to Width * Height * Info.BytesPerPixel div SizeOf(Word) - 1 do
- begin
- W := SwapEndianWord(PWord(Src)^);
- Write(Handle, @W, SizeOf(Word));
- Inc(Src, SizeOf(Word));
- end;
- end
- else
- begin
- // RGB images with 16bit channels: swap RB and endian too
- for I := 0 to Width * Height - 1 do
- with PColor64Rec(Src)^ do
- begin
- if MapInfo.TupleType = ttRGBAlpha then
- Pixel64.A := SwapEndianWord(A);
- Pixel64.R := SwapEndianWord(B);
- Pixel64.G := SwapEndianWord(G);
- Pixel64.B := SwapEndianWord(R);
- Write(Handle, @Pixel64, Info.BytesPerPixel);
- Inc(Src, Info.BytesPerPixel);
- end;
- end;
- end;
- end
- else
- begin
- // Floating point images (no need to swap endian here - little
- // endian is specified in file header)
- if MapInfo.TupleType = ttGrayScaleFP then
- begin
- // Grayscale images can be written in one Write call
- Write(Handle, Bits, Size);
- end
- else
- begin
- // Expected data format of PFM RGB file is B32G32R32F which is not
- // supported by Imaging. We must write pixels one by one and
- // write only RGB part of A32B32G32B32 image.
- Src := Bits;
- for I := 0 to Width * Height - 1 do
- begin
- Write(Handle, Src, SizeOf(Single) * 3);
- Inc(Src, Info.BytesPerPixel);
- end;
- end;
- end;
- end;
- Result := True;
- finally
- if MustBeFreed then
- FreeImage(ImageToSave);
- end;
- end;
- function TPortableMapFileFormat.TestFormat(Handle: TImagingHandle): Boolean;
- var
- Id: TChar4;
- ReadCount: LongInt;
- begin
- Result := False;
- if Handle <> nil then
- with GetIO do
- begin
- ReadCount := Read(Handle, @Id, SizeOf(Id));
- Seek(Handle, -ReadCount, smFromCurrent);
- Result := (Id[0] = 'P') and (Id[1] in [FIdNumbers[0], FIdNumbers[1]]) and
- (Id[2] in WhiteSpaces);
- end;
- end;
- { TPBMFileFormat }
- constructor TPBMFileFormat.Create;
- begin
- inherited Create;
- FName := SPBMFormatName;
- FCanSave := False;
- AddMasks(SPBMMasks);
- FIdNumbers := '14';
- end;
- { TPGMFileFormat }
- constructor TPGMFileFormat.Create;
- begin
- inherited Create;
- FName := SPGMFormatName;
- FSupportedFormats := PGMSupportedFormats;
- AddMasks(SPGMMasks);
- RegisterOption(ImagingPGMSaveBinary, @FSaveBinary);
- FIdNumbers := '25';
- end;
- function TPGMFileFormat.SaveData(Handle: TImagingHandle;
- const Images: TDynImageDataArray; Index: Integer): Boolean;
- var
- MapInfo: TPortableMapInfo;
- begin
- FillChar(MapInfo, SizeOf(MapInfo), 0);
- if FSaveBinary then
- MapInfo.FormatId := FIdNumbers[1]
- else
- MapInfo.FormatId := FIdNumbers[0];
- MapInfo.Binary := FSaveBinary;
- Result := SaveDataInternal(Handle, Images, Index, MapInfo);
- end;
- procedure TPGMFileFormat.ConvertToSupported(var Image: TImageData;
- const Info: TImageFormatInfo);
- var
- ConvFormat: TImageFormat;
- begin
- if Info.IsFloatingPoint then
- // All FP images go to 16bit
- ConvFormat := ifGray16
- else if Info.HasGrayChannel then
- // Grayscale will be 8 or 16 bit - depends on input's bitcount
- ConvFormat := IffFormat(Info.BytesPerPixel div Info.ChannelCount > 1,
- ifGray16, ifGray8)
- else if Info.BytesPerPixel > 4 then
- // Large bitcounts -> 16bit
- ConvFormat := ifGray16
- else
- // Rest of the formats -> 8bit
- ConvFormat := ifGray8;
- ConvertImage(Image, ConvFormat);
- end;
- { TPPMFileFormat }
- constructor TPPMFileFormat.Create;
- begin
- inherited Create;
- FName := SPPMFormatName;
- FSupportedFormats := PPMSupportedFormats;
- AddMasks(SPPMMasks);
- RegisterOption(ImagingPPMSaveBinary, @FSaveBinary);
- FIdNumbers := '36';
- end;
- function TPPMFileFormat.SaveData(Handle: TImagingHandle;
- const Images: TDynImageDataArray; Index: Integer): Boolean;
- var
- MapInfo: TPortableMapInfo;
- begin
- FillChar(MapInfo, SizeOf(MapInfo), 0);
- if FSaveBinary then
- MapInfo.FormatId := FIdNumbers[1]
- else
- MapInfo.FormatId := FIdNumbers[0];
- MapInfo.Binary := FSaveBinary;
- Result := SaveDataInternal(Handle, Images, Index, MapInfo);
- end;
- procedure TPPMFileFormat.ConvertToSupported(var Image: TImageData;
- const Info: TImageFormatInfo);
- var
- ConvFormat: TImageFormat;
- begin
- if Info.IsFloatingPoint then
- // All FP images go to 48bit RGB
- ConvFormat := ifR16G16B16
- else if Info.HasGrayChannel then
- // Grayscale will be 24 or 48 bit RGB - depends on input's bitcount
- ConvFormat := IffFormat(Info.BytesPerPixel div Info.ChannelCount > 1,
- ifR16G16B16, ifR8G8B8)
- else if Info.BytesPerPixel > 4 then
- // Large bitcounts -> 48bit RGB
- ConvFormat := ifR16G16B16
- else
- // Rest of the formats -> 24bit RGB
- ConvFormat := ifR8G8B8;
- ConvertImage(Image, ConvFormat);
- end;
- { TPAMFileFormat }
- constructor TPAMFileFormat.Create;
- begin
- inherited Create;
- FName := SPAMFormatName;
- FSupportedFormats := PAMSupportedFormats;
- AddMasks(SPAMMasks);
- FIdNumbers := '77';
- end;
- function TPAMFileFormat.SaveData(Handle: TImagingHandle;
- const Images: TDynImageDataArray; Index: Integer): Boolean;
- var
- MapInfo: TPortableMapInfo;
- begin
- FillChar(MapInfo, SizeOf(MapInfo), 0);
- MapInfo.FormatId := FIdNumbers[0];
- MapInfo.Binary := True;
- MapInfo.HasPAMHeader := True;
- Result := SaveDataInternal(Handle, Images, Index, MapInfo);
- end;
- procedure TPAMFileFormat.ConvertToSupported(var Image: TImageData;
- const Info: TImageFormatInfo);
- var
- ConvFormat: TImageFormat;
- begin
- if Info.IsFloatingPoint then
- ConvFormat := IffFormat(Info.HasAlphaChannel, ifA16R16G16B16, ifR16G16B16)
- else if Info.HasGrayChannel then
- ConvFormat := IffFormat(Info.HasAlphaChannel, ifA16Gray16, ifGray16)
- else
- begin
- if Info.BytesPerPixel <= 4 then
- ConvFormat := IffFormat(Info.HasAlphaChannel, ifA8R8G8B8, ifR8G8B8)
- else
- ConvFormat := IffFormat(Info.HasAlphaChannel, ifA16R16G16B16, ifR16G16B16);
- end;
- ConvertImage(Image, ConvFormat);
- end;
- { TPFMFileFormat }
- constructor TPFMFileFormat.Create;
- begin
- inherited Create;
- FName := SPFMFormatName;
- AddMasks(SPFMMasks);
- FIdNumbers := 'Ff';
- FSupportedFormats := PFMSupportedFormats;
- end;
- function TPFMFileFormat.SaveData(Handle: TImagingHandle;
- const Images: TDynImageDataArray; Index: Integer): Boolean;
- var
- Info: TImageFormatInfo;
- MapInfo: TPortableMapInfo;
- begin
- FillChar(MapInfo, SizeOf(MapInfo), 0);
- Info := GetFormatInfo(Images[Index].Format);
- if (Info.ChannelCount > 1) or Info.IsIndexed then
- MapInfo.TupleType := ttRGBFP
- else
- MapInfo.TupleType := ttGrayScaleFP;
- if MapInfo.TupleType = ttGrayScaleFP then
- MapInfo.FormatId := FIdNumbers[1]
- else
- MapInfo.FormatId := FIdNumbers[0];
- MapInfo.Binary := True;
- Result := SaveDataInternal(Handle, Images, Index, MapInfo);
- end;
- procedure TPFMFileFormat.ConvertToSupported(var Image: TImageData;
- const Info: TImageFormatInfo);
- begin
- if (Info.ChannelCount > 1) or Info.IsIndexed then
- ConvertImage(Image, ifA32B32G32R32F)
- else
- ConvertImage(Image, ifR32F);
- end;
- initialization
- RegisterImageFileFormat(TPBMFileFormat);
- RegisterImageFileFormat(TPGMFileFormat);
- RegisterImageFileFormat(TPPMFileFormat);
- RegisterImageFileFormat(TPAMFileFormat);
- RegisterImageFileFormat(TPFMFileFormat);
- {
- File Notes:
- -- TODOS ----------------------------------------------------
- - nothing now
- -- 0.24.3 Changes/Bug Fixes -----------------------------------
- - Improved compatibility of 16bit/component image loading.
- - Changes for better thread safety.
- -- 0.21 Changes/Bug Fixes -----------------------------------
- - Made modifications to ASCII PNM loading to be more "stream-safe".
- - Fixed bug: indexed images saved as grayscale in PFM.
- - Changed converting to supported formats little bit.
- - Added scaling of channel values (non-FP and non-mono images) according
- to MaxVal.
- - Added buffering to loading of PNM files. More than 10x faster now
- for text files.
- - Added saving support to PGM, PPM, PAM, and PFM format.
- - Added PFM file format.
- - Initial version created.
- }
- end.
|