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中实现转化为位图的过程就可以了。typeTRASGraphic = 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;{下面是实现部分的代码。}

解决方案 »

  1.   

    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;
      

  2.   

    {上面的代码中多次出现如下代码:
    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);加上这几句代码,一个完整的图像解析组件就完成了。
      

  3.   

    为什么我得delphi找不到jpeg.pas呀
      

  4.   

    jpeg.pas
    在安装光盘的Extras\Jpeg目录下主要代码在
    Extras\Jpeg\src下用C写的
      

  5.   

    向楼主学习,GraphicEX的源码可以参考
      

  6.   

    看来还是要读读VCL的源码。不知你借鉴了GraphicEx没有,那个德国人的方法和你的类似。Jpeg现在最麻烦的问题就是快速缩略图问题,几百兆的Jpeg如果用TCanvas缩略几乎不可能...ehom阁下,你有关于Jpeg文件格式的详细资料吗?谢谢!
      

  7.   

    我发现Photoshop, PhotoImpact, ACDSee生成的Jpeg文件的文件头都不同,不知如何处理。
      

  8.   

    Mike Lischke?我知道,以前发过邮件给他,提交了GraphicEx中的BUG关于图像解析的代码我收集了很多,当然也包括GraphicEx不是方法类似,而是VCL中本身就是这样设计的~~~我选SUN光栅图做例子就是因为它简单,而且网上没有类似Delphi代码JPEG的资料网上很多,也很杂,一两篇文章也说不清楚,可以自己搜索一下有些专业书籍上资料比较详细主要是两种资料,一种是文件本身结构的了解,一种就是数据处理的相关资料(RGB->YCbCr、离散余弦变换、量化编码)JPEG格式快速生成缩略图确实是难点,可考虑只提取部分数据解码得到缩略图,而不是处理完所有数据后再生成缩略图!要不怎么说ACDSee好呢,国内各种所谓和ACDSee一样的软件实际在技术细节上差距还很大关于文件头不一样的问题,我比较了一下PhotoShop 6和ACDSee 4,它们的文件头是有一些区别,代表的区别如下:1.PhotoShop 6是按JFIF1.02储存的,ACDSee 4是按JFIF1.01储存(见0x0C)
    2.PhotoShop 6会默认加入分辨率信息72像素/英寸(16进制0x48),而ACDSee储存即使以前有,转存后也会去掉!当然这信息只在打印时有用,对图像浏览来说没什么用!(见0x0D-0x11)下面的Exif信息也有所不同,这是当然的,不过这应该不属于文件头的范围吧?
      

  9.   

    请ehom老大帮忙看看这个帖子:http://expert.csdn.net/Expert/topic/1935/1935807.xml?temp=.8323786ACDSee 95年就有如此的水平,到目前都没人超越...快10年了。豪杰大眼睛就是不如它。共享软件中我最崇拜ACDSee。有时间把它反汇编研究一下...
      

  10.   

    呵呵,ACDSee解析JPEG是用了Intel JPEG Library,针对Intel架构(如MMX)进行了优化C:\Program Files\Common Files\ACD Systems\PlugIns\ijl11.dll
      

  11.   

    >呵呵,ACDSee解析JPEG是用了Intel JPEG Library,针对Intel架构(如MMX)进行了优化>C:\Program Files\Common Files\ACD Systems\PlugIns\ijl11.dll不见得吧?老版本的ACDSee 2.3就没有任何Dll, 没有在Common Files建立目录,速度依然快...ehome, 你对游戏编程有研究吗?阁下研究过Quake和Unreal吗?QuakeIII中使用Jpeg和Tga, Tga个头太大,虽然可以显示24Bits色,可我看不出和Jpeg有什么区别,不只id为什么大量使用Tga图片,你怎么看?
      

  12.   

    一般的JPEG当然看不出差别,找个1M以上的JPEG试试几百毫秒到几秒的差距都有可能,而且Intel JPEG Library不管用于JPEG的解码!开源项目libjpeg的速度也很快,我的意思只是说明JPEG解析从来不是ACDSee的优势!为什么用TGA?因为它方便,如24色,交换Red和Blue后就可直接利用(没用RLE压缩),如用
    gluBuild2DMipmaps创建贴图!当然现在也有用JPEG做贴图的(转成位图处理),但JPEG是有损压缩,丢弃了图像的高频信息,用于照片效果是不错,但用于边缘十分清楚的简单物体(游戏贴图多是这种图像),就会产生较明显的"方块效应"!对于游戏来说画面质量比大小更重要!
      

  13.   

    Intel JPEG Library我没有试过,游戏板块的研究这个的比较多...你研究的好深入呀...佩服!ISee的计划你认为可以超过ACDSee吗?
      

  14.   

    方块效应?你的意思是“锯齿”吗?哪PCX呢?你对3dMax有研究吗?
      

  15.   

    QuakeIII中一个死亡竞赛的场景中有一面墙的Tga帖图,大小有两兆多,id把它切成了几块分别帖。光一面墙就消耗2M,真不知道他们是如何分配内存的。不过我觉得游戏的创意比画面更为重要...国内的游戏现在过于追求日本漫画的唯美风格,大量雷同...>我的意思只是说明JPEG解析从来不是ACDSee的优势!更正:我认为ACDSee的优势在于快速缩略和支持较多的图像格式,仅此而已...ehom, 能介绍几本关于图像处理和游戏编程的书吗?谢谢!呵呵,我也姓周,是你的本家呀... :)
      

  16.   

    ISee的计划如何,你可以自己去体会,找找图像解析插件的BUG,做做程序的修正工作也可以,因为这是自由软件方块不是指“锯齿”,是说在图像中的物体边缘出现色块PCX也是种流行的图像格式,其中的图像数据也是记录所有像素的颜色信息,如24位PCX图的一行数据是RGB分别按行储存,而不是按像素储存RGB信息我对3DMax没研究,以前偶尔会把3DMax、3DMaya这些当玩具玩JPEG只是文件小,但解析完后,在内存里都是一样大的位图 游戏开发的书,我一朋友推荐:
    Windows游戏大师编程、游戏程序设计、还有本IDSoft写的、书名不详VC++数字图像处理
      

  17.   

    Windows游戏大师编程 我到处没找到电子版的...头大...>IDSoft写的、书名不详,太可惜了...
      

  18.   

    买了《Windows游戏大师编程》...不过更本没有讲OpenGL, 全部是DirectX...