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 |