DEADSOFTWARE

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