<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
的版本更新引起的。