几个星期以来,我一直在努力用 C 语言创建一个可以将 BMP 文件顺时针或逆时针旋转 90 度的函数。无论我做什么或如何读取/更改标题似乎都不起作用,我什至尝试让人工智能生成代码,但可惜似乎没有任何作用,我不知道我做错了什么或如果我的配置有问题或者什么。如果我遗漏了什么或者有更简单的方法,请告诉我
谢谢。
编辑:在这里删除了第二个问题,稍后会问,抱歉
(抱歉,是西班牙语)
typedef struct
{
unsigned short bm; //tipo de fichero "BM"
unsigned int tam; //tamanyo
unsigned short res1; //reservado
unsigned short res2; //reservado
unsigned int inicio; //inicio datos de imagen
unsigned int head; //tamanyo de la cabecera del bitmap
unsigned int x; //ancho en pixeles
unsigned int y; //alto en pixeles
unsigned short planes; //numero de planos
unsigned short tam_pun; //tamanyo de cada punto
unsigned int comp; //compresion
unsigned int img; //tamanyo imagen
unsigned int r_hor; //resolucion horizontal
unsigned int r_ver; //resolucion vertical
unsigned int t_color; //tamanyo tabla de color
unsigned int impor; //contador de colores importantes
}bmpHeader;
void rotar(FILE *f, int rotar) {
bmpHeader header;
unsigned char *data;
unsigned char *data_rot;
int i, j, k;
// Leer la cabecera del BMP
fread(&header, sizeof(bmpHeader), 1, f);
// Asignar memoria para los datos de la imagen
data = (unsigned char *)malloc(header.img);
data_rot = (unsigned char *)malloc(header.img);
// Leer los datos de la imagen
fread(data, header.img, 1, f);
// Rotar la imagen
if (rotar == 0) {
// Rotar 90 grados a la derecha
for (i = 0; i < header.x; i++) {
for (j = 0; j < header.y; j++) {
k = (header.x - i - 1) * header.y + j;
data_rot[k] = data[i * header.y + j];
}
}
} else {
// Rotar 90 grados a la izquierda
for (i = 0; i < header.x; i++) {
for (j = 0; j < header.y; j++) {
k = i * header.y + (header.y - j - 1);
data_rot[k] = data[i * header.y + j];
}
}
}
// Escribir la cabecera del BMP
fwrite(&header, sizeof(bmpHeader), 1, f);
// Escribir los datos de la imagen
fwrite(data_rot, header.img, 1, f);
// Liberar la memoria
free(data);
free(data_rot);
}
6
3 个回答
3
BMP 文件的一个问题是,由于文件开头的两字节“BM”魔法,标头中的所有 4 字节值都未对齐。因此,通常最好将其从“bmpHeader”结构中删除并单独读取。
bmpHeader header;
uint16_t magic;
if (fread(magic, 1, sizeof(magic), f) != sizeof(magic) || magic != 0x4d42 ||
fread(&header, 1, sizeof(header), f) != sizeof(header)) {
fprintf(stderr, "error reading bmp header");
exit(1);
}
这样您就不必担心标题中的对齐或填充。将幻数检查为 uint16_t 而不是 2 个字符也会检查字节顺序,以确保它适合您的机器 – 如果它是 0x424d,您需要对所有内容进行字节交换。
您还应该始终检查调用的返回值fread
!
3
-
另一种方法是告诉编译器结构内部没有填充,例如在 gcc/clang 或MSVC 上使用。
–
-
2@AndreasWenzel:是的,但是没有可靠/可移植的方法来做到这一点——这取决于编译器和系统,它是否会工作或导致未对齐的陷阱。
– -
使用库,请参阅
–
|
我不愿意推荐任何特定的库来读取和写入 BMP 文件(但我会推荐)。你应该为自己省去一些悲伤并使用一个。谷歌找到了我,看起来不错。如果您想使用 C++,那么就是您想要做出的选择。当然,您可以疯狂地使用像这样的更大的库,但这对于您的需求来说完全是多余的。
无论如何,一旦你有了一个为你加载和保存图像数据的库,你就可以不再担心诸如步幅之类的事情了。只需执行旋转即可。
如果您想坚持自己加载和保存
…那么您应该努力将图像数据加载到仅表示打包的 32 位 ARGB 像素数据的结构中。这再次使您在正确修改图像时不必处理像素格式和行步长等问题。
换句话说,将任务分成更简单的任务,不要让一项任务的复杂性蔓延到不相关的任务中。
1
-
1如果你不想使用现有的库,你应该像库一样构造你的代码——有单独的例程来加载/存储位图和旋转位图,并且每个例程都有干净的接口,所以 main 变得很简单,只有 3 个调用+错误检查和报告。
–
|
旋转图像的尺寸可能与原始图像的尺寸不匹配。
header.img
指定图像的大小(以字节为单位)。对于未压缩的 RGB 位图,可以将其设置为 0。
我已经调整了您的代码,使其适用于所有未压缩的格式。
请注意,bmpHeader
andRGBQUAD
结构必须是字节对齐的:#pragma pack(1)
or __attribute__((packed))
(取决于编译器)。
#include <iostream>
#include <io.h>
#include <setjmp.h>
#define TRY do { jmp_buf ex_buf__; if (!setjmp(ex_buf__)) {
#define CATCH } else {
#define ETRY }} while(0)
#define THROW longjmp(ex_buf__, 1)
#pragma pack(push, 1) // set alignment to 1 byte boundary
typedef struct /*__attribute__((packed))*/ {
unsigned short bm; //tipo de fichero "BM"
unsigned int tam; //tamanyo
unsigned short res1; //reservado
unsigned short res2; //reservado
unsigned int inicio; //inicio datos de imagen
unsigned int head; //tamanyo de la cabecera del bitmap
unsigned int x; //ancho en pixeles
unsigned int y; //alto en pixeles
unsigned short planes; //numero de planos
unsigned short tam_pun; //tamanyo de cada punto
unsigned int comp; //compresion
unsigned int img; //tamanyo imagen
unsigned int r_hor; //resolucion horizontal
unsigned int r_ver; //resolucion vertical
unsigned int t_color; //tamanyo tabla de color
unsigned int impor; //contador de colores importantes
} bmpHeader;
typedef struct /*__attribute__((packed))*/ {
unsigned char blue;
unsigned char green;
unsigned char red;
unsigned char reserved;
} RGBQUAD;
#pragma pack(pop) // restore original alignment from stack
int get_stride(bmpHeader& header) {
return (((header.x * header.tam_pun) + 31) & ~31) >> 3;
}
int sgn(int val) {
return (0 < val) - (val < 0);
}
void rotar(FILE* f, int rotar) { // fopen mode: rb+
bmpHeader src_header;
bmpHeader dst_header;
RGBQUAD* palette = NULL;
unsigned char* data = NULL;
unsigned char* data_rot = NULL;
unsigned char m;
int i, j, k, b;
TRY {
// Get actual source file size
fseek(f, 0, SEEK_END);
int src_file_size = ftell(f);
fseek(f, 0, SEEK_SET);
// Leer la cabecera del BMP
if (fread(&src_header, sizeof(bmpHeader), 1, f) != 1) THROW;
if (src_header.bm != 0x4d42 ||
src_header.x <= 0 ||
src_header.y == 0 ||
src_header.head != 40 ||
src_header.t_color < 0) THROW;
memcpy(&dst_header, &src_header, sizeof(bmpHeader));
// positive height: the bitmap is a bottom - up
// negative height: the bitmap is a top - down
int src_height = abs((int)src_header.y);
int dst_height = src_header.x;
int dy = sgn((int)src_header.y);
dst_header.x = src_height;
dst_header.y = dst_height * dy;
dst_header.r_hor = src_header.r_ver;
dst_header.r_ver = src_header.r_hor;
int src_stride = get_stride(src_header);
int dst_stride = get_stride(dst_header);
int src_map_size = src_stride * src_height;
int dst_map_size = dst_stride * dst_height;
int palette_size = src_header.inicio - sizeof(bmpHeader);
if (palette_size < sizeof(RGBQUAD) * src_header.t_color) THROW;
if (src_file_size < sizeof(bmpHeader) + palette_size + src_map_size) THROW;
int dst_file_Size = sizeof(bmpHeader) + palette_size + dst_map_size;
dst_header.tam = dst_file_Size;
dst_header.img = dst_map_size;
if (palette_size) {
if (!(palette = (RGBQUAD*)malloc(palette_size))) THROW;
if (fread(palette, palette_size, 1, f) != 1) THROW;
}
// Asignar memoria para los datos de la imagen
if (!(data = (unsigned char*)malloc(src_map_size))) THROW;
// Leer los datos de la imagen
if (fread(data, src_map_size, 1, f) != 1) THROW;
// Zero empty spaces
if (!(data_rot = (unsigned char*)malloc(dst_map_size))) THROW;
memset(data_rot, 0, dst_map_size);
switch (src_header.tam_pun) {
case 1:
// Rotar la imagen
if (rotar == dy < 0) {
// Rotar 90 grados a la derecha (>)
for (i = 0; i < src_header.x; i++) {
for (j = 0; j < src_height; j++) {
k = (dst_height - 1 - i) * dst_stride + (j >> 3);
if (data[(i >> 3) + j * src_stride] & (0x80 >> (i & 7)))
data_rot[k] |= (0x80 >> (j & 7));
}
}
}
else {
// Rotar 90 grados a la izquierda (<)
for (i = 0; i < src_header.x; i++) {
for (j = 0; j < src_height; j++) {
k = i * dst_stride + ((dst_header.x - 1 - j) >> 3);
if (data[(i >> 3) + j * src_stride] & (0x80 >> (i & 7)))
data_rot[k] |= (0x80 >> ((dst_header.x - 1 - j) & 7));
}
}
}
break;
case 4:
// Rotar la imagen
if (rotar == dy < 0) {
// Rotar 90 grados a la derecha (>)
for (i = 0; i < src_header.x; i++) {
for (j = 0; j < src_height; j++) {
k = (dst_height - 1 - i) * dst_stride + (j >> 1);
m = i & 1
? data[(i >> 1) + j * src_stride] & 0x0F
: data[(i >> 1) + j * src_stride] >> 4;
data_rot[k] |= (j & 1) ? m : m << 4;
}
}
}
else {
// Rotar 90 grados a la izquierda (<)
for (i = 0; i < src_header.x; i++) {
for (j = 0; j < src_height; j++) {
k = i * dst_stride + ((dst_header.x - 1 - j) >> 1);
m = i & 1
? data[(i >> 1) + j * src_stride] & 0x0F
: data[(i >> 1) + j * src_stride] >> 4;
data_rot[k] |= ((dst_header.x - 1 - j) & 1) ? m : m << 4;
}
}
}
break;
case 8:
case 16:
case 24:
case 32:
int bpp = src_header.tam_pun >> 3; // Bytes per pixel
// Rotar la imagen
if (rotar == dy < 0) {
// Rotar 90 grados a la derecha (>)
for (i = 0; i < src_header.x; i++) {
for (j = 0; j < src_height; j++) {
for (b = 0; b < bpp; b++) {
k = (dst_height - 1 - i) * dst_stride + j * bpp + b;
data_rot[k] = data[i * bpp + j * src_stride + b];
}
}
}
}
else {
// Rotar 90 grados a la izquierda (<)
for (i = 0; i < src_header.x; i++) {
for (j = 0; j < src_height; j++) {
for (b = 0; b < bpp; b++) {
k = i * dst_stride + (dst_header.x - 1 - j) * bpp + b;
data_rot[k] = data[i * bpp + j * src_stride + b];
}
}
}
}
break;
}
// Return to start position
fseek(f, 0, SEEK_SET);
// Escribir la cabecera del BMP
if (fwrite(&dst_header, sizeof(bmpHeader), 1, f) != 1) THROW;
// Palette
if (palette_size) {
if (fwrite(palette, palette_size, 1, f) != 1) THROW;
}
// Escribir los datos de la imagen
if (fwrite(data_rot, dst_map_size, 1, f) != 1) THROW;
// Flush buffer before truncate
fflush(f);
// Truncate if necessary
_chsize(_fileno(f), dst_file_Size);
}
CATCH {
}
ETRY;
// Liberar la memoria
if (palette) free(palette);
if (data) free(data);
if (data_rot) free(data_rot);
}
3
-
1在编译器 gcc/clang 上,没有填充字节的选项是。
–
-
此代码不适用于具有负高度的位图(表示自上而下的位图)。请参阅我对该问题的评论以获取更多信息。
–
-
安德烈亚斯·温泽尔,我把骨灰撒在头上,我忘记了负高度。代码已经修复了。
–
|
–
–
biCompression
结构中字段的值是多少?我相信这个字段应该与您结构中的字段相对应。comp
–
biCompression
我之前评论中提到的字段值的另一个原因。如果它的值为0
,那么您使用的是未压缩的 RGB,这意味着每个像素有 3 个字节,而不是 1 个字节。–
bmpHeader
自己创建的吗?如果是这样,那么我建议您使用(requires#include <stdint.h>
) 代替unsigned short
和uint32_t
,unsigned int
因为 anint
不保证在所有平台上具有 4 字节的大小,而 auint32_t
保证在所有平台上具有相同的大小。–
|