通过剪切旋转GIF





5.00/5 (2投票s)
通过剪切方法旋转 GIF 图像。
引言
旋转图像是一个常见需求,特别是带有透明度的 GIF。然而,不能使用旋转矩阵,因为它需要对像素进行插值,这在颜色索引图像中是不可能的。但是,可以使用剪切方法进行旋转。
背景
要旋转一个点,我们必须对 x 和 y 分量应用正弦和余弦。具体来说
x' = x cos(theta) - y sin(theta)
y' = x sin(theta) + y cos(theta)
因此,可以通过迭代目标像素、应用此方程式的逆运算(theta = -theta)并找到源图像中的正确点来旋转图像。但是,样本点恰好击中像素的可能性极低。因此,我们必须对四个最接近的像素进行插值,以获得输出像素。如果我们不这样做,并选择最接近的像素,我们将得到奇怪的伪影,因为源像素被加倍和丢弃,这在技术上称为“混叠”。
对于真彩色图像,插值相对简单(只要我们不要求数学上完美的结果),但对于颜色索引图像,例如 GIF 文件,这是不可能的,因为我们只有一组有限的像素可供选择。因此,使用旋转矩阵方法,我们必须接受混叠。
然而,有可能避免这种情况。通过使用另一种旋转算法。旋转等价于三个剪切变换。要将图像旋转 180 度,我们可以剪切整个宽度,剪切整个高度,然后再次剪切整个宽度。这应该很容易可视化,并让你对该算法有一个直观的理解。它也适用于其他值。
有一些附带条件。只能按整个像素旋转。因此,对于有限的图像,不允许进行小旋转。图像行和列被分成源像素的运行,这本身就是混叠的来源。但是结果相对较好,并且该方法已经确立。
代码被打包成一个可运行的程序。 GIF 图像是矩形的,并且只能在内切圆内旋转几何图形,而不会改变图像的尺寸。因此,该程序适用于带有透明度的 GIF。扩大尺寸的替代方案将很快导致图像大小失控膨胀。
使用代码
代码都是纯 ANSI C。只需键入
gcc *.c
即可获得可执行文件。
/**
Rotate an image, using the shearing method.
Params:
binary - the binary or colour-indexed image
width - image width
height - image height
cx - centre x, y co-ordinates (pass in 1.5, 1.5 for the centre of a 3x3 image)
cy - centre x, y co-ordinates (pass in 1.5, 1.5 for the centre of a 3x3 image)
theta - angle to rotate by
out[out] - buffer for output (can't be the same as input)
Returns: 0 for success
Conventional image rotation by the matrix method causes destination pixels to be
sub-samples of source pixels. This isn't a problem with continous tone images where
the new pixel values can be obtained by interpolation. However with binary or
colour-index images, interpolation isn't possible. The shearing method preserves the
pixels, at some cost in rotational accuracy.
*/
int rotatebyshear(unsigned char *binary, int width, int height, double cx, double cy, double theta, unsigned char *out)
{
double alpha;
double beta;
int dpx;
int tx, ty;
int x, y;
assert(binary != out);
theta = fmod(theta, 2 * PI);
if(theta >= -PI/2 && theta <= PI/2)
{
alpha = -tan(theta/2);
beta = sin(theta);
for(y=0;y<height;y++)
{
dpx = (int) floor(alpha * (y - cy) + 0.5);
for(x=0;x<width;x++)
{
ty = y + (int) floor(beta * (x + dpx - cx) + 0.5);
tx = x + dpx + (int) floor(alpha * (ty - cy) + 0.5);
if(tx >= 0 && tx < width && ty >= 0 && ty < height)
out[y*width+x] = binary[ty*width+tx];
else
out[y*width+x] = 0;
}
}
}
else
{
alpha = -tan( (theta + PI) / 2);
beta = sin(theta + PI);
for(y=0;y<height;y++)
{
dpx = (int) floor(alpha * (y - cy) + 0.5);
for(x=0;x<width;x++)
{
ty = y + (int) floor(beta * (x + dpx - cx) + 0.5);
tx = x + dpx + (int) floor(alpha * (ty - cy) + 0.5);
tx = (int) (cx-(tx - cx));
ty = (int) (cy-(ty - cy));
if(tx >= 0 && tx < width && ty >= 0 && ty < height)
out[y*width+x] = binary[ty*width+tx];
else
out[y*width+x] = 0;
}
}
}
return 0;
}
这就是你想要的功能。
剪切变换由矩阵给出
1 | sx |
0 | 1 |
并且是最基本的简单变换之一。
结果
Lena 256 GIF | 旋转后的 Lena |
---|---|
![]() | ![]() |
结果并不完美。你会看到 Lena 的帽子和左脸颊上出现严重的“锯齿”,当然图像边缘本身也出现了混叠。但是保留了所有像素。在连续色调区域中没有带状或质量下降。
关注点
你可能想知道我们是如何获得如此高质量的 256 色 Lena,她的肩膀上根本没有任何机械带状的。当然,我们使用了主成分分析算法。
历史
该代码是 github 上二进制图像库的一部分
https://github.com/MalcolmMcLean/binaryimagelibrary