GraphicsMagick 的 OpenCL 开发记录(十一)

文章目录

  • 对`ImageMagick`的`number_channels`及`PixelChannelMap`结构体中的`channel`和`offset`成员的理解
  • 又一个闪退问题

<2022-03-26 Sat>

ImageMagicknumber_channelsPixelChannelMap结构体中的channeloffset成员的理解

这个标题有点长,可能文章的内容也有点长,但是思路越来越清晰。先来看PixelChannelMap的结构体定义:

typedef struct _PixelChannelMap
{
  PixelChannel
    channel;

  PixelTrait
    traits;

  ssize_t
    offset;
} PixelChannelMap;

PixelChannelMapImageMagick_Image结构体中对应成员channel_map

PixelChannelMap
  *channel_map;

首先要说的是,channel_map在逐个计算像素的过程中非常重要,拿ImageMagickresize.c:HorizontalFilter()函数为例:

for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
{
  double
    alpha,
    gamma,
    pixel;

  PixelChannel
    channel;

  PixelTrait
    resize_traits,
    traits;

  ssize_t
    j;

  ssize_t
    k;

  channel=GetPixelChannelChannel(image,i);
  traits=GetPixelChannelTraits(image,channel);
  resize_traits=GetPixelChannelTraits(resize_image,channel);
  if ((traits == UndefinedPixelTrait) ||
      (resize_traits == UndefinedPixelTrait))
    continue;
  if (((resize_traits & CopyPixelTrait) != 0) ||
      (GetPixelWriteMask(resize_image,q) <= (QuantumRange/2)))
    {
      j=(ssize_t) (MagickMin(MagickMax(bisect,(double) start),(double)
        stop-1.0)+0.5);
      k=y*(contribution[n-1].pixel-contribution[0].pixel+1)+
        (contribution[j-start].pixel-contribution[0].pixel);
      SetPixelChannel(resize_image,channel,p[k*GetPixelChannels(image)+i],
        q);
      continue;
    }
  pixel=0.0;
  if ((resize_traits & BlendPixelTrait) == 0)
    {
      /*
        No alpha blending.
      */
      for (j=0; j < n; j++)
      {
        k=y*(contribution[n-1].pixel-contribution[0].pixel+1)+
          (contribution[j].pixel-contribution[0].pixel);
        alpha=contribution[j].weight;
        pixel+=alpha*p[k*GetPixelChannels(image)+i];
      }
      SetPixelChannel(resize_image,channel,ClampToQuantum(pixel),q);
      continue;
    }
  /*
    Alpha blending.
  */
  gamma=0.0;
  for (j=0; j < n; j++)
  {
    k=y*(contribution[n-1].pixel-contribution[0].pixel+1)+
      (contribution[j].pixel-contribution[0].pixel);
    alpha=contribution[j].weight*QuantumScale*
      GetPixelAlpha(image,p+k*GetPixelChannels(image));
    pixel+=alpha*p[k*GetPixelChannels(image)+i];
    gamma+=alpha;
  }
  gamma=PerceptibleReciprocal(gamma);
  SetPixelChannel(resize_image,channel,ClampToQuantum(gamma*pixel),q);
}

这个循环中的GetPixelChannels()函数就是返回number_channels的值:

static inline size_t GetPixelChannels(const Image *magick_restrict image)
{
  return(image->number_channels);
}

即处理每个像素的所有channel,通过GetPixelChannelChannel()函数以通道的offset成员获得对应的channel

static inline PixelChannel GetPixelChannelChannel(
  const Image *magick_restrict image,const ssize_t offset)
{
  return(image->channel_map[offset].channel);
}

返回的channelPixelChannel类型,这个定义在上面的文章中已经给出了:

typedef enum
{
  UndefinedPixelChannel = 0,
  RedPixelChannel = 0,
  CyanPixelChannel = 0,
  GrayPixelChannel = 0,
  LPixelChannel = 0,
  LabelPixelChannel = 0,
  YPixelChannel = 0,
  aPixelChannel = 1,
  GreenPixelChannel = 1,
  MagentaPixelChannel = 1,
  CbPixelChannel = 1,
  bPixelChannel = 2,
  BluePixelChannel = 2,
  YellowPixelChannel = 2,
  CrPixelChannel = 2,
  BlackPixelChannel = 3,
  AlphaPixelChannel = 4,
  IndexPixelChannel = 5,
  ReadMaskPixelChannel = 6,
  WriteMaskPixelChannel = 7,
  MetaPixelChannel = 8,
  CompositeMaskPixelChannel = 9,
  IntensityPixelChannel = MaxPixelChannels,  /* ???? */
  CompositePixelChannel = MaxPixelChannels,  /* ???? */
  SyncPixelChannel = MaxPixelChannels+1      /* not a real channel */
} PixelChannel;  /* must correspond to ChannelType */

看定义,我之前还在奇怪为什么enum类型指定了好多相同的0值,1值,现在终于明白了。即比如RGBCMYK两种形式RC都是第0个通道,GM都是第1个通道,依次类推。CMYK中的K就是black,在PixelChannel中对应的是BlackPixelChannel

重点就是SetPixelChannel()这个函数:

static inline void SetPixelChannel(const Image *magick_restrict image,
  const PixelChannel channel,const Quantum quantum,
  Quantum *magick_restrict pixel)
{
  if (image->channel_map[channel].traits != UndefinedPixelTrait)
    pixel[image->channel_map[channel].offset]=quantum;
}

这里忽略理解traits,最后一个参数pixel就是处理像素后的目标地址,代码中的channel是通过循环i获取的,offset是通过channel获取的,最终计算出了pixel的真正地址,然后将计算好的quantum赋值进去。

最后,channel_map中的channeloffset是在哪里初始化的?我对比了代码发现只在:

static inline void SetPixelChannelAttributes(
  const Image *magick_restrict image,const PixelChannel channel,
  const PixelTrait traits,const ssize_t offset)
{
  if ((ssize_t) channel >= MaxPixelChannels)
    return;
  if (offset >= MaxPixelChannels)
    return;
  image->channel_map[offset].channel=channel;
  image->channel_map[channel].offset=offset;
  image->channel_map[channel].traits=traits;
}

SetPixelChannelAttributes()只在InitializePixelChannelMap()函数中会被调用,InitializePixelChannelMap()这个函数有点熟悉,在之前了解number_channels的文章中做过了介绍,这个函数内部计算并初始化了number_channels的值。

我对GraphicsMagickImageMagick进行了比较,GraphicsMagickImageMagick进行了精简,代码:

for (y=0; y < (long) destination->rows; y++)
  {
    double
      weight;

    DoublePixelPacket
      pixel;

    long
      j;

    register long
      i;

    pixel=zero;
    if ((destination->matte) || (destination->colorspace == CMYKColorspace))
      {
        double
          transparency_coeff,
          normalize;

        normalize=0.0;
        for (i=0; i < n; i++)
          {
            j=y*(contribution[n-1].pixel-contribution[0].pixel+1)+
              (contribution[i].pixel-contribution[0].pixel);
            weight=contribution[i].weight;
            transparency_coeff = weight * (1 - ((double) p[j].opacity/TransparentOpacity));
            pixel.red+=transparency_coeff*p[j].red;
            pixel.green+=transparency_coeff*p[j].green;
            pixel.blue+=transparency_coeff*p[j].blue;
            pixel.opacity+=weight*p[j].opacity;
            normalize += transparency_coeff;
          }
        normalize = 1.0 / (AbsoluteValue(normalize) <= MagickEpsilon ? 1.0 : normalize);
        pixel.red *= normalize;
        pixel.green *= normalize;
        pixel.blue *= normalize;
        q[y].red=RoundDoubleToQuantum(pixel.red);
        q[y].green=RoundDoubleToQuantum(pixel.green);
        q[y].blue=RoundDoubleToQuantum(pixel.blue);
        q[y].opacity=RoundDoubleToQuantum(pixel.opacity);
      }
    else
      {
        for (i=0; i < n; i++)
          {
            j=(long) (y*(contribution[n-1].pixel-contribution[0].pixel+1)+
                      (contribution[i].pixel-contribution[0].pixel));
            weight=contribution[i].weight;
            pixel.red+=weight*p[j].red;
            pixel.green+=weight*p[j].green;
            pixel.blue+=weight*p[j].blue;
          }
        q[y].red=RoundDoubleToQuantum(pixel.red);
        q[y].green=RoundDoubleToQuantum(pixel.green);
        q[y].blue=RoundDoubleToQuantum(pixel.blue);
        q[y].opacity=OpaqueOpacity;
      }

    if ((indexes != (IndexPacket *) NULL) &&
        (source_indexes != (IndexPacket *) NULL))
      {
        i=Min(Max((long) (center+0.5),start),stop-1);
        j=y*(contribution[n-1].pixel-contribution[0].pixel+1)+
          (contribution[i-start].pixel-contribution[0].pixel);
        indexes[y]=source_indexes[j];
      }
  }

从上面的代码可看出,在GraphicsMagick中只处理了matte通道或CMYKColorsapce,它们都是四通道的。但是看到代码中的indexes,不知道这个在GraphicsMagick中的意义及是否在ImageMagick中有相关对应。

可以在GraphicsMagick中通过搜索indexes_valid来找到一些有用的信息:

/*
  Indexes are valid if the image storage class is PseudoClass or the
  colorspace is CMYK.
*/
cache_info->indexes_valid=((image->storage_class == PseudoClass) ||
                           (image->colorspace == CMYKColorspace));

原来是这样,这里的PseudoClass就是pseudocolor,可以在wikiIndexed color中找到介绍,之所以叫做索引颜色,是因为为了节省内存或磁盘空间,颜色信息不是直接由图片的像素所携带,而是存放在一个单独的颜色表中或者调色板中。

从上面贴出来的ImageMagickGraphicsMagick的代码对比发现,下面两段代码类似:

// ImageMagick
if (((resize_traits & CopyPixelTrait) != 0) ||
    (GetPixelWriteMask(resize_image,q) <= (QuantumRange/2)))
  {
    j=(ssize_t) (MagickMin(MagickMax(bisect,(double) start),(double)
      stop-1.0)+0.5);
    k=y*(contribution[n-1].pixel-contribution[0].pixel+1)+
      (contribution[j-start].pixel-contribution[0].pixel);
    SetPixelChannel(resize_image,channel,p[k*GetPixelChannels(image)+i],
      q);
    continue;
  }
// GraphicsMagick
if ((indexes != (IndexPacket *) NULL) &&
    (source_indexes != (IndexPacket *) NULL))
  {
    i=Min(Max((long) (center+0.5),start),stop-1);
    j=y*(contribution[n-1].pixel-contribution[0].pixel+1)+
      (contribution[i-start].pixel-contribution[0].pixel);
    indexes[y]=source_indexes[j];
  }

依然参考InitializePixelChannelMap()的代码,结合刚刚知道的GraphicsMagick对于PseudoClassCMYKColorspaceindexes有效,在ImageMagick中则CopyPixelTrait对应IndexPixelChannel

if (image->colorspace == CMYKColorspace)
  SetPixelChannelAttributes(image,BlackPixelChannel,trait,n++);
if (image->alpha_trait != UndefinedPixelTrait)
  SetPixelChannelAttributes(image,AlphaPixelChannel,CopyPixelTrait,n++);
if (image->storage_class == PseudoClass)
  SetPixelChannelAttributes(image,IndexPixelChannel,CopyPixelTrait,n++);
if ((image->channels & ReadMaskChannel) != 0)
  SetPixelChannelAttributes(image,ReadMaskPixelChannel,CopyPixelTrait,n++);
if ((image->channels & WriteMaskChannel) != 0)
  SetPixelChannelAttributes(image,WriteMaskPixelChannel,CopyPixelTrait,n++);
if ((image->channels & CompositeMaskChannel) != 0)
  SetPixelChannelAttributes(image,CompositeMaskPixelChannel,CopyPixelTrait,
    n++);

不同的是ImageMagickCMYKColorspace没有CopyPixelTrait特性。

小结一下:以目前的开发状态,将ImageMagick中的CopyPixelTraitGraphicsMagick中的indexes对应起来。

又一个闪退问题

今天突然发现:

[ysouyno@arch ~]$ export MAGICK_OCL_DEVICE=true
[ysouyno@arch ~]$ gm display ~/temp/bg1a.jpg
Abort was called at 39 line in file:
/build/intel-compute-runtime/src/compute-runtime-22.11.22682/shared/source/built_ins/built_ins.cpp
gm display: abort due to signal 6 (SIGABRT) "Abort"...
Aborted (core dumped)
[ysouyno@arch ~]$ clinfo
Abort was called at 39 line in file:
/build/intel-compute-runtime/src/compute-runtime-22.11.22682/shared/source/built_ins/built_ins.cpp
Aborted (core dumped)

clinfo都运行不了了,它也同样闪退了,看来肯定不是我代码的问题了。可能就是因为intel-compute-runtime的版本更新引起的。

你可能感兴趣的:(GraphicsMagick,ImageMagick,OpenCL,c++)