1. 屏幕像素排列
像素點(diǎn)在屏幕上是按行和列來排列的邦危,我們看到的屏幕的像素坐標(biāo)系是這樣的:
接下的旋轉(zhuǎn)也會根據(jù)行列按屏幕像素坐標(biāo)系來進(jìn)行烁登。本文以順時針旋轉(zhuǎn)4:3 的RGB圖像為例。假設(shè)件相,有存儲原RGB圖像像素點(diǎn)的數(shù)組src, 存儲旋轉(zhuǎn)后的像素點(diǎn)的數(shù)組dst衰倦,兩個數(shù)組長度一致发侵。
2. 原圖
按照屏幕的像素坐標(biāo)系,坐標(biāo)(y, x ) 的像素點(diǎn)即為第y行枫浙,第x列的像素點(diǎn)刨肃。進(jìn)行旋轉(zhuǎn)時可以根據(jù)原坐標(biāo)(y, x )計算出新坐標(biāo)(y', x'),將所有像素點(diǎn)的坐標(biāo)逐個映射就可以得到旋轉(zhuǎn)后的圖像箩帚。
3. 旋轉(zhuǎn)90度
對比原圖和上圖的像素排列可知真友,像素點(diǎn)的原坐標(biāo)為(y, x),圖像的height為3紧帕,width為4盔然。旋轉(zhuǎn)90度之后,新坐標(biāo)為(x, height - y - 1)是嗜,并且圖像寬高也互換了愈案,height為4,width為3鹅搪。
因此站绪,旋轉(zhuǎn)90度就是將像素點(diǎn)從 src[y * width + x] 映射到 dst[x* height + height - y - 1]。
4. 旋轉(zhuǎn)180度
對比原圖和上圖的像素排列可知丽柿,像素點(diǎn)的原坐標(biāo)為(y, x)恢准,圖像的height為3,width為4甫题。旋轉(zhuǎn)180度之后馁筐,新坐標(biāo)為(height - y - 1, width - x - 1),圖像寬高不變幔睬。
因此眯漩,旋轉(zhuǎn)180度就是將像素點(diǎn)從 src[y * width + x] 映射到 dst[ (height - y - 1) * width + width - x - 1]。
5. 旋轉(zhuǎn)270度
對比原圖和上圖的像素排列可知,像素點(diǎn)的原坐標(biāo)為(y, x)赦抖,圖像的height為3舱卡,width為4。旋轉(zhuǎn)270度之后队萤,新坐標(biāo)為(width - x - 1, y)轮锥,并且圖像寬高也互換了,height為4要尔,width為3舍杜。
因此,旋轉(zhuǎn)270度就是將像素點(diǎn)從 src[y * width + x] 映射到 dst[ (width - x - 1) * height + y]赵辕。
6. RGB及RGBA旋轉(zhuǎn)
RGB和RGBA的旋轉(zhuǎn)原理是一樣的既绩,只不過RGB為3個字節(jié)(24位),RGBA為4個字節(jié)(32位)还惠,旋轉(zhuǎn)時需要使用不同的步長饲握。代碼實現(xiàn):
static inline void
rotateRGB90(unsigned char *src, unsigned char *dst, int width, int height, int bpp) {
int dstIndex = 0;
int srcIndex = 0;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
//(y, x) -> (x, height - y - 1)
//y * width + x, -> x* height + height - y - 1
dstIndex = (x * height + height - y - 1) * bpp;
for (int i = 0; i < bpp; i++) {
dst[dstIndex + i] = src[srcIndex++];
}
}
}
}
static inline void rotateRGB90(int *src, int *dst, int width, int height) {
int dstIndex = 0;
int srcIndex = 0;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
//(y, x) -> (x, height - y - 1)
//y * width + x, -> x* height + height - y - 1
dstIndex = x * height + height - y - 1;
dst[dstIndex] = src[srcIndex++];
}
}
}
static inline void
rotateRGB180(unsigned char *src, unsigned char *dst, int width, int height, int bpp) {
int dstIndex = 0;
int srcIndex = 0;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
//(y, x) -> (height - y - 1, width - x - 1)
//y * width + x, -> (height - y - 1) * width + width - x - 1
dstIndex = ((height - y - 1) * width + width - x - 1) * bpp;
for (int i = 0; i < bpp; i++) {
dst[dstIndex + i] = src[srcIndex++];
}
}
}
}
static inline void rotateRGB180(int *src, int *dst, int width, int height) {
int dstIndex = 0;
int srcIndex = 0;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
//(y, x) -> (height - y - 1, width - x - 1)
//y * width + x, -> (height - y - 1) * width + width - x - 1
dstIndex = (height - y - 1) * width + width - x - 1;
dst[dstIndex] = src[srcIndex++];
}
}
}
static inline void
rotateRGB270(unsigned char *src, unsigned char *dst, int width, int height, int bpp) {
int dstIndex = 0;
int srcIndex = 0;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
//(y, x) -> (width - x - 1, y)
//y * width + x, -> (width - x - 1) * height + y
dstIndex = ((width - x - 1) * height + y) * bpp;
for (int i = 0; i < bpp; i++) {
dst[dstIndex + i] = src[srcIndex++];
}
}
}
}
static inline void rotateRGB270(int *src, int *dst, int width, int height) {
int dstIndex = 0;
int srcIndex = 0;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
//(y, x) -> (width - x - 1, y)
//y * width + x, -> (width - x - 1) * height + y
dstIndex = (width - x - 1) * height + y;
dst[dstIndex] = src[srcIndex++];
}
}
}
void rotateRGB(unsigned char *src, unsigned char *dst, int width, int height, float degree) {
if (degree == 90.0f) {
rotateRGB90(src, dst, width, height, 3);
} else if (degree == 180.0f) {
rotateRGB180(src, dst, width, height, 3);
} else if (degree == 270.0f) {
rotateRGB270(src, dst, width, height, 3);
} else {
return;
}
}
void rotateRGBA(unsigned char *src, unsigned char *dst, int width, int height, float degree) {
if (degree == 90.0f) {
rotateRGB90(src, dst, width, height, 4);
} else if (degree == 180.0f) {
rotateRGB180(src, dst, width, height, 4);
} else if (degree == 270.0f) {
rotateRGB270(src, dst, width, height, 4);
} else {
return;
}
}
void rotateRGBAInt(int *src, int *dst, int width, int height, float degree) {
if (degree == 90.0f) {
rotateRGB90(src, dst, width, height);
} else if (degree == 180.0f) {
rotateRGB180(src, dst, width, height);
} else if (degree == 270.0f) {
rotateRGB270(src, dst, width, height);
} else {
return;
}
}
7. YUV420旋轉(zhuǎn)
YUV420分為YUV420P和YUV420SP。YUV420P每個像素的Y蚕键、U救欧、V通道分別連續(xù)存儲,根據(jù)UV通道順序不同又分為I420和YV12锣光。YUV420SP每個像素的Y通道連續(xù)存儲笆怠,UV通道交替存儲,根據(jù)UV通道順序不同又分為NV12和NV21誊爹。如果你對YUV420不是很了解蹬刷,請戳:YUV_420_888介紹及YUV420轉(zhuǎn)RGBA
7.1 YUV420P旋轉(zhuǎn)
YUV420P的Y、U替废、V通道按4:1:1分別存儲在不同矩陣中箍铭,并且Y的數(shù)量和像素點(diǎn)數(shù)量相同。因此我們只要參考RGB的旋轉(zhuǎn)椎镣,先旋轉(zhuǎn)Y诈火,再旋轉(zhuǎn)U、V就能完成YUV420P的旋轉(zhuǎn)状答。由于旋轉(zhuǎn)時我們并不關(guān)心U和V順序冷守,所以I420和YV12旋轉(zhuǎn)可以用同一種方法。
static inline void
rotateYUV420P90(unsigned char *srcY, unsigned char *srcU, unsigned char *srcV,
unsigned char *dstY, unsigned char *dstU, unsigned char *dstV,
int width, int height) {
//rotate y
int dstIndex = 0;
int srcIndex = 0;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
dstIndex = x * height + height - y - 1;
dstY[dstIndex] = srcY[srcIndex++];
}
}
//rotate uv
int uvHeight = height / 2;
int uvWidth = width / 2;
int dstUVIndex = 0;
int srcUVIndex = 0;
for (int y = 0; y < uvHeight; y++) {
for (int x = 0; x < uvWidth; x++) {
dstUVIndex = x * uvHeight + uvHeight - y - 1;
dstU[dstUVIndex] = srcU[srcUVIndex];
dstV[dstUVIndex] = srcV[srcUVIndex];
srcUVIndex++;
}
}
}
static inline void
rotateYUV420P180(unsigned char *srcY, unsigned char *srcU, unsigned char *srcV,
unsigned char *dstY, unsigned char *dstU, unsigned char *dstV,
int width, int height) {
//rotate y
int dstIndex = 0;
int srcIndex = 0;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
dstIndex = (height - y - 1) * width + width - x - 1;
dstY[dstIndex] = srcY[srcIndex++];
}
}
//rotate uv
int uvHeight = height / 2;
int uvWidth = width / 2;
int dstUVIndex = 0;
int srcUVIndex = 0;
for (int y = 0; y < uvHeight; y++) {
for (int x = 0; x < uvWidth; x++) {
dstUVIndex = (uvHeight - y - 1) * uvWidth + uvWidth - x - 1;
dstU[dstUVIndex] = srcU[srcUVIndex];
dstV[dstUVIndex] = srcV[srcUVIndex];
srcUVIndex++;
}
}
}
static inline void
rotateYUV420P270(unsigned char *srcY, unsigned char *srcU, unsigned char *srcV,
unsigned char *dstY, unsigned char *dstU, unsigned char *dstV,
int width, int height) {
//rotate y
int dstIndex = 0;
int srcIndex = 0;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
dstIndex = (width - x - 1) * height + y;
dstY[dstIndex] = srcY[srcIndex++];
}
}
//rotate uv
int uvHeight = height / 2;
int uvWidth = width / 2;
int dstUVIndex = 0;
int srcUVIndex = 0;
for (int y = 0; y < uvHeight; y++) {
for (int x = 0; x < uvWidth; x++) {
dstUVIndex = (uvWidth - x - 1) * uvHeight + y;
dstU[dstUVIndex] = srcU[srcUVIndex];
dstV[dstUVIndex] = srcV[srcUVIndex];
srcUVIndex++;
}
}
}
void
rotateYUV420P(unsigned char *src, unsigned char *dst, int width, int height, float degree) {
unsigned char *pSrcY = src;
unsigned char *pSrcU = src + width * height;
unsigned char *pSrcV = src + width * height / 4 * 5;
unsigned char *pDstY = dst;
unsigned char *pDstU = dst + width * height;
unsigned char *pDstV = dst + width * height / 4 * 5;
if (degree == 90.0f) {
rotateYUV420P90(pSrcY, pSrcU, pSrcV, pDstY, pDstU, pDstV, width, height);
} else if (degree == 180.0f) {
rotateYUV420P180(pSrcY, pSrcU, pSrcV, pDstY, pDstU, pDstV, width, height);
} else if (degree == 270.0f) {
rotateYUV420P270(pSrcY, pSrcU, pSrcV, pDstY, pDstU, pDstV, width, height);
} else {
return;
}
}
需要注意的是惊科,如果是旋轉(zhuǎn)90度或者270度拍摇,由于旋轉(zhuǎn)后寬和高互換了,旋轉(zhuǎn)后轉(zhuǎn)換RGB時yRowStride和uvRowStride也要相應(yīng)的由 width 和 width/2 改為 height 和 height/2馆截。
if (rotationDegree == 90.0f || rotationDegree == 270.0f) {
NativeUtils.I420ToRGBAInt(rotatedYUV420, rgba, height, width, height, height / 2, 1);
ImageUtils.RGBAToJPEG(context, rgba, height, width, parent, fileName);
} else if (rotationDegree == 180.0f) {
NativeUtils.I420ToRGBAInt(rotatedYUV420, rgba, width, height, width, width / 2, 1);
ImageUtils.RGBAToJPEG(context, rgba, width, height, parent, fileName);
} else {
NativeUtils.I420ToRGBAInt(yuv420, rgba, width, height, width, width / 2, 1);
ImageUtils.RGBAToJPEG(context, rgba, width, height, parent, fileName);
}
7.2 YUV420SP旋轉(zhuǎn)
與YUV420P一樣充活,YUV420SP的旋轉(zhuǎn)蜂莉,也是先旋轉(zhuǎn)Y,再旋轉(zhuǎn)U混卵、V映穗。旋轉(zhuǎn)時也不關(guān)心U和V順序,NV12和NV21可以用同一種方法幕随。
區(qū)別在于YUV420SP的UV通道是交替存儲的蚁滋,所以我們像RGB旋轉(zhuǎn)時一樣,設(shè)置一個步長2赘淮。
static inline void
rotateYUV420SP90(unsigned char *srcY, unsigned char *srcUV, unsigned char *dstY,
unsigned char *dstUV, int width, int height) {
//rotate y
int dstIndex = 0;
int srcIndex = 0;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
dstIndex = x * height + height - y - 1;
dstY[dstIndex] = srcY[srcIndex++];
}
}
//rotate uv
int uvHeight = height >> 1;
int uvWidth = width >> 1;
int dstUVIndex = 0;
int srcUVIndex = 0;
for (int y = 0; y < uvHeight; y++) {
for (int x = 0; x < uvWidth; x++) {
dstUVIndex = (x * uvHeight + uvHeight - y - 1) << 1;
dstUV[dstUVIndex] = srcUV[srcUVIndex++];
dstUV[dstUVIndex + 1] = srcUV[srcUVIndex++];
}
}
}
static inline void
rotateYUV420SP180(unsigned char *srcY, unsigned char *srcUV, unsigned char *dstY,
unsigned char *dstUV, int width, int height) {
//rotate y
int dstIndex = 0;
int srcIndex = 0;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
dstIndex = (height - y - 1) * width + width - x - 1;
dstY[dstIndex] = srcY[srcIndex++];
}
}
//rotate uv
int uvHeight = height >> 1;
int uvWidth = width >> 1;
int dstUVIndex = 0;
int srcUVIndex = 0;
for (int y = 0; y < uvHeight; y++) {
for (int x = 0; x < uvWidth; x++) {
dstUVIndex = ((uvHeight - y - 1) * uvWidth + uvWidth - x - 1) << 1;
dstUV[dstUVIndex] = srcUV[srcUVIndex++];
dstUV[dstUVIndex + 1] = srcUV[srcUVIndex++];
}
}
}
static inline void
rotateYUV420SP270(unsigned char *srcY, unsigned char *srcUV, unsigned char *dstY,
unsigned char *dstUV, int width, int height) {
//rotate y
int dstIndex = 0;
int srcIndex = 0;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
dstIndex = (width - x - 1) * height + y;
dstY[dstIndex] = srcY[srcIndex++];
}
}
//rotate uv
int uvHeight = height >> 1;
int uvWidth = width >> 1;
int dstUVIndex = 0;
int srcUVIndex = 0;
for (int y = 0; y < uvHeight; y++) {
for (int x = 0; x < uvWidth; x++) {
dstUVIndex = ((uvWidth - x - 1) * uvHeight + y) << 1;
dstUV[dstUVIndex] = srcUV[srcUVIndex++];
dstUV[dstUVIndex + 1] = srcUV[srcUVIndex++];
}
}
}
void
rotateYUV420SP(unsigned char *src, unsigned char *dst, int width, int height, float degree) {
unsigned char *pSrcY = src;
unsigned char *pSrcUV = src + width * height;
unsigned char *pDstY = dst;
unsigned char *pDstUV = dst + width * height;
if (degree == 90.0f) {
rotateYUV420SP90(pSrcY, pSrcUV, pDstY, pDstUV, width, height);
} else if (degree == 180.0f) {
rotateYUV420SP180(pSrcY, pSrcUV, pDstY, pDstUV, width, height);
} else if (degree == 270.0f) {
rotateYUV420SP270(pSrcY, pSrcUV, pDstY, pDstUV, width, height);
} else {
return;
}
}
同樣需要注意的是辕录,如果是旋轉(zhuǎn)90度或者270度,旋轉(zhuǎn)后寬和高互換了梢卸,轉(zhuǎn)換RGB時需要使用旋轉(zhuǎn)后的寬高走诞。
測試效果:
本文中的代碼已經(jīng)上傳到github:https://github.com/qiuxintai/YUV420Converter
8. 本文參考
http://www.reibang.com/p/7e602dea3ca1
感謝原作者的辛勤付出。