无论您拥有加载图像的任何功能,都应该为您提供两件事:一是图像信息,例如宽度和高度。另一个是图像数据,它是一个数字数组。
假设你得到图像b1
和b2
,b1
和b2
是 GLubyte 的数组。假设b1_w
,b1_h
和是b2_w
and的宽度和b2_h
高度(应该由位图加载函数提供给您)。b1
b2
你想做的包括两部分,首先,你需要伸展b1
。其次,你需要把它写在b2
. 事实上,你可以同时做这两件事,但现在,我会把它们分开。
拉伸
要拉伸图像,您需要能够进行一些简单的数学运算。不过,在进入地图之前,我将告诉您图像转换(旋转、拉伸等)的基本概念。您可以做的最简单的事情是遍历目标图像的像素,将它们转换回原始图片,然后决定从原始图片中获取什么颜色并分配给该像素。
例如,如果您想将图像从宽度和高度拉伸到w1
and ,则转换如下:h1
w2
h2
xt = w2/w1*xs
yt = h2/h1*ys
其中xt
和yt
是目标图像中的坐标,xs
和ys
是源图像中的坐标。逆变换为:
xs = w1/w2*xt
ys = h1/h2*yt
在继续之前,让我们定义一个宏以使生活更轻松。由于数据数组是一维数组,但我们使用 x 和 y 索引,因此我们编写了一个宏来为我们进行转换。
#define INDEX(x, y, width) (((y)*(width)+(x))*3)
(旁注:请注意,我假设图像存储在 RGB(或 BGR)中,即每个像素具有三个值。另请注意,如果您的图像宽度不是 4 的倍数,那么会有填充和每行宽度不会是width*3
字节,而是width*3+something_to_make_it_divisible_by_4
. 在第二种情况下,您可以像这样编写宏:
#define INDEX(x, y, row_bytes) ((y)*(row_bytes)+(x)*3) // note the parentheses
)
所以你的代码看起来像这样(float
除非另有说明,否则所有宽度/高度和 x/y 值都是)
for (unsigned int yt = 0; yt < h2; ++yt)
for (unsigned int xt = 0; xt < w2; ++xt)
{
float xs = w1/w2*xt;
float ys = h1/h2*yt;
target[INDEX(xt, yt, w2)] = // COMPUTED IN THE NEXT STEP
target[INDEX(xt, yt, w2)+1] = // COMPUTED IN THE NEXT STEP
target[INDEX(xt, yt, w2)+2] = // COMPUTED IN THE NEXT STEP
}
由于存在三个颜色分量(R、G 和 B),因此存在三个赋值。如果您有 alpha 2,请将其设为 4。如果您在灰度中工作,请保持 1(您明白了)
下一步是像素颜色的实际分配。这是不同的拉伸算法不同的地方,拉伸的质量取决于这一步。
所以,这就是这个想法。想象一下xs
,ys
结果是完美的舍入整数。这很简单,您只需在源图像中选择该像素的颜色并将其放入目标中。xs
但是,如果ys
不是整数怎么办?那么你会为目标像素采用哪种颜色呢?这里有两种常用的方法。
最近的邻居
这很容易。它说,选择最接近您计算的位置的像素的颜色。例如,如果(xs, ys)
是(12.78, 98.2)
,您选择点的颜色(13, 98)
。那是在您的代码中:
unsigned int xs_int = (unsigned int)round(xs);
unsigned int ys_int = (unsigned int)round(ys);
target[INDEX(xt, yt, w2)] = source[INDEX(xs_int, ys_int, w1)]
线性化
这种方法需要一些数学知识。它说的是,你取周围四个像素之间的线性化值,(xs, ys)
然后选择它作为目标像素的颜色。那个怎么样?我会给你看。想象一下这四个像素和计算出的(xs, ys)
v_tl v_tr
+------------+
| |
| .(xs, ys) |
| |
| |
| |
+------------+
v_bl v_br
如图所示,周围四个像素的值为v_{t,b}{l,r}(top、bottom、left、right的缩写)。要对 position 的值进行线性化(xs, ys)
,您可以获取的线性化值(floor(xs), ys)
并(ceil(xs), ys)
再次对其进行线性化,或者首先对 y 然后 x 执行相同的操作,这是相同的。所以你得到:
unsigned int x_bl = (unsigned int)floor(xs);
unsigned int y_bl = (unsigned int)floor(ys);
float v_l = v_bl+(v_tl-v_bl)*(ys-y_bl); // (floor(xs), ys)
// note that in the end, there must be a (x_tl-x_bl) but this value is 1
float v_r = v_br+(v_tr-v_br)*(ys-y_bl); // (ceil(xs), ys)
float v = v_l+(v_r-v_l)*(xs-x_bl);
最后,你得到
target[INDEX(xt, yt, w2)] = v;
请注意,您应该对图像的每个通道(例如 R、G 和 B)进行这种线性化,并将适当的 v 放入适当的索引中(INDEX(...)、INDEX(...)+1 和 INDEX( ...)+2 例如)。
请注意,在这两种方法中,您必须检查源图像中的计算索引,以免超出数组范围。如果你这样做了,你可以为那些超出数组边界的像素假设黑色/白色/中灰色。或者,您可以检查那些像素,如果像素不在数组中,您可以将方法更改为仅适用于源图像内的像素。
在另一张图片上书写
这部分很简单。你有图像stretched_b2
,你想把它写在b1
. stretched_b2
如果您要写入的左下角位置b1
是(x_start, y_start)
,您只需编写:
for (unsigned int y = 0; y < h2_stretched; ++y)
for (unsigned int x = 0; x < w2_stretched; ++x)
{
b1[INDEX(x_start+x, y_start+y, w1)] =
stretched_b2[INDEX(x, y, w2_stretched)];
b1[INDEX(x_start+x, y_start+y, w1)+1] =
stretched_b2[INDEX(x, y, w2_stretched)+1];
b1[INDEX(x_start+x, y_start+y, w1)+2] =
stretched_b2[INDEX(x, y, w2_stretched)+2];
}
你想把图像放在这里:
(b1 -40 的宽度中间,b1-30 的高度中间) 到 (b1+40 的宽度中间,b1+30 的高度中间)
很容易,你有
w2_stretched = 80 // or +1 if you want to be really precise,
// but for now let's say 80 to keep it a multiple of 4
h2_stretched = 60
x_start = w1/2-40
y_start = h1/2-30
结合两个部分
现在记住在拉伸部分,你有的部分
for (unsigned int yt = 0; yt < h2; ++yt)
for (unsigned int xt = 0; xt < w2; ++xt)
在你拥有的放置部分
for (unsigned int y = 0; y < h2_stretched; ++y)
for (unsigned int x = 0; x < w2_stretched; ++x)
那么在拉伸部分h2
,w2
是拉伸目标图像的高度和宽度,还记得吗?这就是我所说的h2_stretched
,w2_stretched
在放置部分。如您所见,这些 for 循环迭代相同的东西。现在在拉伸部分,您计算目标图像的值,并在放置部分复制它b1
。好吧,您可以直接计算该值并将其覆盖,而不是将其保存在临时目标图像中b1
,因此您将拥有:
for (unsigned int yt = 0; yt < h2; ++yt)
for (unsigned int xt = 0; xt < w2; ++xt)
{
float xs = w1/w2*xt;
float ys = h1/h2*yt;
b1[INDEX(x_start+xt, y_start+yt, w1)] = compute_with_mentioned_methods;
b1[INDEX(x_start+xt, y_start+yt, w1)+1] = compute_with_mentioned_methods;
b1[INDEX(x_start+xt, y_start+yt, w1)+2] = compute_with_mentioned_methods;
}
最后一点:图像处理虽然是微不足道的,但有很多索引检查,而且很难在第一次就正确处理。为分段错误做好准备,如果您需要一段时间来调试它,请不要感到沮丧。
后期注意事项:我解释了这些供您学习,但是使用 OpenGL,如果我是您,我会将两个图像都转换为纹理,QUADS
在适当的位置绘制并让 OpenGL 为您拉伸图像。