DEADSOFTWARE

added Vampyre Imaging Library; now textures can be in various formats, including...
[d2df-sdl.git] / src / lib / vampimg / ImagingBitmap.pas
1 {
2 $Id: ImagingBitmap.pas 129 2008-08-06 20:01:30Z galfar $
3 Vampyre Imaging Library
4 by Marek Mauder
5 http://imaginglib.sourceforge.net
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
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.
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.
26 For more information about the LGPL: http://www.gnu.org/copyleft/lesser.html
27 }
29 { This unit contains image format loader/saver for Windows Bitmap images.}
30 unit ImagingBitmap;
32 {$I ImagingOptions.inc}
34 interface
36 uses
37 ImagingTypes, Imaging, ImagingUtility, ImagingFormats, ImagingIO;
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;
62 implementation
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;
71 const
72 { Bitmap file identifier 'BM'.}
73 BMMagic: Word = 19778;
75 { Constants for the TBitmapInfoHeader.Compression field.}
76 BI_RGB = 0;
77 BI_RLE8 = 1;
78 BI_RLE4 = 2;
79 BI_BITFIELDS = 3;
81 V3InfoHeaderSize = 40;
82 V4InfoHeaderSize = 108;
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;
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;
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;
127 { Used in RLE encoding and decoding.}
128 TRLEOpcode = packed record
129 Count: Byte;
130 Command: Byte;
131 end;
132 PRLEOpcode = ^TRLEOpcode;
134 { TBitmapFileFormat class implementation }
136 constructor TBitmapFileFormat.Create;
137 begin
138 inherited Create;
139 FName := SBitmapFormatName;
140 FCanLoad := True;
141 FCanSave := True;
142 FIsMultiImageFormat := False;
143 FSupportedFormats := BitmapSupportedFormats;
145 FUseRLE := BitmapDefaultRLE;
147 AddMasks(SBitmapMasks);
148 RegisterOption(ImagingBitmapRLE, @FUseRLE);
149 end;
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;
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
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);
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;
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;
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;
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);
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);
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;
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);
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 Info.PaletteEntries - 1 do
488 Palette[I].A := $FF;
489 end;
491 // Seek to the beginning of image bits
492 Seek(Handle, StartPos + LongInt(BF.Offset), smFromBeginning);
494 case BI.Compression of
495 BI_RGB: LoadRGB;
496 BI_RLE4: LoadRLE4;
497 BI_RLE8: LoadRLE8;
498 BI_BITFIELDS: LoadRGB;
499 end;
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;
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;
538 Result := True;
539 finally
540 FreeMemNil(Data);
541 end;
542 end;
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;
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;
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;
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];
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;
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;
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;
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);
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));
716 BF.Offset := Tell(Handle) - StartPos;
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;
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;
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);
748 Result := True;
749 finally
750 if MustBeFreed then
751 FreeImage(ImageToSave);
752 end;
753 end;
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;
777 ConvertImage(Image, ConvFormat);
778 end;
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;
795 initialization
796 RegisterImageFileFormat(TBitmapFileFormat);
799 File Notes:
801 -- TODOS ----------------------------------------------------
802 - nothing now
803 - Add option to choose to save V3 or V4 headers.
805 -- 0.25.0 Changes/Bug Fixes ---------------------------------
806 - Fixed problem with indexed BMP loading - some pal entries
807 could end up with alpha=0.
809 -- 0.23 Changes/Bug Fixes -----------------------------------
810 - Now saves bitmaps as bottom-up for better compatibility
811 (mainly Lazarus' TImage!).
812 - Fixed crash when loading bitmaps with headers larger than V4.
813 - Temp hacks to disable V4 headers for 32bit images (compatibility with
814 other soft).
816 -- 0.21 Changes/Bug Fixes -----------------------------------
817 - Removed temporary data allocation for image with aligned scanlines.
818 They are now directly written to output so memory requirements are
819 much lower now.
820 - Now uses and recognizes BITMAPINFOHEADERV4 when loading/saving.
821 Mainly for formats with alpha channels.
822 - Added ifR5G6B5 to supported formats, changed converting to supported
823 formats little bit.
824 - Rewritten SaveRLE8 nested procedure. Old code was long and
825 mysterious - new is short and much more readable.
826 - MakeCompatible method moved to base class, put ConvertToSupported here.
827 GetSupportedFormats removed, it is now set in constructor.
828 - Rewritten LoadRLE4 and LoadRLE8 nested procedures.
829 Should be less buggy an more readable (load inspired by Colosseum Builders' code).
830 - Made public properties for options registered to SetOption/GetOption
831 functions.
832 - Addded alpha check to 32b bitmap loading too (teh same as in 16b
833 bitmap loading).
834 - Moved Convert1To8 and Convert4To8 to ImagingFormats
835 - Changed extensions to filename masks.
836 - Changed SaveData, LoadData, and MakeCompatible methods according
837 to changes in base class in Imaging unit.
839 -- 0.19 Changes/Bug Fixes -----------------------------------
840 - fixed wrong const that caused A4R4G4B4 BMPs to load as A1R5G5B5
841 - fixed the bug that caused 8bit RLE compressed bitmaps to load as
842 whole black
844 -- 0.17 Changes/Bug Fixes -----------------------------------
845 - 16 bit images are usually without alpha but some has alpha
846 channel and there is no indication of it - so I have added
847 a check: if all pixels of image are with alpha = 0 image is treated
848 as X1R5G5B5 otherwise as A1R5G5B5
850 -- 0.13 Changes/Bug Fixes -----------------------------------
851 - when loading 1/4 bit images with dword aligned dimensions
852 there was ugly memory rewritting bug causing image corruption
856 end.