summaryrefslogtreecommitdiff
path: root/img/bmpreader.pas
diff options
context:
space:
mode:
Diffstat (limited to 'img/bmpreader.pas')
-rw-r--r--img/bmpreader.pas308
1 files changed, 308 insertions, 0 deletions
diff --git a/img/bmpreader.pas b/img/bmpreader.pas
new file mode 100644
index 00000000..71d7ee73
--- /dev/null
+++ b/img/bmpreader.pas
@@ -0,0 +1,308 @@
+{
+ fpImg - Free Pascal Imaging Library
+ Copyright (C) 2000 by
+ Areca Systems GmbH / Sebastian Guenther, sg@freepascal.org
+
+ Image reader for BMP files
+
+ See the file COPYING, included in this distribution,
+ for details about the copyright.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+}
+
+
+unit BMPReader;
+
+{$IFDEF Debug}
+{$ASSERTIONS On}
+{$ENDIF}
+
+interface
+
+uses Classes, GFXBase, ImageIO;
+
+type
+ DWORD = LongWord;
+ LONG = LongInt;
+
+ TBitmapFileHeader = packed record
+ bfType: WORD;
+ bfSize: DWORD;
+ bfReserved1: WORD;
+ bfReserved2: WORD;
+ bfOffBits: DWORD;
+ end;
+
+ TBitmapInfoHeader = packed record
+ biSize: DWORD;
+ biWidth: LONG;
+ biHeight: LONG;
+ biPlanes: WORD;
+ biBitCount: WORD;
+ biCompression: DWORD;
+ biSizeImage: DWORD;
+ biXPelsPerMeter: LONG;
+ biYPelsPerMeter: LONG;
+ biClrUsed: DWORD;
+ biClrImportant: DWORD;
+ end;
+
+ PRGBQuad = ^TRGBQuad;
+ TRGBQuad = packed record
+ rgbBlue, rgbGreen, rgbRed, rgbReserved: BYTE;
+ end;
+
+
+ TBMPReader = class(TImageReader)
+ protected
+ FFileHeader: TBitmapFileHeader;
+ FInfoHeader: TBitmapInfoHeader;
+ FBMPPalette: PRGBQuad;
+ FFileStride: LongWord;
+
+ HeaderBytesRead, PalBytesRead: Integer;
+ ScanlinesLeft: Integer;
+ ThisSegmentHeight: Integer;
+ ScanlinesLeftInSegment: Integer;
+ ScanlineBytesDone: LongWord;
+ CurScanline: Pointer;
+
+ procedure DoProcessHeaderData(AStream: TStream); override;
+ function DoGetImageSegmentStartY(ASegmentHeight: Integer): Integer;
+ override;
+ procedure InitImageReading; override;
+ procedure InitSegmentReading;
+ procedure DoProcessImageData(AStream: TStream); override;
+ public
+ destructor Destroy; override;
+ property FileHeader: TBitmapFileHeader read FFileHeader;
+ property InfoHeader: TBitmapInfoHeader read FInfoHeader;
+ property BMPPalette: PRGBQuad read FBMPPalette;
+ property FileStride: LongWord read FFileStride;
+ end;
+
+
+implementation
+
+
+destructor TBMPReader.Destroy;
+begin
+ if Assigned(Palette) then
+ FreeMem(FPalette);
+ if Assigned(BMPPalette) then
+ FreeMem(FBMPPalette);
+ inherited Destroy;
+end;
+
+procedure TBMPReader.DoProcessHeaderData(AStream: TStream);
+var
+ DataOffset: LongWord;
+ HaveRead, BytesToSkip, i: Integer;
+ IsFirstRead: Boolean;
+ SkipBuffer: array[0..1023] of Byte;
+begin
+ if HeaderBytesRead < SizeOf(FileHeader) then
+ begin
+ HaveRead := AStream.Read(PChar(@FileHeader)[HeaderBytesRead],
+ SizeOf(FileHeader) - HeaderBytesRead);
+ if HaveRead = 0 then
+ raise EImgOutOfData.Create;
+ Inc(HeaderBytesRead, HaveRead);
+ IsFirstRead := False;
+ end else
+ IsFirstRead := True;
+
+ if HeaderBytesRead < SizeOf(FileHeader) + SizeOf(InfoHeader) then
+ begin
+ HaveRead := AStream.Read(
+ PChar(@InfoHeader)[HeaderBytesRead - SizeOf(FileHeader)],
+ SizeOf(FileHeader) + SizeOf(InfoHeader) - HeaderBytesRead);
+ if HaveRead = 0 then
+ if IsFirstRead then
+ raise EImgOutOfData.Create
+ else
+ exit;
+ IsFirstRead := False;
+ Inc(HeaderBytesRead, HaveRead);
+ end;
+
+ if HeaderBytesRead = SizeOf(FileHeader) + SizeOf(InfoHeader) then
+ begin
+ case InfoHeader.biBitCount of
+ 1: FPaletteSize := 2;
+ 4: FPaletteSize := 16;
+ 8: FPaletteSize := 256;
+ end;
+ if PaletteSize > 0 then
+ begin
+ GetMem(FBMPPalette, PaletteSize * SizeOf(TRGBQuad));
+ GetMem(FPalette, PaletteSize * SizeOf(TGfxColor));
+ end;
+ end;
+
+ if HeaderBytesRead >= SizeOf(FileHeader) + SizeOf(InfoHeader) then
+ begin
+ DataOffset := FileHeader.bfOffBits;
+ if HeaderBytesRead < DataOffset then
+ begin
+ BytesToSkip := DataOffset - HeaderBytesRead;
+ if BytesToSkip > SizeOf(SkipBuffer) then
+ BytesToSkip := SizeOf(SkipBuffer);
+ HaveRead := AStream.Read(SkipBuffer, BytesToSkip);
+ if HaveRead = 0 then
+ if IsFirstRead then
+ raise EImgOutOfData.Create
+ else
+ exit;
+ IsFirstRead := False;
+ Inc(HeaderBytesRead, HaveRead);
+ if PalBytesRead < PaletteSize * SizeOf(TGfxPixel) then
+ begin
+ Move(SkipBuffer, PByte(FBMPPalette)[PalBytesRead], HaveRead);
+ Inc(PalBytesRead, HaveRead);
+ end;
+ end;
+
+ if HeaderBytesRead = DataOffset then
+ begin
+ FWidth := InfoHeader.biWidth;
+ FHeight := InfoHeader.biHeight;
+
+ if PaletteSize > 0 then
+ for i := 0 to PaletteSize - 1 do
+ begin
+ Palette[i].Red := BMPPalette[i].rgbRed * 257;
+ Palette[i].Green := BMPPalette[i].rgbGreen * 257;
+ Palette[i].Blue := BMPPalette[i].rgbBlue * 257;
+ Palette[i].Alpha := 0;
+ end;
+
+ case InfoHeader.biBitCount of
+ 1:
+ begin
+ FFileStride := ((Width + 31) shr 3) and not 3;
+ FPixelFormat.FormatType := ftMono;
+ end;
+ 4:
+ begin
+ FFileStride := ((Width + 7) shr 1) and not 3;
+ FPixelFormat.FormatType := ftPal4;
+ end;
+ 8:
+ begin
+ FFileStride := (Width + 3) and not 3;
+ FPixelFormat.FormatType := ftPal8;
+ end;
+ 24:
+ begin
+ FFileStride := (Width * 3 + 3) and not 3;
+ FPixelFormat := PixelFormatBGR24;
+ end;
+ else
+ raise EImgUnsupportedPixelFormat.Create;
+ end;
+ end;
+
+ HeaderFinished;
+ end;
+end;
+
+function TBMPReader.DoGetImageSegmentStartY(ASegmentHeight: Integer): Integer;
+begin
+ Result := ScanlinesLeft - ASegmentHeight;
+ if Result < 0 then
+ Result := 0;
+end;
+
+procedure TBMPReader.InitImageReading;
+begin
+ ScanlinesLeft := Height;
+ InitSegmentReading;
+end;
+
+procedure TBMPReader.InitSegmentReading;
+begin
+ ThisSegmentHeight := ScanlinesLeft;
+ if ThisSegmentHeight > SegmentHeight then
+ ThisSegmentHeight := SegmentHeight;
+ ScanlinesLeftInSegment := ThisSegmentHeight;
+ ScanlineBytesDone := 0;
+ CurScanline := SegmentData + (ThisSegmentHeight - 1) * SegmentStride;
+end;
+
+procedure TBMPReader.DoProcessImageData(AStream: TStream);
+
+ procedure ScanlineDone;
+ begin
+ Dec(ScanlinesLeftInSegment);
+ Dec(ScanlinesLeft);
+
+ if ScanlinesLeftInSegment = 0 then
+ begin
+ SegmentFinished(ScanlinesLeft, ThisSegmentHeight);
+ if ScanlinesLeft = 0 then
+ ImageFinished
+ else
+ InitSegmentReading;
+ end else
+ Dec(CurScanline, SegmentStride);
+ end;
+
+var
+ ReadMayFail: Boolean;
+ ToRead, HaveRead: Integer;
+begin
+ if ScanlineBytesDone > 0 then
+ begin
+ ToRead := SegmentStride;
+ if ToRead > FileStride then
+ ToRead := FileStride;
+ Dec(ToRead, ScanlineBytesDone);
+ HaveRead := AStream.Read(PChar(CurScanline)[ScanlineBytesDone], ToRead);
+ if HaveRead = 0 then
+ raise EImgOutOfData.Create;
+ if HaveRead = ToRead then
+ begin
+ ScanlineBytesDone := 0;
+ ScanlineDone
+ end else
+ begin
+ Inc(ScanlineBytesDone, HaveRead);
+ exit;
+ end;
+ ReadMayFail := True;
+ end else
+ ReadMayFail := False;
+
+ while ScanlinesLeft > 0 do
+ begin
+ ToRead := SegmentStride;
+ if ToRead > FileStride then
+ ToRead := FileStride;
+
+ HaveRead := AStream.Read(CurScanline^, ToRead);
+
+ if HaveRead = 0 then
+ if ReadMayFail then
+ exit
+ else
+ raise EImgOutOfData.Create;
+
+ if HaveRead < ToRead then
+ begin
+ ScanlineBytesDone := HaveRead;
+ break;
+ end;
+
+ // Handle the ordinary case: a full scanline has been read
+ if ToRead < FileStride then
+ AStream.Position := AStream.Position + FileStride - ToRead;
+ ReadMayFail := True;
+ ScanlineDone;
+ end;
+end;
+
+end.