程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 更多編程語言 >> Delphi >> Delphi中如何編寫圖像解析組件

Delphi中如何編寫圖像解析組件

編輯:Delphi

  Delphi作為一個強大的RAD開發工具,在應用軟件的開發方面一直有著它的獨特優勢。這種優勢同樣體現在圖像相關軟件的開發上。如果你要在桌面上放置一張圖像,只需要簡單的在桌面上放置一個Image控件,然後就可以通過其Image屬性任意的加載BMP、WMF、EMF等格式的圖像。如果還想增加對JPEG的支持,只需要添加一個JPEG單元即可。甚至在Image中加載一張JPEG後,Delphi會自動添加一個JPEG單元。一切做起來就是這麼的簡單。基本格式都已經封裝在了VCL中,那麼Delphi對類似JPEG這樣圖像格式的支持是如何實現的呢?

  其實從TPicture中很容易看出其中的實現過程,它可以理解為所有圖像對象的容器。

  如JPEG.pas中有如下兩句代碼:

  TPicture.RegisterFileFormat('jpeg', sJPEGImageFile, TJPEGImage);
  TPicture.RegisterFileFormat('jpg', sJPEGImageFile, TJPEGImage);

  (sJPEGImageFile = 'JPEG Image File',見JConsts.pas)

  什麼意思呢?可以理解為將TJPEGImage注冊為jpeg、jpg兩種後綴圖像文件的類。

  其實質就是將後綴,圖像描述,具體圖像解析類等信息保存到了FileFormats。

  具體見如下代碼:

  var FileFormats: TFileFormatsList = nil;

  class procedure TPicture.RegisterFileFormat(const AExtension,
    ADescription: string; AGraphicClass: TGraphicClass);
  begin
    GetFileFormats.Add(AExtension, ADescription, 0, AGraphicClass);
  end;

  function GetFileFormats: TFileFormatsList;
  begin
    if FileFormats = nil then FileFormats := TFileFormatsList.Create;
    Result := FileFormats;
  end;

  而TPicture默認支持四種圖像格式是因為TFileFormatsList的構造函數中已進行了添加。

  constructor TFileFormatsList.Create;
  begin
    inherited Create;
    Add('wmf', SVMetafiles, 0, TMetafile);
    Add('emf', SVEnhMetafiles, 0, TMetafile);
    Add('ico', SVIcons, 0, TIcon);
    Add('bmp', SVBitmaps, 0, TBitmap);
  end;

  也正是通過FileFormats中保存的信息,控件OpenPictureDialog中自動生成了所支持文件類型的列表。

  那麼該如何編寫這些圖像解析類呢?

  TGraphic是TBitmap、TIcon、TMetafile對象的基類。同樣這裡的圖像解析類也應該從TGraphic派生,利用很多VCL中已經封裝了的代碼,可以省去很多工作。

  實現基本功能一般只需要重載三個成員:

  TXXXImage = class(TGraphic)
  protected
    procedure Draw(ACanvas: TCanvas; const Rect: TRect); override;//繪制圖像到畫布
  public
    procedure LoadFromStream(Stream: TStream); override; //從流中獲取圖像數據
    procedure SaveToStream(Stream: TStream); override; //將圖像數據寫入流中
  end;

  因為TGraphic.LoadFromFile/TGraphic.SaveToFile中已經實現了由文件名讀取數據到流的/將流中的數據寫入到對應文件的功能,無特殊需要這裡可以不用重載。而成員Draw自然就是用於實現將圖像繪制到畫布,由於TCanvas對GDI的完善封裝,這裡不需要考慮如何將圖像利用GDI繪制到窗體的這個過程。剩下的就只是編寫圖像解析部分的代碼啦。

  下面就以RAS格式為例做進一步的探討。

  這裡沒有用TGraphic作為基類,而是用了TBitmap,這樣進一步把Draw的實現過程都省了,只需要在LoadFromStream中實現轉化為位圖的過程就可以了。

  type

  TRASGraphic = class(TBitmap)
  public
    procedure LoadFromStream(Stream: TStream); override;
    procedure SaveToStream(Stream: TStream); override;
  end;

  //定義描述RAS文件頭的記錄類型
  TRASHeader = packed record
    Magic,               //標記
    Width,               //寬
    Height,              //高
    Depth,               //色深
    Length,              //圖像數據長度,可能會等於0
    RasType,             //格式類型
    MapType,             //調色板類型
    MapLength: Cardinal; //調色板數據長度
  end;

  //定義一個用來描述RAS文件頭的記錄類型是非常必要的

  const

  //定義代表RAS所有類型的常量
    RT_OLD = 0;
    RT_STANDARD = 1;
    RT_BYTE_ENCODED = 2;
    RT_FORMAT_RGB = 3;
    RT_FORMAT_TIFF = 4;
    RT_FORMAT_IFF = 5;
    RT_EXPERIMENTAL = $FFFF;

  //定義代表調色板類型的常量
    RMT_NONE = 0;//無調色板數據
    RMT_EQUAL_RGB = 1;
    RMT_RAW = 2;

  {如果RAS的格式為RT_OLD,數據長度可能為0}

  
  function SwapLong(const Value: Cardinal): Cardinal;
  asm
    BSWAP EAX//調用字節交換指令
  end;

  //拋出異常,參數為具體的異常信息
  procedure RasError(const ErrorString: String);
  begin
    raise EInvalidGraphic.Create(ErrorString);
  end;

  {下面是實現部分的代碼。}

  procedure TRASGraphic.LoadFromStream(Stream: TStream);
  var
    Header: TRASHeader;
    Row8: PByte;
    Row24: PRGBTriple;
    Row32: PRGBQuad;
    PMap: PByte;
    Y: Integer;
    I: Integer;
    MapReaded: Boolean;
    Pal: TMaxLogPalette;
    R,G,B:array[0..255] of Byte;
    ColorByte: Byte;
  begin
  with Stream do
  begin
    ReadBuffer(Header, SizeOf(Header)); //將文件頭數據讀取到記錄Header中
    with Header do
    begin
      Width := SwapLong(Width);
      Height := SwapLong(Height);
      Depth := SwapLong(Depth);
      Length := SwapLong(Length);
      RASType := SwapLong(RASType);
      MapType := SwapLong(MapType);
      MapLength := SwapLong(MapLength);
    end;
    //由於讀取數據的順序問題,這裡需要調用上面的SwapLong改變順序。
    if (Header.Magic = $956AA659) and
    (Header.Width<>0) and (Header.Height<>0) and
    (Header.Depth in [1,8,24,32]) and (Header.RasType in [RT_OLD,RT_STANDARD,RT_BYTE_ENCODED,RT_FORMAT_RGB]) then
    begin
      Width := Header.Width;
      Height := Header.Height;
      MapReaded := False;

      case Header.Depth of
        1:PixelFormat := pf1Bit;
        8:
        begin
          PixelFormat := pf8Bit;

          case Header.MapType of
            RMT_NONE:
            begin
              Pal.palVersion:=$300;
              Pal.palNumEntrIEs:=256;
              for I := 0 to 255 do
              begin
                Pal.palPalEntry[I].peRed:=I;
                Pal.palPalEntry[I].peGreen:=I;
                Pal.palPalEntry[I].peBlue:=I;
                Pal.palPalEntry[I].peFlags:=0;
              end;
              Palette := CreatePalette(PLogPalette(@Pal)^);
              //當圖像色深為8位,而又不存在調色板信息時,創建一個8位的灰度調色板
            end;
            RMT_EQUAL_RGB:
            begin
              if (Header.MapLength = 3*256) then
              begin
                Pal.palVersion:=$300;
                Pal.palNumEntrIEs:=256;
                ReadBuffer(R,256);
                ReadBuffer(G,256);
                ReadBuffer(B,256);
                for I := 0 to 255 do
                begin
                  Pal.palPalEntry[I].peRed:=R[I];
                  Pal.palPalEntry[I].peGreen:=G[I];
                  Pal.palPalEntry[I].peBlue:=B[I];
                  Pal.palPalEntry[I].peFlags:=0;
                end;
                Palette := CreatePalette(PLogPalette(@Pal)^);
                //讀取文件中的調色板信息
                //相關調色板操作的API請查詢MSDN
              end
              else
                RasError('調色板長度錯誤!');
              MapReaded := True;
            end;
            RMT_RAW:
            begin
              RasError('不支持的文件格式!');
            end;
          end;
        end;
        24:PixelFormat := pf24Bit;
        32:
        begin
          PixelFormat := pf32Bit;
          //
        end;
      end;

      if (not MapReaded) and (Header.MapLength>0) then
      begin
        Position := Position + Header.MapLength;
      end;
      //如果調色板長度不為0,而又未正確讀取相關信息時,跳過這一段數據

      case Header.Depth of
        8:
        begin
          if Header.RasType = RT_BYTE_ENCODED then
          begin
            //ENCODE
            //關於RLE壓縮的編碼解碼請自行查閱資料
            RasError('不支持壓縮格式!');
          end
          else
          begin
            for Y := 0 to Height-1 do
            begin
              Row8:=ScanLine[Y];
              ReadBuffer(Row8^,Width);
              if (Width mod 2)=1 then
              begin
                 Position := Position + 1;
              end;
            end;
          end;
        end;{end of 8Bit}
        24:
        begin
          case Header.RasType of
            RT_OLD,
            RT_STANDARD:
            begin
              for Y := 0 to Height-1 do
              begin
                Row24:=ScanLine[Y];
                ReadBuffer(Row24^,Width*3);
                if (Width mod 2)=1 then
                begin
                   Position := Position + 1;
                end;
              end;
            end;
            RT_BYTE_ENCODED:
            begin
              //ENCODE
              //關於RLE壓縮的編碼解碼請自行查閱資料
              RasError('不支持壓縮格式!');
            end;
            RT_FORMAT_RGB:
            begin
              for Y := 0 to Height-1 do
              begin
                Row24:=ScanLine[Y];
                ReadBuffer(Row24^,Width*3);
                for I := 0 to Width-1 do
                begin
                  ColorByte := Row24^.rgbtRed;
                  Row24^.rgbtRed := Row24^.rgbtBlue;
                  Row24^.rgbtBlue := ColorByte;
                  Inc(Row24);
                end;
                //當為RT_FORMAT_RGB格式時,按RGB獲取數據,這裡需要交換R和B的值
                if (Width mod 2)=1 then
                begin
                   Position := Position + 1;
                end;
              end;
            end;{end of RT_FORMAT_RGB}
            else
              RasError('不支持的文件格式!');
          end;
        end;{end of 24Bit}
        32:
        begin
          case Header.RasType of
            RT_OLD,
            RT_STANDARD:
            begin
              for Y := 0 to Height-1 do
              begin
                Row32:=ScanLine[Y];
                ReadBuffer(Row32^,Width*4);
                for I := 0 to Width-1 do
                begin
                  ColorByte := Row32^.rgbReserved;
                  Row32^.rgbReserved := Row32^.rgbBlue;
                  Row32^.rgbBlue := Row32^.rgbGreen;
                  Row32^.rgbGreen := Row32^.rgbRed;
                  Row32^.rgbRed := ColorByte;
                  Inc(Row32);
                end;
                //32位色時,需要調整讀取後數據的順序
              end;
            end;
            RT_BYTE_ENCODED:
            begin
              //ENCODE
              //關於RLE壓縮的編碼解碼請自行查閱資料
              RasError('不支持壓縮格式!');
            end;
            RT_FORMAT_RGB:
            begin
              For Y := 0 to Height-1 do
              begin
                Row32:=ScanLine[Y];
                ReadBuffer(Row32^,Width*4);
                for I := 0 to Width-1 do
                begin
                  ColorByte := Row32^.rgbBlue;
                  Row32^.rgbBlue := Row32^.rgbReserved;
                  Row32^.rgbReserved := ColorByte;
                  ColorByte := Row32^.rgbGreen;
                  Row32^.rgbGreen := Row32^.rgbRed;
                  Row32^.rgbRed := ColorByte;
                  Inc(Row32);
                end;
                //這裡將順序調整和R和B值的交換的代碼進行了合並
              end;
            end;{end of RT_FORMAT_RGB}
            else
              RasError('不支持的文件格式!');
          end;{end of 32Bit}

        end;
        else
        begin
          FreeImage;
          RasError('不支持的文件格式!');
        end;
      end;
    end
    else
      RasError('不支持的文件格式!');

  end;{end with}
  end;

  {上面的代碼中多次出現如下代碼:
  if (Width mod 2)=1 then
  begin
    Position := Position + 1;
  end;
  這是因為每行的數據都要按字對齊,既每行的數據都要用偶數的字節記錄。當每個像素的顏色信息用1字節(8位)或3字節(24位)記錄且每行像素數為奇數時,要補齊一個字節。所以這裡跳過一個字節。
  後面代碼中的
  if (Width mod 2) = 1 then
  begin
    FillByte:=0;
    Stream.Write(FillByte,1);
  end;
  也是基於同一道理。}

  procedure TRASGraphic.SaveToStream(Stream: TStream);
  var
    Header: TRASHeader;
    Row8: PByte;
    Row24: PRGBTriple;
    Row32: PRGBQuad;
    FillByte: Byte;
    Y: Integer;
    I: Integer;
    Pal: TMaxLogPalette;
    R,G,B:array[0..255] of Byte;
  begin
  Header.Magic := $956AA659;
  Header.Width := SwapLong(Width);
  Header.Height := SwapLong(Height);
  Header.RasType := SwapLong(RT_STANDARD);
  if (PixelFormat = pf1bit) or (PixelFormat = pf4bit) then
    PixelFormat:=pf8bit
  else if (PixelFormat <> pf8bit) and (PixelFormat <> pf24bit) and (PixelFormat <> pf32bit) then
    PixelFormat:=pf24bit;
  case PixelFormat of
    pf8bit:
    begin
      Header.Length := SwapLong(Height*(Width+(Width mod 2)));
      Header.Depth := SwapLong(8);
      Header.MapType := SwapLong(RMT_EQUAL_RGB);
      Header.MapLength := SwapLong(3*256);
      Stream.WriteBuffer(Header,SizeOf(Header));
      GetPaletteEntrIEs(Palette, 0, 256, Pal.palPalEntry);
      for I := 0 to 255 do
      begin
        R[I]:=Pal.palPalEntry[I].peRed;
        G[I]:=Pal.palPalEntry[I].peGreen;
        B[I]:=Pal.palPalEntry[I].peBlue;
      end;
      //相關調色板操作的API請查詢MSDN
      Stream.WriteBuffer(R,256);
      Stream.WriteBuffer(G,256);
      Stream.WriteBuffer(B,256);
      for Y := 0 to Height-1 do
      begin
        Row8 := ScanLine[Y];
        Stream.WriteBuffer(Row8^,Width);
        if (Width mod 2) = 1 then
        begin
          FillByte:=0;
          Stream.Write(FillByte,1);
        end;
      end;
    end;
    pf32bit:
    begin
      Header.Length := SwapLong(Height*Width*4);
      Header.Depth := SwapLong(32);
      Header.MapType := SwapLong(RMT_NONE);
      Header.MapLength := 0;
      Stream.WriteBuffer(Header,SizeOf(Header));
      for Y := 0 to Height-1 do
      begin
        Row32 := ScanLine[Y];
        for I := 0 to Width-1 do
        begin
          Stream.WriteBuffer(Row32.rgbReserved,1);
          Stream.WriteBuffer(Row32^,3);
          Inc(Row32);
        end;
      end;
    end;
    else
    begin
      Header.Length := SwapLong(Height*Width*3);
      Header.Depth := SwapLong(24);
      Header.MapType := SwapLong(RMT_NONE);
      Header.MapLength := 0;
      Stream.WriteBuffer(Header,SizeOf(Header));
      for Y := 0 to Height-1 do
      begin
        Row24 := ScanLine[Y];
        Stream.WriteBuffer(Row24^,Width*3);
        if (Width mod 2) = 1 then
        begin
          FillByte:=0;
          Stream.Write(FillByte,1);
        end;     
      end;
    end;
  end;
  //SaveToStream基本上就是LoadFromStream的逆過程。

  end;

  initialization
    TPicture.RegisterFileFormat('RAS', 'Sun RAS', TRASGraphic);
  finalization
    TPicture.UnregisterGraphicClass(TRASGraphic);

  加上這幾句代碼,一個完整的圖像解析組件就完成了。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved