RGB和YUV420旋转90/180/270度

1. 屏幕像素排列

像素点在屏幕上是按行和列来排列的,我们看到的屏幕的像素坐标系是这样的:

rotate.png

接下的旋转也会根据行列按屏幕像素坐标系来进行。本文以顺时针旋转4:3 的RGB图像为例。假设,有存储原RGB图像像素点的数组src, 存储旋转后的像素点的数组dst,两个数组长度一致。

2. 原图

按照屏幕的像素坐标系,坐标(y, x ) 的像素点即为第y行,第x列的像素点。进行旋转时可以根据原坐标(y, x )计算出新坐标(y', x'),将所有像素点的坐标逐个映射就可以得到旋转后的图像。

rotate0.png

3. 旋转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
感谢原作者的辛勤付出。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,504评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,434评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,089评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,378评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,472评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,506评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,519评论 3 413
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,292评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,738评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,022评论 2 329
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,194评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,873评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,536评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,162评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,413评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,075评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,080评论 2 352