| 1 | {*****************************************************************************}
|
|---|
| 2 | {
|
|---|
| 3 | This original file was part of the Free Pascal's "Free Components Library".
|
|---|
| 4 | Copyright (c) 2003 by Mazen NEIFER of the Free Pascal development team
|
|---|
| 5 |
|
|---|
| 6 | BMP reader implementation.
|
|---|
| 7 |
|
|---|
| 8 | See the file COPYING.FPC, included in this distribution,
|
|---|
| 9 | for details about the copyright.
|
|---|
| 10 |
|
|---|
| 11 | This program is distributed in the hope that it will be useful,
|
|---|
| 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|---|
| 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|---|
| 14 | }
|
|---|
| 15 | {*****************************************************************************}
|
|---|
| 16 | { 08/2005 by Giulio Bernardi:
|
|---|
| 17 | - Added support for 16 and 15 bpp bitmaps.
|
|---|
| 18 | - If we have bpp <= 8 make an indexed image instead of converting it to RGB
|
|---|
| 19 | - Support for RLE4 and RLE8 decoding
|
|---|
| 20 | - Support for top-down bitmaps
|
|---|
| 21 |
|
|---|
| 22 | 03/2014 by circular:
|
|---|
| 23 | - RLE optimisation using a read buffer
|
|---|
| 24 | - direct access to pixels with TBGRABitmap
|
|---|
| 25 | - vertical shrink option with MinifyHeight,WantedHeight,OutputHeight (useful for thumbnails)
|
|---|
| 26 | 01/2017 by circular:
|
|---|
| 27 | - support for OS/2 1.x format
|
|---|
| 28 | - support for headerless files
|
|---|
| 29 | }
|
|---|
| 30 |
|
|---|
| 31 | {$mode objfpc}
|
|---|
| 32 | {$h+}
|
|---|
| 33 |
|
|---|
| 34 | unit BGRAReadBMP;
|
|---|
| 35 |
|
|---|
| 36 | interface
|
|---|
| 37 |
|
|---|
| 38 | uses FPImage, classes, sysutils, BMPcomn, BGRABitmapTypes;
|
|---|
| 39 |
|
|---|
| 40 | type
|
|---|
| 41 | TBMPTransparencyOption = (toAuto, toTransparent, toOpaque);
|
|---|
| 42 | TBitMapInfoHeader = BMPcomn.TBitMapInfoHeader;
|
|---|
| 43 | TBitMapFileHeader = BMPcomn.TBitMapFileHeader;
|
|---|
| 44 | TOS2BitmapHeader = packed record
|
|---|
| 45 | bcSize: DWORD;
|
|---|
| 46 | bcWidth: Word;
|
|---|
| 47 | bcHeight: Word;
|
|---|
| 48 | bcPlanes: Word;
|
|---|
| 49 | bcBitCount: Word;
|
|---|
| 50 | end;
|
|---|
| 51 | TMinimumBitmapHeader = packed record
|
|---|
| 52 | Size:longint;
|
|---|
| 53 | Width:longint;
|
|---|
| 54 | Height:longint;
|
|---|
| 55 | Planes:word;
|
|---|
| 56 | BitCount:word;
|
|---|
| 57 | end;
|
|---|
| 58 | TBitmapSubFormat = (bsfWithFileHeader, bsfHeaderless, bsfHeaderlessWithMask);
|
|---|
| 59 | TReadScanlineProc = procedure(Row : Integer; Stream : TStream) of object;
|
|---|
| 60 | TWriteScanlineProc = procedure(Row : Integer; Img : TFPCustomImage) of object;
|
|---|
| 61 | TProgressProc = procedure(Percent: integer; var ShouldContinue: boolean) of object;
|
|---|
| 62 |
|
|---|
| 63 |
|
|---|
| 64 | { TBGRAReaderBMP }
|
|---|
| 65 |
|
|---|
| 66 | TBGRAReaderBMP = class (TBGRAImageReader)
|
|---|
| 67 | Private
|
|---|
| 68 | DeltaX, DeltaY : integer; // Used for the never-used delta option in RLE
|
|---|
| 69 | TopDown : boolean; // If set, bitmap is stored top down instead of bottom up
|
|---|
| 70 | Procedure FreeBufs; // Free (and nil) buffers.
|
|---|
| 71 | protected
|
|---|
| 72 | ReadSize : Integer; // Size (in bytes) of 1 scanline.
|
|---|
| 73 | BFH: TBitMapFileHeader; // The file header
|
|---|
| 74 | BFI: TBitMapInfoHeader; // The header as read from the stream.
|
|---|
| 75 | FPaletteEntrySize: integer; // 4 for Windows, 3 for OS/2 1.x
|
|---|
| 76 | FPalette : PFPcolor; // Buffer with Palette entries. (useless now)
|
|---|
| 77 | FBGRAPalette : PBGRAPixel;
|
|---|
| 78 | LineBuf : PByte; // Buffer for 1 scanline. Can be Byte, Word, TColorRGB or TColorRGBA
|
|---|
| 79 | RedMask, GreenMask, BlueMask : longword; //Used if Compression=bi_bitfields
|
|---|
| 80 | RedShift, GreenShift, BlueShift : shortint;
|
|---|
| 81 | FOutputHeight: integer;
|
|---|
| 82 | FOriginalHeight: Integer;
|
|---|
| 83 | FTransparencyOption: TBMPTransparencyOption;
|
|---|
| 84 | FBuffer: packed array of byte;
|
|---|
| 85 | FBufferPos, FBufferSize: integer;
|
|---|
| 86 | FBufferStream: TStream;
|
|---|
| 87 | FHasAlphaValues: boolean;
|
|---|
| 88 | FMaskData: PByte;
|
|---|
| 89 | FMaskDataSize: integer;
|
|---|
| 90 | // SetupRead will allocate the needed buffers, and read the colormap if needed.
|
|---|
| 91 | procedure SetupRead(nPalette, nRowBits: Integer; Stream : TStream); virtual;
|
|---|
| 92 | function CountBits(Value : byte) : shortint;
|
|---|
| 93 | function ShiftCount(Mask : longword) : shortint;
|
|---|
| 94 | function ExpandColor(value : longword) : TFPColor;
|
|---|
| 95 | function ExpandColorBGRA(value : longword) : TBGRAPixel;
|
|---|
| 96 | procedure ExpandRLE8ScanLine(Row : Integer; Stream : TStream);
|
|---|
| 97 | procedure ExpandRLE4ScanLine(Row : Integer; Stream : TStream);
|
|---|
| 98 | procedure ReadScanLine(Row : Integer; Stream : TStream); virtual;
|
|---|
| 99 | procedure SkipScanLine(Row : Integer; Stream : TStream); virtual;
|
|---|
| 100 | procedure WriteScanLine(Row : Integer; Img : TFPCustomImage); virtual;
|
|---|
| 101 | procedure WriteScanLineBGRA(Row : Integer; Img : TFPCustomImage); virtual;
|
|---|
| 102 | procedure ReadMaskLine({%H-}Row : Integer; Stream : TStream); virtual;
|
|---|
| 103 | procedure SkipMaskLine({%H-}Row : Integer; Stream : TStream); virtual;
|
|---|
| 104 | procedure WriteMaskLine(Row : Integer; Img : TFPCustomImage); virtual;
|
|---|
| 105 | // required by TFPCustomImageReader
|
|---|
| 106 | procedure InternalRead (Stream:TStream; Img:TFPCustomImage); override;
|
|---|
| 107 | function InternalCheck (Stream:TStream) : boolean; override;
|
|---|
| 108 | procedure InitReadBuffer(AStream: TStream; ASize: integer);
|
|---|
| 109 | procedure CloseReadBuffer;
|
|---|
| 110 | function GetNextBufferByte: byte;
|
|---|
| 111 | procedure MakeOpaque(Img: TFPCustomImage);
|
|---|
| 112 | procedure LoadMask(Stream:TStream; Img:TFPCustomImage; var ShouldContinue: boolean);
|
|---|
| 113 | procedure MainProgressProc(Percent: integer; var ShouldContinue: boolean);
|
|---|
| 114 | procedure ImageVerticalLoop(Stream:TStream; Img:TFPCustomImage;
|
|---|
| 115 | ReadProc, SkipProc: TReadScanlineProc; WriteProc: TWriteScanlineProc;
|
|---|
| 116 | ProgressProc: TProgressProc; var ShouldContinue: boolean);
|
|---|
| 117 | public
|
|---|
| 118 | MinifyHeight,WantedHeight: integer;
|
|---|
| 119 | Hotspot: TPoint;
|
|---|
| 120 | Subformat: TBitmapSubFormat;
|
|---|
| 121 | constructor Create; override;
|
|---|
| 122 | destructor Destroy; override;
|
|---|
| 123 | property OriginalHeight: integer read FOriginalHeight;
|
|---|
| 124 | property OutputHeight: integer read FOutputHeight;
|
|---|
| 125 | property TransparencyOption: TBMPTransparencyOption read FTransparencyOption write FTransparencyOption;
|
|---|
| 126 | function GetQuickInfo(AStream: TStream): TQuickImageInfo; override;
|
|---|
| 127 | function GetBitmapDraft(AStream: TStream; {%H-}AMaxWidth, AMaxHeight: integer; out AOriginalWidth,AOriginalHeight: integer): TBGRACustomBitmap; override;
|
|---|
| 128 | end;
|
|---|
| 129 |
|
|---|
| 130 | function MakeBitmapFileHeader(AData: TStream): TBitMapFileHeader;
|
|---|
| 131 |
|
|---|
| 132 | implementation
|
|---|
| 133 |
|
|---|
| 134 | uses math;
|
|---|
| 135 |
|
|---|
| 136 | function MakeBitmapFileHeader(AData: TStream): TBitMapFileHeader;
|
|---|
| 137 | var header: PBitMapInfoHeader;
|
|---|
| 138 | headerSize: integer;
|
|---|
| 139 | extraSize: integer;
|
|---|
| 140 | os2header: TOS2BitmapHeader;
|
|---|
| 141 | begin
|
|---|
| 142 | AData.Position := 0;
|
|---|
| 143 | headerSize := LEtoN(AData.ReadDWord);
|
|---|
| 144 | if headerSize = sizeof(TOS2BitmapHeader) then //OS2 1.x
|
|---|
| 145 | begin
|
|---|
| 146 | AData.ReadBuffer({%H-}os2header,sizeof(os2header));
|
|---|
| 147 | if LEtoN(os2header.bcBitCount) in [1,2,4,8] then
|
|---|
| 148 | begin
|
|---|
| 149 | extraSize := 3*(1 shl LEtoN(os2header.bcBitCount));
|
|---|
| 150 | end else
|
|---|
| 151 | extraSize := 0;
|
|---|
| 152 | result.bfType:= Word('BM');
|
|---|
| 153 | result.bfSize := NtoLE(Integer(sizeof(TBitMapFileHeader) + AData.Size));
|
|---|
| 154 | result.bfReserved:= 0;
|
|---|
| 155 | result.bfOffset := NtoLE(Integer(sizeof(TBitMapFileHeader) + headerSize + extraSize));
|
|---|
| 156 | end else
|
|---|
| 157 | begin
|
|---|
| 158 | if (headerSize < 16) or (headerSize > AData.Size) or (headerSize > 1024) then
|
|---|
| 159 | raise exception.Create('Invalid header size');
|
|---|
| 160 | getmem(header, headerSize);
|
|---|
| 161 | try
|
|---|
| 162 | fillchar(header^, headerSize,0);
|
|---|
| 163 | header^.Size := NtoLE(headerSize);
|
|---|
| 164 | AData.ReadBuffer((PByte(header)+4)^, headerSize-4);
|
|---|
| 165 | if LEtoN(header^.Compression) = BI_BITFIELDS then
|
|---|
| 166 | extraSize := 4*3
|
|---|
| 167 | else if LEtoN(header^.BitCount) in [1,2,4,8] then
|
|---|
| 168 | begin
|
|---|
| 169 | if header^.ClrUsed > 0 then
|
|---|
| 170 | extraSize := 4*header^.ClrUsed
|
|---|
| 171 | else
|
|---|
| 172 | extraSize := 4*(1 shl header^.BitCount);
|
|---|
| 173 | end else
|
|---|
| 174 | extraSize := 0;
|
|---|
| 175 | result.bfType:= Word('BM');
|
|---|
| 176 | result.bfSize := NtoLE(Integer(sizeof(TBitMapFileHeader) + AData.Size));
|
|---|
| 177 | result.bfReserved:= 0;
|
|---|
| 178 | result.bfOffset := NtoLE(Integer(sizeof(TBitMapFileHeader) + headerSize + extraSize));
|
|---|
| 179 | finally
|
|---|
| 180 | freemem(header);
|
|---|
| 181 | end;
|
|---|
| 182 | end;
|
|---|
| 183 | end;
|
|---|
| 184 |
|
|---|
| 185 | function RGBAToFPColor(Const RGBA: TColorRGBA) : TFPcolor;
|
|---|
| 186 | begin
|
|---|
| 187 | with Result, RGBA do
|
|---|
| 188 | begin
|
|---|
| 189 | Red :=(R shl 8) or R;
|
|---|
| 190 | Green :=(G shl 8) or G;
|
|---|
| 191 | Blue :=(B shl 8) or B;
|
|---|
| 192 | Alpha :=(A shl 8) or A
|
|---|
| 193 | end;
|
|---|
| 194 | end;
|
|---|
| 195 |
|
|---|
| 196 | Function RGBToFPColor(Const RGB : TColorRGB) : TFPColor;
|
|---|
| 197 |
|
|---|
| 198 | begin
|
|---|
| 199 | with Result,RGB do
|
|---|
| 200 | begin {Use only the high byte to convert the color}
|
|---|
| 201 | Red := (R shl 8) + R;
|
|---|
| 202 | Green := (G shl 8) + G;
|
|---|
| 203 | Blue := (B shl 8) + B;
|
|---|
| 204 | Alpha := AlphaOpaque;
|
|---|
| 205 | end;
|
|---|
| 206 | end;
|
|---|
| 207 |
|
|---|
| 208 | constructor TBGRAReaderBMP.Create;
|
|---|
| 209 |
|
|---|
| 210 | begin
|
|---|
| 211 | inherited create;
|
|---|
| 212 | FTransparencyOption := toTransparent;
|
|---|
| 213 | Subformat:= bsfWithFileHeader;
|
|---|
| 214 | end;
|
|---|
| 215 |
|
|---|
| 216 | destructor TBGRAReaderBMP.Destroy;
|
|---|
| 217 |
|
|---|
| 218 | begin
|
|---|
| 219 | FreeBufs;
|
|---|
| 220 | inherited destroy;
|
|---|
| 221 | end;
|
|---|
| 222 |
|
|---|
| 223 | function TBGRAReaderBMP.GetQuickInfo(AStream: TStream): TQuickImageInfo;
|
|---|
| 224 | var headerSize: dword;
|
|---|
| 225 | os2header: TOS2BitmapHeader;
|
|---|
| 226 | minHeader: TMinimumBitmapHeader;
|
|---|
| 227 | totalDepth: integer;
|
|---|
| 228 | headerPos: int64;
|
|---|
| 229 | begin
|
|---|
| 230 | fillchar({%H-}result, sizeof(result), 0);
|
|---|
| 231 | headerPos := AStream.Position;
|
|---|
| 232 | if AStream.Read({%H-}headerSize, sizeof(headerSize)) <> sizeof(headerSize) then exit;
|
|---|
| 233 | headerSize := LEtoN(headerSize);
|
|---|
| 234 |
|
|---|
| 235 | //check presence of file header
|
|---|
| 236 | if (headerSize and $ffff) = BMmagic then
|
|---|
| 237 | begin
|
|---|
| 238 | headerPos += sizeof(TBitMapFileHeader);
|
|---|
| 239 | AStream.Position := headerPos;
|
|---|
| 240 | if AStream.Read(headerSize, sizeof(headerSize)) <> sizeof(headerSize) then exit;
|
|---|
| 241 | headerSize := LEtoN(headerSize);
|
|---|
| 242 | end;
|
|---|
| 243 |
|
|---|
| 244 | AStream.Position := headerPos;
|
|---|
| 245 |
|
|---|
| 246 | if headerSize = sizeof(TOS2BitmapHeader) then //OS2 1.x
|
|---|
| 247 | begin
|
|---|
| 248 | if AStream.Read({%H-}os2header, sizeof(os2header)) <> sizeof(os2header) then exit;
|
|---|
| 249 | result.width := LEtoN(os2header.bcWidth);
|
|---|
| 250 | result.height := LEtoN(os2header.bcHeight);
|
|---|
| 251 | result.colorDepth := LEtoN(os2header.bcBitCount);
|
|---|
| 252 | result.alphaDepth := 0;
|
|---|
| 253 | end
|
|---|
| 254 | else
|
|---|
| 255 | if headerSize >= sizeof(minHeader) then
|
|---|
| 256 | begin
|
|---|
| 257 | if AStream.Read({%H-}minHeader, sizeof(minHeader)) <> sizeof(minHeader) then exit;
|
|---|
| 258 | result.width := LEtoN(minHeader.Width);
|
|---|
| 259 | result.height := LEtoN(minHeader.Height);
|
|---|
| 260 | totalDepth := LEtoN(minHeader.BitCount);
|
|---|
| 261 | if totalDepth > 24 then
|
|---|
| 262 | begin
|
|---|
| 263 | result.colorDepth:= 24;
|
|---|
| 264 | result.alphaDepth:= 8;
|
|---|
| 265 | end else
|
|---|
| 266 | begin
|
|---|
| 267 | result.colorDepth := totalDepth;
|
|---|
| 268 | result.alphaDepth:= 0;
|
|---|
| 269 | end;
|
|---|
| 270 | end else
|
|---|
| 271 | begin
|
|---|
| 272 | result.width := 0;
|
|---|
| 273 | result.height:= 0;
|
|---|
| 274 | result.colorDepth:= 0;
|
|---|
| 275 | result.alphaDepth:= 0;
|
|---|
| 276 | end;
|
|---|
| 277 | end;
|
|---|
| 278 |
|
|---|
| 279 | function TBGRAReaderBMP.GetBitmapDraft(AStream: TStream; AMaxWidth,
|
|---|
| 280 | AMaxHeight: integer; out AOriginalWidth, AOriginalHeight: integer): TBGRACustomBitmap;
|
|---|
| 281 | var
|
|---|
| 282 | bmpFormat: TBGRAReaderBMP;
|
|---|
| 283 | prevStreamPos: Int64;
|
|---|
| 284 | begin
|
|---|
| 285 | bmpFormat:= TBGRAReaderBMP.Create;
|
|---|
| 286 | bmpFormat.Subformat:= Subformat;
|
|---|
| 287 | bmpFormat.MinifyHeight := AMaxHeight*2;
|
|---|
| 288 | result := BGRABitmapFactory.Create;
|
|---|
| 289 | prevStreamPos := AStream.Position;
|
|---|
| 290 | try
|
|---|
| 291 | result.LoadFromStream(AStream, bmpFormat);
|
|---|
| 292 | AOriginalWidth:= result.Width;
|
|---|
| 293 | AOriginalHeight:= bmpFormat.OriginalHeight;
|
|---|
| 294 | finally
|
|---|
| 295 | bmpFormat.Free;
|
|---|
| 296 | AStream.Position := prevStreamPos;
|
|---|
| 297 | end;
|
|---|
| 298 | end;
|
|---|
| 299 |
|
|---|
| 300 | procedure TBGRAReaderBMP.FreeBufs;
|
|---|
| 301 | begin
|
|---|
| 302 | If (LineBuf<>Nil) then
|
|---|
| 303 | begin
|
|---|
| 304 | FreeMem(LineBuf);
|
|---|
| 305 | LineBuf:=Nil;
|
|---|
| 306 | end;
|
|---|
| 307 | If (FPalette<>Nil) then
|
|---|
| 308 | begin
|
|---|
| 309 | FreeMem(FPalette);
|
|---|
| 310 | FPalette:=Nil;
|
|---|
| 311 | end;
|
|---|
| 312 | If (FBGRAPalette<>Nil) then
|
|---|
| 313 | begin
|
|---|
| 314 | FreeMem(FBGRAPalette);
|
|---|
| 315 | FBGRAPalette:=Nil;
|
|---|
| 316 | end;
|
|---|
| 317 | end;
|
|---|
| 318 |
|
|---|
| 319 | { Counts how many bits are set }
|
|---|
| 320 | function TBGRAReaderBMP.CountBits(Value : byte) : shortint;
|
|---|
| 321 | var i,bits : shortint;
|
|---|
| 322 | begin
|
|---|
| 323 | bits:=0;
|
|---|
| 324 | for i:=0 to 7 do
|
|---|
| 325 | begin
|
|---|
| 326 | if (value mod 2)<>0 then inc(bits);
|
|---|
| 327 | value:=value shr 1;
|
|---|
| 328 | end;
|
|---|
| 329 | Result:=bits;
|
|---|
| 330 | end;
|
|---|
| 331 |
|
|---|
| 332 | { If compression is bi_bitfields, there could be arbitrary masks for colors.
|
|---|
| 333 | Although this is not compatible with windows9x it's better to know how to read these bitmaps
|
|---|
| 334 | We must determine how to switch the value once masked
|
|---|
| 335 | Example: 0000 0111 1110 0000, if we shr 5 we have 00XX XXXX for the color, but these bits must be the
|
|---|
| 336 | highest in the color, so we must shr (5-(8-6))=3, and we have XXXX XX00.
|
|---|
| 337 | A negative value means "shift left" }
|
|---|
| 338 | function TBGRAReaderBMP.ShiftCount(Mask : longword) : shortint;
|
|---|
| 339 | var tmp : shortint;
|
|---|
| 340 | begin
|
|---|
| 341 | tmp:=0;
|
|---|
| 342 | if Mask=0 then
|
|---|
| 343 | begin
|
|---|
| 344 | Result:=0;
|
|---|
| 345 | exit;
|
|---|
| 346 | end;
|
|---|
| 347 |
|
|---|
| 348 | while (Mask mod 2)=0 do { rightmost bit is 0 }
|
|---|
| 349 | begin
|
|---|
| 350 | inc(tmp);
|
|---|
| 351 | Mask:= Mask shr 1;
|
|---|
| 352 | end;
|
|---|
| 353 | tmp:=tmp-(8-CountBits(Mask and $FF));
|
|---|
| 354 | Result:=tmp;
|
|---|
| 355 | end;
|
|---|
| 356 |
|
|---|
| 357 | function TBGRAReaderBMP.ExpandColor(value : longword) : TFPColor;
|
|---|
| 358 | var tmpr, tmpg, tmpb : longword;
|
|---|
| 359 | col : TColorRGB;
|
|---|
| 360 | begin
|
|---|
| 361 | {$IFDEF ENDIAN_BIG}
|
|---|
| 362 | value:=swap(value);
|
|---|
| 363 | {$ENDIF}
|
|---|
| 364 | tmpr:=value and RedMask;
|
|---|
| 365 | tmpg:=value and GreenMask;
|
|---|
| 366 | tmpb:=value and BlueMask;
|
|---|
| 367 | if RedShift < 0 then col.R:=byte(tmpr shl (-RedShift))
|
|---|
| 368 | else col.R:=byte(tmpr shr RedShift);
|
|---|
| 369 | if GreenShift < 0 then col.G:=byte(tmpg shl (-GreenShift))
|
|---|
| 370 | else col.G:=byte(tmpg shr GreenShift);
|
|---|
| 371 | if BlueShift < 0 then col.B:=byte(tmpb shl (-BlueShift))
|
|---|
| 372 | else col.B:=byte(tmpb shr BlueShift);
|
|---|
| 373 | Result:=RGBToFPColor(col);
|
|---|
| 374 | end;
|
|---|
| 375 |
|
|---|
| 376 | function TBGRAReaderBMP.ExpandColorBGRA(value: longword): TBGRAPixel;
|
|---|
| 377 | var tmpr, tmpg, tmpb : longword;
|
|---|
| 378 | begin
|
|---|
| 379 | {$IFDEF ENDIAN_BIG}
|
|---|
| 380 | value:=swap(value);
|
|---|
| 381 | {$ENDIF}
|
|---|
| 382 | tmpr:=value and RedMask;
|
|---|
| 383 | tmpg:=value and GreenMask;
|
|---|
| 384 | tmpb:=value and BlueMask;
|
|---|
| 385 | if RedShift < 0 then result.red:=byte(tmpr shl (-RedShift))
|
|---|
| 386 | else result.red:=byte(tmpr shr RedShift);
|
|---|
| 387 | if GreenShift < 0 then result.green:=byte(tmpg shl (-GreenShift))
|
|---|
| 388 | else result.green:=byte(tmpg shr GreenShift);
|
|---|
| 389 | if BlueShift < 0 then result.blue:=byte(tmpb shl (-BlueShift))
|
|---|
| 390 | else result.blue:=byte(tmpb shr BlueShift);
|
|---|
| 391 | result.alpha:= 255;
|
|---|
| 392 | end;
|
|---|
| 393 |
|
|---|
| 394 | procedure TBGRAReaderBMP.SetupRead(nPalette, nRowBits: Integer; Stream : TStream);
|
|---|
| 395 |
|
|---|
| 396 | var
|
|---|
| 397 | ColInfo: ARRAY OF TColorRGBA;
|
|---|
| 398 | ColInfo3: packed array of TColorRGB;
|
|---|
| 399 | i,colorPresent: Integer;
|
|---|
| 400 |
|
|---|
| 401 | begin
|
|---|
| 402 | if ((BFI.Compression=BI_RGB) and (BFI.BitCount=16)) then { 5 bits per channel, fixed mask }
|
|---|
| 403 | begin
|
|---|
| 404 | RedMask:=$7C00; RedShift:=7;
|
|---|
| 405 | GreenMask:=$03E0; GreenShift:=2;
|
|---|
| 406 | BlueMask:=$001F; BlueShift:=-3;
|
|---|
| 407 | end
|
|---|
| 408 | else if ((BFI.Compression=BI_BITFIELDS) and (BFI.BitCount in [16,32])) then { arbitrary mask }
|
|---|
| 409 | begin
|
|---|
| 410 | Stream.Read(RedMask,4);
|
|---|
| 411 | Stream.Read(GreenMask,4);
|
|---|
| 412 | Stream.Read(BlueMask,4);
|
|---|
| 413 | {$IFDEF ENDIAN_BIG}
|
|---|
| 414 | RedMask:=swap(RedMask);
|
|---|
| 415 | GreenMask:=swap(GreenMask);
|
|---|
| 416 | BlueMask:=swap(BlueMask);
|
|---|
| 417 | {$ENDIF}
|
|---|
| 418 | RedShift:=ShiftCount(RedMask);
|
|---|
| 419 | GreenShift:=ShiftCount(GreenMask);
|
|---|
| 420 | BlueShift:=ShiftCount(BlueMask);
|
|---|
| 421 | end
|
|---|
| 422 | else if nPalette>0 then
|
|---|
| 423 | begin
|
|---|
| 424 | GetMem(FPalette, nPalette*SizeOf(TFPColor));
|
|---|
| 425 | GetMem(FBGRAPalette, nPalette*SizeOf(TBGRAPixel));
|
|---|
| 426 | SetLength(ColInfo, nPalette);
|
|---|
| 427 | if BFI.ClrUsed>0 then
|
|---|
| 428 | colorPresent:= min(BFI.ClrUsed,nPalette)
|
|---|
| 429 | else
|
|---|
| 430 | colorPresent:= nPalette;
|
|---|
| 431 | if FPaletteEntrySize = 3 then
|
|---|
| 432 | begin
|
|---|
| 433 | setlength(ColInfo3, nPalette);
|
|---|
| 434 | Stream.Read(ColInfo3[0],colorPresent*SizeOf(TColorRGB));
|
|---|
| 435 | for i := 0 to colorPresent-1 do
|
|---|
| 436 | ColInfo[i].RGB := ColInfo3[i];
|
|---|
| 437 | end
|
|---|
| 438 | else
|
|---|
| 439 | begin
|
|---|
| 440 | Stream.Read(ColInfo[0],colorPresent*SizeOf(TColorRGBA));
|
|---|
| 441 | end;
|
|---|
| 442 | for i := 0 to High(ColInfo) do
|
|---|
| 443 | begin
|
|---|
| 444 | FPalette[i] := RGBToFPColor(ColInfo[i].RGB);
|
|---|
| 445 | FBGRAPalette[i]:= FPColorToBGRA(FPalette[i]);
|
|---|
| 446 | end
|
|---|
| 447 | end
|
|---|
| 448 | else if BFI.ClrUsed>0 then { Skip palette }
|
|---|
| 449 | {$PUSH}{$HINTS OFF}
|
|---|
| 450 | Stream.Position := Stream.Position + BFI.ClrUsed*SizeOf(TColorRGBA);
|
|---|
| 451 | {$POP}
|
|---|
| 452 | ReadSize:=((nRowBits + 31) div 32) shl 2;
|
|---|
| 453 | GetMem(LineBuf,ReadSize);
|
|---|
| 454 | end;
|
|---|
| 455 |
|
|---|
| 456 | procedure TBGRAReaderBMP.InternalRead(Stream:TStream; Img:TFPCustomImage);
|
|---|
| 457 |
|
|---|
| 458 | Var
|
|---|
| 459 | i, pallen : Integer;
|
|---|
| 460 | BadCompression : boolean;
|
|---|
| 461 | WriteScanlineProc: TWriteScanlineProc;
|
|---|
| 462 | headerSize: longword;
|
|---|
| 463 | os2header: TOS2BitmapHeader;
|
|---|
| 464 | shouldContinue: boolean;
|
|---|
| 465 |
|
|---|
| 466 | begin
|
|---|
| 467 | shouldContinue:=true;
|
|---|
| 468 | Progress(psStarting,0,false,EmptyRect,'',shouldContinue);
|
|---|
| 469 | if not shouldContinue then exit;
|
|---|
| 470 |
|
|---|
| 471 | headerSize := LEtoN(Stream.ReadDWord);
|
|---|
| 472 | fillchar({%H-}BFI,SizeOf(BFI),0);
|
|---|
| 473 | if headerSize = sizeof(TOS2BitmapHeader) then
|
|---|
| 474 | begin
|
|---|
| 475 | fillchar({%H-}os2header,SizeOf(os2header),0);
|
|---|
| 476 | Stream.Read(os2header.bcWidth,min(SizeOf(os2header),headerSize)-sizeof(DWord));
|
|---|
| 477 | BFI.Size := 16;
|
|---|
| 478 | BFI.Width := LEtoN(os2header.bcWidth);
|
|---|
| 479 | BFI.Height := LEtoN(os2header.bcHeight);
|
|---|
| 480 | BFI.Planes := LEtoN(os2header.bcPlanes);
|
|---|
| 481 | BFI.BitCount := LEtoN(os2header.bcBitCount);
|
|---|
| 482 | FPaletteEntrySize:= 3;
|
|---|
| 483 | end else
|
|---|
| 484 | begin
|
|---|
| 485 | Stream.Read(BFI.Width,min(SizeOf(BFI),headerSize)-sizeof(DWord));
|
|---|
| 486 | {$IFDEF ENDIAN_BIG}
|
|---|
| 487 | SwapBMPInfoHeader(BFI);
|
|---|
| 488 | {$ENDIF}
|
|---|
| 489 | BFI.Size := headerSize;
|
|---|
| 490 | FPaletteEntrySize:= 4;
|
|---|
| 491 | end;
|
|---|
| 492 | { This will move past any junk after the BFI header }
|
|---|
| 493 | Stream.Position:=Stream.Position-SizeOf(BFI)+BFI.Size;
|
|---|
| 494 | with BFI do
|
|---|
| 495 | begin
|
|---|
| 496 | BadCompression:=false;
|
|---|
| 497 | if ((Compression=BI_RLE4) and (BitCount<>4)) then BadCompression:=true;
|
|---|
| 498 | if ((Compression=BI_RLE8) and (BitCount<>8)) then BadCompression:=true;
|
|---|
| 499 | if ((Compression=BI_BITFIELDS) and (not (BitCount in [16,32]))) then BadCompression:=true;
|
|---|
| 500 | if not (Compression in [BI_RGB..BI_BITFIELDS]) then BadCompression:=true;
|
|---|
| 501 | if BadCompression then
|
|---|
| 502 | raise FPImageException.Create('Bad BMP compression mode');
|
|---|
| 503 | TopDown:=(Height<0);
|
|---|
| 504 | Height:=abs(Height);
|
|---|
| 505 | FOriginalHeight := Height;
|
|---|
| 506 | if (TopDown and (not (Compression in [BI_RGB,BI_BITFIELDS]))) then
|
|---|
| 507 | raise FPImageException.Create('Top-down bitmaps cannot be compressed');
|
|---|
| 508 | Img.SetSize(0,0);
|
|---|
| 509 | if BitCount<=8 then
|
|---|
| 510 | begin
|
|---|
| 511 | Img.UsePalette:=true;
|
|---|
| 512 | Img.Palette.Clear;
|
|---|
| 513 | end
|
|---|
| 514 | else Img.UsePalette:=false;
|
|---|
| 515 | Case BFI.BitCount of
|
|---|
| 516 | 1 : { Monochrome }
|
|---|
| 517 | SetupRead(2,Width,Stream);
|
|---|
| 518 | 4 :
|
|---|
| 519 | SetupRead(16,Width*4,Stream);
|
|---|
| 520 | 8 :
|
|---|
| 521 | SetupRead(256,Width*8,Stream);
|
|---|
| 522 | 16 :
|
|---|
| 523 | SetupRead(0,Width*8*2,Stream);
|
|---|
| 524 | 24:
|
|---|
| 525 | SetupRead(0,Width*8*3,Stream);
|
|---|
| 526 | 32:
|
|---|
| 527 | SetupRead(0,Width*8*4,Stream);
|
|---|
| 528 | else raise exception.Create('Invalid bit depth ('+inttostr(BFI.BitCount)+')');
|
|---|
| 529 | end;
|
|---|
| 530 | end;
|
|---|
| 531 | if Subformat = bsfHeaderlessWithMask then BFI.Height := BFI.Height div 2;
|
|---|
| 532 | Try
|
|---|
| 533 | { Note: it would be better to Fill the image palette in setupread instead of creating FPalette.
|
|---|
| 534 | FPalette is indeed useless but we cannot remove it since it's not private :\ }
|
|---|
| 535 | pallen:=0;
|
|---|
| 536 | if BFI.BitCount<=8 then
|
|---|
| 537 | if BFI.ClrUsed>0 then pallen:=BFI.ClrUsed
|
|---|
| 538 | else pallen:=(1 shl BFI.BitCount);
|
|---|
| 539 | if pallen>0 then
|
|---|
| 540 | begin
|
|---|
| 541 | if FPalette = nil then raise exception.Create('Internal error: palette object not initialized');
|
|---|
| 542 | Img.Palette.Count:=pallen;
|
|---|
| 543 | for i:=0 to pallen-1 do
|
|---|
| 544 | Img.Palette.Color[i]:=FPalette[i];
|
|---|
| 545 | end;
|
|---|
| 546 | if (MinifyHeight > 0) and (MinifyHeight < BFI.Height) then FOutputHeight:= MinifyHeight else
|
|---|
| 547 | if WantedHeight > 0 then FOutputHeight:= WantedHeight else
|
|---|
| 548 | FOutputHeight:= BFI.Height;
|
|---|
| 549 |
|
|---|
| 550 | if (BFI.Compression=BI_RLE8) or(BFI.Compression=BI_RLE4) then InitReadBuffer(Stream,2048);
|
|---|
| 551 | FHasAlphaValues:= false;
|
|---|
| 552 |
|
|---|
| 553 | Img.SetSize(BFI.Width,FOutputHeight);
|
|---|
| 554 |
|
|---|
| 555 | if Img is TBGRACustomBitmap then
|
|---|
| 556 | WriteScanlineProc := @WriteScanLineBGRA else
|
|---|
| 557 | WriteScanlineProc := @WriteScanLine;
|
|---|
| 558 |
|
|---|
| 559 | ImageVerticalLoop(Stream, Img, @ReadScanLine, @SkipScanLine, WriteScanlineProc,
|
|---|
| 560 | @MainProgressProc, shouldContinue);
|
|---|
| 561 |
|
|---|
| 562 | if shouldContinue then
|
|---|
| 563 | begin
|
|---|
| 564 | if not FHasAlphaValues and (TransparencyOption = toAuto) and (BFI.BitCount = 32) then
|
|---|
| 565 | MakeOpaque(Img);
|
|---|
| 566 | if (BFI.Compression=BI_RLE8) or(BFI.Compression=BI_RLE4) then CloseReadBuffer;
|
|---|
| 567 |
|
|---|
| 568 | if Subformat = bsfHeaderlessWithMask then LoadMask(Stream,Img, shouldContinue);
|
|---|
| 569 |
|
|---|
| 570 | Progress(psEnding,100,false,EmptyRect,'',shouldContinue);
|
|---|
| 571 | end;
|
|---|
| 572 |
|
|---|
| 573 | finally
|
|---|
| 574 | FreeBufs;
|
|---|
| 575 | end;
|
|---|
| 576 | end;
|
|---|
| 577 |
|
|---|
| 578 | procedure TBGRAReaderBMP.ExpandRLE8ScanLine(Row : Integer; Stream : TStream);
|
|---|
| 579 | var i,j,k : integer;
|
|---|
| 580 | b0, b1 : byte;
|
|---|
| 581 | begin
|
|---|
| 582 | i:=0;
|
|---|
| 583 | while true do
|
|---|
| 584 | begin
|
|---|
| 585 | { let's see if we must skip pixels because of delta... }
|
|---|
| 586 | if DeltaY<>-1 then
|
|---|
| 587 | begin
|
|---|
| 588 | if Row=DeltaY then j:=DeltaX { If we are on the same line, skip till DeltaX }
|
|---|
| 589 | else j:=ReadSize; { else skip up to the end of this line }
|
|---|
| 590 | while (i<j) do
|
|---|
| 591 | begin
|
|---|
| 592 | LineBuf[i]:=0;
|
|---|
| 593 | inc(i);
|
|---|
| 594 | end;
|
|---|
| 595 |
|
|---|
| 596 | if Row=DeltaY then { we don't need delta anymore }
|
|---|
| 597 | DeltaY:=-1
|
|---|
| 598 | else break; { skipping must continue on the next line, we are finished here }
|
|---|
| 599 | end;
|
|---|
| 600 |
|
|---|
| 601 | b0 := GetNextBufferByte; b1 := GetNextBufferByte;
|
|---|
| 602 | if b0<>0 then { number of repetitions }
|
|---|
| 603 | begin
|
|---|
| 604 | if b0+i>ReadSize then
|
|---|
| 605 | raise FPImageException.Create('Bad BMP RLE chunk at row '+inttostr(row)+', col '+inttostr(i)+', file offset $'+inttohex(Stream.Position,16) );
|
|---|
| 606 | j:=i+b0;
|
|---|
| 607 | while (i<j) do
|
|---|
| 608 | begin
|
|---|
| 609 | LineBuf[i]:=b1;
|
|---|
| 610 | inc(i);
|
|---|
| 611 | end;
|
|---|
| 612 | end
|
|---|
| 613 | else
|
|---|
| 614 | case b1 of
|
|---|
| 615 | 0: break; { end of line }
|
|---|
| 616 | 1: break; { end of file }
|
|---|
| 617 | 2: begin { Next pixel position. Skipped pixels should be left untouched, but we set them to zero }
|
|---|
| 618 | b0 := GetNextBufferByte; b1 := GetNextBufferByte;
|
|---|
| 619 | DeltaX:=i+b0; DeltaY:=Row+b1;
|
|---|
| 620 | end
|
|---|
| 621 | else begin { absolute mode }
|
|---|
| 622 | if b1+i>ReadSize then
|
|---|
| 623 | raise FPImageException.Create('Bad BMP RLE chunk at row '+inttostr(row)+', col '+inttostr(i)+', file offset $'+inttohex(Stream.Position,16) );
|
|---|
| 624 | for k := b1-1 downto 0 do
|
|---|
| 625 | Begin
|
|---|
| 626 | LineBuf[i] := GetNextBufferByte;
|
|---|
| 627 | Inc(i);
|
|---|
| 628 | end;
|
|---|
| 629 | { aligned on 2 bytes boundary: every group starts on a 2 bytes boundary, but absolute group
|
|---|
| 630 | could end on odd address if there is a odd number of elements, so we pad it }
|
|---|
| 631 | if (b1 mod 2)<>0 then GetNextBufferByte;
|
|---|
| 632 | end;
|
|---|
| 633 | end;
|
|---|
| 634 | end;
|
|---|
| 635 | end;
|
|---|
| 636 |
|
|---|
| 637 | procedure TBGRAReaderBMP.ExpandRLE4ScanLine(Row : Integer; Stream : TStream);
|
|---|
| 638 | var i,j,tmpsize : integer;
|
|---|
| 639 | b0, b1 : byte;
|
|---|
| 640 | nibline : pbyte; { temporary array of nibbles }
|
|---|
| 641 | even : boolean;
|
|---|
| 642 | begin
|
|---|
| 643 | tmpsize:=ReadSize*2; { ReadSize is in bytes, while nibline is made of nibbles, so it's 2*readsize long }
|
|---|
| 644 | getmem(nibline,tmpsize);
|
|---|
| 645 | if nibline=nil then
|
|---|
| 646 | raise FPImageException.Create('Out of memory');
|
|---|
| 647 | try
|
|---|
| 648 | i:=0;
|
|---|
| 649 | while true do
|
|---|
| 650 | begin
|
|---|
| 651 | { let's see if we must skip pixels because of delta... }
|
|---|
| 652 | if DeltaY<>-1 then
|
|---|
| 653 | begin
|
|---|
| 654 | if Row=DeltaY then j:=DeltaX { If we are on the same line, skip till DeltaX }
|
|---|
| 655 | else j:=tmpsize; { else skip up to the end of this line }
|
|---|
| 656 | while (i<j) do
|
|---|
| 657 | begin
|
|---|
| 658 | NibLine[i]:=0;
|
|---|
| 659 | inc(i);
|
|---|
| 660 | end;
|
|---|
| 661 |
|
|---|
| 662 | if Row=DeltaY then { we don't need delta anymore }
|
|---|
| 663 | DeltaY:=-1
|
|---|
| 664 | else break; { skipping must continue on the next line, we are finished here }
|
|---|
| 665 | end;
|
|---|
| 666 |
|
|---|
| 667 | b0 := GetNextBufferByte; b1:= GetNextBufferByte;
|
|---|
| 668 | if b0<>0 then { number of repetitions }
|
|---|
| 669 | begin
|
|---|
| 670 | if b0+i>tmpsize then
|
|---|
| 671 | raise FPImageException.Create('Bad BMP RLE chunk at row '+inttostr(row)+', col '+inttostr(i)+', file offset $'+inttohex(Stream.Position,16) );
|
|---|
| 672 | even:=true;
|
|---|
| 673 | j:=i+b0;
|
|---|
| 674 | while (i<j) do
|
|---|
| 675 | begin
|
|---|
| 676 | if even then NibLine[i]:=(b1 and $F0) shr 4
|
|---|
| 677 | else NibLine[i]:=b1 and $0F;
|
|---|
| 678 | inc(i);
|
|---|
| 679 | even:=not even;
|
|---|
| 680 | end;
|
|---|
| 681 | end
|
|---|
| 682 | else
|
|---|
| 683 | case b1 of
|
|---|
| 684 | 0: break; { end of line }
|
|---|
| 685 | 1: break; { end of file }
|
|---|
| 686 | 2: begin { Next pixel position. Skipped pixels should be left untouched, but we set them to zero }
|
|---|
| 687 | b0 := GetNextBufferByte; b1:= GetNextBufferByte;
|
|---|
| 688 | DeltaX:=i+b0; DeltaY:=Row+b1;
|
|---|
| 689 | end
|
|---|
| 690 | else begin { absolute mode }
|
|---|
| 691 | if b1+i>tmpsize then
|
|---|
| 692 | raise FPImageException.Create('Bad BMP RLE chunk at row '+inttostr(row)+', col '+inttostr(i)+', file offset $'+inttohex(Stream.Position,16) );
|
|---|
| 693 | j:=i+b1;
|
|---|
| 694 | even:=true;
|
|---|
| 695 | while (i<j) do
|
|---|
| 696 | begin
|
|---|
| 697 | if even then
|
|---|
| 698 | begin
|
|---|
| 699 | b0 := GetNextBufferByte;
|
|---|
| 700 | NibLine[i]:=(b0 and $F0) shr 4;
|
|---|
| 701 | end
|
|---|
| 702 | else NibLine[i]:=b0 and $0F;
|
|---|
| 703 | inc(i);
|
|---|
| 704 | even:=not even;
|
|---|
| 705 | end;
|
|---|
| 706 | { aligned on 2 bytes boundary: see rle8 for details }
|
|---|
| 707 | b1:=b1+(b1 mod 2);
|
|---|
| 708 | if (b1 mod 4)<>0 then GetNextBufferByte;
|
|---|
| 709 | end;
|
|---|
| 710 | end;
|
|---|
| 711 | end;
|
|---|
| 712 | { pack the nibline into the linebuf }
|
|---|
| 713 | for i:=0 to ReadSize-1 do
|
|---|
| 714 | LineBuf[i]:=(NibLine[i*2] shl 4) or NibLine[i*2+1];
|
|---|
| 715 | finally
|
|---|
| 716 | FreeMem(nibline)
|
|---|
| 717 | end;
|
|---|
| 718 | end;
|
|---|
| 719 |
|
|---|
| 720 | procedure TBGRAReaderBMP.ReadScanLine(Row : Integer; Stream : TStream);
|
|---|
| 721 | begin
|
|---|
| 722 | if BFI.Compression=BI_RLE8 then ExpandRLE8ScanLine(Row,Stream)
|
|---|
| 723 | else if BFI.Compression=BI_RLE4 then ExpandRLE4ScanLine(Row,Stream)
|
|---|
| 724 | else Stream.Read(LineBuf[0],ReadSize);
|
|---|
| 725 | end;
|
|---|
| 726 |
|
|---|
| 727 | procedure TBGRAReaderBMP.SkipScanLine(Row: Integer; Stream: TStream);
|
|---|
| 728 | begin
|
|---|
| 729 | if (BFI.Compression=BI_RLE8) or(BFI.Compression=BI_RLE4) then ReadScanLine(Row,Stream)
|
|---|
| 730 | else Stream.Position := Stream.Position+ReadSize;
|
|---|
| 731 | end;
|
|---|
| 732 |
|
|---|
| 733 | procedure TBGRAReaderBMP.WriteScanLine(Row : Integer; Img : TFPCustomImage);
|
|---|
| 734 |
|
|---|
| 735 | Var
|
|---|
| 736 | Column : Integer;
|
|---|
| 737 | c: TFPColor;
|
|---|
| 738 | begin
|
|---|
| 739 | Case BFI.BitCount of
|
|---|
| 740 | 1 :
|
|---|
| 741 | for Column:=0 to Img.Width-1 do
|
|---|
| 742 | if ((LineBuf[Column div 8] shr (7-(Column and 7)) ) and 1) <> 0 then
|
|---|
| 743 | img.Pixels[Column,Row]:=1
|
|---|
| 744 | else
|
|---|
| 745 | img.Pixels[Column,Row]:=0;
|
|---|
| 746 | 4 :
|
|---|
| 747 | for Column:=0 to img.Width-1 do
|
|---|
| 748 | img.Pixels[Column,Row]:=(LineBuf[Column div 2] shr (((Column+1) and 1)*4)) and $0f;
|
|---|
| 749 | 8 :
|
|---|
| 750 | for Column:=0 to img.Width-1 do
|
|---|
| 751 | img.Pixels[Column,Row]:=LineBuf[Column];
|
|---|
| 752 | 16 :
|
|---|
| 753 | for Column:=0 to img.Width-1 do
|
|---|
| 754 | img.colors[Column,Row]:=ExpandColor(PWord(LineBuf)[Column]);
|
|---|
| 755 | 24 :
|
|---|
| 756 | for Column:=0 to img.Width-1 do
|
|---|
| 757 | img.colors[Column,Row]:=RGBToFPColor(PColorRGB(LineBuf)[Column]);
|
|---|
| 758 | 32 :
|
|---|
| 759 | for Column:=0 to img.Width-1 do
|
|---|
| 760 | if BFI.Compression=BI_BITFIELDS then
|
|---|
| 761 | img.colors[Column,Row]:=ExpandColor(PLongWord(LineBuf)[Column])
|
|---|
| 762 | else
|
|---|
| 763 | begin
|
|---|
| 764 | if FTransparencyOption = toOpaque then
|
|---|
| 765 | img.colors[Column,Row]:=RGBToFPColor(PColorRGB(PColorRGBA(LineBuf)+Column)^)
|
|---|
| 766 | else
|
|---|
| 767 | begin
|
|---|
| 768 | c := RGBAToFPColor(PColorRGBA(LineBuf)[Column]);
|
|---|
| 769 | if c.alpha <> 0 then FHasAlphaValues:= true;
|
|---|
| 770 | img.colors[Column,Row]:= c;
|
|---|
| 771 | end;
|
|---|
| 772 | end;
|
|---|
| 773 | end;
|
|---|
| 774 | end;
|
|---|
| 775 |
|
|---|
| 776 | procedure TBGRAReaderBMP.WriteScanLineBGRA(Row: Integer; Img: TFPCustomImage);
|
|---|
| 777 |
|
|---|
| 778 | Var
|
|---|
| 779 | Column : Integer;
|
|---|
| 780 | PDest: PBGRAPixel;
|
|---|
| 781 | PSrc: PByte;
|
|---|
| 782 | begin
|
|---|
| 783 | PDest := TBGRACustomBitmap(Img).ScanLine[Row];
|
|---|
| 784 | Case BFI.BitCount of
|
|---|
| 785 | 1 :
|
|---|
| 786 | for Column:=0 to Img.Width-1 do
|
|---|
| 787 | begin
|
|---|
| 788 | if ((LineBuf[Column div 8] shr (7-(Column and 7)) ) and 1) <> 0 then
|
|---|
| 789 | PDest^ := FBGRAPalette[1]
|
|---|
| 790 | else
|
|---|
| 791 | PDest^ := FBGRAPalette[0];
|
|---|
| 792 | inc(PDest);
|
|---|
| 793 | end;
|
|---|
| 794 | 4 :
|
|---|
| 795 | for Column:=0 to img.Width-1 do
|
|---|
| 796 | begin
|
|---|
| 797 | PDest^ := FBGRAPalette[(LineBuf[Column div 2] shr (((Column+1) and 1)*4)) and $0f];
|
|---|
| 798 | inc(PDest);
|
|---|
| 799 | end;
|
|---|
| 800 | 8 :
|
|---|
| 801 | for Column:=0 to img.Width-1 do
|
|---|
| 802 | begin
|
|---|
| 803 | PDest^ := FBGRAPalette[LineBuf[Column]];
|
|---|
| 804 | inc(PDest);
|
|---|
| 805 | end;
|
|---|
| 806 | 16 :
|
|---|
| 807 | for Column:=0 to img.Width-1 do
|
|---|
| 808 | begin
|
|---|
| 809 | PDest^ :=ExpandColorBGRA(PWord(LineBuf)[Column]);
|
|---|
| 810 | inc(PDest);
|
|---|
| 811 | end;
|
|---|
| 812 | 24 : begin
|
|---|
| 813 | PSrc := LineBuf;
|
|---|
| 814 | for Column:=0 to img.Width-1 do
|
|---|
| 815 | begin
|
|---|
| 816 | PDest^ := BGRA((Psrc+2)^,(Psrc+1)^,(Psrc)^);
|
|---|
| 817 | inc(PDest);
|
|---|
| 818 | inc(PSrc,3);
|
|---|
| 819 | end;
|
|---|
| 820 | end;
|
|---|
| 821 | 32 :
|
|---|
| 822 | if BFI.Compression=BI_BITFIELDS then
|
|---|
| 823 | begin
|
|---|
| 824 | for Column:=0 to img.Width-1 do
|
|---|
| 825 | begin
|
|---|
| 826 | PDest^:=ExpandColorBGRA(PLongWord(LineBuf)[Column]);
|
|---|
| 827 | inc(PDest);
|
|---|
| 828 | end;
|
|---|
| 829 | end else
|
|---|
| 830 | if FTransparencyOption = toOpaque then
|
|---|
| 831 | begin
|
|---|
| 832 | if TBGRAPixel_RGBAOrder then
|
|---|
| 833 | begin
|
|---|
| 834 | PSrc := LineBuf;
|
|---|
| 835 | for Column:=0 to img.Width-1 do
|
|---|
| 836 | begin
|
|---|
| 837 | PDest^:= BGRA((PSrc)^,(PSrc+1)^,(PSrc+2)^);
|
|---|
| 838 | inc(PDest);
|
|---|
| 839 | Inc(PSrc,4);
|
|---|
| 840 | end;
|
|---|
| 841 | end
|
|---|
| 842 | else
|
|---|
| 843 | begin
|
|---|
| 844 | PSrc := LineBuf;
|
|---|
| 845 | for Column:=0 to img.Width-1 do
|
|---|
| 846 | begin
|
|---|
| 847 | PDest^:= BGRA((PSrc+2)^,(PSrc+1)^,(PSrc+1)^);
|
|---|
| 848 | inc(PDest);
|
|---|
| 849 | Inc(PSrc,4);
|
|---|
| 850 | end;
|
|---|
| 851 | end;
|
|---|
| 852 | end else
|
|---|
| 853 | begin
|
|---|
| 854 | if TBGRAPixel_RGBAOrder then
|
|---|
| 855 | begin
|
|---|
| 856 | PSrc := LineBuf;
|
|---|
| 857 | for Column:=0 to img.Width-1 do
|
|---|
| 858 | begin
|
|---|
| 859 | PDest^:= BGRA((PSrc+2)^,(PSrc+1)^,(PSrc)^,(PSrc+3)^);
|
|---|
| 860 | if PDest^.alpha <> 0 then FHasAlphaValues:= true;
|
|---|
| 861 | inc(PDest);
|
|---|
| 862 | Inc(PSrc,4);
|
|---|
| 863 | end;
|
|---|
| 864 | end
|
|---|
| 865 | else
|
|---|
| 866 | begin
|
|---|
| 867 | PSrc := LineBuf;
|
|---|
| 868 | for Column:=0 to img.Width-1 do
|
|---|
| 869 | begin
|
|---|
| 870 | PDest^ := PBGRAPixel(PSrc)^;
|
|---|
| 871 | if PDest^.alpha <> 0 then FHasAlphaValues:= true;
|
|---|
| 872 | inc(PDest);
|
|---|
| 873 | Inc(PSrc,4);
|
|---|
| 874 | end;
|
|---|
| 875 | end;
|
|---|
| 876 | end;
|
|---|
| 877 | end;
|
|---|
| 878 | end;
|
|---|
| 879 |
|
|---|
| 880 | procedure TBGRAReaderBMP.ReadMaskLine(Row: Integer; Stream: TStream);
|
|---|
| 881 | begin
|
|---|
| 882 | FillChar(FMaskData^, FMaskDataSize, 0);
|
|---|
| 883 | Stream.Read(FMaskData^, FMaskDataSize);
|
|---|
| 884 | end;
|
|---|
| 885 |
|
|---|
| 886 | procedure TBGRAReaderBMP.SkipMaskLine(Row: Integer; Stream: TStream);
|
|---|
| 887 | begin
|
|---|
| 888 | Stream.Position := Stream.Position+FMaskDataSize;
|
|---|
| 889 | end;
|
|---|
| 890 |
|
|---|
| 891 | procedure TBGRAReaderBMP.WriteMaskLine(Row: Integer; Img: TFPCustomImage);
|
|---|
| 892 | var x, maskPos: integer;
|
|---|
| 893 | bit: byte;
|
|---|
| 894 | bmp: TBGRACustomBitmap;
|
|---|
| 895 | pimg: PBGRAPixel;
|
|---|
| 896 | begin
|
|---|
| 897 | if Img is TBGRACustomBitmap then
|
|---|
| 898 | bmp := TBGRACustomBitmap(Img)
|
|---|
| 899 | else
|
|---|
| 900 | exit;
|
|---|
| 901 |
|
|---|
| 902 | maskPos := 0;
|
|---|
| 903 | bit := $80;
|
|---|
| 904 | pimg := bmp.ScanLine[Row];
|
|---|
| 905 | for x := 0 to bmp.Width-1 do
|
|---|
| 906 | begin
|
|---|
| 907 | if (FMaskData[maskPos] and bit) <> 0 then //if AND mask is non zero, value is kept
|
|---|
| 908 | begin
|
|---|
| 909 | if pimg^.alpha = 255 then
|
|---|
| 910 | begin
|
|---|
| 911 | pimg^.alpha := 0;
|
|---|
| 912 | if dword(pimg^) <> 0 then
|
|---|
| 913 | begin
|
|---|
| 914 | bmp.NeedXorMask;
|
|---|
| 915 | bmp.XorMask.SetPixel(x,Row,pimg^);
|
|---|
| 916 | end;
|
|---|
| 917 | end;
|
|---|
| 918 | end;
|
|---|
| 919 | inc(pimg);
|
|---|
| 920 | bit := bit shr 1;
|
|---|
| 921 | if bit = 0 then
|
|---|
| 922 | begin
|
|---|
| 923 | bit := $80;
|
|---|
| 924 | inc(maskPos);
|
|---|
| 925 | end;
|
|---|
| 926 | end;
|
|---|
| 927 | end;
|
|---|
| 928 |
|
|---|
| 929 | function TBGRAReaderBMP.InternalCheck (Stream:TStream) : boolean;
|
|---|
| 930 | begin
|
|---|
| 931 | fillchar(BFH, sizeof(BFH), 0);
|
|---|
| 932 | if Subformat in [bsfHeaderless,bsfHeaderlessWithMask] then
|
|---|
| 933 | begin
|
|---|
| 934 | result := true;
|
|---|
| 935 | Hotspot := Point(0,0);
|
|---|
| 936 | end else
|
|---|
| 937 | begin
|
|---|
| 938 | if stream.Read(BFH,SizeOf(BFH)) <> sizeof(BFH) then
|
|---|
| 939 | begin
|
|---|
| 940 | result := false;
|
|---|
| 941 | exit;
|
|---|
| 942 | end;
|
|---|
| 943 | Hotspot := Point(LEtoN(PWord(@BFH.bfReserved)^),LEtoN((PWord(@BFH.bfReserved)+1)^));
|
|---|
| 944 | {$IFDEF ENDIAN_BIG}
|
|---|
| 945 | SwapBMPFileHeader(BFH);
|
|---|
| 946 | {$ENDIF}
|
|---|
| 947 | With BFH do
|
|---|
| 948 | Result:=(bfType=BMmagic); // Just check magic number
|
|---|
| 949 | end;
|
|---|
| 950 | end;
|
|---|
| 951 |
|
|---|
| 952 | procedure TBGRAReaderBMP.InitReadBuffer(AStream: TStream; ASize: integer);
|
|---|
| 953 | begin
|
|---|
| 954 | setLength(FBuffer,ASize);
|
|---|
| 955 | FBufferSize := AStream.Read(FBuffer[0],ASize);
|
|---|
| 956 | FBufferPos := 0;
|
|---|
| 957 | FBufferStream := AStream;
|
|---|
| 958 | end;
|
|---|
| 959 |
|
|---|
| 960 | procedure TBGRAReaderBMP.CloseReadBuffer;
|
|---|
| 961 | begin
|
|---|
| 962 | FBufferStream.Position:= FBufferStream.Position-FBufferSize+FBufferPos;
|
|---|
| 963 | end;
|
|---|
| 964 |
|
|---|
| 965 | function TBGRAReaderBMP.GetNextBufferByte: byte;
|
|---|
| 966 | begin
|
|---|
| 967 | if FBufferPos < FBufferSize then
|
|---|
| 968 | begin
|
|---|
| 969 | result := FBuffer[FBufferPos];
|
|---|
| 970 | inc(FBufferPos);
|
|---|
| 971 | end else
|
|---|
| 972 | if FBufferSize = 0 then
|
|---|
| 973 | result := 0
|
|---|
| 974 | else
|
|---|
| 975 | begin
|
|---|
| 976 | FBufferSize := FBufferStream.Read(FBuffer[0],length(FBuffer));
|
|---|
| 977 | FBufferPos := 0;
|
|---|
| 978 | if FBufferPos < FBufferSize then
|
|---|
| 979 | begin
|
|---|
| 980 | result := FBuffer[FBufferPos];
|
|---|
| 981 | inc(FBufferPos);
|
|---|
| 982 | end else
|
|---|
| 983 | result := 0;
|
|---|
| 984 | end;
|
|---|
| 985 | end;
|
|---|
| 986 |
|
|---|
| 987 | procedure TBGRAReaderBMP.MakeOpaque(Img: TFPCustomImage);
|
|---|
| 988 | var c: TFPColor;
|
|---|
| 989 | x,y: NativeInt;
|
|---|
| 990 | begin
|
|---|
| 991 | if Img is TBGRACustomBitmap then
|
|---|
| 992 | TBGRACustomBitmap(Img).AlphaFill(255)
|
|---|
| 993 | else
|
|---|
| 994 | for y := 0 to Img.Height-1 do
|
|---|
| 995 | for x := 0 to Img.Width-1 do
|
|---|
| 996 | begin
|
|---|
| 997 | c := Img.Colors[x,y];
|
|---|
| 998 | c.alpha := alphaOpaque;
|
|---|
| 999 | Img.Colors[x,y] := c;
|
|---|
| 1000 | end;
|
|---|
| 1001 | end;
|
|---|
| 1002 |
|
|---|
| 1003 | procedure TBGRAReaderBMP.LoadMask(Stream: TStream; Img: TFPCustomImage; var ShouldContinue: boolean);
|
|---|
| 1004 | begin
|
|---|
| 1005 | if Img is TBGRACustomBitmap then TBGRACustomBitmap(Img).DiscardXorMask;
|
|---|
| 1006 | FMaskDataSize := ((Img.Width+31) div 32)*4; //padded to dword
|
|---|
| 1007 | getmem(FMaskData, FMaskDataSize);
|
|---|
| 1008 | try
|
|---|
| 1009 | ImageVerticalLoop(Stream,Img, @ReadMaskLine, @SkipMaskLine, @WriteMaskLine, nil, ShouldContinue);
|
|---|
| 1010 | finally
|
|---|
| 1011 | freemem(FMaskData);
|
|---|
| 1012 | FMaskData := nil;
|
|---|
| 1013 | FMaskDataSize := 0;
|
|---|
| 1014 | end;
|
|---|
| 1015 | end;
|
|---|
| 1016 |
|
|---|
| 1017 | procedure TBGRAReaderBMP.MainProgressProc(Percent: integer;
|
|---|
| 1018 | var ShouldContinue: boolean);
|
|---|
| 1019 | begin
|
|---|
| 1020 | Progress(psRunning,Percent,false,EmptyRect,'',ShouldContinue);
|
|---|
| 1021 | end;
|
|---|
| 1022 |
|
|---|
| 1023 | procedure TBGRAReaderBMP.ImageVerticalLoop(Stream: TStream;
|
|---|
| 1024 | Img: TFPCustomImage; ReadProc, SkipProc: TReadScanlineProc;
|
|---|
| 1025 | WriteProc: TWriteScanlineProc; ProgressProc: TProgressProc;
|
|---|
| 1026 | var ShouldContinue: boolean);
|
|---|
| 1027 | var
|
|---|
| 1028 | prevPercent, percent, percentAdd : byte;
|
|---|
| 1029 | percentMod : longword;
|
|---|
| 1030 | percentAcc, percentAccAdd : longword;
|
|---|
| 1031 | PrevSourceRow,SourceRow, SourceRowDelta, SourceLastRow: integer;
|
|---|
| 1032 | SourceRowAdd: integer;
|
|---|
| 1033 | SourceRowAcc,SourceRowMod: integer;
|
|---|
| 1034 | SourceRowAccAdd: integer;
|
|---|
| 1035 | OutputLastRow, OutputRow, OutputRowDelta: integer;
|
|---|
| 1036 | begin
|
|---|
| 1037 | if OutputHeight <= 0 then exit;
|
|---|
| 1038 |
|
|---|
| 1039 | percent:=0;
|
|---|
| 1040 | percentAdd := 100 div BFI.Height;
|
|---|
| 1041 | percentAcc:=BFI.Height div 2;
|
|---|
| 1042 | percentAccAdd := 100 mod BFI.Height;
|
|---|
| 1043 | percentMod:=BFI.Height;
|
|---|
| 1044 |
|
|---|
| 1045 | DeltaX:=-1; DeltaY:=-1;
|
|---|
| 1046 | if TopDown then
|
|---|
| 1047 | begin
|
|---|
| 1048 | SourceRowDelta := 1;
|
|---|
| 1049 | SourceRow := 0;
|
|---|
| 1050 | SourceLastRow := BFI.Height-1;
|
|---|
| 1051 | end else
|
|---|
| 1052 | begin
|
|---|
| 1053 | SourceRowDelta := -1;
|
|---|
| 1054 | SourceRow := BFI.Height-1;
|
|---|
| 1055 | SourceLastRow := 0;
|
|---|
| 1056 | end;
|
|---|
| 1057 | OutputRowDelta:= SourceRowDelta;
|
|---|
| 1058 |
|
|---|
| 1059 | SourceRowAdd := (BFI.Height div OutputHeight)*SourceRowDelta;
|
|---|
| 1060 | SourceRowAcc := OutputHeight div 2;
|
|---|
| 1061 | SourceRowAccAdd := BFI.Height mod OutputHeight;
|
|---|
| 1062 | SourceRowMod := OutputHeight;
|
|---|
| 1063 | If TopDown then
|
|---|
| 1064 | begin
|
|---|
| 1065 | OutputRow := 0;
|
|---|
| 1066 | OutputLastRow := OutputHeight-1;
|
|---|
| 1067 | end
|
|---|
| 1068 | else
|
|---|
| 1069 | begin
|
|---|
| 1070 | OutputRow := OutputHeight-1;
|
|---|
| 1071 | OutputLastRow := 0;
|
|---|
| 1072 | end;
|
|---|
| 1073 |
|
|---|
| 1074 | PrevSourceRow := SourceRow-SourceRowDelta;
|
|---|
| 1075 |
|
|---|
| 1076 | while ShouldContinue and (SourceRow <> SourceLastRow+SourceRowDelta) do
|
|---|
| 1077 | begin
|
|---|
| 1078 | while PrevSourceRow <> SourceRow do
|
|---|
| 1079 | begin
|
|---|
| 1080 | inc(PrevSourceRow, SourceRowDelta);
|
|---|
| 1081 | if PrevSourceRow = SourceRow then
|
|---|
| 1082 | ReadProc(PrevSourceRow,Stream)
|
|---|
| 1083 | else
|
|---|
| 1084 | SkipProc(PrevSourceRow,Stream);
|
|---|
| 1085 | end;
|
|---|
| 1086 | WriteProc(OutputRow,Img);
|
|---|
| 1087 | if OutputRow = OutputLastRow then break;
|
|---|
| 1088 |
|
|---|
| 1089 | inc(OutputRow,OutputRowDelta);
|
|---|
| 1090 | inc(SourceRow,SourceRowAdd);
|
|---|
| 1091 | inc(SourceRowAcc,SourceRowAccAdd);
|
|---|
| 1092 | if SourceRowAcc >= SourceRowMod then
|
|---|
| 1093 | begin
|
|---|
| 1094 | dec(SourceRowAcc,SourceRowMod);
|
|---|
| 1095 | Inc(SourceRow,SourceRowDelta);
|
|---|
| 1096 | end;
|
|---|
| 1097 |
|
|---|
| 1098 | prevPercent := percent;
|
|---|
| 1099 | inc(percent,percentAdd);
|
|---|
| 1100 | inc(percentAcc,percentAccAdd);
|
|---|
| 1101 | if percentAcc>=percentMod then inc(percent);
|
|---|
| 1102 | if (percent<>prevPercent) and Assigned(ProgressProc) then ProgressProc(percent, ShouldContinue);
|
|---|
| 1103 | end;
|
|---|
| 1104 | end;
|
|---|
| 1105 |
|
|---|
| 1106 | initialization
|
|---|
| 1107 |
|
|---|
| 1108 | DefaultBGRAImageReader[ifBmp] := TBGRAReaderBMP;
|
|---|
| 1109 |
|
|---|
| 1110 | end.
|
|---|