1. 屏幕像素排列
像素点在屏幕上是按行和列来排列的,我们看到的屏幕的像素坐标系是这样的:
rotate.png接下的旋转也会根据行列按屏幕像素坐标系来进行。本文以顺时针旋转4:3 的RGB图像为例。假设,有存储原RGB图像像素点的数组src, 存储旋转后的像素点的数组dst,两个数组长度一致。
2. 原图
按照屏幕的像素坐标系,坐标(y, x ) 的像素点即为第y行,第x列的像素点。进行旋转时可以根据原坐标(y, x )计算出新坐标(y', x'),将所有像素点的坐标逐个映射就可以得到旋转后的图像。
rotate0.png3. 旋转90度
rotate90.png对比原图和上图的像素排列可知,像素点的原坐标为(y, x),图像的height为3,width为4。旋转90度之后,新坐标为(x, height - y - 1),并且图像宽高也互换了,height为4,width为3。
因此,旋转90度就是将像素点从 src[y * width + x] 映射到 dst[x* height + height - y - 1]。
4. 旋转180度
rotate180.png对比原图和上图的像素排列可知,像素点的原坐标为(y, x),图像的height为3,width为4。旋转180度之后,新坐标为(height - y - 1, width - x - 1),图像宽高不变。
因此,旋转180度就是将像素点从 src[y * width + x] 映射到 dst[ (height - y - 1) * width + width - x - 1]。
5. 旋转270度
270.png对比原图和上图的像素排列可知,像素点的原坐标为(y, x),图像的height为3,width为4。旋转270度之后,新坐标为(width - x - 1, y),并且图像宽高也互换了,height为4,width为3。
因此,旋转270度就是将像素点从 src[y * width + x] 映射到 dst[ (width - x - 1) * height + y]。
6. RGB及RGBA旋转
RGB和RGBA的旋转原理是一样的,只不过RGB为3个字节(24位),RGBA为4个字节(32位),旋转时需要使用不同的步长。代码实现:
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旋转
YUV420分为YUV420P和YUV420SP。YUV420P每个像素的Y、U、V通道分别连续存储,根据UV通道顺序不同又分为I420和YV12。YUV420SP每个像素的Y通道连续存储,UV通道交替存储,根据UV通道顺序不同又分为NV12和NV21。如果你对YUV420不是很了解,请戳:YUV_420_888介绍及YUV420转RGBA
7.1 YUV420P旋转
YUV420P的Y、U、V通道按4:1:1分别存储在不同矩阵中,并且Y的数量和像素点数量相同。因此我们只要参考RGB的旋转,先旋转Y,再旋转U、V就能完成YUV420P的旋转。由于旋转时我们并不关心U和V顺序,所以I420和YV12旋转可以用同一种方法。
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;
}
}
需要注意的是,如果是旋转90度或者270度,由于旋转后宽和高互换了,旋转后转换RGB时yRowStride和uvRowStride也要相应的由 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旋转
与YUV420P一样,YUV420SP的旋转,也是先旋转Y,再旋转U、V。旋转时也不关心U和V顺序,NV12和NV21可以用同一种方法。
区别在于YUV420SP的UV通道是交替存储的,所以我们像RGB旋转时一样,设置一个步长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;
}
}
同样需要注意的是,如果是旋转90度或者270度,旋转后宽和高互换了,转换RGB时需要使用旋转后的宽高。
测试效果:
测试效果.png本文中的代码已经上传到github:https://github.com/qiuxintai/YUV420Converter
8. 本文参考
https://www.jianshu.com/p/7e602dea3ca1
感谢原作者的辛勤付出。
网友评论