再次发问,如何通过一张位图的颜色来确定窗口的形状?
比如位图上黑的地方透明,白的地方不透明。要快速的算法。

解决方案 »

  1.   

    使用LMD4.52或ThemeEngine等组件最好,不用编码
    www.51delphi.com
    www.playicq.com
    有下载
      

  2.   

    这里有现成的
    procedure TfrmMain.setformit(han:thandle;src:timage);
    var
       data:prgndata;
       x,y:integer;
       size:integer;
       st,tnt,ye:integer;
       r1,r2:hrgn;
    begin   st:=0;
       r1:=0;
       ye:=0;
       r2:=0;
       for y:=0 to src.Picture.Bitmap.Height-1 do
       begin
       x:=-1;
         repeat
          x:=x+1;
          tnt:=0;
         while (src.picture.bitmap.Canvas.Pixels[x,y]<>clblack)
         and (x<=src.Picture.Bitmap.Width) do
         begin
            if tnt=0 then
            begin
            tnt:=1;
            st:=x;
         end;
        x:=x+1;
        end;
        if tnt=1
        then begin
        if ye=0 then begin ye:=1;r1:=createrectrgn(st,y,x-1,y+1); end
        else begin
        r2:=createrectrgn(st,y,x-1,y+1);
        if r2<>0 then combinergn(r1,r1,r2,RGN_OR);
        deleteobject(r2)
      end;
      end;
      until x>=src.Picture.Bitmap.Width;
    end;
      size:=getregiondata(r1,0,nil);
      getmem(data,size);
      getregiondata(r1,size,data);
      setwindowrgn(han,r1,true);
    end;
      

  3.   

    to  laihecongxi(兴哥) :   这个算法太慢,我已试过。有没有更好的算法?
      

  4.   

    to cg1120: 那些组件要收钱吧?
      

  5.   

    to zwjchina(蒲石) :   是你以前恢复过,我今天试用了一下你的那个算法,太慢。
      

  6.   

    转贴:
    现在有许多Delphi方面的书都有讲如何建立圆形、椭圆形、星形等等非标准形状的窗口,不过实用性并不大。因为如果是一个不规则图形的话,你要怎么才能画好呢?就算可以那又要用多少时间呢?所以啦,我介绍下面的建立任意图片形状窗口的方法!只是可惜是别人发明的非我原创 :( 回答:
      下面的程序段是一个外国人的原算法!(我最早看到算法,可他是不是抄的我就不知道了)原来是一个控件,不过在我的电脑上不能用。由万重大侠改写了并且发布在他的网站上:
      http://mantousoft.51.net 注释是我写的。
    function Tform1.CreateRegion(wMask: TBitmap; wColor: TColor; hControl: THandle): HRGN;
    var
      dc, dc_c          : HDC;
      Rgn, TempRgn      : HRGN;
      X, Y, BeginY      : Integer;
      line              : boolean;
      color             : TColor;
    begin {代码风格不统一,因为有些是照抄那个外国人的。
      dc := GetWindowDC(hControl);
      dc_c := CreateCompatibleDC(dc);
      SelectObject(dc_c, wMask.Handle);
      BeginY := 0;{这句可以不要,有了可以避免编译器警告。}
      Rgn := CreateRectRgn(0, 0, 0, 0); {先初始化一个空的区域给Rgn。}
      for X := 0 to wMask.Width - 1 do
      begin
        line := False;
        for Y := 0 to wMask.Height - 1 do
        begin
          color := GetPixel(dc_c, X, Y);
          if not (color = wColor) then
          begin
            if not line then
            begin
              line := True;
              BeginY := Y;
            end;
          end;
          if (color = wColor) or (Y = wMask.Height - 1) then
          begin
            if line then
            begin
              line := False;
              TempRgn := CreateRectRgn(X, BeginY, X + 1, Y);
              CombineRgn(Rgn, Rgn, TempRgn, RGN_OR);
      {把图形以连续得线段为单位生成区域,并且合并到总的区域中}
            end;
          end;
        end;
      end;
      ReleaseDC(hControl, dc);
      DeleteObject(dc);
      Result := Rgn;
    end;procedure TForm1.FormCreate(Sender: TObject);
    var
      w1                : TBitmap;
      w2                : TColor;
      rgn               : HRGN;
    begin
      w1 := TBitmap.Create;
      w1.Assign(image1.Picture.Bitmap);
      w2 := w1.Canvas.Pixels[0, 0];
      rgn := CreateRegion(w1, w2, Handle);
      if rgn <> 0 then
      begin
        SetWindowRgn(Handle, rgn, true);
      end;
      w1.Free;
    end;不过后来看到了罗云彬(http://asm.yeah.net)大侠给出了不同的算法,我以为更好些,源程序可是用100%的汇编写成的(厉害吧!)我改为了Delphi的样子!首先解释两个函数。
      1、原形:function CreateRectRgn(p1, p2, p3, p4: Integer): HRGN; stdcall;创建一个由点p1,p2和p3,p4描述的矩形区域;
      2、原形:function CombineRgn(p1, p2, p3: HRGN; p4: Integer): Integer; stdcall;把p2和p3区域合并为区域p1,p4是合并方式,p4取值如下:RGN_AND 交集合并,RGN_COPY拷贝p2的内容,RGN_DIFF合并p2和p3不相交的地方,RGN_OR并集合并,RGN_XOR不同时在p2和p3中的部分。
      下面给出完整源程序:
    function Tform1.CreateRegion(wMask: TBitmap; wColor: TColor; hControl: THandle): HRGN;
    var
      dc, dc_c          : HDC;
      rgn               : HRGN;
      x, y              : integer;
      coord             : TPoint;
      line              : boolean;
      color             : TColor;
    begin
      dc := GetWindowDC(hControl);
      dc_c := CreateCompatibleDC(dc);
      {创建一个与特定设备场景一致的内存设备场景}
      SelectObject(dc_c, wMask.Handle);
      {把位图选入内存设备场景这样才可以操作!}
      BeginPath(dc);
      {启动一个路径分支。
      在这个命令后执行的GDI绘图命令会自动成为路径的一部分。
      对线段的连接会结合到一起。并且设备场景中任何现成的路径都会被清除。}
      for x := 0 to wMask.Width - 1 do
      begin
        line := false;
        for y := 0 to wMask.Height - 1 do
        begin
          color := GetPixel(dc_c, x, y);
          if not (color = wColor) then
          begin
            if not line then
      {如果这个不同颜色的线段开始了当然就不记录了!}
            begin
              line := true; {这是记录下这一列连续的不是背景颜色的颜色段的第一个位子。}
              coord.x := x; {其实这个X是不用的,因为coord.x是恒等于x的。但尊重源作者,保留!}
              coord.y := y;
            end;
          end;
          if (color = wColor) or (y = wMask.Height - 1) then {如果这个颜色段完了或者到了图片底部}
          begin
            if line then
            begin
              line := false;
              MoveToEx(dc, coord.x, coord.y, nil); {就把画线的起点移到这个列线段不同颜色的开始}
              LineTo(dc, coord.x, y);
              LineTo(dc, coord.x + 1, y);
              LineTo(dc, coord.x + 1, coord.y); {上面三个LineTo()语句就是画一个2×y的矩形。
      一定要这样才可以画上每个点!并且连在一起的如果你查查Win32 SDK手册你还可以用Rectangle(dc, coord.x, coord.y, x + 2, y)等其他的画图函数代替从MoveToEx()开始的四个语句!也是一样的,只要记住要把点画完而且要重叠地画才可以把路径连在一起!}
              CloseFigure(dc); {描绘到一个路径时,关闭当前打开的图形。万重大侠说不可以少,我不理解,但是多了不错。}
            end;
          end;
        end;
      end;
      EndPath(dc); {结束画路径}
      rgn := PathToRegion(dc); {连接路径为区域的函数。}
      ReleaseDC(hControl, dc); {释放资源,公式化的必须使用。}
      Result := rgn;
      

  7.   

    上面的算法是减少CreateRegion所耗费的时间,究竟能少多少我没试过,这个问题要查查计算机图形学相关的资料。
      

  8.   

    我的机器是P4 1.7G ,用800*600的bmp图片,从按下按钮到区域创建完成,都将近有2″钟的时间。
      

  9.   

    我在一台较慢的机器上测试,发现试用 pathToRgn 的方法耗时更长。
      

  10.   

    to goomoo(古木) zwjchina(蒲石) 上次回答我的应该和你的一样,用500*300的bmp图片速度还凑合
    几秒钟,你的居然有2分钟?我的计算机赛扬1G,256内存。你内存多大。强烈强烈关注这方面的算法。
      

  11.   

    to zwjchina(蒲石) 上次你给我的回答中是否需要加上下面的。CombineRgn(Rg1, Rg1, Rg2, RGN_OR);
            DeleteObject(Rg2);  //我加上的。
      

  12.   

    不到两秒钟。800×600像素。第二钟算法估计是多了几个 LineTo 的原因。
      

  13.   

    “单个的单轴凸形”是我自己想的一个名字,在数学中应该有准确的术语。
    指的是沿x轴(或y)轴画y轴的平行线,每条直线与形状的边界最多有两个交点,另一个轴可以不理会。
      

  14.   

    我的这台机器是P41.7G的,512M内存,都需要两秒钟,别的机器不是慢死了?
      

  15.   

    我安装上述想法重新写了一个函数,使用更方便如下://此函数通过一张位图来创建一个区域,支持bmp格式和jpg格式
    //位图中黑色的地方将不透明,非黑色的地方透明,
    //创建完毕后可以通过SetWindowRgn函数设置窗口或控件的形状,如:
    //setWindowRgn(handle,zjs_createImageRegion('c:\goomoo.bmp'),true);
    uses jpeg;
    function zjs_createImageRegion(imageFile:TFilename):HRGN;
    var
      bitmap:TBitmap;             //位图对象
      jpg:TJpegImage;
      hRegion,hTmpRegion:HRGN;    //hRegion: 最终区域 hTmpRegion 临时区域
      h,l,left,right:integer;    //h:行 l:列  left:临时区域的左边 right:临时区域的右边
      started:Boolean;    //是否开始记录
      ext:String;         //图片扩展名
    begin
      if not fileExists(imageFile) then
      begin
        result:=0;
        exit; //文件不存在则退出,并返回0
      end;
      bitmap:=TBitmap.Create;
     try
      ext:=extractFileExt(imageFile);
      if (ext='.jpg') or (ext='.jpeg') then   //如果是jpg图片
      begin
        jpg:=TJpegImage.Create;
        try
          jpg.LoadFromFile(imageFile);
          bitmap.Assign(jpg);     //从Jpg中把位图拷贝过来
        finally
          jpg.Free;               //及时释放jpg
        end;
      end
      else if ext='.bmp' then
        bitmap.LoadFromFile(imageFile)   //载入位图
      else
      begin
        result:=0;
        exit;
      end;  hRegion:=createRectRgn(0,0,0,0);    //创建一个初始的空的区域
      //初始化变量,消除警告信息
      left:=0; right:=0;
      //开始扫描图片的每个像素
      for h:=0 to bitmap.Height-1 do
      begin
        started:=false;   
        for l:=0 to bitmap.Width-1 do
        begin
          if bitmap.Canvas.Pixels[l,h]=clBlack then   //黑色为不透明区域,其他颜色为透明区域
          begin
            if not started then
            begin
              started:=true;    //开始记录右边
              left:=l; right:=l+1;
            end
            else    //started
            begin
              right:=l;
            end;
          end
          else
          begin
            if started then     //如果已经开始并且该点的颜色不是黑色
            begin
              started:=false;   //结束记录右边
              hTmpRegion:=createRectRgn(left,h,right,h+1);
              combineRgn(hRegion,hRegion,hTmpRegion,RGN_OR);
            end;
          end;
        end;
        if started then   //到了图片的右边还没有结束
        begin
          if right>bitmap.Width-1 then right:=bitmap.Width-1;
          hTmpRegion:=createRectRgn(left,h,right,h+1);
          combineRgn(hRegion,hRegion,hTmpRegion,RGN_OR);
        end;
      end;
     finally
      bitmap.Free;
     end;
      Result:=hRegion;
    end;
      

  16.   

    下面这个函数是新的算法,速度很要很多(差不多是原来的三分之一),不过并不适合所有的图片,只适合Y轴无凹形的图片(比如葫芦形)。
    思路如下:
     1.找到开始有黑色的一行,并将行号存入currentRow中。并将该行中的左端点和右端点分别存放于 lastLeft 和 lastRight 变量中。然后跳出循环。
     2.进入下一行。依据上一行的left和right来搜索本行的left和right。这样很快。找到后就合并区域。
     3.循环直到有一行没有找到left和right(也就是没有黑色)就跳出。
     
        这个算法因为没有搜索图片的所有像素,所以快很多。希望能起个抛砖引玉的作用。
    //此函数通过Y轴无凹形的图片(比如葫芦形)来创建区域。
    function zjs_CreateYProtrudeImageRgn(imageFile:TFilename):HRGN;
    label
      firstLineFounded,L_leftFound,L_rightFound;
    var
      hRegion,hTmpRegion:HRGN;  //hRegion:最终区域 hTmpRegion:临时区域
      bitmap:TBitmap;           //位图对象
      left,right,lastLeft,lastRight,row,col,currentRow:integer;
      started,leftFound,rightFound:boolean;          //started:是否开始记录
    begin
      //初始化变量
      started:=false;
      currentRow:=0;
      left:=-1; right:=-1;
      hRegion:=createRectRgn(0,0,0,0);
      bitmap:=TBitmap.Create;
      try
        bitmap.LoadFromFile(imageFile);
        //找到第一行的left和right
        for row:=0 to bitmap.Height-1 do
        begin
          for col:=0 to bitmap.Width-1 do
          begin
            if bitmap.Canvas.Pixels[col,row]=clBlack then   //找到第一个黑点
            begin
              if not Started then
              begin
                started:=true;
                left:=col;  right:=col+1;
                currentRow:=row;  //记下当前是第几行
                continue;
              end
              else
                right:=col;
            end;
          end;  { for col}
          if started then
          begin
            hTmpRegion:=createRectRgn(left,row,right,row+1);
            combineRgn(hRegion,hRegion,hTmpRegion,RGN_OR);
            goto firstLineFounded;
          end;
        end; {for row}
        firstLineFounded:
        if (left<>-1) then //确认已经找到了第一条黑线
        begin
          lastLeft:=left; lastRight:=right;
          for row:=currentRow+1 to bitmap.Height-1 do
          begin
            leftFound:=false;
            rightFound:=false;        //找到本行的左边
            if bitmap.Canvas.Pixels[0,row]=clBlack then
            begin
              left:=0;
              leftFound:=true;
              goto L_leftFound;
            end
            else
            begin
              col:=lastLeft;
              while col>=0 do     //往左搜索
              begin
                if (bitmap.Canvas.Pixels[col,row]<>clBlack) and (bitmap.Canvas.Pixels[col+1,row]=clBlack) then
                begin
                  left:=col;
                  leftFound:=true;
                  goto L_leftFound;
                end;
                col:=col-1;
              end;
              col:=lastLeft;
              while col<lastRight do  //往右搜索
              begin
                if (bitmap.Canvas.Pixels[col,row]<>clBlack) and (bitmap.Canvas.Pixels[col+1,row]=clBlack) then
                begin
                  left:=col;
                  leftFound:=true;
                  goto L_leftFound;   //跳出循环
                end;
                col:=col+1;
              end;
            end;
            L_leftFound:
            
            //找到本行的右边
            if bitmap.Canvas.Pixels[bitmap.Width-1,row]=clBlack then
            begin
              right:=bitmap.Width-1;
              rightFound:=true;
              goto L_rightFound;
            end
            else
            begin
              col:=lastRight;
              while col<bitmap.Width-1 do //往右搜索
              begin
                if (bitmap.Canvas.Pixels[col,row]=clBlack) and (bitmap.Canvas.Pixels[col+1,row]<>clBlack) then
                begin
                  right:=col+1;
                  rightFound:=true;
                  goto L_rightFound;
                end;
                col:=col+1;
              end;
              while col>lastLeft do        //往左搜索
              begin
                if (bitmap.Canvas.Pixels[col,row]=clBlack) and (bitmap.Canvas.Pixels[col+1,row]<>clBlack) then
                begin
                  right:=col+1;
                  rightFound:=true;
                  goto L_rightFound;
                end;
                col:=col-1;
              end;
            end;
            L_rightFound:        if leftFound and rightFound then
            begin
              lastLeft:=left;   lastRight:=right;
              hTmpRegion:=createRectRgn(left,row,right,row+1);
              combineRgn(hRegion,hRegion,hTmpRegion,RGN_OR);
            end
            else
              break;
          end; {for row}
        end;
      finally
        bitmap.Free;
      end;
      result:=hRegion;
    end;
      

  17.   

    goomoo(古木) 说了这么多了,那位有通用的解决方案也让大家学习一下。
      

  18.   

    我上面写的一个是通用的函数
    function zjs_createImageRegion(imageFile:TFilename):HRGN;只是速度不尽如人意。