C/C++ 旋转 BMP 图像
Posted
技术标签:
【中文标题】C/C++ 旋转 BMP 图像【英文标题】:C/C++ rotate an BMP image 【发布时间】:2016-11-15 22:55:39 【问题描述】:我正在尝试使用 ,但它不起作用。
我已经制作了一些用于读取、写入和旋转的函数。读取和写入功能工作正常,但由于某种原因无法旋转。
编辑(正弦、余弦和旋转函数)
BMP 结构:
struct BMP
int width;
int height;
unsigned char header[54];
unsigned char *pixels;
int size;
;
写:
void writeBMP(string filename, BMP image)
string fileName = "Output Files/" + filename;
FILE *out = fopen(fileName.c_str(), "wb");
fwrite(image.header, sizeof(unsigned char), 54, out);
int i;
unsigned char tmp;
for (i = 0; i < image.size; i += 3)
tmp = image.pixels[i];
image.pixels[i] = image.pixels[i + 2];
image.pixels[i + 2] = tmp;
fwrite(image.pixels, sizeof(unsigned char), image.size, out); // read the rest of the data at once
fclose(out);
阅读:
BMP readBMP(string filename)
BMP image;
int i;
string fileName = "Input Files/" + filename;
FILE *f = fopen(fileName.c_str(), "rb");
fread(image.header, sizeof(unsigned char), 54, f); // read the 54-byte header
// extract image height and width from header
image.width = *(int *) &image.header[18];
image.height = *(int *) &image.header[22];
image.size = 3 * image.width * image.height;
image.pixels = new unsigned char[image.size]; // allocate 3 bytes per pixel
fread(image.pixels, sizeof(unsigned char), image.size, f); // read the rest of the data at once
fclose(f);
for (i = 0; i < image.size; i += 3)
unsigned char tmp = image.pixels[i];
image.pixels[i] = image.pixels[i + 2];
image.pixels[i + 2] = tmp;
return image;
旋转:
BMP rotate(BMP image, double degree)
BMP newImage = image;
unsigned char *pixels = new unsigned char[image.size];
double radians = (degree * M_PI) / 180;
int sinf = (int) sin(radians);
int cosf = (int) cos(radians);
double x0 = 0.5 * (image.width - 1); // point to rotate about
double y0 = 0.5 * (image.height - 1); // center of image
// rotation
for (int x = 0; x < image.width; x++)
for (int y = 0; y < image.height; y++)
long double a = x - x0;
long double b = y - y0;
int xx = (int) (+a * cosf - b * sinf + x0);
int yy = (int) (+a * sinf + b * cosf + y0);
if (xx >= 0 && xx < image.width && yy >= 0 && yy < image.height)
pixels[(y * image.height + x) * 3 + 0] = image.pixels[(yy * image.height + xx) * 3 + 0];
pixels[(y * image.height + x) * 3 + 1] = image.pixels[(yy * image.height + xx) * 3 + 1];
pixels[(y * image.height + x) * 3 + 2] = image.pixels[(yy * image.height + xx) * 3 + 2];
newImage.pixels = pixels;
return newImage;
主要:
int main()
BMP image = readBMP("InImage_2.bmp");
image = rotate(image,180);
writeBMP("Output-11.bmp", image);
return 0;
sin=0.8939966636
(弧度)和cos=-0.44807361612
(弧度)表示这张图片应该旋转90度。
这是我的原图:
现在是我的结果:
有人可以帮我理解我在这里做错了什么吗?我真的需要这个功能。
我不能为此代码使用任何第三方库。
【问题讨论】:
在旋转中,newImage = image。因此,您在旋转时会覆盖图像像素。 PIxel 寻址应该是[y * image.width + x]
,假设是光栅顺序
@samgak 我认为应该对旋转方向产生一些影响。但无论如何它都不起作用...我已经编辑了我的 旋转函数 添加了新结果以及您所说的内容。而在这一刻……我有一个比以前更奇怪的结果。
我会说问题是因为你截断了 sin 和 cos: int sinf = (int) sin(radians); int cosf = (int) cos(弧度);通常的做法是将浮点数保留到最后一刻。
【参考方案1】:
它必须以 bmp 使用的相同像素格式处理旋转。您只为每个像素转换一个字节。像素看起来更宽。既然问题已经确定,这应该很容易解决。
如果您需要更快的速度,请注意您的不变量(x 和 y)在每次迭代时都会递增:
for (int y = 0; y < image.height; y++)
double a = x - x0;
double b = y - y0;
int xx = (int) (+a * cos - b * sin + x0);
将 b 移出循环并将乘法改为加法:
double b = -y0;
for (int y = 0; y < image.height; ++y)
int xx = (int) (a * cos - b + x0);
b += sin;
注意到 a * cos 是整个 y 循环的常数吗?将其融合到 b。对 x0 执行相同操作。
double b = a * cos - y0 + x0;
for (int y = 0; y < image.height; ++y)
int xx = (int) (- b);
b += sin;
注意 -b 也有成本?否定 b。
double b = -(a * cos - y0 + x0);
for (int y = 0; y < image.height; ++y)
int xx = (int) b;
b -= sin;
看看我们在那里做了什么?下一步:摆脱双打。使用定点。浮点到整数的转换可能代价高昂。充其量,它们在这里毫无用处。
最后但同样重要的是,您正在垂直写入内存。这对写入组合非常非常不利,并且会大大降低性能。考虑更改循环顺序,使 x-loop 位于最里面。
额外:对图像使用平铺内存布局,以提高读取时的内存局部性。缓存将更有效地工作。在这里不是很重要,因为您只处理一次图像并且平铺会比加速更昂贵。但是,如果您想为旋转设置动画,那么平铺应该会让您受益。此外,通过平铺,性能不会因旋转角度而波动,因此动画将更加一致(并且更快)。
编辑:添加说明如何支持每像素更多字节:
pixels[(y * image.height + x) * 3 + 0] = image.pixels[(yy * image.height + xx) * 3 + 0];
pixels[(y * image.height + x) * 3 + 1] = image.pixels[(yy * image.height + xx) * 3 + 1];
pixels[(y * image.height + x) * 3 + 2] = image.pixels[(yy * image.height + xx) * 3 + 2];
这开始有点难以阅读,但你确实看到我们在做什么了吗?
【讨论】:
好吧……如果你谈到速度,是的,我将来会改变它,首先它们都是我需要让我的轮换工作的。It must handle the rotation in the same pixel format the bmp uses.
,好吧...我不知道该怎么做。我的意思是我认为我已经这样做了。你能帮我写这部分代码吗?因为我不知道如何做到这一点,而且我尝试了几个小时但我什么也没得到......如果你能为我做这将非常有帮助。
我添加了一些代码来演示如何处理更宽的像素。我注意到您很乐意在不释放内存的情况下覆盖旧图像指针。您可能还想修复该内存泄漏。如果您容易出现此类错误(std::vector、std::array、std::unique_ptr 或类似),我建议您使用一些对 RAII 友好的类型进行存储。
我会在完成这个问题后看看我将如何处理内存泄漏。在这一刻,让我们说……我的问题完成了 50%。我现在还有 2 个问题,1。我不知道为什么,但它没有在中心旋转的图像,它在中心旁边的某个地方(你可以看到我的新结果,因为我已经更新了我的问题)和第二个,2。我不知道为什么,但我失去了图像的颜色属性,由于某种原因我调用了旋转函数后,我的所有图像都是黑色和什么。你能帮我解决这两个问题吗?
将 y * image.height 固定为 y * image.width,与 yy 相同。您正在处理错误的图像。在读取和写入 bmp 时交换(红色,蓝色)的 for 循环也是不必要的,删除它。最后但并非最不重要的一点是,不要调用您的旋转函数 .. 只需读写 bmp 并查看读写是否会破坏颜色。您需要确信事情在一定程度上可以正常工作,这样您才能发现问题(现在问题可能出现在任何地方)。【参考方案2】:
BMP newImage = image;
这会将newImage
设置为image
。 BMP结构是:
unsigned char *pixels;
所以,newImage
和 image
都有相同的 pixels
指针。
不幸的是,轮换代码中的逻辑假定 newImage
和 image
是不同的独立缓冲区。
您需要做更多的工作来创建您的newImage
对象。您将需要分配自己的图像缓冲区,将内存清除为空图像,并将其pixels
设置为指向它。
要正确执行此操作,您的 BMP
类应设为 Rule Of Three compliant。
【讨论】:
好吧,如果你说的是真的,那么我只需要制作一组新的像素,如unsigned char *pixels = new unsigned char[image.size];
并使用它。最后我需要做一些类似`newImage.pixels = pixel;`的事情。所以我这样做了,我得到了一个更奇怪的结果...我已经通过添加详细信息和我的新结果来编辑我的 旋转函数,你能再检查一次吗?
“我得到一个更奇怪的结果”不是有效的问题描述。
嗯,我的意思是,我没有得到我期望的结果。我已经更新了我的问题,因此您可以检查我尝试过的方法,但它不起作用,您会看到新的结果。
你的读写函数清楚地显示pixels[]
数组中每个像素占用三个字节,每行有width*3
字节(RGB分量)。您的旋转代码假定每个像素占用一个字节,并且每行有 width
字节。失败。在每次通过旋转循环时,您应该一次移动三个字节,包括一个像素。 pixels[]
数组中每个像素的计算必须相应调整。
...在分配新的像素数组等时需要做同样的调整...【参考方案3】:
您可以将您的像素(缓冲区)复制到一个新的、单独且相同类型/大小的像素(如前所述),然后使用以下方式旋转它:
http://www.sourcetricks.com/2012/07/rotate-matrix-by-90-degrees.html
然后您可以将新缓冲区重新复制回原始像素缓冲区,清理(释放)它(编辑:这意味着副本),最后重绘所有。
编辑:
I was thinking in pseudo code:
rotate image
imagecopy = duplicate (image)
(do the rotation on imagecopy)
copypixels (imagecopy (to), image)
free imagecopy
copy 和 dup 是 for 循环,但 dups 添加了一个 malloc
和“newImage.pixels = 像素;”将不起作用,您必须遍历两个数组并像“writeBMP”中一样一一复制值
哦,通过
它必须以 bmp 使用的相同像素格式处理旋转。
我认为他的意思是使用整数,例如 int 或 long 而不是 float 或 double没有好处的代码
【讨论】:
好吧,如果你再次检查我的旋转功能.. 你会看到我已经为像素添加了一个新的缓冲区,这里是unsigned char *pixels = new unsigned char[image.size];
,在这个函数的最后我会做newImage.pixels = pixels;
所以我没有这个问题了
旋转图像 imagecopy = duplicate (image) (在 imagecopy 上进行旋转)
image = copy (imagecopy) free imagecopy 对不起,我所有的 cmets 都很烂,我是新手 :) copy 和 dup 是 for 循环,但 dups 添加了一个 malloc
和“newImage.pixels = 像素;”没那么容易,你必须遍历两个数组并像“writeBMP”一样一一复制值
好的,但我不明白...我的意思是,我用这个BMP newImage = image; unsigned char *pixels = new unsigned char[image.size];
制作了一个新的像素数组,之后我只使用这个新的像素数组.在这个函数的最后,我正在做 image.pixels = 像素。不好吗?我的意思是它就像一个重复......?没有?以上是关于C/C++ 旋转 BMP 图像的主要内容,如果未能解决你的问题,请参考以下文章