图像旋转算法与实现

图像旋转是指图像以某一点为中心旋转一定的角度,形成一幅新的图像的过程。当然这个点通常就是图像的中心。既然是按照中心旋转,自然会有这样一个属性:旋转前和旋转后的点离中心的位置不变.

根据这个属性,我们可以得到旋转后的点的坐标与原坐标的对应关系。由于原图像的坐标是以左上角为原点的,所以我们先把坐标转换为以图像中心为原点。假设原图像的宽为w,高为h,(x0,y0)为原坐标内的一点,转换坐标后的点为(x1,y1)。那么不难得到:

x1 = x0 - w/2; y1 = -y0 + h/2;

在新的坐标系下,假设点(x0,y0)距离原点的距离为r,点与原点之间的连线与x轴的夹角为b,旋转的角度为a,旋转后的点为(x1,y1), 如下图所示。

image

那么有以下结论:

x0=rcosb;y0=rsinb

x1 = rcos(b-a) = rcosbcosa+rsinbsina=x0cosa+y0sina;

y1=rsin(b-a)=rsinbcosa-rcosbsina=-x0sina+y0cosa;

得到了转换后的坐标,我们只需要把这些坐标再转换为原坐标系即可。这里还有一点要注意,旋转后的图像的长和宽会发生变化,因此要计算新图像的长和宽。

#include "stdafx.h"
#include <stdio.h>
#include <string>
#include <math.h>
#include <windows.h>
 using namespace std;

 #define PI 3.1415926535
//角度到弧度转化
#define RADIAN(angle) ((angle)*PI/180.0)

void Rotation(const string& srcFile,const string& desFile,int angle)
{
    BITMAPFILEHEADER bmfHeader;
    BITMAPINFOHEADER bmiHeader;
    
    FILE *pFile;
    if ((pFile = fopen(srcFile.c_str(),"rb")) == NULL)
    {
        printf("open bmp file error.");
        exit(-1);
    }
    //读取文件和Bitmap头信息
    fseek(pFile,0,SEEK_SET);
    fread(&bmfHeader,sizeof(BITMAPFILEHEADER),1,pFile);
    fread(&bmiHeader,sizeof(BITMAPINFOHEADER),1,pFile);
    //先不支持小于16位的位图
    int bitCount = bmiHeader.biBitCount;
    if (bitCount < 16)
    {        
        exit(-1);
    }
    int srcW = bmiHeader.biWidth;
    int srcH = bmiHeader.biHeight;
    //原图像每一行去除偏移量的字节数
    int lineSize = bitCount * srcW / 8;
    //偏移量,windows系统要求每个扫描行按四字节对齐
    int alignBytes = ((bmiHeader.biWidth * bitCount + 31) & ~31) / 8L
        - bmiHeader.biWidth * bitCount / 8L;
    //原图像缓存
    int srcBufSize = lineSize * srcH;
    BYTE* srcBuf = new BYTE[srcBufSize];
    int i,j;
    //读取文件中数据
    for (i = 0; i < srcH; i++)
    {        
        fread(&srcBuf[lineSize * i],lineSize,1,pFile);
        fseek(pFile,alignBytes,SEEK_CUR);
    }
    //以图像中心为原点左上角,右上角,左下角和右下角的坐标,用于计算旋转后的图像的宽和高
    POINT pLT,pRT,pLB,pRB;
    pLT.x = -srcW/2;pLT.y = srcH/2;
    pRT.x = srcW/2;pRT.y = srcH/2;
    pLB.x = -srcW/2;pLB.y = -srcH/2;
    pRB.x = srcW/2; pRB.y = -srcH/2;
    //旋转之后的坐标
    POINT pLTN,pRTN,pLBN,pRBN;
    double sina = sin(RADIAN(angle));
    double cosa = cos(RADIAN(angle));
    pLTN.x = pLT.x*cosa + pLT.y*sina;    
    pLTN.y = -pLT.x*sina + pLT.y*cosa;
    pRTN.x = pRT.x*cosa + pRT.y*sina;
    pRTN.y = -pRT.x*sina + pRT.y*cosa;
    pLBN.x = pLB.x*cosa + pLB.y*sina;
    pLBN.y = -pLB.x*sina + pLB.y*cosa;
    pRBN.x = pRB.x*cosa + pRB.y*sina;
    pRBN.y = -pRB.x*sina + pRB.y*cosa;
    //旋转后图像宽和高
    int desWidth = max(abs(pRBN.x - pLTN.x),abs(pRTN.x - pLBN.x));
    int desHeight = max(abs(pRBN.y - pLTN.y),abs(pRTN.y - pLBN.y));
    //分配旋转后图像的缓存
    int desBufSize = ((desWidth * bitCount + 31) / 32) * 4 * desHeight;
    BYTE *desBuf = new BYTE[desBufSize];
    //将所有像素都预置为白色
    memset(desBuf,255,desBufSize);
    //新图像每一行字节数,带有偏移量
    int desLineSize = ((desWidth * bitCount + 31) / 32) * 4;        
    //通过新图像的坐标,计算对应的原图像的坐标
    for (i = 0; i < desHeight; i++)
    {        
        for (j = 0; j < desWidth; j++)
        {
            //转换到以图像为中心的坐标系,并进行逆旋转
            int tX = (j - desWidth / 2)*cos(RADIAN(360 - angle)) + (-i + desHeight / 2)*sin(RADIAN(360 - angle));
            int tY = -(j - desWidth / 2)*sin(RADIAN(360 - angle)) + (-i + desHeight / 2)*cos(RADIAN(360 - angle));
            //如果这个坐标不在原图像内,则不赋值
            if (tX > srcW / 2 || tX < -srcW / 2 || tY > srcH / 2 || tY < -srcH / 2)
            {
                continue;
            }
            //再转换到原坐标系下
            int tXN = tX + srcW / 2; int tYN = abs(tY - srcH / 2);
            //值拷贝
            memcpy(&desBuf[i * desLineSize + j * bitCount / 8],&srcBuf[tYN * lineSize + tXN * bitCount / 8],3);            
        }
    }

    //创建目标文件
    HFILE hfile = _lcreat(desFile.c_str(),0);    
    //文件头信息
    BITMAPFILEHEADER nbmfHeader;    
    nbmfHeader.bfType = 0x4D42;
    nbmfHeader.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER)
        + desWidth * desHeight * bitCount / 8;
    nbmfHeader.bfReserved1 = 0;
    nbmfHeader.bfReserved2 = 0;
    nbmfHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
    //Bitmap头信息
    BITMAPINFOHEADER   bmi; 
    bmi.biSize=sizeof(BITMAPINFOHEADER); 
    bmi.biWidth=desWidth; 
    bmi.biHeight=desHeight; 
    bmi.biPlanes=1; 
    bmi.biBitCount=bitCount; 
    bmi.biCompression=BI_RGB; 
    bmi.biSizeImage=0; 
    bmi.biXPelsPerMeter=0; 
    bmi.biYPelsPerMeter=0; 
    bmi.biClrUsed=0; 
    bmi.biClrImportant=0; 
    
    //写入文件头信息
    _lwrite(hfile,(LPCSTR)&nbmfHeader,sizeof(BITMAPFILEHEADER));
    //写入Bitmap头信息
    _lwrite(hfile,(LPCSTR)&bmi,sizeof(BITMAPINFOHEADER));
    //写入图像数据
    _lwrite(hfile,(LPCSTR)desBuf,desBufSize);
    _lclose(hfile);
}

int main(int argc, char* argv[])
{
    FILE *pFile;
    if ((pFile = fopen("e://t.bmp","rb")) == NULL)
    {
        printf("open bmp file error.");
        return -1;
    }
    string srcFile("e://t.bmp");
    string desFile("e://Rotation.bmp");
    Rotation(srcFile,desFile,150);
    system("pause");
    return 0;
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,670评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,928评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,926评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,238评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,112评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,138评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,545评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,232评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,496评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,596评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,369评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,226评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,600评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,906评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,185评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,516评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,721评论 2 335

推荐阅读更多精彩内容

  • Android变形矩阵——Matrix 对于图像的图形变换,Android系统是通过矩阵来进行处理的,每个像素点都...
    JC_Hou阅读 585评论 0 2
  • 图像的几何变换又称为图像空间变换,它将一幅图像中的坐标位置映射到另一幅图像中的新生位置。几何变换不改变图像的像素值...
    还浴月阅读 2,915评论 0 1
  • 图像色彩处理 Android对图片的处理,通常用到的数据结构就是位图—Bitmap,它包含了一张图片的所有数据。整...
    SheldonChen4215阅读 4,185评论 0 6
  • PHP中GD库的使用 GD简介 PHP 不仅限于只产生 HTML 的输出,还可以创建及操作多种不同格式的图像文件。...
    dptms阅读 1,000评论 0 2
  • 前言 在开发中我们大多都会使用Masonry来做约束,因为这个的确方便。但是呢最近在做一个自己的项目的时候偶然发现...
    KODIE阅读 2,558评论 0 1