<2022-03-26 Sat>
ImageMagick
的number_channels
及PixelChannelMap
结构体中的channel
和offset
成员的理解这个标题有点长,可能文章的内容也有点长,但是思路越来越清晰。先来看PixelChannelMap
的结构体定义:
typedef struct _PixelChannelMap
{
PixelChannel
channel;
PixelTrait
traits;
ssize_t
offset;
} PixelChannelMap;
PixelChannelMap
在ImageMagick
的_Image
结构体中对应成员channel_map
:
PixelChannelMap
*channel_map;
首先要说的是,channel_map
在逐个计算像素的过程中非常重要,拿ImageMagick
的resize.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);
}
返回的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++)
{
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
,可以在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
的版本更新引起的。