RGB和YUV420旋轉(zhuǎn)90/180/270度

1. 屏幕像素排列

像素點(diǎn)在屏幕上是按行和列來排列的邦危,我們看到的屏幕的像素坐標(biāo)系是這樣的:

rotate.png

接下的旋轉(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)后的圖像箩帚。

rotate0.png

3. 旋轉(zhuǎn)90度

rotate90.png

對比原圖和上圖的像素排列可知真友,像素點(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度

rotate180.png

對比原圖和上圖的像素排列可知丽柿,像素點(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度

270.png

對比原圖和上圖的像素排列可知,像素點(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)后的寬高走诞。

測試效果:
測試效果.png

本文中的代碼已經(jīng)上傳到github:https://github.com/qiuxintai/YUV420Converter

8. 本文參考

http://www.reibang.com/p/7e602dea3ca1
感謝原作者的辛勤付出。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末低剔,一起剝皮案震驚了整個濱河市速梗,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌襟齿,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件枕赵,死亡現(xiàn)場離奇詭異猜欺,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)拷窜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進(jìn)店門开皿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人篮昧,你說我怎么就攤上這事赋荆。” “怎么了懊昨?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵窄潭,是天一觀的道長。 經(jīng)常有香客問我酵颁,道長嫉你,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任躏惋,我火速辦了婚禮幽污,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘簿姨。我一直安慰自己距误,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著准潭,像睡著了一般攘乒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上惋鹅,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天则酝,我揣著相機(jī)與錄音,去河邊找鬼闰集。 笑死沽讹,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的武鲁。 我是一名探鬼主播爽雄,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼沐鼠!你這毒婦竟也來了挚瘟?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤饲梭,失蹤者是張志新(化名)和其女友劉穎乘盖,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體憔涉,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡订框,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了兜叨。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片穿扳。...
    茶點(diǎn)故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖国旷,靈堂內(nèi)的尸體忽然破棺而出矛物,到底是詐尸還是另有隱情,我是刑警寧澤跪但,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布履羞,位于F島的核電站,受9級特大地震影響特漩,放射性物質(zhì)發(fā)生泄漏吧雹。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一涂身、第九天 我趴在偏房一處隱蔽的房頂上張望雄卷。 院中可真熱鬧,春花似錦蛤售、人聲如沸丁鹉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽揣钦。三九已至雳灾,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間冯凹,已是汗流浹背谎亩。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留宇姚,地道東北人匈庭。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像浑劳,于是被迫代替她去往敵國和親阱持。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評論 2 354