源自:http://hi.baidu.com/prince_hyai/item/d328959f62360bc7b72531d3
注意该代码中直接读取文件头是错误的,struct结构体内存对齐并非在结构体末尾增加空间,而是在变量后面增加字节空间。
比如:
typedef struct {
uint16_t bfType; // 位图文件的类型,必须为BM(1-2字节)
uint32_t bfSize; // 位图文件的大小,以字节为单位(3-6字节)
uint16_t bfReserved1; // 位图文件保留字,必须为0(7-8字节)
uint16_t bfReserved2; // 位图文件保留字,必须为0(9-10字节)
uint32_t bfOffBits; // 位图数据的起始位置,以相对于位图(11-14字节)
} BMPFILEHEADER;
这个结构体会补2个字节,位置在bfType变量后面,而非最后那个变量bfOffBits后面。
因此,分次读取文件才能获取到正确的结果。
#ifndef _BMPLOAD_H_ #define _BMPLOAD_H_ #include <stdio.h> #include <stdint.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> // 纹理图像结构 typedef struct { int width; // 纹理宽度 int height; // 纹理高度 int components; // 每个象素对应的字节数,3:24位图,4:带alpha通道的24位图 unsigned char *data; // 纹理数据 unsigned int unpack_size; // 纹理数据大小 } TEXTUREIMAGE; // BMP文件头(14字节) typedef struct { uint16_t bfType; // 位图文件的类型,必须为BM(1-2字节) uint32_t bfSize; // 位图文件的大小,以字节为单位(3-6字节) uint16_t bfReserved1; // 位图文件保留字,必须为0(7-8字节) uint16_t bfReserved2; // 位图文件保留字,必须为0(9-10字节) uint32_t bfOffBits; // 位图数据的起始位置,以相对于位图(11-14字节) } BMPFILEHEADER; // BMP信息头(40字节) typedef struct { uint32_t biSize; // 本结构所占用字节数(15-18字节) uint32_t biWidth; // 位图的宽度,以像素为单位(19-22字节) uint32_t biHeight; // 位图的高度,以像素为单位(23-26字节) uint16_t biPlanes; // 目标设备的级别,必须为1(27-28字节) uint16_t biBitCount; // 每个像素所需的位数,必须是1(双色),4(16色),8(256色)或24(真彩色)之一(29-30字节) uint32_t biCompression; // 位图压缩类型,必须是 0(不压缩),1(BI_RLE8压缩类型)或2(BI_RLE4压缩类型)之一(31-34字节) uint32_t biSizeImage; // 位图的大小,以字节为单位(35-38字节) uint32_t biXPelsPerMeter;// 位图水平分辨率,每米像素数(39-42字节) uint32_t biYPelsPerMeter;// 位图垂直分辨率,每米像素数(43-46字节) uint32_t biClrUsed; // 位图实际使用的颜色表中的颜色数(47-50字节) uint32_t biClrImportant; // 位图显示过程中重要的颜色数(51-54字节) } BMPINFOHEADER; #endif // 读取BMP文件创建纹理 int LoadBmp(char *filename, TEXTUREIMAGE *textureImg) { int i, j; FILE *file; BMPFILEHEADER bmpFile; BMPINFOHEADER bmpInfo; int pixel_size; unsigned int unpack_size; struct stat finfo; memset(&bmpFile, 0, sizeof bmpFile); memset(&bmpInfo, 0, sizeof bmpInfo); memset(textureImg, 0, sizeof (TEXTUREIMAGE)); if (-1 == stat(filename, &finfo)) { perror("Error to stat"); return -1; } // 打开文件 file = fopen(filename, "rb "); if (file == NULL) { perror("Open file error"); return -1; } // 获取文件头 fread(&bmpFile.bfType, 2, 1, file); fread(&bmpFile.bfSize, 4, 1, file); // 防止文件不完整 if (finfo.st_size != bmpFile.bfSize) { printf("file size:%d bmp info size: %d, file not complete!\n"); return -1; } fread(&bmpFile.bfReserved1, 2, 1, file); fread(&bmpFile.bfReserved2, 2, 1, file); fread(&bmpFile.bfOffBits, 4, 1, file); //printf("bmpFile.bfType:%X\nbmpFile.bfSize:%u\nbmpFile.bfReserved1:%d\nbmpFile.bfReserved2:%d\nbmpFile.bfOffBits:%d\n", bmpFile.bfType, bmpFile.bfSize, bmpFile.bfReserved1, bmpFile.bfReserved2, bmpFile.bfOffBits); fread(&bmpInfo, 40, 1, file); /* printf("bmpInfo.biSize:%d\n", bmpInfo.biSize); printf("bmpInfo.biWidth:%d\n", bmpInfo.biWidth); printf("bmpInfo.biHeight:%d\n", bmpInfo.biHeight); printf("bmpInfo.biPlanes:%d\n", bmpInfo.biPlanes); printf("bmpInfo.biBitCount:%d\n", bmpInfo.biBitCount); printf("bmpInfo.biCompression:%d\n", bmpInfo.biCompression); printf("bmpInfo.biSizeImage:%d\n", bmpInfo.biSizeImage); printf("bmpInfo.biXPelsPerMeter:%d\n", bmpInfo.biXPelsPerMeter); printf("bmpInfo.biYPelsPerMeter:%d\n", bmpInfo.biYPelsPerMeter); printf("bmpInfo.biClrUsed:%d\n", bmpInfo.biClrUsed); printf("bmpInfo.biClrImportant:%d\n", bmpInfo.biClrImportant); */ // 验证文件类型 if (bmpFile.bfType != 0x4D42) { printf("File Type Error\n"); fclose(file); return -1; } if (bmpInfo.biCompression != 0) { printf("file compressed!\n"); return -1; } if (bmpInfo.biBitCount != 24) { printf("only support 24 bit map\n"); fclose(file); return -1; } // 获取图像色彩数 pixel_size = bmpInfo.biBitCount >> 3; // 有效数据大小 unpack_size = bmpInfo.biWidth * bmpInfo.biHeight * pixel_size; textureImg->data = (unsigned char*) malloc(unpack_size); if (textureImg->data == NULL) { fclose(file); return -1; } int bytes_add = bmpInfo.biWidth * pixel_size % 4; // 读取文件数据 for (i = 0; i < bmpInfo.biHeight; i++) { for (j = 0; j < bmpInfo.biWidth; j++) { // 红色分量 fread( textureImg->data + (i * bmpInfo.biWidth + j) * pixel_size + 2, sizeof(unsigned char), 1, file); // 绿色分量 fread( textureImg->data + (i * bmpInfo.biWidth + j) * pixel_size + 1, sizeof(unsigned char), 1, file); // 蓝色分量 fread(textureImg->data + (i * bmpInfo.biWidth + j) * pixel_size + 0, sizeof(unsigned char), 1, file); // Alpha分量 if (pixel_size == 4) { fread(textureImg-> data + (i * bmpInfo.biWidth + j) * pixel_size + 3, sizeof(unsigned char), 1, file); } } if (bytes_add > 0) { // if pixel_size == 4 then bytes_add == 0 fseek(file, bytes_add, SEEK_CUR); } } // 记录图像相关参数 textureImg->width = bmpInfo.biWidth; textureImg->height = bmpInfo.biHeight; textureImg->components = pixel_size; textureImg->unpack_size = unpack_size; fclose(file); return 0; } /** * @brief 检测图片数据的颜色数量 * @param[in] jpg_data 解压后的JPG图片数据,注意用于计算必须为unsigned * @param[in] unpack_size 解压后图片数据长度 * @param[in] width JPG图片宽度 * @param[in] height JPG图片高度 * @param[in] components 图片每像素字节数 * @param[in] max_color 颜色数量超过这个值就认为是正常信的图片 * @return 颜色数量超过max_color就返回0,认为是正常图片,否则返回2,是垃圾信图片 */ static int scan_jpg_color(unsigned char *jpg_data, size_t unpack_size, int width, int height, int components, int max_color) { if (NULL == jpg_data) return 0; register int i, j, pixel, colors = 0; char *bitHash = (char *) malloc(2 * 1024 * 1024 * sizeof (char)); if (NULL == bitHash) { perror("malloc error"); return 0; } memset(bitHash, 0, 2 * 1024 * 1024 * sizeof (char)); // 遍历一遍像素点 for (i=0; i<unpack_size; i+=components) { pixel = 0; for (j=0; j<components; j++) { pixel = (pixel << 8) | jpg_data[i+j]; } bitHash[pixel / 8] |= 1 << (pixel % 8); } for (i=0; i<2*1024*1024*8; i++) { if (bitHash[i / 8] & (1 << (i % 8))) { colors++; if (colors > max_color) { printf("colors more than %d!\n", max_color); free(bitHash); return 0; } } } free(bitHash); printf("color's count:%d\n", colors); return 2; } /** * @brief 根据图片数据和位置坐标,初始化数组samp * @param[in] jpg_data 解压后的JPG图片数据,注意用于计算必须为unsigned * @param[in] width JPG图片宽度 * @param[in] height JPG图片高度 * @param[in] components 图片每像素字节数 * @param[out] samp 待初始化的数组 * @param[in] pos 在图片中的坐标 * @param[in] num 几个坐标点,也代表了数组元素的个数 * @return 返回初始化了数组几个数字 */ static int init_pixel_array(unsigned char *jpg_data, int width, int height, int components, int samp[], int pos[][2], int num) { int pixel, i, j, k = 0; size_t pos_num; for (i=0; i<num; i++) { if (width < pos[i][0] || height < pos[i][1]) continue; // 如果点不在图片内,继续下一个 pos_num = pos[i][1] * width * components + pos[i][0] * components; pixel = 0; for (j=0; j<components; j++) { // RGB的值存入一个整数 pixel = (pixel << 8) | jpg_data[pos_num+j]; } samp[k] = pixel; if (k > 0) { if (samp[k-1] == samp[k]) { k--; } } k++; } return k; } #define JPG_SAMP_PIXELS 5 static int scan_jpg_samps(unsigned char *jpg_data, size_t unpack_size, int width, int height, int components, float max_rate) { if (NULL == jpg_data) return 0; register int i, j, k = 0, m; register unsigned int pixel; register unsigned int max_num = max_rate * (unpack_size / 3); // 最多可以有多少个像素点相同 int samp[JPG_SAMP_PIXELS] = {-1, -1, -1, -1, -1}; int pos[JPG_SAMP_PIXELS][2] = {{0, 0}, {25, 25}, {50, 50}, {(width-1), 0}, {width/2, height/2}}; unsigned int count[JPG_SAMP_PIXELS] = {0}; //size_t pos_num; //printf("max same pixel:%u\n", max_num); k = init_pixel_array(jpg_data, width, height, components, samp, pos, JPG_SAMP_PIXELS); //printf("k=%d samp[0] = %X samp[1] = %X samp[2]=%X samp[3] = %X samp[4] = %X\n", k, samp[0], samp[1], samp[2], samp[3], samp[4]); // 遍历图片的每一个像素点 for (i=0; i<unpack_size; i+=components) { pixel = 0; for (j=0; j<components; j++) { pixel = (pixel << 8) | jpg_data[i+j]; } for (m=0; m<k; m++) { if (pixel == samp[m]) { count[m]++; if (count[m] > max_num) { printf("Spam JPG: [%d] same color's pixel max than %u!\n", m, max_num); return 1; } break; // 如果跟其中一个采样点相同,就不可能跟剩下的几个相同了 } } } int pixel_num = unpack_size / 3; for (m=0; m<k; m++) { printf("Ham JPG: same pixel count[%d] = %u (%f).\n", m, count[m], count[m] / (float)pixel_num); } return 0; } /** * @brief 统计与采样点相同的像素点个数和颜色数量 * @param[in] jpg_data 解压后的JPG图片数据,注意按位计算时必须用无符号类型 * @param[in] unpack_size 解压后图片数据长度 * @param[in] width JPG图片宽度 * @param[in] height JPG图片高度 * @param[in] components 图片每像素字节数 * @param[in] max_rate 像素点与采样点相同数量所占比例超过此值就认为是垃圾信的图片 * @param[in] max_color 颜色数量超过这个值就认为是正常信的图片 * @return 返回0:颜色数量超过max_color且相同颜色点数小于要求的值;返回1:相同颜色点数过多;返回2:颜色数太少 */ static int scan_jpg_samp_color(unsigned char *jpg_data, size_t unpack_size, int width, int height, int components, float max_rate, int max_color) { if (NULL == jpg_data) return 0; register int i, j; register int samp = -1; register unsigned int pixel; register unsigned int count = 0; register int colors = 0; register unsigned int max_num = max_rate * (unpack_size / 3); // 最多可以有多少个像素点相同 int pos_x = width-1, pos_y = 0; size_t pos_num; //printf("max same pixel:%u\n", max_num); if (components > 3) { //printf("components=%d return 0!\n", components); return 0; } pos_num = pos_y * width * components + pos_x * components; pixel = 0; for (j=0; j<components; j++) { // RGB的值存入一个整数 pixel = (pixel << 8) | jpg_data[pos_num+j]; } samp = pixel; // 24位真彩色,2的24次方,每一个位代表一种颜色,需要2^24/8=2MB空间 char *bitHash = (char *) malloc(2 * 1024 * 1024 * sizeof (char)); if (NULL == bitHash) { perror("malloc error"); return 0; } memset(bitHash, 0, 2 * 1024 * 1024 * sizeof (char)); // 遍历图片的每一个像素点 //register size_t mid_pos = unpack_size / 3 / 2 * 3; //register int k; for (i=0; i<unpack_size; i+=components) { pixel = 0; for (j=0; j<components; j++) { pixel = (pixel << 8) | jpg_data[i+j]; } if (pixel == samp) { count++; if (count > max_num) { printf("same color's pixel max than %u, Spam JPG!\n", max_num); free(bitHash); return 1; } } // 将这个数值所在的位置一 bitHash[pixel / 8] |= (1 << (pixel % 8)); /* 这个地方多比较一次,对于正常图片,能省6%=(1.282-1.202)/1.282的时间,但是对于垃圾图片,要多耗费40%的时间 * 上面数据是通过某些图片总结出来的,所以不留算了。 if (i == mid_pos) { colors = 0; for (k=0; k<2*1024*1024*8; k++) { if (bitHash[k / 8] & (1 << (k % 8))) colors++; if (colors > max_color) { printf("Ham JPG: colors more than %d!\n", max_color); free(bitHash); return 0; } } } */ } int pixel_num = unpack_size / 3; printf("same pixel count = %u(%f)\n", count, count / (float)pixel_num); colors = 0; for (i=0; i<2*1024*1024*8; i++) { if (bitHash[i / 8] & (1 << (i % 8))) { colors++; if (colors > max_color) { printf("Ham JPG: colors more than %d!\n", max_color); free(bitHash); return 0; } } } free(bitHash); printf("Spam JPG: color's count:%d\n", colors); return 2; } /** * @brief 统计与采样点相同的像素点个数和颜色数量 * @param[in] jpg_data 加压后的JPG图片数据,注意按位计算时必须用无符号类型 * @param[in] unpack_size 解压后图片数据长度 * @param[in] width JPG图片宽度 * @param[in] height JPG图片高度 * @param[in] components 图片每像素字节数 * @param[in] max_rate 像素点与采样点相同数量所占比例超过此值就认为是垃圾信的图片 * @param[in] max_color 颜色数量超过这个值就认为是正常信的图片 * @return 返回0:颜色数量超过max_color且相同颜色点数小于要求的值;返回1:相同颜色点数过多;返回2:颜色数太少 */ static int scan_jpg_samps_color(unsigned char *jpg_data, size_t unpack_size, int width, int height, int components, float max_rate, int max_color) { if (NULL == jpg_data) return 0; register int i, j, k = 0, m; register unsigned int pixel; register int colors = 0; register unsigned int max_num = max_rate * (unpack_size / 3); // 最多可以有多少个像素点相同 int samp[JPG_SAMP_PIXELS] = {-1, -1, -1, -1, -1}; int pos[JPG_SAMP_PIXELS][2] = {{0, 0}, {25, 25}, {50, 50}, {(width-1), 0}, {width/2, height/2}}; unsigned int count[JPG_SAMP_PIXELS] = {0}; //printf("max same pixel:%u\n", max_num); k = init_pixel_array(jpg_data, width, height, components, samp, pos, JPG_SAMP_PIXELS); //printf("k=%d samp[0] = %X samp[1] = %X samp[2]=%X samp[3] = %X samp[4] = %X\n", k, samp[0], samp[1], samp[2], samp[3], samp[4]); // 24位真彩色,2的24次方,每一个位代表一种颜色,需要2^24/8=2MB空间 char *bitHash = (char *) malloc(2 * 1024 * 1024 * sizeof (char)); if (NULL == bitHash) { perror("malloc error"); return 0; } memset(bitHash, 0, 2 * 1024 * 1024 * sizeof (char)); // 遍历图片的每一个像素点 //register size_t mid_pos = unpack_size / 3 / 2 * 3; //register int k; for (i=0; i<unpack_size; i+=components) { pixel = 0; for (j=0; j<components; j++) { pixel = (pixel << 8) | jpg_data[i+j]; } for (m=0; m<k; m++) { if (pixel == samp[m]) { count[m]++; if (count[m] > max_num) { printf("same color's pixel max than %u, Spam JPG!\n", max_num); free(bitHash); return 1; } } } // 将这个数值所在的位置一 bitHash[pixel / 8] |= (1 << (pixel % 8)); /* 这个地方多比较一次,对于正常图片,能省6%=(1.282-1.202)/1.282的时间,但是对于垃圾图片,要多耗费40%的时间 * 上面数据是通过某些图片总结出来的,所以不留算了。 if (i == mid_pos) { colors = 0; for (k=0; k<2*1024*1024*8; k++) { if (bitHash[k / 8] & (1 << (k % 8))) colors++; if (colors > max_color) { printf("Ham JPG: colors more than %d!\n", max_color); free(bitHash); return 0; } } } */ } int pixel_num = unpack_size / 3; for (m=0; m<k; m++) printf("same pixel count = %u(%f)\n", count[m], count[m] / (float)pixel_num); colors = 0; for (i=0; i<2*1024*1024*8; i++) { if (bitHash[i / 8] & (1 << (i % 8))) { colors++; if (colors > max_color) { printf("Ham JPG: colors more than %d!\n", max_color); free(bitHash); return 0; } } } free(bitHash); printf("Spam JPG: color's count:%d\n", colors); return 2; } int main(int argc, char **argv) { TEXTUREIMAGE image; if (0 == LoadBmp( "test.bmp", &image)) { printf("加载图片成功\n"); printf("==============================================\n"); scan_jpg_color(image.data, image.unpack_size, image.width, image.height, image.components, 40000); printf("==============================================\n"); scan_jpg_samps(image.data, image.unpack_size, image.width, image.height, image.components, 0.1); printf("==============================================\n"); scan_jpg_samp_color(image.data, image.unpack_size, image.width, image.height, image.components, 0.1, 40000); printf("==============================================\n"); scan_jpg_samps_color(image.data, image.unpack_size, image.width, image.height, image.components, 0.1, 40000); printf("==============================================\n"); free(image.data); } else { printf("加载图片失败\n"); } return 0; }