{%MainUnit ../graphics.pp}

{******************************************************************************
                                    TBitmap
 ******************************************************************************

 *****************************************************************************
  This file is part of the Lazarus Component Library (LCL)

  See the file COPYING.modifiedLGPL.txt, included in this distribution,
  for details about the license.
 *****************************************************************************
}

function TestStreamIsBMP(const AStream: TStream): boolean;
var
  Signature: array[0..1] of Char;
  ReadSize: Integer;
  OldPosition: TStreamSeekType;
begin
  OldPosition:=AStream.Position;
  ReadSize:=AStream.Read(Signature, SizeOf(Signature));
  Result:=(ReadSize=2) and (Signature[0]='B') and (Signature[1]='M');
  //debugln('TestStreamIsBMP ',DbgStr(Signature[0]),' ',DbgStr(Signature[1]));
  AStream.Position:=OldPosition;
end;

type

  { THeaderStream }

  THeaderStream = class(TStream)
  private
    FSource: TStream;
    FSourceStart: Int64;
    FHeadPos: Integer;
    FHeadPtr: PByte;
    FHeadSize: Integer;
  protected
  public
    function Read(var Buffer; Count: Longint): Longint; override;
    function Seek(const Offset: Int64; Origin: TSeekOrigin): Int64; override;
    constructor Create(ASource: TStream; AHeader: Pointer; ASize: Integer);
  end;

{ THeaderStream }

constructor THeaderStream.Create(ASource: TStream; AHeader: Pointer; ASize: Integer);
begin
  inherited Create;
  FSource := ASource;
  FSourceStart := ASource.Position;
  FHeadPtr := AHeader;
  FHeadSize := ASize;
end;

function THeaderStream.Read(var Buffer; Count: Longint): Longint;
var
  len: Integer;
  buf: PByte;
begin
  if Count <= 0 then Exit(0);

  if FHeadPos < FHeadSize
  then begin
    len := Min(FHeadSize - FHeadPos, Count);
    Move(FHeadPtr[FHeadPos], Buffer, len);
    Dec(Count, len);
    Inc(FHeadPos, len);
    if Count = 0 then Exit(len);
    buf := @Buffer;
    Inc(buf, len);
  end
  else begin
    len := 0;
    buf := @Buffer;
  end;
  Result := FSource.Read(buf^, Count) + len;
end;

function THeaderStream.Seek(const Offset: Int64; Origin: TSeekOrigin): Int64;
begin
  case Origin of
    soBeginning: begin
      Result := Offset;
    end;
    soCurrent: begin
      Result := FHeadPos + Offset;
      if FHeadPos = FHeadSize
      then Inc(Result, FSource.Position);
    end;
    soEnd: begin
      Result := FHeadSize + FSource.Size - FSourceStart + Offset;
    end;
  end;

  if Result < FHeadSize
  then begin
    FHeadPos := Result;
    FSource.Seek(FSourceStart, soBeginning);
  end
  else begin
    FHeadPos := FHeadSize;
    FSource.Seek(FSourceStart + Result - FHeadSize, soBeginning);
  end;
end;


{ TBitmap }

class function TBitmap.GetFileExtensions: string;
begin
  Result:='bmp';
end;

function TBitmap.GetResourceType: TResourceType;
begin
  Result := RT_BITMAP;
end;

procedure TBitmap.LoadFromStream(AStream: TStream; ASize: Cardinal);
var
  S: THeaderStream;
  Header: TBitmapFileHeader;
begin
  if AStream is TResourceStream then
  begin
    if TestStreamIsBMP(AStream) then
      { Handle the special case of an RC_RTDATA resource which contains a
        complete, functional TBitmap structure (BitmapFileHeader+BitmapInfoHeader). }
      inherited LoadFromStream(AStream, ASize)
    else
    begin
      { Normal case of an RT_BITMAP resource lacking the BitmapFileHeader }
      FillChar(Header, SizeOf(Header), 0);
      { Create a BMP header ordered as it would be on disc, noting that if the CPU
        is big-endian this will be the "wrong way round" for numeric operations. }
      {$IFNDEF ENDIAN_BIG}
      Header.bfType := $4d42;
      Header.bfSize := SizeOf(Header) + ASize;
      {$ELSE}
      Header.bfType := $424d;
      Header.bfSize := swap(SizeOf(Header) + ASize);
      {$ENDIF}
      //Header.bfOffBits := 0; //data follow immediately

      S := THeaderStream.Create(AStream, @Header, SizeOf(Header));
      try
        inherited LoadFromStream(S, SizeOf(Header) + ASize);
      finally
        S.Free;
      end;
    end;
  end
  else
    inherited LoadFromStream(AStream, ASize);
end;

class function TBitmap.GetReaderClass: TFPCustomImageReaderClass;
begin
  Result := TLazReaderBMP;
end;

class function TBitmap.GetSharedImageClass: TSharedRasterImageClass;
begin
  Result := TSharedBitmap;
end;
    
class function TBitmap.GetWriterClass: TFPCustomImageWriterClass;
begin
  Result := TLazWriterBMP;
end;

procedure TBitmap.InitializeReader(AImage: TLazIntfImage; AReader: TFPCustomImageReader);
var
  LazReader: TLazReaderBMP absolute AReader;
begin
  inherited;

  if not (AReader is TLazReaderBMP) then Exit;
  
  // TransparentMode
  //   tmAuto: use left bottom pixel
  //   tmFixed: use color
  //
  // TransparentColor:
  //   clDefault: use left, bottom pixel color as transparent color (*)
  //   clNone: load image opaque (*)
  //   otherwise: use TransparentColor as transparent color
  //
  //   (*) these are Lazarus extensions

  if (TransparentMode = tmAuto) or (TransparentColor = clDefault)
  then begin
    LazReader.MaskMode := lrmmAuto;
  end
  else begin
    if TransparentColor = clNone
    then begin
      LazReader.MaskMode := lrmmNone;
    end
    else begin
      LazReader.MaskMode := lrmmColor;
      LazReader.MaskColor := TColorToFPColor(TransparentColor);
    end;
  end;
end;

