Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (28.1 kB)

1
{
2
  $Id: ImagingBitmap.pas 94 2007-06-21 19:29:49Z 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 image format loader/saver for Windows Bitmap images.}
30
unit ImagingBitmap;
31
32
{$I ImagingOptions.inc}
33
34
interface
35
36
uses
37
  ImagingTypes, Imaging, ImagingUtility, ImagingFormats, ImagingIO;
38
39
type
40
  { Class for loading and saving Windows Bitmap images.
41
    It can load/save 8bit indexed, 16, 24, 32 bit RGB or ARGB
42
    images with or without RLE compression. It can also load 1/4 bit
43
    indexed images and OS2 bitmaps.}
44
  TBitmapFileFormat = class(TImageFileFormat)
45
  protected
46
    FUseRLE: LongBool;
47
    function LoadData(Handle: TImagingHandle; var Images: TDynImageDataArray;
48
      OnlyFirstLevel: Boolean): Boolean; override;
49
    function SaveData(Handle: TImagingHandle; const Images: TDynImageDataArray;
50
      Index: LongInt): Boolean; override;
51
    procedure ConvertToSupported(var Image: TImageData;
52
      const Info: TImageFormatInfo); override;
53
  public
54
    constructor Create; override;
55
    function TestFormat(Handle: TImagingHandle): Boolean; override;
56
  published  
57
    { Controls that RLE compression is used during saving. Accessible trough
58
      ImagingBitmapRLE option.}
59
    property UseRLE: LongBool read FUseRLE write FUseRLE;
60
  end;
61
62
implementation
63
64
const
65
  SBitmapFormatName = 'Windows Bitmap Image';
66
  SBitmapMasks =      '*.bmp,*.dib';
67
  BitmapSupportedFormats: TImageFormats = [ifIndex8, ifA1R5G5B5, ifA4R4G4B4,
68
    ifR5G6B5, ifR8G8B8, ifA8R8G8B8, ifX1R5G5B5, ifX4R4G4B4, ifX8R8G8B8];
69
  BitmapDefaultRLE = True;  
70
71
const
72
  { Bitmap file identifier 'BM'.}
73
  BMMagic: Word = 19778;
74
75
  { Constants for the TBitmapInfoHeader.Compression field.}
76
  BI_RGB = 0;
77
  BI_RLE8 = 1;
78
  BI_RLE4 = 2;
79
  BI_BITFIELDS = 3;
80
81
  V3InfoHeaderSize = 40;
82
  V4InfoHeaderSize = 108; 
83
84
type
85
  { File Header for Windows/OS2 bitmap file.}
86
  TBitmapFileHeader = packed record
87
    ID: Word;           // Is always 19778 : 'BM'
88
    Size: LongWord;     // Filesize
89
    Reserved1: Word;
90
    Reserved2: Word;
91
    Offset: LongWord;   // Offset from start pos to beginning of image bits
92
  end;
93
94
  { Info Header for Windows bitmap file version 4.}
95
  TBitmapInfoHeader = packed record
96
    Size: LongWord;
97
    Width: LongInt;
98
    Height: LongInt;
99
    Planes: Word;
100
    BitCount: Word;
101
    Compression: LongWord;
102
    SizeImage: LongWord;
103
    XPelsPerMeter: LongInt;
104
    YPelsPerMeter: LongInt;
105
    ClrUsed: LongInt;
106
    ClrImportant: LongInt;
107
    RedMask: LongWord;
108
    GreenMask: LongWord;
109
    BlueMask: LongWord;
110
    AlphaMask: LongWord;
111
    CSType: LongWord;
112
    EndPoints: array[0..8] of LongWord;
113
    GammaRed: LongWord;
114
    GammaGreen: LongWord;
115
    GammaBlue: LongWord;
116
  end;
117
118
  { Info Header for OS2 bitmaps.}
119
  TBitmapCoreHeader = packed record
120
    Size: LongWord;
121
    Width: Word;
122
    Height: Word;
123
    Planes: Word;
124
    BitCount: Word;
125
  end;
126
127
  { Used in RLE encoding and decoding.} 
128
  TRLEOpcode = packed record
129
    Count: Byte;
130
    Command: Byte;
131
  end;
132
  PRLEOpcode = ^TRLEOpcode;
133
134
{ TBitmapFileFormat class implementation }
135
136
constructor TBitmapFileFormat.Create;
137
begin
138
  inherited Create;
139
  FName := SBitmapFormatName;
140
  FCanLoad := True;
141
  FCanSave := True;
142
  FIsMultiImageFormat := False;
143
  FSupportedFormats := BitmapSupportedFormats;
144
145
  FUseRLE := BitmapDefaultRLE;
146
147
  AddMasks(SBitmapMasks);
148
  RegisterOption(ImagingBitmapRLE, @FUseRLE);
149
end;
150
151
function TBitmapFileFormat.LoadData(Handle: TImagingHandle;
152
  var Images: TDynImageDataArray; OnlyFirstLevel: Boolean): Boolean;
153
var
154
  BF: TBitmapFileHeader;
155
  BI: TBitmapInfoHeader;
156
  BC: TBitmapCoreHeader;
157
  IsOS2: Boolean;
158
  PalRGB: PPalette24;
159
  I, FPalSize, AlignedSize, StartPos, HeaderSize, AlignedWidthBytes, WidthBytes: LongInt;
160
  Info: TImageFormatInfo;
161
  Data: Pointer;
162
163
  procedure LoadRGB;
164
  var
165
    I: LongInt;
166
    LineBuffer: PByte;
167
  begin
168
    with Images[0], GetIO do
169
    begin
170
      // If BI.Height is < 0 then image data are stored non-flipped
171
      // but default in windows is flipped so if Height is positive we must
172
      // flip it
173
174
      if BI.BitCount < 8 then
175
      begin
176
        // For 1 and 4 bit images load aligned data, they will be converted to
177
        // 8 bit and unaligned later
178
        GetMem(Data, AlignedSize);
179
180
        if BI.Height < 0 then
181
          Read(Handle, Data, AlignedSize)
182
        else
183
          for I := Height - 1 downto 0 do
184
            Read(Handle, @PByteArray(Data)[I * AlignedWidthBytes], AlignedWidthBytes);
185
      end
186
      else
187
      begin
188
        // Images with pixels of size >= 1 Byte are read line by line and
189
        // copied to image bits without padding bytes
190
        GetMem(LineBuffer, AlignedWidthBytes);
191
        try
192
          if BI.Height < 0 then
193
            for I := 0 to Height - 1 do
194
            begin
195
              Read(Handle, LineBuffer, AlignedWidthBytes);
196
              Move(LineBuffer^, PByteArray(Bits)[I * WidthBytes], WidthBytes);
197
            end
198
          else
199
            for I := Height - 1 downto 0 do
200
            begin
201
              Read(Handle, LineBuffer, AlignedWidthBytes);
202
              Move(LineBuffer^, PByteArray(Bits)[I * WidthBytes], WidthBytes);
203
            end;
204
        finally
205
          FreeMemNil(LineBuffer);
206
        end;
207
      end;
208
    end;
209
  end;
210
211
  procedure LoadRLE4;
212
  var
213
    RLESrc: PByteArray;
214
    Row, Col, WriteRow, I: LongInt;
215
    SrcPos: LongWord;
216
    DeltaX, DeltaY, Low, High: Byte;
217
    Pixels: PByteArray;
218
    OpCode: TRLEOpcode;
219
    NegHeightBitmap: Boolean;
220
  begin
221
    GetMem(RLESrc, BI.SizeImage);
222
    GetIO.Read(Handle, RLESrc, BI.SizeImage);
223
    with Images[0] do
224
    try
225
      Low := 0;
226
      Pixels := Bits;
227
      SrcPos := 0;
228
      NegHeightBitmap := BI.Height < 0;
229
      Row := 0; // Current row in dest image
230
      Col := 0; // Current column in dest image
231
      // Row in dest image where actuall writting will be done
232
      WriteRow := Iff(NegHeightBitmap, Row, Height - 1 - Row);
233
      while (Row < Height) and (SrcPos < BI.SizeImage) do
234
      begin
235
        // Read RLE op-code
236
        OpCode := PRLEOpcode(@RLESrc[SrcPos])^;
237
        Inc(SrcPos, SizeOf(OpCode));
238
        if OpCode.Count = 0 then
239
        begin
240
          // A byte Count of zero means that this is a special
241
          // instruction.
242
          case OpCode.Command of
243
            0:
244
              begin
245
                // Move to next row
246
                Inc(Row);
247
                WriteRow := Iff(NegHeightBitmap, Row, Height - 1 - Row);
248
                Col := 0;
249
              end ;
250
            1: Break; // Image is finished
251
            2:
252
              begin
253
                // Move to a new relative position
254
                DeltaX := RLESrc[SrcPos];
255
                DeltaY := RLESrc[SrcPos + 1];
256
                Inc(SrcPos, 2);
257
                Inc(Col, DeltaX);
258
                Inc(Row, DeltaY);
259
              end
260
          else
261
            // Do not read data after EOF
262
            if SrcPos + OpCode.Command > BI.SizeImage then
263
              OpCode.Command := BI.SizeImage - SrcPos;
264
            // Take padding bytes and nibbles into account
265
            if Col + OpCode.Command > Width then
266
              OpCode.Command := Width - Col;
267
            // Store absolute data. Command code is the
268
            // number of absolute bytes to store
269
            for I := 0 to OpCode.Command - 1 do
270
            begin
271
              if (I and 1) = 0 then
272
              begin
273
                High := RLESrc[SrcPos] shr 4;
274
                Low := RLESrc[SrcPos] and $F;
275
                Pixels[WriteRow * Width + Col] := High;
276
                Inc(SrcPos);
277
              end
278
              else
279
                Pixels[WriteRow * Width + Col] := Low;
280
              Inc(Col);
281
            end;
282
            // Odd number of bytes is followed by a pad byte
283
            if (OpCode.Command mod 4) in [1, 2] then
284
              Inc(SrcPos);
285
          end;
286
        end
287
        else
288
        begin
289
          // Take padding bytes and nibbles into account
290
          if Col + OpCode.Count > Width then
291
            OpCode.Count := Width - Col;
292
          // Store a run of the same color value
293
          for I := 0 to OpCode.Count - 1 do
294
          begin
295
            if (I and 1) = 0 then
296
              Pixels[WriteRow * Width + Col] := OpCode.Command shr 4
297
            else
298
              Pixels[WriteRow * Width + Col] := OpCode.Command and $F;
299
            Inc(Col);
300
          end;
301
        end;
302
      end;
303
    finally
304
      FreeMem(RLESrc);
305
    end;
306
  end;
307
308
  procedure LoadRLE8;
309
  var
310
    RLESrc: PByteArray;
311
    SrcCount, Row, Col, WriteRow: LongInt;
312
    SrcPos: LongWord;
313
    DeltaX, DeltaY: Byte;
314
    Pixels: PByteArray;
315
    OpCode: TRLEOpcode;
316
    NegHeightBitmap: Boolean;
317
  begin
318
    GetMem(RLESrc, BI.SizeImage);
319
    GetIO.Read(Handle, RLESrc, BI.SizeImage);
320
    with Images[0] do
321
    try
322
      Pixels := Bits;
323
      SrcPos := 0;
324
      NegHeightBitmap := BI.Height < 0;
325
      Row := 0; // Current row in dest image
326
      Col := 0; // Current column in dest image
327
      // Row in dest image where actuall writting will be done
328
      WriteRow := Iff(NegHeightBitmap, Row, Height - 1 - Row);
329
      while (Row < Height) and (SrcPos < BI.SizeImage) do
330
      begin
331
        // Read RLE op-code
332
        OpCode := PRLEOpcode(@RLESrc[SrcPos])^;
333
        Inc(SrcPos, SizeOf(OpCode));
334
        if OpCode.Count = 0 then
335
        begin
336
          // A byte Count of zero means that this is a special
337
          // instruction.
338
          case OpCode.Command of
339
            0:
340
              begin
341
                // Move to next row
342
                Inc(Row);
343
                WriteRow := Iff(NegHeightBitmap, Row, Height - 1 - Row);
344
                Col := 0;
345
              end ;
346
            1: Break; // Image is finished
347
            2:
348
              begin
349
                // Move to a new relative position
350
                DeltaX := RLESrc[SrcPos];
351
                DeltaY := RLESrc[SrcPos + 1];
352
                Inc(SrcPos, 2);
353
                Inc(Col, DeltaX);
354
                Inc(Row, DeltaY);
355
              end
356
          else
357
            SrcCount := OpCode.Command;
358
            // Do not read data after EOF
359
            if SrcPos + OpCode.Command > BI.SizeImage then
360
              OpCode.Command := BI.SizeImage - SrcPos;
361
            // Take padding bytes into account
362
            if Col + OpCode.Command > Width then
363
              OpCode.Command := Width - Col;
364
            // Store absolute data. Command code is the
365
            // number of absolute bytes to store
366
            Move(RLESrc[SrcPos], Pixels[WriteRow * Width + Col], OpCode.Command);
367
            Inc(SrcPos, SrcCount);
368
            Inc(Col, OpCode.Command);
369
            // Odd number of bytes is followed by a pad byte
370
            if (SrcCount mod 2) = 1 then
371
              Inc(SrcPos);
372
          end;
373
        end
374
        else
375
        begin
376
          // Take padding bytes into account
377
          if Col + OpCode.Count > Width then
378
            OpCode.Count := Width - Col;
379
          // Store a run of the same color value. Count is number of bytes to store
380
          FillChar(Pixels [WriteRow * Width + Col], OpCode.Count, OpCode.Command);
381
          Inc(Col, OpCode.Count);
382
        end;
383
      end;
384
    finally
385
      FreeMem(RLESrc);
386
    end;
387
  end;
388
389
begin
390
  Data := nil;
391
  SetLength(Images, 1);
392
  with GetIO, Images[0] do
393
  try
394
    FillChar(BI, SizeOf(BI), 0);
395
    StartPos := Tell(Handle);
396
    Read(Handle, @BF, SizeOf(BF));
397
    Read(Handle, @BI.Size, SizeOf(BI.Size));
398
    IsOS2 := BI.Size = SizeOf(TBitmapCoreHeader);
399
400
    // Bitmap Info reading
401
    if IsOS2 then
402
    begin
403
      // OS/2 type bitmap, reads info header without 4 already read bytes
404
      Read(Handle, @PByteArray(@BC)[SizeOf(BI.Size)],
405
        SizeOf(TBitmapCoreHeader) - SizeOf(BI.Size));
406
      with BI do
407
      begin
408
        ClrUsed := 0;
409
        Compression := BI_RGB;
410
        BitCount := BC.BitCount;
411
        Height := BC.Height;
412
        Width := BC.Width;
413
      end;
414
    end
415
    else
416
    begin
417
      // Windows type bitmap
418
      HeaderSize := Min(BI.Size - SizeOf(BI.Size), SizeOf(BI) - SizeOf(BI.Size)); // do not read more than size of BI!
419
      Read(Handle, @PByteArray(@BI)[SizeOf(BI.Size)], HeaderSize);
420
      // SizeImage can be 0 for BI_RGB images, but it is here because of:
421
      // I saved 8bit bitmap in Paint Shop Pro 8 as OS2 RLE compressed.
422
      // It wrote strange 64 Byte Info header with SizeImage set to 0
423
      // Some progs were able to open it, some were not.
424
      if BI.SizeImage = 0 then
425
        BI.SizeImage := BF.Size - BF.Offset;
426
    end;
427
    // Bit mask reading. Only read it if there is V3 header, V4 header has
428
    // masks laoded already (only masks for RGB in V3).
429
    if (BI.Compression = BI_BITFIELDS) and (BI.Size = V3InfoHeaderSize) then
430
      Read(Handle, @BI.RedMask, SizeOf(BI.RedMask) * 3);
431
432
    case BI.BitCount of
433
      1, 4, 8: Format := ifIndex8;
434
      16:
435
        if BI.RedMask = $0F00 then
436
          // Set XRGB4 or ARGB4 according to value of alpha mask
437
          Format := IffFormat(BI.AlphaMask = 0, ifX4R4G4B4, ifA4R4G4B4)
438
        else if BI.RedMask = $F800 then
439
          Format := ifR5G6B5
440
        else
441
          // R5G5B5 is default 16bit format (with Compression = BI_RGB or masks).
442
          // We set it to A1.. and later there is a check if there are any alpha values
443
          // and if not it is changed to X1R5G5B5
444
          Format := ifA1R5G5B5;
445
      24: Format := ifR8G8B8;
446
      32: Format := ifA8R8G8B8; // As with R5G5B5 there is alpha check later 
447
    end;
448
449
    NewImage(BI.Width, Abs(BI.Height), Format, Images[0]);
450
    Info := GetFormatInfo(Format);
451
    WidthBytes := Width * Info.BytesPerPixel;
452
    AlignedWidthBytes := (((Width * BI.BitCount) + 31) shr 5) * 4;
453
    AlignedSize := Height * LongInt(AlignedWidthBytes);
454
455
    // Palette settings and reading
456
    if BI.BitCount <= 8 then
457
    begin
458
      // Seek to the begining of palette
459
      Seek(Handle, StartPos + SizeOf(TBitmapFileHeader) + LongInt(BI.Size),
460
        smFromBeginning);
461
      if IsOS2 then
462
      begin
463
        // OS/2 type
464
        FPalSize := 1 shl BI.BitCount;
465
        GetMem(PalRGB, FPalSize * SizeOf(TColor24Rec));
466
        try
467
          Read(Handle, PalRGB, FPalSize * SizeOf(TColor24Rec));
468
          for I := 0 to FPalSize - 1 do
469
          with PalRGB[I] do
470
          begin
471
            Palette[I].R := R;
472
            Palette[I].G := G;
473
            Palette[I].B := B;
474
          end;
475
        finally
476
          FreeMemNil(PalRGB);
477
        end;
478
      end
479
      else
480
      begin
481
        // Windows type
482
        FPalSize := BI.ClrUsed;
483
        if FPalSize = 0 then
484
          FPalSize := 1 shl BI.BitCount;
485
        Read(Handle, Palette, FPalSize * SizeOf(TColor32Rec));
486
      end;
487
      for I := 0 to FPalSize - 1 do
488
        Palette[I].A := $FF;
489
    end;
490
491
    // Seek to the beginning of image bits
492
    Seek(Handle, StartPos + LongInt(BF.Offset), smFromBeginning);
493
494
    case BI.Compression of
495
      BI_RGB: LoadRGB;
496
      BI_RLE4: LoadRLE4;
497
      BI_RLE8: LoadRLE8;
498
      BI_BITFIELDS: LoadRGB;
499
    end;
500
501
    if BI.AlphaMask = 0 then
502
    begin
503
      // Alpha mask is not stored in file (V3) or not defined.
504
      // Check alpha channels of loaded images if they might contain them.
505
      if Format = ifA1R5G5B5 then
506
      begin
507
        // Check if there is alpha channel present in A1R5GB5 images, if it is not
508
        // change format to X1R5G5B5
509
        if not Has16BitImageAlpha(Width * Height, Bits) then
510
          Format := ifX1R5G5B5;
511
      end
512
      else if Format = ifA8R8G8B8 then
513
      begin
514
        // Check if there is alpha channel present in A8R8G8B8 images, if it is not
515
        // change format to X8R8G8B8
516
        if not Has32BitImageAlpha(Width * Height, Bits) then
517
          Format := ifX8R8G8B8;
518
      end;
519
    end;
520
521
    if BI.BitCount < 8 then
522
    begin
523
      // 1 and 4 bpp images are supported only for loading which is now
524
      // so we now convert them to 8bpp (and unalign scanlines).
525
      case BI.BitCount of
526
        1: Convert1To8(Data, Bits, Width, Height, AlignedWidthBytes);
527
        4:
528
          begin
529
            // RLE4 bitmaps are translated to 8bit during RLE decoding
530
            if BI.Compression <> BI_RLE4 then
531
               Convert4To8(Data, Bits, Width, Height, AlignedWidthBytes);
532
          end;
533
      end;
534
      // Enlarge palette
535
      ReallocMem(Palette, Info.PaletteEntries * SizeOf(TColor32Rec));
536
    end;
537
538
    Result := True;
539
  finally
540
    FreeMemNil(Data);
541
  end;
542
end;
543
544
function TBitmapFileFormat.SaveData(Handle: TImagingHandle;
545
  const Images: TDynImageDataArray; Index: LongInt): Boolean;
546
var
547
  StartPos, EndPos, I, Pad, PadSize, WidthBytes: LongInt;
548
  BF: TBitmapFileHeader;
549
  BI: TBitmapInfoHeader;
550
  Info: TImageFormatInfo;
551
  ImageToSave: TImageData;
552
  MustBeFreed: Boolean;
553
554
  procedure SaveRLE8;
555
  const
556
    BufferSize = 8 * 1024;
557
  var
558
    X, Y, I, SrcPos: LongInt;
559
    DiffCount, SameCount: Byte;
560
    Pixels: PByteArray;
561
    Buffer: array[0..BufferSize - 1] of Byte;
562
    BufferPos: LongInt;
563
564
    procedure WriteByte(ByteToWrite: Byte);
565
    begin
566
      if BufferPos = BufferSize then
567
      begin
568
        // Flush buffer if necessary
569
        GetIO.Write(Handle, @Buffer, BufferPos);
570
        BufferPos := 0;
571
      end;
572
      Buffer[BufferPos] := ByteToWrite;
573
      Inc(BufferPos);
574
    end;
575
576
  begin
577
    BufferPos := 0;
578
    with GetIO, ImageToSave do
579
    begin
580
      for Y := Height - 1 downto 0 do
581
      begin
582
        X := 0;
583
        SrcPos := 0;
584
        Pixels := @PByteArray(Bits)[Y * Width];
585
586
        while X < Width do
587
        begin
588
          SameCount := 1;
589
          DiffCount := 0;
590
          // Determine run length
591
          while X + SameCount < Width do
592
          begin
593
            // If we reach max run length or byte with different value
594
            // we end this run
595
            if (SameCount = 255) or (Pixels[SrcPos + SameCount] <> Pixels[SrcPos]) then
596
              Break;
597
            Inc(SameCount);
598
          end;
599
600
          if SameCount = 1 then
601
          begin
602
            // If there are not some bytes with the same value we
603
            // compute how many different bytes are there
604
            while X + DiffCount < Width do
605
            begin
606
              // Stop diff byte counting if there two bytes with the same value
607
              // or DiffCount is too big
608
              if (DiffCount = 255) or (Pixels[SrcPos + DiffCount + 1] =
609
                Pixels[SrcPos + DiffCount]) then
610
                Break;
611
              Inc(DiffCount);
612
            end;
613
          end;
614
615
          // Now store absolute data (direct copy image->file) or
616
          // store RLE code only (number of repeats + byte to be repeated)
617
          if DiffCount > 2 then
618
          begin
619
            // Save 'Absolute Data' (0 + number of bytes) but only
620
            // if number is >2 because (0+1) and (0+2) are other special commands
621
            WriteByte(0);
622
            WriteByte(DiffCount);
623
            // Write absolute data to buffer
624
            for I := 0 to DiffCount - 1 do
625
              WriteByte(Pixels[SrcPos + I]);
626
            Inc(X, DiffCount);
627
            Inc(SrcPos, DiffCount);
628
            // Odd number of bytes must be padded
629
            if (DiffCount mod 2) = 1 then
630
              WriteByte(0);
631
          end
632
          else
633
          begin
634
            // Save number of repeats and byte that should be repeated
635
            WriteByte(SameCount);
636
            WriteByte(Pixels[SrcPos]);
637
            Inc(X, SameCount);
638
            Inc(SrcPos, SameCount);
639
          end;
640
        end;
641
        // Save 'End Of Line' command
642
        WriteByte(0);
643
        WriteByte(0);
644
      end;
645
      // Save 'End Of Bitmap' command
646
      WriteByte(0);
647
      WriteByte(1);
648
      // Flush buffer
649
      GetIO.Write(Handle, @Buffer, BufferPos);
650
    end;
651
  end;
652
653
begin
654
  Result := False;
655
  if MakeCompatible(Images[Index], ImageToSave, MustBeFreed) then
656
  with GetIO, ImageToSave do
657
  try
658
    Info := GetFormatInfo(Format);
659
    StartPos := Tell(Handle);
660
    FillChar(BF, SizeOf(BF), 0);
661
    FillChar(BI, SizeOf(BI), 0);
662
    // Other fields will be filled later - we don't know all values now
663
    BF.ID := BMMagic;
664
    Write(Handle, @BF, SizeOf(BF));
665
    if Info.HasAlphaChannel and (Info.BytesPerPixel = 2){V4 temp hack} then
666
      // Save images with alpha in V4 format
667
      BI.Size := V4InfoHeaderSize
668
    else
669
      // Save images without alpha in V3 format - for better compatibility
670
      BI.Size := V3InfoHeaderSize;
671
    BI.Width := Width;
672
    BI.Height := Height;
673
    BI.Planes := 1;
674
    BI.BitCount := Info.BytesPerPixel * 8;
675
    BI.XPelsPerMeter := 2835; // 72 dpi
676
    BI.YPelsPerMeter := 2835; // 72 dpi
677
    // Set compression
678
    if (Info.BytesPerPixel = 1) and FUseRLE then
679
      BI.Compression := BI_RLE8
680
    else if (Info.HasAlphaChannel or
681
      ((BI.BitCount = 16) and (Format <> ifX1R5G5B5))) and (Info.BytesPerPixel = 2){V4 temp hack} then
682
      BI.Compression := BI_BITFIELDS
683
    else
684
      BI.Compression := BI_RGB;
685
    // Write header (first time)
686
    Write(Handle, @BI, BI.Size);
687
688
    // Write mask info
689
    if BI.Compression = BI_BITFIELDS then
690
    begin
691
      if BI.BitCount = 16 then
692
      with Info.PixelFormat^ do
693
      begin
694
        BI.RedMask   := RBitMask;
695
        BI.GreenMask := GBitMask;
696
        BI.BlueMask  := BBitMask;
697
        BI.AlphaMask := ABitMask;
698
      end
699
      else
700
      begin
701
        // Set masks for A8R8G8B8
702
        BI.RedMask   := $00FF0000;
703
        BI.GreenMask := $0000FF00;
704
        BI.BlueMask  := $000000FF;
705
        BI.AlphaMask := $FF000000;
706
      end;
707
      // If V3 header is used RGB masks must be written to file separately.
708
      // V4 header has embedded masks (V4 is default for formats with alpha).
709
      if BI.Size = V3InfoHeaderSize then
710
        Write(Handle, @BI.RedMask, SizeOf(BI.RedMask) * 3);
711
    end;
712
    // Write palette
713
    if Palette <> nil then
714
      Write(Handle, Palette, Info.PaletteEntries * SizeOf(TColor32Rec));
715
716
    BF.Offset := Tell(Handle) - StartPos;
717
718
    if BI.Compression <> BI_RLE8 then
719
    begin
720
      // Save uncompressed data, scanlines must be filled with pad bytes
721
      // to be multiples of 4, save as bottom-up (Windows native) bitmap
722
      Pad := 0;
723
      WidthBytes := Width * Info.BytesPerPixel;
724
      PadSize := ((Width * BI.BitCount + 31) div 32) * 4 - WidthBytes;
725
726
      for I := Height - 1 downto 0 do
727
      begin
728
        Write(Handle, @PByteArray(Bits)[I * WidthBytes], WidthBytes);
729
        if PadSize > 0 then
730
          Write(Handle, @Pad, PadSize);
731
      end;
732
    end
733
    else
734
    begin
735
      // Save data with RLE8 compression
736
      SaveRLE8;
737
    end;
738
739
    EndPos := Tell(Handle);
740
    Seek(Handle, StartPos, smFromBeginning);
741
    // Rewrite header with new values
742
    BF.Size := EndPos - StartPos;
743
    BI.SizeImage := BF.Size - BF.Offset;
744
    Write(Handle, @BF, SizeOf(BF));
745
    Write(Handle, @BI, BI.Size);
746
    Seek(Handle, EndPos, smFromBeginning);
747
748
    Result := True;
749
  finally
750
    if MustBeFreed then
751
      FreeImage(ImageToSave);
752
  end;
753
end;
754
755
procedure TBitmapFileFormat.ConvertToSupported(var Image: TImageData;
756
  const Info: TImageFormatInfo);
757
var
758
  ConvFormat: TImageFormat;
759
begin
760
  if Info.IsFloatingPoint then
761
    // Convert FP image to RGB/ARGB according to presence of alpha channel
762
    ConvFormat := IffFormat(Info.HasAlphaChannel, ifA8R8G8B8, ifR8G8B8)
763
  else if Info.HasGrayChannel or Info.IsIndexed then
764
    // Convert all grayscale and indexed images to Index8 unless they have alpha
765
    // (preserve it)
766
    ConvFormat := IffFormat(Info.HasAlphaChannel, ifA8R8G8B8, ifIndex8)
767
  else if Info.HasAlphaChannel then
768
    // Convert images with alpha channel to A8R8G8B8
769
    ConvFormat := ifA8R8G8B8
770
  else if Info.UsePixelFormat then
771
    // Convert 16bit RGB images (no alpha) to X1R5G5B5
772
    ConvFormat := ifX1R5G5B5
773
  else
774
    // Convert all other formats to R8G8B8
775
    ConvFormat := ifR8G8B8;
776
777
  ConvertImage(Image, ConvFormat);
778
end;
779
780
function TBitmapFileFormat.TestFormat(Handle: TImagingHandle): Boolean;
781
var
782
  Hdr: TBitmapFileHeader;
783
  ReadCount: LongInt;
784
begin
785
  Result := False;
786
  if Handle <> nil then
787
  with GetIO do
788
  begin
789
    ReadCount := Read(Handle, @Hdr, SizeOf(Hdr));
790
    Seek(Handle, -ReadCount, smFromCurrent);
791
    Result := (Hdr.ID = BMMagic) and (ReadCount = SizeOf(Hdr));
792
  end;
793
end;
794
795
initialization
796
  RegisterImageFileFormat(TBitmapFileFormat);
797
798
{
799
  File Notes:
800
801
  -- TODOS ----------------------------------------------------
802
    - nothing now
803
    - Add option to choose to save V3 or V4 headers. 
804
805
  -- 0.23 Changes/Bug Fixes -----------------------------------
806
    - Now saves bitmaps as bottom-up for better compatibility
807
      (mainly Lazarus' TImage!).
808
    - Fixed crash when loading bitmaps with headers larger than V4.
809
    - Temp hacks to disable V4 headers for 32bit images (compatibility with
810
      other soft).
811
812
  -- 0.21 Changes/Bug Fixes -----------------------------------
813
    - Removed temporary data allocation for image with aligned scanlines.
814
      They are now directly written to output so memory requirements are
815
      much lower now.
816
    - Now uses and recognizes BITMAPINFOHEADERV4 when loading/saving.
817
      Mainly for formats with alpha channels.
818
    - Added ifR5G6B5 to supported formats, changed converting to supported
819
      formats little bit.
820
    - Rewritten SaveRLE8 nested procedure. Old code was long and
821
      mysterious - new is short and much more readable.
822
    - MakeCompatible method moved to base class, put ConvertToSupported here.
823
      GetSupportedFormats removed, it is now set in constructor.
824
    - Rewritten LoadRLE4 and LoadRLE8 nested procedures.
825
      Should be less buggy an more readable (load inspired by Colosseum Builders' code).
826
    - Made public properties for options registered to SetOption/GetOption
827
      functions. 
828
    - Addded alpha check to 32b bitmap loading too (teh same as in 16b
829
      bitmap loading).
830
    - Moved Convert1To8 and Convert4To8 to ImagingFormats
831
    - Changed extensions to filename masks.
832
    - Changed SaveData, LoadData, and MakeCompatible methods according
833
      to changes in base class in Imaging unit.
834
835
  -- 0.19 Changes/Bug Fixes -----------------------------------
836
    - fixed wrong const that caused A4R4G4B4 BMPs to load as A1R5G5B5
837
    - fixed the bug that caused 8bit RLE compressed bitmaps to load as
838
      whole black
839
840
  -- 0.17 Changes/Bug Fixes -----------------------------------
841
    - 16 bit images are usually without alpha but some has alpha
842
      channel and there is no indication of it - so I have added
843
      a check: if all pixels of image are with alpha = 0 image is treated
844
      as X1R5G5B5 otherwise as A1R5G5B5
845
846
  -- 0.13 Changes/Bug Fixes -----------------------------------
847
    - when loading 1/4 bit images with dword aligned dimensions
848
      there was ugly memory rewritting bug causing image corruption
849
850
}
851
852
end.
853