<2022-03-26 Sat>
对ImageMagick的number_channels及PixelChannelMap结构体中的channel和offset成员的理解
这个标题有点长,可能文章的内容也有点长,但是思路越来越清晰。先来看PixelChannelMap的结构体定义:
typedef struct _PixelChannelMap
{PixelChannelchannel;PixelTraittraits;ssize_toffset;
} PixelChannelMap;
PixelChannelMap在ImageMagick的_Image结构体中对应成员channel_map:
PixelChannelMap*channel_map;
首先要说的是,channel_map在逐个计算像素的过程中非常重要,拿ImageMagick的resize.c:HorizontalFilter()函数为例:
for (i=0; i < (ssize_t) GetPixelChannels(image); i++)
{doublealpha,gamma,pixel;PixelChannelchannel;PixelTraitresize_traits,traits;ssize_tj;ssize_tk;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);
}
返回的channel是PixelChannel类型,这个定义在上面的文章中已经给出了:
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值,现在终于明白了。即比如RGB和CMYK两种形式R和C都是第0个通道,G和M都是第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中的channel和offset是在哪里初始化的?我对比了代码发现只在:
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的值。
我对GraphicsMagick和ImageMagick进行了比较,GraphicsMagick对ImageMagick进行了精简,代码:
for (y=0; y < (long) destination->rows; y++){doubleweight;DoublePixelPacketpixel;longj;register longi;pixel=zero;if ((destination->matte) || (destination->colorspace == CMYKColorspace)){doubletransparency_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 thecolorspace is CMYK.
*/
cache_info->indexes_valid=((image->storage_class == PseudoClass) ||(image->colorspace == CMYKColorspace));
原来是这样,这里的PseudoClass就是pseudocolor,可以在wiki的Indexed color中找到介绍,之所以叫做索引颜色,是因为为了节省内存或磁盘空间,颜色信息不是直接由图片的像素所携带,而是存放在一个单独的颜色表中或者调色板中。
从上面贴出来的ImageMagick和GraphicsMagick的代码对比发现,下面两段代码类似:
// 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对于PseudoClass和CMYKColorspace时indexes有效,在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++);
不同的是ImageMagick中CMYKColorspace没有CopyPixelTrait特性。
小结一下:以目前的开发状态,将ImageMagick中的CopyPixelTrait与GraphicsMagick中的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的版本更新引起的。