Statistics
| Branch: | Tag: | Revision:

root / Imaging / ImagingPortableMaps.pas @ 0:95bd93c28625

History | View | Annotate | Download (32 kB)

1
{
2
  $Id: ImagingPortableMaps.pas 107 2007-11-06 23:37:48Z galfar $
3
  Vampyre Imaging Library
4
  by Marek Mauder
5
  http://imaginglib.sourceforge.net
6
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
11
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.
15
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.
25
26
  For more information about the LGPL: http://www.gnu.org/copyleft/lesser.html
27
}
28
29
{ This unit contains loader/saver for Portable Maps file format family (or PNM).
30
  That includes PBM, PGM, PPM, PAM, and PFM formats.}
31
unit ImagingPortableMaps;
32
33
{$I ImagingOptions.inc}
34
35
interface
36
37
uses
38
  SysUtils, ImagingTypes, Imaging, ImagingFormats, ImagingUtility;
39
40
type
41
  { Types of pixels of PNM images.}
42
  TTupleType = (ttInvalid, ttBlackAndWhite, ttGrayScale, ttRGB, ttBlackAndWhiteAlpha,
43
    ttGrayScaleAlpha, ttRGBAlpha, ttGrayScaleFP, ttRGBFP);
44
45
  { Record with info about PNM image used in both loading and saving functions.}
46
  TPortableMapInfo = record
47
    Width: LongInt;
48
    Height: LongInt;
49
    FormatId: Char;
50
    MaxVal: LongInt;
51
    BitCount: LongInt;
52
    Depth: LongInt;
53
    TupleType: TTupleType;
54
    Binary: Boolean;
55
    HasPAMHeader: Boolean;
56
    IsBigEndian: Boolean;
57
  end;
58
59
  { Base class for Portable Map file formats (or Portable AnyMaps or PNM).
60
    There are several types of PNM file formats that share common
61
    (simple) structure. This class can actually load all supported PNM formats.
62
    Saving is also done by this class but descendants (each for different PNM
63
    format) control it.}
64
  TPortableMapFileFormat = class(TImageFileFormat)
65
  protected
66
    FIdNumbers: TChar2;
67
    FSaveBinary: LongBool;
68
    FMapInfo: TPortableMapInfo;
69
    function LoadData(Handle: TImagingHandle; var Images: TDynImageDataArray;
70
      OnlyFirstLevel: Boolean): Boolean; override;
71
    function SaveData(Handle: TImagingHandle; const Images: TDynImageDataArray;
72
      Index: LongInt): Boolean; override;
73
  public
74
    constructor Create; override;
75
    function TestFormat(Handle: TImagingHandle): Boolean; override;
76
  published  
77
    { If set to True images will be saved in binary format. If it is False
78
      they will be saved in text format (which could result in 5-10x bigger file).
79
      Default is value True. Note that PAM and PFM files are always saved in binary.}
80
    property SaveBinary: LongBool read FSaveBinary write FSaveBinary;
81
  end;
82
83
  { Portable Bit Map is used to store monochrome 1bit images. Raster data
84
    can be saved as text or binary data. Either way value of 0 represents white
85
    and 1 is black. As Imaging does not have support for 1bit data formats
86
    PBM images can be loaded but not saved. Loaded images are returned in
87
    ifGray8 format (witch pixel values scaled from 1bit to 8bit).}
88
  TPBMFileFormat = class(TPortableMapFileFormat)
89
  public
90
    constructor Create; override;
91
  end;
92
93
  { Portable Gray Map is used to store grayscale 8bit or 16bit images.
94
    Raster data can be saved as text or binary data.}
95
  TPGMFileFormat = class(TPortableMapFileFormat)
96
  protected
97
    function SaveData(Handle: TImagingHandle; const Images: TDynImageDataArray;
98
      Index: LongInt): Boolean; override;
99
    procedure ConvertToSupported(var Image: TImageData;
100
      const Info: TImageFormatInfo); override;
101
  public
102
    constructor Create; override;
103
  end;
104
105
  { Portable Pixel Map is used to store RGB images with 8bit or 16bit channels.
106
    Raster data can be saved as text or binary data.}
107
  TPPMFileFormat = class(TPortableMapFileFormat)
108
  protected
109
    function SaveData(Handle: TImagingHandle; const Images: TDynImageDataArray;
110
      Index: LongInt): Boolean; override;
111
    procedure ConvertToSupported(var Image: TImageData;
112
      const Info: TImageFormatInfo); override;
113
  public
114
    constructor Create; override;
115
  end;
116
117
  { Portable Arbitrary Map is format that can store image data formats
118
    of PBM, PGM, and PPM formats with optional alpha channel. Raster data
119
    can be stored only in binary format. All data formats supported
120
    by this format are ifGray8, ifGray16, ifA8Gray8, ifA16Gray16,
121
    ifR8G8B8, ifR16G16R16, ifA8R8G8B8, and ifA16R16G16B16.}
122
  TPAMFileFormat = class(TPortableMapFileFormat)
123
  protected
124
    function SaveData(Handle: TImagingHandle; const Images: TDynImageDataArray;
125
      Index: LongInt): Boolean; override;
126
    procedure ConvertToSupported(var Image: TImageData;
127
      const Info: TImageFormatInfo); override;
128
  public
129
    constructor Create; override;
130
  end;
131
132
  { Portable Float Map is unofficial extension of PNM format family which
133
    can store images with floating point pixels. Raster data is saved in
134
    binary format as array of IEEE 32 bit floating point numbers. One channel
135
    or RGB images are supported by PFM format (so no alpha).}
136
  TPFMFileFormat = class(TPortableMapFileFormat)
137
  protected
138
    function SaveData(Handle: TImagingHandle; const Images: TDynImageDataArray;
139
      Index: LongInt): Boolean; override;
140
    procedure ConvertToSupported(var Image: TImageData;
141
      const Info: TImageFormatInfo); override;
142
  public
143
    constructor Create; override;
144
  end;
145
146
implementation
147
148
const
149
  PortableMapDefaultBinary = True;
150
151
  SPBMFormatName = 'Portable Bit Map';
152
  SPBMMasks =      '*.pbm';
153
  SPGMFormatName = 'Portable Gray Map';
154
  SPGMMasks =      '*.pgm';
155
  PGMSupportedFormats = [ifGray8, ifGray16];
156
  SPPMFormatName = 'Portable Pixel Map';
157
  SPPMMasks =      '*.ppm';
158
  PPMSupportedFormats = [ifR8G8B8, ifR16G16B16];
159
  SPAMFormatName = 'Portable Arbitrary Map';
160
  SPAMMasks =      '*.pam';
161
  PAMSupportedFormats = [ifGray8, ifGray16, ifA8Gray8, ifA16Gray16,
162
    ifR8G8B8, ifR16G16B16, ifA8R8G8B8, ifA16R16G16B16];
163
  SPFMFormatName = 'Portable Float Map';
164
  SPFMMasks =      '*.pfm';
165
  PFMSupportedFormats = [ifR32F, ifA32B32G32R32F];
166
167
const
168
  { TAB, CR, LF, and Space are used as seperators in Portable map headers and data.}
169
  WhiteSpaces = [#9, #10, #13, #32];
170
  SPAMWidth = 'WIDTH';
171
  SPAMHeight = 'HEIGHT';
172
  SPAMDepth = 'DEPTH';
173
  SPAMMaxVal = 'MAXVAL';
174
  SPAMTupleType = 'TUPLTYPE';
175
  SPAMEndHdr = 'ENDHDR';
176
177
  { Size of buffer used to speed up text PNM loading/saving.}
178
  LineBufferCapacity = 16 * 1024;
179
180
  TupleTypeNames: array[TTupleType] of string = (
181
    'INVALID', 'BLACKANDWHITE', 'GRAYSCALE', 'RGB',
182
    'BLACKANDWHITE_ALPHA', 'GRAYSCALE_ALPHA', 'RGB_ALPHA', 'GRAYSCALEFP',
183
    'RGBFP');
184
185
{ TPortableMapFileFormat }
186
187
constructor TPortableMapFileFormat.Create;
188
begin
189
  inherited Create;
190
  FCanLoad := True;
191
  FCanSave := True;
192
  FIsMultiImageFormat := False;
193
  FSaveBinary := PortableMapDefaultBinary;
194
end;
195
196
function TPortableMapFileFormat.LoadData(Handle: TImagingHandle;
197
  var Images: TDynImageDataArray; OnlyFirstLevel: Boolean): Boolean;
198
var
199
  I, ScanLineSize, MonoSize: LongInt;
200
  Dest: PByte;
201
  MonoData: Pointer;
202
  Info: TImageFormatInfo;
203
  PixelFP: TColorFPRec;
204
  LineBuffer: array[0..LineBufferCapacity - 1] of Char;
205
  LineEnd, LinePos: LongInt;
206
207
  procedure CheckBuffer;
208
  begin
209
    if (LineEnd = 0) or (LinePos = LineEnd) then
210
    begin
211
      // Reload buffer if its is empty or its end was reached
212
      LineEnd := GetIO.Read(Handle, @LineBuffer[0], LineBufferCapacity);
213
      LinePos := 0;
214
    end;
215
  end;
216
217
  procedure FixInputPos;
218
  begin
219
    // Sets input's position to its real pos as it would be without buffering
220
    if LineEnd > 0 then
221
    begin
222
      GetIO.Seek(Handle, -LineEnd + LinePos, smFromCurrent);
223
      LineEnd := 0;
224
    end;
225
  end;
226
227
  function ReadString: string;
228
  var
229
    S: AnsiString;
230
    C: Char;
231
  begin
232
    // First skip all whitespace chars
233
    SetLength(S, 1);
234
    repeat
235
      CheckBuffer;
236
      S[1] := LineBuffer[LinePos];
237
      Inc(LinePos);
238
      if S[1] = '#' then
239
      repeat
240
        // Comment detected, skip everything until next line is reached
241
        CheckBuffer;
242
        S[1] := LineBuffer[LinePos];
243
        Inc(LinePos);
244
      until S[1] = #10;
245
    until not(S[1] in WhiteSpaces);
246
    // Now we have reached some chars other than white space, read them until
247
    // there is whitespace again
248
    repeat
249
      SetLength(S, Length(S) + 1);
250
      CheckBuffer;
251
      S[Length(S)] := LineBuffer[LinePos];
252
      Inc(LinePos);
253
      // Repeat until current char is whitespace or end of file is reached
254
      // (Line buffer has 0 bytes which happens only on EOF)
255
    until (S[Length(S)] in WhiteSpaces) or (LineEnd = 0);
256
    // Get rid of last char - whitespace or null
257
    SetLength(S, Length(S) - 1);
258
    // Move position to the beginning of next string (skip white space - needed
259
    // to make the loader stop at the right input position)
260
    repeat
261
      CheckBuffer;
262
      C := LineBuffer[LinePos];
263
      Inc(LinePos);
264
    until not (C in WhiteSpaces) or (LineEnd = 0);
265
    // Dec pos, current is the beggining of the the string
266
    Dec(LinePos);
267
268
    Result := S;
269
  end;
270
271
  function ReadIntValue: LongInt; {$IFDEF USE_INLINE}inline;{$ENDIF}
272
  begin
273
    Result := StrToInt(ReadString);
274
  end;
275
276
  function ParseHeader: Boolean;
277
  var
278
    Id: TChar2;
279
    I: TTupleType;
280
    TupleTypeName: string;
281
    Scale: Single;
282
    OldSeparator: Char;
283
  begin
284
    Result := False;
285
    with GetIO do
286
    begin
287
      FillChar(FMapInfo, SizeOf(FMapInfo), 0);
288
      Read(Handle, @Id, SizeOf(Id));
289
      if Id[1] in ['1'..'6'] then
290
      begin
291
        // Read header for PBM, PGM, and PPM files
292
        FMapInfo.Width := ReadIntValue;
293
        FMapInfo.Height := ReadIntValue;
294
        if Id[1] in ['1', '4'] then
295
        begin
296
          FMapInfo.MaxVal := 1;
297
          FMapInfo.BitCount := 1
298
        end
299
        else
300
        begin
301
          // Read channel max value, <=255 for 8bit images, >255 for 16bit images
302
          // but some programs think its max colors so put <=256 here
303
          FMapInfo.MaxVal := ReadIntValue;
304
          FMapInfo.BitCount := Iff(FMapInfo.MaxVal <= 256, 8, 16);
305
        end;
306
307
        FMapInfo.Depth := 1;
308
        case Id[1] of
309
          '1', '4': FMapInfo.TupleType := ttBlackAndWhite;
310
          '2', '5': FMapInfo.TupleType := ttGrayScale;
311
          '3', '6':
312
            begin
313
              FMapInfo.TupleType := ttRGB;
314
              FMapInfo.Depth := 3;
315
            end;
316
        end;
317
      end
318
      else if Id[1] = '7' then
319
      begin
320
        // Read values from PAM header
321
        // WIDTH
322
        if (ReadString <> SPAMWidth) then Exit;
323
        FMapInfo.Width := ReadIntValue;
324
        // HEIGHT
325
        if (ReadString <> SPAMheight) then Exit;
326
        FMapInfo.Height := ReadIntValue;
327
        // DEPTH
328
        if (ReadString <> SPAMDepth) then Exit;
329
        FMapInfo.Depth := ReadIntValue;
330
        // MAXVAL
331
        if (ReadString <> SPAMMaxVal) then Exit;
332
        FMapInfo.MaxVal := ReadIntValue;
333
        FMapInfo.BitCount := Iff(FMapInfo.MaxVal <= 256, 8, 16);
334
        // TUPLETYPE
335
        if (ReadString <> SPAMTupleType) then Exit;
336
        TupleTypeName := ReadString;
337
        for I := Low(TTupleType) to High(TTupleType) do
338
          if SameText(TupleTypeName, TupleTypeNames[I]) then
339
          begin
340
            FMapInfo.TupleType := I;
341
            Break;
342
          end;
343
        // ENDHDR
344
        if (ReadString <> SPAMEndHdr) then Exit;
345
      end
346
      else if Id[1] in ['F', 'f'] then
347
      begin
348
        // Read header of PFM file
349
        FMapInfo.Width := ReadIntValue;
350
        FMapInfo.Height := ReadIntValue;
351
        OldSeparator := DecimalSeparator;
352
        DecimalSeparator := '.';
353
        Scale := StrToFloatDef(ReadString, 0);
354
        DecimalSeparator := OldSeparator;
355
        FMapInfo.IsBigEndian := Scale > 0.0;
356
        if Id[1] = 'F' then
357
          FMapInfo.TupleType := ttRGBFP
358
        else
359
          FMapInfo.TupleType := ttGrayScaleFP;
360
        FMapInfo.Depth := Iff(FMapInfo.TupleType = ttRGBFP, 3, 1);
361
        FMapInfo.BitCount := Iff(FMapInfo.TupleType = ttRGBFP, 96, 32);
362
      end;
363
364
      FixInputPos;
365
      FMapInfo.Binary := (Id[1] in ['4', '5', '6', '7', 'F', 'f']);
366
      // Check if values found in header are valid
367
      Result := (FMapInfo.Width > 0) and (FMapInfo.Height > 0) and
368
        (FMapInfo.BitCount in [1, 8, 16, 32, 96]) and (FMapInfo.TupleType <> ttInvalid);
369
      // Now check if image has proper number of channels (PAM)
370
      if Result then
371
        case FMapInfo.TupleType of
372
          ttBlackAndWhite, ttGrayScale:           Result := FMapInfo.Depth = 1;
373
          ttBlackAndWhiteAlpha, ttGrayScaleAlpha: Result := FMapInfo.Depth = 2;
374
          ttRGB:      Result := FMapInfo.Depth = 3;
375
          ttRGBAlpha: Result := FMapInfo.Depth = 4;
376
        end;
377
    end;
378
  end;
379
380
begin
381
  Result := False;
382
  LineEnd := 0;
383
  LinePos := 0;
384
  SetLength(Images, 1);
385
  with GetIO, Images[0] do
386
  begin
387
    Format := ifUnknown;
388
    // Try to parse file header
389
    if not ParseHeader then Exit;
390
    // Select appropriate data format based on values read from file header
391
    case FMapInfo.TupleType of
392
      ttBlackAndWhite:      Format := ifGray8;
393
      ttBlackAndWhiteAlpha: Format := ifA8Gray8;
394
      ttGrayScale:          Format := IffFormat(FMapInfo.BitCount = 8, ifGray8, ifGray16);
395
      ttGrayScaleAlpha:     Format := IffFormat(FMapInfo.BitCount = 8, ifA8Gray8, ifA16Gray16);
396
      ttRGB:                Format := IffFormat(FMapInfo.BitCount = 8, ifR8G8B8, ifR16G16B16);
397
      ttRGBAlpha:           Format := IffFormat(FMapInfo.BitCount = 8, ifA8R8G8B8, ifA16R16G16B16);
398
      ttGrayScaleFP:        Format := ifR32F;
399
      ttRGBFP:              Format := ifA32B32G32R32F;
400
    end;
401
    // Exit if no matching data format was found
402
    if Format = ifUnknown then Exit;
403
404
    NewImage(FMapInfo.Width, FMapInfo.Height, Format, Images[0]);
405
    Info := GetFormatInfo(Format);
406
407
    // Now read pixels from file to dest image
408
    if not FMapInfo.Binary then
409
    begin
410
      Dest := Bits;
411
      for I := 0 to Width * Height - 1 do
412
      begin
413
        case Format of
414
          ifGray8:
415
            begin
416
              Dest^ := ReadIntValue;
417
              if FMapInfo.BitCount = 1 then
418
                // If source is 1bit mono image (where 0=white, 1=black)
419
                // we must scale it to 8bits
420
                Dest^ := 255 - Dest^ * 255;
421
            end;
422
          ifGray16: PWord(Dest)^ := ReadIntValue;
423
          ifR8G8B8:
424
            with PColor24Rec(Dest)^ do
425
            begin
426
              R := ReadIntValue;
427
              G := ReadIntValue;
428
              B := ReadIntValue;
429
            end;
430
          ifR16G16B16:
431
            with PColor48Rec(Dest)^ do
432
            begin
433
              R := ReadIntValue;
434
              G := ReadIntValue;
435
              B := ReadIntValue;
436
            end;
437
        end;
438
        Inc(Dest, Info.BytesPerPixel);
439
      end;
440
    end
441
    else
442
    begin
443
      if FMapInfo.BitCount > 1 then
444
      begin
445
        if not (FMapInfo.TupleType in [ttGrayScaleFP, ttRGBFP]) then
446
        begin
447
          // Just copy bytes from binary Portable Maps (non 1bit, non FP)
448
          Read(Handle, Bits, Size);
449
        end
450
        else
451
        begin
452
          Dest := Bits;
453
          // FP images are in BGR order and endian swap maybe needed.
454
          // Some programs store scanlines in bottom-up order but
455
          // I will stick with Photoshops behaviour here
456
          for I := 0 to Width * Height - 1 do
457
          begin
458
            Read(Handle, @PixelFP, FMapInfo.BitCount shr 3);
459
            if FMapInfo.TupleType = ttRGBFP then
460
            with PColorFPRec(Dest)^ do
461
            begin
462
              A := 1.0;
463
              R := PixelFP.R;
464
              G := PixelFP.G;
465
              B := PixelFP.B;
466
              if FMapInfo.IsBigEndian then
467
                SwapEndianLongWord(PLongWord(Dest), 3);
468
            end
469
            else
470
            begin
471
              PSingle(Dest)^ := PixelFP.B;
472
              if FMapInfo.IsBigEndian then
473
                SwapEndianLongWord(PLongWord(Dest), 1);
474
            end;
475
            Inc(Dest, Info.BytesPerPixel);
476
          end;
477
        end;
478
479
        if FMapInfo.TupleType in [ttBlackAndWhite, ttBlackAndWhiteAlpha] then
480
        begin
481
          // Black and white PAM files must be scaled to 8bits. Note that
482
          // in PAM files 1=white, 0=black (reverse of PBM)
483
          for I := 0 to Width * Height * Iff(FMapInfo.TupleType = ttBlackAndWhiteAlpha, 2, 1) - 1 do
484
            PByteArray(Bits)[I] := PByteArray(Bits)[I] * 255;
485
        end;
486
        if FMapInfo.TupleType in [ttRGB, ttRGBAlpha] then
487
        begin
488
          // Swap channels of RGB/ARGB images. Binary RGB image files use BGR order.
489
          SwapChannels(Images[0], ChannelBlue, ChannelRed);
490
        end;
491
        if FMapInfo.BitCount = 16 then
492
        begin
493
          Dest := Bits;
494
          for I := 0 to Width * Height * Info.BytesPerPixel div SizeOf(Word) - 1 do
495
          begin
496
            PWord(Dest)^ := SwapEndianWord(PWord(Dest)^);
497
            Inc(Dest, SizeOf(Word));
498
          end;
499
        end;
500
      end
501
      else
502
      begin
503
        // Handle binary PBM files (ttBlackAndWhite 1bit)
504
        ScanLineSize := (Width + 7) div 8;
505
        // Get total binary data size, read it from file to temp
506
        // buffer and convert the data to Gray8
507
        MonoSize := ScanLineSize * Height;
508
        GetMem(MonoData, MonoSize);
509
        try
510
          Read(Handle, MonoData, MonoSize);
511
          Convert1To8(MonoData, Bits, Width, Height, ScanLineSize);
512
          // 1bit mono images must be scaled to 8bit (where 0=white, 1=black)
513
          for I := 0 to Width * Height - 1 do
514
            PByteArray(Bits)[I] := 255 - PByteArray(Bits)[I] * 255;
515
        finally
516
          FreeMem(MonoData);
517
        end;
518
      end;
519
    end;
520
521
    FixInputPos;
522
523
    if (FMapInfo.MaxVal <> Pow2Int(FMapInfo.BitCount) - 1) and
524
      (FMapInfo.TupleType in [ttGrayScale, ttGrayScaleAlpha, ttRGB, ttRGBAlpha]) then
525
    begin
526
      Dest := Bits;
527
      // Scale color values according to MaxVal we got from header
528
      // if necessary.
529
      for I := 0 to Width * Height * Info.BytesPerPixel div (FMapInfo.BitCount shr 3) - 1 do
530
      begin
531
        if FMapInfo.BitCount = 8 then
532
          Dest^ := Dest^ * 255 div FMapInfo.MaxVal
533
        else
534
          PWord(Dest)^ := PWord(Dest)^ * 65535 div FMapInfo.MaxVal;
535
        Inc(Dest, FMapInfo.BitCount shr 3);
536
      end;
537
    end;
538
539
    Result := True;
540
  end;
541
end;
542
543
function TPortableMapFileFormat.SaveData(Handle: TImagingHandle;
544
  const Images: TDynImageDataArray; Index: Integer): Boolean;
545
const
546
  LineDelimiter  = #10;
547
  PixelDelimiter = #32;
548
var
549
  ImageToSave: TImageData;
550
  MustBeFreed: Boolean;
551
  Info: TImageFormatInfo;
552
  I, LineLength: LongInt;
553
  Src: PByte;
554
  Pixel32: TColor32Rec;
555
  Pixel64: TColor64Rec;
556
  W: Word;
557
558
  procedure WriteString(S: string; Delimiter: Char = LineDelimiter);
559
  begin
560
    SetLength(S, Length(S) + 1);
561
    S[Length(S)] := Delimiter;
562
    GetIO.Write(Handle, @S[1], Length(S));
563
    Inc(LineLength, Length(S));
564
  end;
565
566
  procedure WriteHeader;
567
  var
568
    OldSeparator: Char;
569
  begin
570
    WriteString('P' + FMapInfo.FormatId);
571
    if not FMapInfo.HasPAMHeader then
572
    begin
573
      // Write header of PGM, PPM, and PFM files
574
      WriteString(IntToStr(ImageToSave.Width));
575
      WriteString(IntToStr(ImageToSave.Height));
576
      case FMapInfo.TupleType of
577
        ttGrayScale, ttRGB: WriteString(IntToStr(Pow2Int(FMapInfo.BitCount) - 1));
578
        ttGrayScaleFP, ttRGBFP:
579
          begin
580
            OldSeparator := DecimalSeparator;
581
            DecimalSeparator := '.';
582
            // Negative value indicates that raster data is saved in little endian
583
            WriteString(FloatToStr(-1.0));
584
            DecimalSeparator := OldSeparator;
585
          end;
586
      end;
587
    end
588
    else
589
    begin
590
      // Write PAM file header
591
      WriteString(Format('%s %d', [SPAMWidth, ImageToSave.Width]));
592
      WriteString(Format('%s %d', [SPAMHeight, ImageToSave.Height]));
593
      WriteString(Format('%s %d', [SPAMDepth, FMapInfo.Depth]));
594
      WriteString(Format('%s %d', [SPAMMaxVal, Pow2Int(FMapInfo.BitCount) - 1]));
595
      WriteString(Format('%s %s', [SPAMTupleType, TupleTypeNames[FMapInfo.TupleType]]));
596
      WriteString(SPAMEndHdr);
597
    end;
598
  end;
599
600
begin
601
  Result := False;
602
  if MakeCompatible(Images[Index], ImageToSave, MustBeFreed) then
603
  with GetIO, ImageToSave do
604
  try
605
    Info := GetFormatInfo(Format);
606
    // Fill values of MapInfo record that were not filled by
607
    // descendants in their SaveData methods
608
    FMapInfo.BitCount := (Info.BytesPerPixel div Info.ChannelCount) * 8;
609
    FMapInfo.Depth := Info.ChannelCount;
610
    if FMapInfo.TupleType = ttInvalid then
611
    begin
612
      if Info.HasGrayChannel then
613
      begin
614
        if Info.HasAlphaChannel then
615
          FMapInfo.TupleType := ttGrayScaleAlpha
616
        else
617
          FMapInfo.TupleType := ttGrayScale;
618
      end
619
      else
620
      begin
621
        if Info.HasAlphaChannel then
622
          FMapInfo.TupleType := ttRGBAlpha
623
        else
624
          FMapInfo.TupleType := ttRGB;
625
      end;
626
    end;
627
    // Write file header
628
    WriteHeader;
629
630
    if not FMapInfo.Binary then
631
    begin
632
      Src := Bits;
633
      LineLength := 0;
634
      // For each pixel find its text representation and write it to file
635
      for I := 0 to Width * Height  - 1 do
636
      begin
637
        case Format of
638
          ifGray8: WriteString(IntToStr(Src^), PixelDelimiter);
639
          ifGray16: WriteString(IntToStr(PWord(Src)^), PixelDelimiter);
640
          ifR8G8B8:
641
            with PColor24Rec(Src)^ do
642
              WriteString(SysUtils.Format('%d %d %d', [R, G, B]), PixelDelimiter);
643
          ifR16G16B16:
644
            with PColor48Rec(Src)^ do
645
              WriteString(SysUtils.Format('%d %d %d', [R, G, B]), PixelDelimiter);
646
        end;
647
        // Lines in text PNM images should have length <70 
648
        if LineLength > 65 then
649
        begin
650
          LineLength := 0;
651
          WriteString('', LineDelimiter);
652
        end;
653
        Inc(Src, Info.BytesPerPixel);
654
      end;
655
    end
656
    else
657
    begin
658
      // Write binary images
659
      if not (FMapInfo.TupleType in [ttGrayScaleFP, ttRGBFP]) then
660
      begin
661
        // Save integer binary images
662
        if  FMapInfo.BitCount = 8 then
663
        begin
664
          if FMapInfo.TupleType in [ttGrayScale, ttGrayScaleAlpha] then
665
          begin
666
            // 8bit grayscale images can be written in one Write call
667
            Write(Handle, Bits, Size);
668
          end
669
          else
670
          begin
671
            // 8bit RGB/ARGB images: read and blue must be swapped and
672
            // 3 or 4 bytes must be written
673
            Src := Bits;
674
            for I := 0 to Width * Height - 1 do
675
            with PColor32Rec(Src)^ do
676
            begin
677
              if FMapInfo.TupleType = ttRGBAlpha then
678
                Pixel32.A := A;
679
              Pixel32.R := B;
680
              Pixel32.G := G;
681
              Pixel32.B := R;
682
              Write(Handle, @Pixel32, Info.BytesPerPixel);
683
              Inc(Src, Info.BytesPerPixel);
684
            end;
685
          end;
686
        end
687
        else
688
        begin
689
          // Images with 16bit channels: make sure that channel values are saved in big endian
690
          Src := Bits;
691
          if FMapInfo.TupleType in [ttGrayScale, ttGrayScaleAlpha] then
692
          begin
693
            // 16bit grayscale image
694
            for I := 0 to Width * Height * Info.BytesPerPixel div SizeOf(Word) - 1 do
695
            begin
696
              W := SwapEndianWord(PWord(Src)^);
697
              Write(Handle, @W, SizeOf(Word));
698
              Inc(Src, SizeOf(Word));
699
            end;
700
          end
701
          else
702
          begin
703
            // RGB images with 16bit channels: swap RB and endian too
704
            for I := 0 to Width * Height - 1 do
705
            with PColor64Rec(Src)^ do
706
            begin
707
              if FMapInfo.TupleType = ttRGBAlpha then
708
                Pixel64.A := SwapEndianWord(A);
709
              Pixel64.R := SwapEndianWord(B);
710
              Pixel64.G := SwapEndianWord(G);
711
              Pixel64.B := SwapEndianWord(R);
712
              Write(Handle, @Pixel64, Info.BytesPerPixel);
713
              Inc(Src, Info.BytesPerPixel);
714
            end;
715
          end;
716
        end; 
717
      end
718
      else
719
      begin
720
        // Floating point images (no need to swap endian here - little
721
        // endian is specified in file header)
722
        if FMapInfo.TupleType = ttGrayScaleFP then
723
        begin
724
          // Grayscale images can be written in one Write call
725
          Write(Handle, Bits, Size);
726
        end
727
        else
728
        begin
729
          // Expected data format of PFM RGB file is B32G32R32F which is not
730
          // supported by Imaging. We must write pixels one by one and
731
          // write only RGB part of A32B32G32B32 image.
732
          Src := Bits;
733
          for I := 0 to Width * Height - 1 do
734
          begin
735
            Write(Handle, Src, SizeOf(Single) * 3);
736
            Inc(Src, Info.BytesPerPixel);
737
          end;
738
        end;
739
      end;
740
    end;
741
    Result := True;
742
  finally
743
    if MustBeFreed then
744
      FreeImage(ImageToSave);
745
  end;
746
end;
747
748
function TPortableMapFileFormat.TestFormat(Handle: TImagingHandle): Boolean;
749
var
750
  Id: TChar4;
751
  ReadCount: LongInt;
752
begin
753
  Result := False;
754
  if Handle <> nil then
755
  with GetIO do
756
  begin
757
    ReadCount := Read(Handle, @Id, SizeOf(Id));
758
    Seek(Handle, -ReadCount, smFromCurrent);
759
    Result := (Id[0] = 'P') and (Id[1] in [FIdNumbers[0], FIdNumbers[1]]) and
760
      (Id[2] in WhiteSpaces);
761
  end;
762
end;
763
764
{ TPBMFileFormat }
765
766
constructor TPBMFileFormat.Create;
767
begin
768
  inherited Create;
769
  FName := SPBMFormatName;
770
  FCanSave := False;
771
  AddMasks(SPBMMasks);
772
  FIdNumbers := '14';
773
end;
774
775
{ TPGMFileFormat }
776
777
constructor TPGMFileFormat.Create;
778
begin
779
  inherited Create;
780
  FName := SPGMFormatName;
781
  FSupportedFormats := PGMSupportedFormats;
782
  AddMasks(SPGMMasks);
783
784
  RegisterOption(ImagingPGMSaveBinary, @FSaveBinary);
785
  FIdNumbers := '25';
786
end;
787
788
function TPGMFileFormat.SaveData(Handle: TImagingHandle;
789
  const Images: TDynImageDataArray; Index: Integer): Boolean;
790
begin
791
  FillChar(FMapInfo, SizeOf(FMapInfo), 0);
792
  FMapInfo.FormatId := Iff(FSaveBinary, FIdNumbers[1], FIdNumbers[0]);
793
  FMapInfo.Binary := FSaveBinary;
794
  Result := inherited SaveData(Handle, Images, Index);
795
end;
796
797
procedure TPGMFileFormat.ConvertToSupported(var Image: TImageData;
798
  const Info: TImageFormatInfo);
799
var
800
  ConvFormat: TImageFormat;
801
begin
802
  if Info.IsFloatingPoint then
803
    // All FP images go to 16bit
804
    ConvFormat :=  ifGray16
805
  else if Info.HasGrayChannel then
806
    // Grayscale will be 8 or 16 bit - depends on input's bitcount
807
    ConvFormat := IffFormat(Info.BytesPerPixel div Info.ChannelCount > 1,
808
      ifGray16, ifGray8)
809
  else if Info.BytesPerPixel > 4 then
810
    // Large bitcounts -> 16bit
811
    ConvFormat := ifGray16
812
  else
813
    // Rest of the formats -> 8bit 
814
    ConvFormat := ifGray8;
815
816
  ConvertImage(Image, ConvFormat);
817
end;
818
819
{ TPPMFileFormat }
820
821
constructor TPPMFileFormat.Create;
822
begin
823
  inherited Create;
824
  FName := SPPMFormatName;
825
  FSupportedFormats := PPMSupportedFormats;
826
  AddMasks(SPPMMasks);
827
828
  RegisterOption(ImagingPPMSaveBinary, @FSaveBinary);
829
  FIdNumbers := '36';
830
end;
831
832
function TPPMFileFormat.SaveData(Handle: TImagingHandle;
833
  const Images: TDynImageDataArray; Index: Integer): Boolean;
834
begin
835
  FillChar(FMapInfo, SizeOf(FMapInfo), 0);
836
  FMapInfo.FormatId := Iff(FSaveBinary, FIdNumbers[1], FIdNumbers[0]);
837
  FMapInfo.Binary := FSaveBinary;
838
  Result := inherited SaveData(Handle, Images, Index);
839
end;
840
841
procedure TPPMFileFormat.ConvertToSupported(var Image: TImageData;
842
  const Info: TImageFormatInfo);
843
var
844
  ConvFormat: TImageFormat;
845
begin
846
  if Info.IsFloatingPoint then
847
    // All FP images go to 48bit RGB
848
    ConvFormat :=  ifR16G16B16
849
  else if Info.HasGrayChannel then
850
    // Grayscale will be 24 or 48 bit RGB - depends on input's bitcount
851
    ConvFormat := IffFormat(Info.BytesPerPixel div Info.ChannelCount > 1,
852
      ifR16G16B16, ifR8G8B8)
853
  else if Info.BytesPerPixel > 4 then
854
    // Large bitcounts -> 48bit RGB
855
    ConvFormat := ifR16G16B16
856
  else
857
    // Rest of the formats -> 24bit RGB
858
    ConvFormat := ifR8G8B8;
859
860
  ConvertImage(Image, ConvFormat);
861
end;
862
863
{ TPAMFileFormat }
864
865
constructor TPAMFileFormat.Create;
866
begin
867
  inherited Create;
868
  FName := SPAMFormatName;
869
  FSupportedFormats := PAMSupportedFormats;
870
  AddMasks(SPAMMasks);
871
  FIdNumbers := '77';
872
end;
873
874
function TPAMFileFormat.SaveData(Handle: TImagingHandle;
875
  const Images: TDynImageDataArray; Index: Integer): Boolean;
876
begin
877
  FillChar(FMapInfo, SizeOf(FMapInfo), 0);
878
  FMapInfo.FormatId := FIdNumbers[0];
879
  FMapInfo.Binary := True;
880
  FMapInfo.HasPAMHeader := True;
881
  Result := inherited SaveData(Handle, Images, Index);
882
end;
883
884
procedure TPAMFileFormat.ConvertToSupported(var Image: TImageData;
885
  const Info: TImageFormatInfo);
886
var
887
  ConvFormat: TImageFormat;
888
begin
889
  if Info.IsFloatingPoint then
890
    ConvFormat := IffFormat(Info.HasAlphaChannel, ifA16R16G16B16, ifR16G16B16)
891
  else if Info.HasGrayChannel then
892
    ConvFormat := IffFormat(Info.HasAlphaChannel, ifA16Gray16, ifGray16)
893
  else
894
  begin
895
    if Info.BytesPerPixel <= 4 then
896
      ConvFormat := IffFormat(Info.HasAlphaChannel, ifA8R8G8B8, ifR8G8B8)
897
    else
898
      ConvFormat := IffFormat(Info.HasAlphaChannel, ifA16R16G16B16, ifR16G16B16);
899
  end;
900
  ConvertImage(Image, ConvFormat);
901
end;
902
903
{ TPFMFileFormat }
904
905
constructor TPFMFileFormat.Create;
906
begin
907
  inherited Create;
908
  FName := SPFMFormatName;
909
  AddMasks(SPFMMasks);
910
  FIdNumbers := 'Ff';
911
  FSupportedFormats := PFMSupportedFormats;
912
end;
913
914
function TPFMFileFormat.SaveData(Handle: TImagingHandle;
915
  const Images: TDynImageDataArray; Index: Integer): Boolean;
916
var
917
  Info: TImageFormatInfo;
918
begin
919
  FillChar(FMapInfo, SizeOf(FMapInfo), 0);
920
  Info := GetFormatInfo(Images[Index].Format);
921
  if (Info.ChannelCount > 1) or Info.IsIndexed then
922
    FMapInfo.TupleType := ttRGBFP
923
  else
924
    FMapInfo.TupleType := ttGrayScaleFP;
925
  FMapInfo.FormatId := Iff(FMapInfo.TupleType = ttGrayScaleFP, FIdNumbers[1], FIdNumbers[0]);
926
  FMapInfo.Binary := True;
927
  Result := inherited SaveData(Handle, Images, Index);
928
end;
929
930
procedure TPFMFileFormat.ConvertToSupported(var Image: TImageData;
931
  const Info: TImageFormatInfo);
932
begin
933
  if (Info.ChannelCount > 1) or Info.IsIndexed then
934
    ConvertImage(Image, ifA32B32G32R32F)
935
  else
936
    ConvertImage(Image, ifR32F);
937
end;
938
939
initialization
940
  RegisterImageFileFormat(TPBMFileFormat);
941
  RegisterImageFileFormat(TPGMFileFormat);
942
  RegisterImageFileFormat(TPPMFileFormat);
943
  RegisterImageFileFormat(TPAMFileFormat);
944
  RegisterImageFileFormat(TPFMFileFormat);
945
946
{
947
  File Notes:
948
949
  -- TODOS ----------------------------------------------------
950
    - nothing now
951
952
  -- 0.21 Changes/Bug Fixes -----------------------------------
953
    - Made modifications to ASCII PNM loading to be more "stream-safe". 
954
    - Fixed bug: indexed images saved as grayscale in PFM.
955
    - Changed converting to supported formats little bit.
956
    - Added scaling of channel values (non-FP and non-mono images) according
957
      to MaxVal.
958
    - Added buffering to loading of PNM files. More than 10x faster now
959
      for text files.
960
    - Added saving support to PGM, PPM, PAM, and PFM format.
961
    - Added PFM file format.
962
    - Initial version created.
963
}
964
965
end.