jmjoy
个人博客
怎么使用GraphicsMagick库的MagickWand类添加半透明水印呢?

前言

最近做图片处理功能,参考了各种图片处理库的对比资料,决定使用GraphicsMagick这个库,并且为了方便使用MagickWand这个struct。

基本上一些常用的图片处理功能都没啥问题,却被半透明图片水印这个功能卡住了。这个功能很常用也很重要啊,但是无论是中文还是英文资料,都找不到关于用MagickWand这个struct怎么去处理的线索,硬生生地让我纠结了几天的时间,终于在今天下午的某个时刻,抱着试试的心态,给解决了。

解决方案

下面就说说解决方案吧:

  1. 首先,通过搜索引擎,得知命令行处理半透明图片水印的办法:

    /usr/bin/gm composite -geometry +500+450 -dissolve 30 watermark.png input.jpg output.jpg
    
  2. 既然命令行可以,那就说明这个功能是可以做的,通过查看这个命令处理的源代码,找到了关键的处理代码:

    http://hg.code.sf.net/p/graphicsmagick/code/file/560875f67e82/magick/command.c#l2971

    static MagickPassFail CompositeImageList(ImageInfo *image_info,Image **image,
    Image *composite_image,Image *mask_image,CompositeOptions *option_info,
    ExceptionInfo *exception)
    {
    // ...
              if (option_info->compose == DissolveCompositeOp)
              {
                  register PixelPacket
                  *q;
    
                  /*
                  Create mattes for dissolve.
                  */
                  if (!composite_image->matte)
                    SetImageOpacity(composite_image,OpaqueOpacity);
                  for (y=0; y < (long) composite_image->rows; y++)
                  {
                    q=GetImagePixels(composite_image,0,y,composite_image->columns,1);
                    if (q == (PixelPacket *) NULL)
                        break;
                    for (x=0; x < (long) composite_image->columns; x++)
                    {
                        q->opacity=(Quantum)
                        ((((unsigned long) MaxRGB-q->opacity)*option_info->dissolve)/100.0);
                        q++;
                    }
                    if (!SyncImagePixels(composite_image))
                        break;
                  }
              }
    // ...
               status&=CompositeImage(*image,option_info->compose,
                 composite_image,geometry.x,geometry.y);
    }
    

    思路是先将水印图片,按设置的透明度参数,先设置成半透明,注意SetImageOpacity这个步骤是不能少的。然后再使用CompositeImage这个方法和DissolveCompositeOp这个方式,将水印图片覆盖到原来图片之上。

  3. 但是这些方法应用的struct是Image而不是MagickWandMagickWandImage的一个用户使用友好的封装,MagickWand也提供了MagickCompositeImage这个方法,和CompositeImage是类似的。但是设置透明度这里却不知道怎么搞,遂放弃通过MagickWand设置透明度。

  4. 查找MagickWand的文档找不到获取Image的方法,但是通过查看MagickWand的定义发现了Image字段。

    http://hg.code.sf.net/p/graphicsmagick/code/file/560875f67e82/wand/magick_wand.c#l108

    /*
      Typedef declarations.
    */
    struct _MagickWand
    {
      char
        id[MaxTextExtent];
       
      ExceptionInfo
        exception;
       
      ImageInfo
        *image_info;
       
      QuantizeInfo
        *quantize_info;
       
      Image
        *image,             /* Current working image */
        *images;            /* Whole image list */
       
      unsigned int
        iterator;
       
      unsigned long
        signature;
    };
    

    可惜定义是在.c文件而不是.h文件,或者是作者不想暴露里面的字段给用户,但是为了解决需求,也只能破坏一下封装了。

  5. 将上述的struct _MagickWand定义给copy到项目的.h文件里面,先通过watermark->image获取到水印图片MagickWandimage字段,按照设置水印透明度的代码将水印图片处理好,然后就可以用MagickWandMagickCompositeImage方法去将水印图片覆盖到原图片了。

    完整代码参考以下:

    MagickWand *input;
    MagickWand *watermark;
    
    //...
    
    Image *composite_image = watermark->image;
    
    register PixelPacket *q;
       
    /*
    Create mattes for dissolve.
    */
    if (!composite_image->matte)
      SetImageOpacity(composite_image,OpaqueOpacity);
    for (y=0; y < (long) composite_image->rows; y++)
    {
      q=GetImagePixels(composite_image,0,y,composite_image->columns,1);
      if (q == (PixelPacket *) NULL)
          break;
      for (x=0; x < (long) composite_image->columns; x++)
      {
          q->opacity=(Quantum)
          ((((unsigned long) MaxRGB-q->opacity)*option_info->dissolve)/100.0);
          q++;
      }
      if (!SyncImagePixels(composite_image))
          break;
    }
    
    MagickCompositeImage(input, watermark, DissolveCompositeOp, 500, 450);
    
    

后话

事实上,我是通过Rust的ffi调用GraphicsMagick的C函数来处理这个功能的,目前没有找到完善的GraphicsMagick binding库,所以只能自己来搞了,后面整理好了再开源出来。


Last modified on 2020-04-05