⚠️ 这是一个非官方翻译网站,与 ImageMagick Studio LLC 无关。准确信息请参阅原文(https://imagemagick.org/fx/)

FX 表达式剖析

FX 特效图像运算符 • FX 表达式剖析

FX 特效图像运算符会对图像的每个像素通道应用一个数学表达式。FX 表达式语言提供了一种强大而灵活的图像操作方式,可对图像执行各种各样的运算和变换。可以使用 FX 来:

  • 创建画布、渐变、数学色彩映射
  • 在图像和通道之间移动颜色值
  • 平移、翻转、镜像、旋转、缩放、错切以及一般性地扭曲图像
  • 合并或合成多个图像
  • 对相邻像素进行卷积或合并
  • 生成图像度量或「指纹」

该运算符会遍历图像的所有像素以及每个像素的所有通道,并返回包含结果的新图像。表达式可以引用图像序列中的任意图像,但只会返回第一个图像的副本,并按照你的表达式进行相应的更新。

表达式可以很简单:

magick -size 64x64 canvas:black -channel blue -fx "1/2" fx_navy.png

这里,我们把一个黑色图像转换为海军蓝图像:

black ==> navy

或者,表达式也可以很复杂:

magick rose: \
  -fx "(1.0/(1.0+exp(10.0*(0.5-u)))-0.006693)*1.0092503" \
  rose-sigmoidal.png

该表达式会生成源图像的高对比度版本:

rose ==> rose-sigmoidal

表达式可以包含变量赋值。赋值在大多数情况下能降低表达式的复杂度,并使一些用其他方式可能无法实现的运算成为可能。例如,我们来创建一个径向渐变:

magick -size 70x70 canvas: \
  -fx "Xi=i-w/2; Yj=j-h/2; 1.2*(0.5-hypot(Xi,Yj)/70.0)+0.5" \
  radial-gradient.png

上述命令返回此图像:

radial-gradient

此 FX 表达式向图像添加随机噪声:

magick photo.jpg -fx 'iso=32; rone=rand(); rtwo=rand(); \
  myn=sqrt(-2*ln(rone))*cos(2*Pi*rtwo); myntwo=sqrt(-2*ln(rtwo))* \
  cos(2*Pi*rone); pnoise=sqrt(p)*myn*sqrt(iso)* \
  channel(4.28,3.86,6.68)/255; max(0,p+pnoise)' noisy.png

此 FX 脚本利用循环创建一个朱利亚集合

magick -size 400x400 xc:gray -fx " \
  Xi=2.4*i/w-1.2; \
  Yj=2.4*j/h-1.2; \
  for (pixel=0.0, (hypot(Xi,Yj) < 2.0) && (pixel < 1.0), \
    delta=Xi^2-Yj^2; \
    Yj=2.0*Xi*Yj+0.2; \
    Xi=delta+0.4; \
    pixel+=0.00390625 \
  ); \
  pixel == 1.0 ? 0.0 : pixel" \
  \( -size 1x1 xc:white xc:red xc:orange xc:yellow xc:green1 xc:cyan xc:blue \
     xc:blueviolet xc:white -reverse +append -filter Cubic -resize 1024x1! \) \
  -clut -rotate -90 julia-set.png

Julia Fractals

此 FX 脚本打印前 10 个素数:

magick xc:gray -fx " \
  for (prime=2, prime < 30, composite=0; \
    for (nn=2, nn < (prime/2+1), if ((prime % nn) == 0, composite++, ); nn++); \
      if (composite <= 0, debug(prime), ); prime++)" null:

更多示例请参阅 Using FX, The Special Effects Image Operator

-fx 选项会用以表达式结果更新后的第一个图像的克隆来替换整个图像序列。如果你希望将表达式应用于序列中的每个图像,请改用 +fx。

FX 表达式以单线程方式解释,但除非表达式包含 debug() 函数,否则它会以多线程方式执行。

下一节将讨论 FX 表达式语言。

FX 表达式剖析

FX 表达式语言

正式的 FX 表达式语言定义如下:

numbers:
整数、浮点数、科学记数法(需要 +/−,例如 3.81469e-06)、国际单位制数值后缀(例如 KB、Mib、GB 等)
constants:
E(欧拉数)、Epsilon、Opaque、Phi(黄金比例)、Pi、QuantumRange、QuantumScale、Transparent
FX operators(按优先级排列):
^(幂)、一元 -、、/、%(取模)、+、-、<<、>>、<、<=、>、>=、+=、-=、=、/=、%=、<<=、>>=、&=、|=、++、--、==、!=、&(按位 AND)、|(按位 OR)、&&(逻辑 AND)、||(逻辑 OR)、~(逻辑 NOT)、?:(三元条件)
array:
图像提供受其宽度和高度限定的数组存储(例如 p[-1,-1].r)。图像序列表示多个数组(例如 u.p[0,0].r, v.p[0,0].r)。存储限于 Quantum 值,例如 Q16 构建为 [0..65535],启用 HDRI 的构建为浮点数。
math functions:
abs(), acos(), acosh(), airy(), alt(), asin(), asinh(), atan(), atanh(), atan2(), ceil(), clamp(), cos(), cosh(), debug(), drc(), epoch(), erf(), exp(), floor(), gauss(), gcd(), hypot(), int(), isnan(), j0(), j1(), jinc(), ln(), log(), logtwo(), magicktime(), max(), min(), mod(), not(), pow(), rand(), round(), sign(), sin(), sinc(), sinh(), sqrt(), squish(), tan(), tanh(), trunc()
channel functions:
定义最多 5 个像素通道
color names:
red, cyan, black 等
color functions:
srgb(), srgba(), rgb(), rgba(), cmyk(), cmyka(), hsl(), hsla() 等
color hex values:

ccc, #cbfed0, #b9e1cc00 等

symbols:

u: 列表中的第一个图像
v: 列表中的第二个图像
s: 列表中的当前图像(用于 %[fx:],否则 = u)
t: 列表中当前图像(s)的索引
n: 列表中的图像数量
i: 列偏移
j: 行偏移
p: 要使用的像素(绝对,或相对于当前像素)
w: 此图像的宽度
h: 此图像的高度
z: 通道深度
r: 红色值(来自 RGBA),特定或当前像素的
g: 绿色
b: 蓝色
a: alpha
o: 不透明度
c: 像素 CMYK 颜色的青色值
y: 黄色
m: 品红
k: 黑色
all: 所有通道
this: 此通道
intensity: 像素强度
hue: 像素色相
saturation: 像素饱和度
lightness: 像素明度
luma: 像素亮度(luma)
page.width: 页面宽度
page.height: 页面高度
page.x: 页面 x 偏移
page.y: 页面 y 偏移
printsize.x: x 打印尺寸
printsize.y: y 打印尺寸
resolution.x: x 分辨率
resolution.y: y 分辨率
depth: 图像深度
extent: 图像范围
minima: 图像最小值
maxima: 图像最大值
mean: 图像平均值
median: 图像中位数
standard_deviation: 图像标准差
kurtosis: 图像峰度
skewness: 图像偏度(添加通道指定符可计算该通道的统计量,例如 depth.r)
iterators:
do(), for(), while()
image attributes:
s.depth, s.kurtosis, s.maxima, s.mean, s.minima, s.resolution.x, s.resolution.y, s.skewness, s.standard_deviation
user settings:

将 Fx 符号定义为用户设置,例如:

magick ... -set option:wd1 "%[fx:w/2]" -resize "%[fx:wd1-5]" ...

FX 表达式

FX 表达式可以包含以下任意组合:

x ^ y
幂运算(x 的 y 次方)
( ... )
分组
x * y
乘法
x / y
除法
x % y
取模
x + y
加法
x - y
减法
x << y
左移
x >> y
右移
x < y
布尔关系,若 x < y 返回 1.0,否则返回 0.0
x <= y
布尔关系,若 x <= y 返回 1.0,否则返回 0.0
x > y
布尔关系,若 x > y 返回 1.0,否则返回 0.0
x >= y
布尔关系,若 x >= y 返回 1.0,否则返回 0.0
x == y
布尔关系,若 x == y 在 epsilon(1e-12)以内则返回 1.0,否则返回 0.0。当两值之差在约 1e‑12 以内时视为相等。
x != y
布尔关系,若 x != y 在 epsilon(1e-12)以内则返回 1.0,否则返回 0.0
x & y
按位 AND
x | y
按位 OR
x && y
逻辑 AND 连接词,若 x > 0 且 y > 0 返回 1.0,否则返回 0.0
x || y
逻辑 OR 连接词(包含),若 x > 0 或 y > 0(或两者皆是)返回 1.0,否则返回 0.0
~x
逻辑 NOT 运算符,若并非 x > 0 返回 1.0,否则返回 0.0
+x
一元加,返回 1.0*值
-x
一元减,返回 -1.0*值
condition ? true-statements : false-statements
三元条件表达式,若 condition != 0 返回 true-statements,否则返回 false-statements
x = y
赋值;单字符变量为保留字,请改用 2 个或更多字符(仅限字母组合,例如用 Xi 而非 X1)
x ; y
语句分隔符
phi
常量(1.618034...)
pi
常量(3.14159265359...)
e
常量(2.71828...)
QuantumRange
最大像素值常量(Q8 为 255,Q16 为 65535)
QuantumScale
常量 1.0/QuantumRange
intensity
像素强度,其值遵循 -intensity 选项。
hue
像素色相
saturation
像素饱和度
lightness
像素明度;等于 0.5max(red,green,blue) + 0.5min(red,green,blue)
luminance
像素亮度;等于 0.212656red + 0.715158green + 0.072186*blue
red, green, blue 等
颜色名称

ccc, #cbfed0, #b9e1cc00 等

 颜色十六进制值
rgb(), rgba(), cmyk(), cmyka(), hsl(), hsla()
颜色函数
s, t, u, v, n, i, j, w, h, z, r, g, b, a, o, c, y, m, k
符号
abs(x)
绝对值函数
acos(x)
反余弦函数
acosh(x)
反双曲余弦函数
airy(x)
艾里函数(max=1, min=0);airy(x)=[jinc(x)]2=[2j1(pix)/(pi*x)]2
alt(x)
符号交替函数(若 int(x) 为偶数返回 1.0,为奇数返回 -1.0)
asin(x)
反正弦函数
asinh(x)
反双曲正弦函数
atan(x)
反正切函数
atanh(x)
反双曲正切函数
atan2(y,x)
双变量反正切函数
ceil(x)
不小于参数的最小整数值
channel(...)
支持零到五个参数,例如 channel(0.1) 将第一个通道设为 0.1 并将其他通道清零。
clamp(x)
钳制值
cos(x)
余弦函数
cosh(x)
双曲余弦函数
debug(x)
打印 x(对调试表达式很有用)
do(statements, condition)
当 condition 不等于 0 时迭代
drc(x,y)
动态范围压缩(膝形曲线);drc(x,y)=(x)/(y*(x-1)+1); -1<y<1
epoch(date-time)
将日期时间属性转换为自纪元(00:00:00 UTC)以来的秒数
erf(x)
误差函数
exp(x)
自然指数函数(e 的 x 次方)
floor(x)
不大于参数的最大整数值
for(initialize, condition, statements)
当 condition 不等于 0 时迭代
gauss(x)
高斯函数;gauss(x)=exp(-xx/2)/sqrt(2pi)
gcd(x,y)
最大公约数
hypot(x,y)
x 的 2 次方 + y 的 2 次方 的平方根
if(condition, nonzero-statements, zero-statements)
根据 condition 解释表达式
int(x)
取整函数(返回小于或等于 x 的最大整数)
isnan(x)
若 x 为 NaN 返回 1.0,否则返回 0.0
j0(x)
x 的第一类 0 阶贝塞尔函数
j1(x)
x 的第一类 1 阶贝塞尔函数
jinc(x)
jinc 函数(max=1, min=-0.1323);jinc(x)=2j1(pix)/(pi**x)
ln(x)
自然对数函数
log(x)
以 10 为底的对数
logtwo(x)
以 2 为底的对数
ln(x)
自然对数
magicktime()
自纪元(00:00:00 UTC)以来的当前时间(秒)
max(x, y)
x 和 y 的最大值
min(x, y)
x 和 y 的最小值
mod(x, y)
浮点余数函数
not(x)
若 x 为零返回 1.0,否则返回 0.0
pow(x,y)
幂函数(x 的 y 次方)
rand()
在区间 [0.0, 1.0) 上均匀分布的值,周期为 2 的 128 次方 -1
round()
舍入到整数值,与舍入方向无关
sign(x)
若 x 小于 0.0 返回 -1.0,否则返回 1.0
sin(x)
正弦函数
sinc(x)
sinc 函数(max=1, min=-0.21);sinc(x)=sin(pix)/(pix)
squish(x)
squish 函数;squish(x)=1.0/(1.0+exp(-x))
sinh(x)
双曲正弦函数
sqrt(x)
平方根函数
tan(x)
正切函数
tanh(x)
双曲正切函数
trunc(x)
向零方向舍入到整数
while(condition, statements)
当 condition 不等于 0 时迭代
image.depth, image.kurtosis, image.maxima, image.mean, image.median, image.minima, image.resolution.x, image.resolution.y, image.skewness, image.standard_deviation
图像属性

表达式语义包含以下规则:

  • 符号不区分大小写
  • 每条语句只能有一个三元条件(例如 x ? y : z)
  • 语句是赋值,或是要返回的最终表达式
  • 赋值开始一条语句,它不是运算符
  • 单字符变量为保留字。对保留的内置项赋值会抛出异常;例如 r=3.0; r 返回 Attempted assignment to non-UserSymbol 'r' at '3.0'
  • 一元运算符的优先级低于二元运算符,即一元减(取负)的优先级低于幂运算,所以 -3^2 被解释为 -(3^2) = -9。使用括号来明确你的意图(例如 (-3)^2 = 9)。
  • 使用斜杠('/')符号时必须谨慎。字符串 1/2x 被解释为 (1/2)x。相反的解释应明确写为 1/(2x)。同样,使用括号有助于明确含义,只要存在被误解的可能就应使用括号。
  • 由于 -- 是变量递减运算符,要减去一个负数请使用括号,例如 -4-(-5)。

源图像

符号 u 和 v 分别指当前图像序列中的第一个和第二个图像。要引用序列中的特定图像,可在任意图像引用(通常是 u)后附加其索引,序列开头索引为零。负索引从末尾开始计数。例如 u[0] 是序列中的第一个图像,u[2] 是第三个,u[-1] 是最后一个图像,u[t] 是当前图像。当前图像也可用 s 引用。如果序列号超过序列长度,计数会回绕。因此在一个 3 图像的序列中,u[-1]、u[2] 和 u[5] 都指向同一个(第三个)图像。

举例来说,我们对第一个图像和第三个图像取平均来构成一个图像(第二个(索引 1)图像被忽略并丢弃):

magick image1.jpg image2.jpg image3.jpg -fx "(u+u[2])/2" image.jpg

默认情况下,p, r, g, b, a 等所应用的图像是图像列表中的当前图像 s。除在转义序列 %[fx:...] 中使用外,这与 u 等价。

务必注意第一个图像所起的特殊作用。这是图像序列中唯一被修改的图像,其他图像仅用于其数据。作为示例,请考虑以下命令,并注意设置 -channel red 指示 -fx 只修改绿色通道;红色或蓝色通道中的任何内容都不会改变。思考为何结果不对称会很有启发性。

magick logo: -flop logo: -resize "20%" -channel green -fx "(u+v)/2" image.jpg

logo-sm-flop.png logo-sm.png ==> logo-sm-fx.png

访问像素

所有颜色值都被归一化到 0.0 至 1.0 的范围。alpha 通道的范围从 0.0(完全透明)到 1.0(完全不透明)。

像素逐个处理,但可以使用由 p 表示的像素索引来指定图像的不同像素。例如:

p[-1].g      当前像素正左方像素的绿色值
p[-1,-1].r   当前像素斜左上方像素的红色值

要指定绝对位置,请使用花括号而非方括号。

p{0,0}.r     图像左上角像素的红色值
p{12,34}.b   图像第 12 列、第 34 行像素的蓝色值

位置为整数值时检索所引用像素的颜色,而非整数位置值则根据当前 -interpolate 设置返回混合后的颜色。

图像边界之外的位置检索的值由 -virtual-pixel 选项设置决定。

指定 u.r 来指定当前图像的红色通道。如果不指定通道限定符,则得到当前通道。使用 mean.this 将输出通道设为仅输入通道的平均值。使用 mean.all 设为输入通道的总体平均值。

将表达式应用于选定的图像通道

使用 -channel 设置来指定结果的输出通道。如果未给出输出通道,结果将设置到除不透明度通道以外的所有通道上。例如,要用图像 alpha.png 和 beta.png 的绿色通道的平均值替换 alpha.png 的红色通道,使用:

magick alpha.png beta.png -channel red -fx "(u.g+v.g)/2" gamma.png

结果

-fx 运算符对序列中第一个图像(u)的每个像素的每个通道(由 -channel 设置)评估给定表达式。计算出的值临时存储在该第一个图像的副本(克隆)中,直到所有像素都处理完毕,之后这个单一的新图像替换当前图像序列的列表。因此,在前面的示例中,更新后的 alpha.png 在作为 gamma.png 保存之前,替换了原来的两个图像 alpha.png 和 beta.png。

当前图像 s 设为序列中的第一个图像(u),t 设为其索引 0。符号 i 和 j 引用正在处理的当前像素。

-format 一起使用时,值转义 %[fx:] 对当前图像序列中的每个图像只评估一次。在序列中的每个图像被评估时,s 和 t 依次引用当前图像及其索引,而 i 和 j 设为零,当前通道设为红色(-channel 被忽略)。一个示例:

$ magick canvas:'rgb(25%,50%,75%)' rose: -colorspace gray  \
  -format 'Red channel of NW corner of image #%[fx:t] is %[fx:s]\n' info:
Red channel of NW corner of image #0 is 0.464883
Red channel of NW corner of image #1 is 0.184582

这里我们使用图像索引以不同角度旋转每个图像,并用 -set 配合图像索引为动画中的第一个图像设置不同的暂停延迟:

magick rose: -duplicate 29 -virtual-pixel Gray -distort SRT '%[fx:360.0*t/n]' \
  -set delay '%[fx:t == 0 ? 240 : 10]' -loop 0 rose.gif

此示例测试两幅图像之间的差异,以 RMSE 度量。如果差异大于 0.1 则返回 1;否则返回 0:

magick water.png reference.png -metric RMSE -compare -format "%[fx:%[distortion]>0.1]" info:

颜色转义 %[pixel:] 或 %[hex:] 对每个图像及该图像中的每个颜色通道评估一次(-channel 被忽略)。生成的值随后被转换为颜色字符串(颜色名称或十六进制颜色值)。符号 i 和 j 设为零,s 和 t 依次引用每个当前图像及其索引。

epoch() 方法接受一个日期时间属性,例如:

magick rose.png -precision 16 -format '%[fx:epoch(%%[date:modify])]' info: