| /* |
| * Copyright (C) 2010 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #define LOG_NDEBUG 0 |
| #define LOG_TAG "YUVImage" |
| |
| #include <media/stagefright/YUVImage.h> |
| #include <ui/Rect.h> |
| #include <media/stagefright/MediaDebug.h> |
| |
| namespace android { |
| |
| YUVImage::YUVImage(YUVFormat yuvFormat, int32_t width, int32_t height) { |
| mYUVFormat = yuvFormat; |
| mWidth = width; |
| mHeight = height; |
| |
| size_t numberOfBytes = bufferSize(yuvFormat, width, height); |
| uint8_t *buffer = new uint8_t[numberOfBytes]; |
| mBuffer = buffer; |
| mOwnBuffer = true; |
| |
| initializeYUVPointers(); |
| } |
| |
| YUVImage::YUVImage(YUVFormat yuvFormat, int32_t width, int32_t height, uint8_t *buffer) { |
| mYUVFormat = yuvFormat; |
| mWidth = width; |
| mHeight = height; |
| mBuffer = buffer; |
| mOwnBuffer = false; |
| |
| initializeYUVPointers(); |
| } |
| |
| //static |
| size_t YUVImage::bufferSize(YUVFormat yuvFormat, int32_t width, int32_t height) { |
| int32_t numberOfPixels = width*height; |
| size_t numberOfBytes = 0; |
| if (yuvFormat == YUV420Planar || yuvFormat == YUV420SemiPlanar) { |
| // Y takes numberOfPixels bytes and U/V take numberOfPixels/4 bytes each. |
| numberOfBytes = (size_t)(numberOfPixels + (numberOfPixels >> 1)); |
| } else { |
| LOGE("Format not supported"); |
| } |
| return numberOfBytes; |
| } |
| |
| bool YUVImage::initializeYUVPointers() { |
| int32_t numberOfPixels = mWidth * mHeight; |
| |
| if (mYUVFormat == YUV420Planar) { |
| mYdata = (uint8_t *)mBuffer; |
| mUdata = mYdata + numberOfPixels; |
| mVdata = mUdata + (numberOfPixels >> 2); |
| } else if (mYUVFormat == YUV420SemiPlanar) { |
| // U and V channels are interleaved as VUVUVU. |
| // So V data starts at the end of Y channel and |
| // U data starts right after V's start. |
| mYdata = (uint8_t *)mBuffer; |
| mVdata = mYdata + numberOfPixels; |
| mUdata = mVdata + 1; |
| } else { |
| LOGE("Format not supported"); |
| return false; |
| } |
| return true; |
| } |
| |
| YUVImage::~YUVImage() { |
| if (mOwnBuffer) delete[] mBuffer; |
| } |
| |
| bool YUVImage::getOffsets(int32_t x, int32_t y, |
| int32_t *yOffset, int32_t *uOffset, int32_t *vOffset) const { |
| *yOffset = y*mWidth + x; |
| |
| int32_t uvOffset = (y >> 1) * (mWidth >> 1) + (x >> 1); |
| if (mYUVFormat == YUV420Planar) { |
| *uOffset = uvOffset; |
| *vOffset = uvOffset; |
| } else if (mYUVFormat == YUV420SemiPlanar) { |
| // Since U and V channels are interleaved, offsets need |
| // to be doubled. |
| *uOffset = 2*uvOffset; |
| *vOffset = 2*uvOffset; |
| } else { |
| LOGE("Format not supported"); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool YUVImage::getOffsetIncrementsPerDataRow( |
| int32_t *yDataOffsetIncrement, |
| int32_t *uDataOffsetIncrement, |
| int32_t *vDataOffsetIncrement) const { |
| *yDataOffsetIncrement = mWidth; |
| |
| int32_t uvDataOffsetIncrement = mWidth >> 1; |
| |
| if (mYUVFormat == YUV420Planar) { |
| *uDataOffsetIncrement = uvDataOffsetIncrement; |
| *vDataOffsetIncrement = uvDataOffsetIncrement; |
| } else if (mYUVFormat == YUV420SemiPlanar) { |
| // Since U and V channels are interleaved, offsets need |
| // to be doubled. |
| *uDataOffsetIncrement = 2*uvDataOffsetIncrement; |
| *vDataOffsetIncrement = 2*uvDataOffsetIncrement; |
| } else { |
| LOGE("Format not supported"); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| uint8_t* YUVImage::getYAddress(int32_t offset) const { |
| return mYdata + offset; |
| } |
| |
| uint8_t* YUVImage::getUAddress(int32_t offset) const { |
| return mUdata + offset; |
| } |
| |
| uint8_t* YUVImage::getVAddress(int32_t offset) const { |
| return mVdata + offset; |
| } |
| |
| bool YUVImage::getYUVAddresses(int32_t x, int32_t y, |
| uint8_t **yAddr, uint8_t **uAddr, uint8_t **vAddr) const { |
| int32_t yOffset; |
| int32_t uOffset; |
| int32_t vOffset; |
| if (!getOffsets(x, y, &yOffset, &uOffset, &vOffset)) return false; |
| |
| *yAddr = getYAddress(yOffset); |
| *uAddr = getUAddress(uOffset); |
| *vAddr = getVAddress(vOffset); |
| |
| return true; |
| } |
| |
| bool YUVImage::validPixel(int32_t x, int32_t y) const { |
| return (x >= 0 && x < mWidth && |
| y >= 0 && y < mHeight); |
| } |
| |
| bool YUVImage::getPixelValue(int32_t x, int32_t y, |
| uint8_t *yPtr, uint8_t *uPtr, uint8_t *vPtr) const { |
| CHECK(validPixel(x, y)); |
| |
| uint8_t *yAddr; |
| uint8_t *uAddr; |
| uint8_t *vAddr; |
| if (!getYUVAddresses(x, y, &yAddr, &uAddr, &vAddr)) return false; |
| |
| *yPtr = *yAddr; |
| *uPtr = *uAddr; |
| *vPtr = *vAddr; |
| |
| return true; |
| } |
| |
| bool YUVImage::setPixelValue(int32_t x, int32_t y, |
| uint8_t yValue, uint8_t uValue, uint8_t vValue) { |
| CHECK(validPixel(x, y)); |
| |
| uint8_t *yAddr; |
| uint8_t *uAddr; |
| uint8_t *vAddr; |
| if (!getYUVAddresses(x, y, &yAddr, &uAddr, &vAddr)) return false; |
| |
| *yAddr = yValue; |
| *uAddr = uValue; |
| *vAddr = vValue; |
| |
| return true; |
| } |
| |
| void YUVImage::fastCopyRectangle420Planar( |
| const Rect& srcRect, |
| int32_t destStartX, int32_t destStartY, |
| const YUVImage &srcImage, YUVImage &destImage) { |
| CHECK(srcImage.mYUVFormat == YUV420Planar); |
| CHECK(destImage.mYUVFormat == YUV420Planar); |
| |
| int32_t srcStartX = srcRect.left; |
| int32_t srcStartY = srcRect.top; |
| int32_t width = srcRect.width(); |
| int32_t height = srcRect.height(); |
| |
| // Get source and destination start addresses |
| uint8_t *ySrcAddrBase; |
| uint8_t *uSrcAddrBase; |
| uint8_t *vSrcAddrBase; |
| srcImage.getYUVAddresses(srcStartX, srcStartY, |
| &ySrcAddrBase, &uSrcAddrBase, &vSrcAddrBase); |
| |
| uint8_t *yDestAddrBase; |
| uint8_t *uDestAddrBase; |
| uint8_t *vDestAddrBase; |
| destImage.getYUVAddresses(destStartX, destStartY, |
| &yDestAddrBase, &uDestAddrBase, &vDestAddrBase); |
| |
| // Get source and destination offset increments incurred in going |
| // from one data row to next. |
| int32_t ySrcOffsetIncrement; |
| int32_t uSrcOffsetIncrement; |
| int32_t vSrcOffsetIncrement; |
| srcImage.getOffsetIncrementsPerDataRow( |
| &ySrcOffsetIncrement, &uSrcOffsetIncrement, &vSrcOffsetIncrement); |
| |
| int32_t yDestOffsetIncrement; |
| int32_t uDestOffsetIncrement; |
| int32_t vDestOffsetIncrement; |
| destImage.getOffsetIncrementsPerDataRow( |
| &yDestOffsetIncrement, &uDestOffsetIncrement, &vDestOffsetIncrement); |
| |
| // Copy Y |
| { |
| size_t numberOfYBytesPerRow = (size_t) width; |
| uint8_t *ySrcAddr = ySrcAddrBase; |
| uint8_t *yDestAddr = yDestAddrBase; |
| for (int32_t offsetY = 0; offsetY < height; ++offsetY) { |
| memcpy(yDestAddr, ySrcAddr, numberOfYBytesPerRow); |
| |
| ySrcAddr += ySrcOffsetIncrement; |
| yDestAddr += yDestOffsetIncrement; |
| } |
| } |
| |
| // Copy U |
| { |
| size_t numberOfUBytesPerRow = (size_t) (width >> 1); |
| uint8_t *uSrcAddr = uSrcAddrBase; |
| uint8_t *uDestAddr = uDestAddrBase; |
| // Every other row has an entry for U/V channel values. Hence only |
| // go half the height. |
| for (int32_t offsetY = 0; offsetY < (height >> 1); ++offsetY) { |
| memcpy(uDestAddr, uSrcAddr, numberOfUBytesPerRow); |
| |
| uSrcAddr += uSrcOffsetIncrement; |
| uDestAddr += uDestOffsetIncrement; |
| } |
| } |
| |
| // Copy V |
| { |
| size_t numberOfVBytesPerRow = (size_t) (width >> 1); |
| uint8_t *vSrcAddr = vSrcAddrBase; |
| uint8_t *vDestAddr = vDestAddrBase; |
| // Every other pixel row has a U/V data row. Hence only go half the height. |
| for (int32_t offsetY = 0; offsetY < (height >> 1); ++offsetY) { |
| memcpy(vDestAddr, vSrcAddr, numberOfVBytesPerRow); |
| |
| vSrcAddr += vSrcOffsetIncrement; |
| vDestAddr += vDestOffsetIncrement; |
| } |
| } |
| } |
| |
| void YUVImage::fastCopyRectangle420SemiPlanar( |
| const Rect& srcRect, |
| int32_t destStartX, int32_t destStartY, |
| const YUVImage &srcImage, YUVImage &destImage) { |
| CHECK(srcImage.mYUVFormat == YUV420SemiPlanar); |
| CHECK(destImage.mYUVFormat == YUV420SemiPlanar); |
| |
| int32_t srcStartX = srcRect.left; |
| int32_t srcStartY = srcRect.top; |
| int32_t width = srcRect.width(); |
| int32_t height = srcRect.height(); |
| |
| // Get source and destination start addresses |
| uint8_t *ySrcAddrBase; |
| uint8_t *uSrcAddrBase; |
| uint8_t *vSrcAddrBase; |
| srcImage.getYUVAddresses(srcStartX, srcStartY, |
| &ySrcAddrBase, &uSrcAddrBase, &vSrcAddrBase); |
| |
| uint8_t *yDestAddrBase; |
| uint8_t *uDestAddrBase; |
| uint8_t *vDestAddrBase; |
| destImage.getYUVAddresses(destStartX, destStartY, |
| &yDestAddrBase, &uDestAddrBase, &vDestAddrBase); |
| |
| // Get source and destination offset increments incurred in going |
| // from one data row to next. |
| int32_t ySrcOffsetIncrement; |
| int32_t uSrcOffsetIncrement; |
| int32_t vSrcOffsetIncrement; |
| srcImage.getOffsetIncrementsPerDataRow( |
| &ySrcOffsetIncrement, &uSrcOffsetIncrement, &vSrcOffsetIncrement); |
| |
| int32_t yDestOffsetIncrement; |
| int32_t uDestOffsetIncrement; |
| int32_t vDestOffsetIncrement; |
| destImage.getOffsetIncrementsPerDataRow( |
| &yDestOffsetIncrement, &uDestOffsetIncrement, &vDestOffsetIncrement); |
| |
| // Copy Y |
| { |
| size_t numberOfYBytesPerRow = (size_t) width; |
| uint8_t *ySrcAddr = ySrcAddrBase; |
| uint8_t *yDestAddr = yDestAddrBase; |
| for (int32_t offsetY = 0; offsetY < height; ++offsetY) { |
| memcpy(yDestAddr, ySrcAddr, numberOfYBytesPerRow); |
| |
| ySrcAddr = ySrcAddr + ySrcOffsetIncrement; |
| yDestAddr = yDestAddr + yDestOffsetIncrement; |
| } |
| } |
| |
| // Copy UV |
| { |
| // UV are interleaved. So number of UV bytes per row is 2*(width/2). |
| size_t numberOfUVBytesPerRow = (size_t) width; |
| uint8_t *vSrcAddr = vSrcAddrBase; |
| uint8_t *vDestAddr = vDestAddrBase; |
| // Every other pixel row has a U/V data row. Hence only go half the height. |
| for (int32_t offsetY = 0; offsetY < (height >> 1); ++offsetY) { |
| memcpy(vDestAddr, vSrcAddr, numberOfUVBytesPerRow); |
| |
| vSrcAddr += vSrcOffsetIncrement; |
| vDestAddr += vDestOffsetIncrement; |
| } |
| } |
| } |
| |
| // static |
| bool YUVImage::fastCopyRectangle( |
| const Rect& srcRect, |
| int32_t destStartX, int32_t destStartY, |
| const YUVImage &srcImage, YUVImage &destImage) { |
| if (srcImage.mYUVFormat == destImage.mYUVFormat) { |
| if (srcImage.mYUVFormat == YUV420Planar) { |
| fastCopyRectangle420Planar( |
| srcRect, |
| destStartX, destStartY, |
| srcImage, destImage); |
| } else if (srcImage.mYUVFormat == YUV420SemiPlanar) { |
| fastCopyRectangle420SemiPlanar( |
| srcRect, |
| destStartX, destStartY, |
| srcImage, destImage); |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| uint8_t clamp(uint8_t v, uint8_t minValue, uint8_t maxValue) { |
| CHECK(maxValue >= minValue); |
| |
| if (v < minValue) return minValue; |
| else if (v > maxValue) return maxValue; |
| else return v; |
| } |
| |
| void YUVImage::yuv2rgb(uint8_t yValue, uint8_t uValue, uint8_t vValue, |
| uint8_t *r, uint8_t *g, uint8_t *b) const { |
| *r = yValue + (1.370705 * (vValue-128)); |
| *g = yValue - (0.698001 * (vValue-128)) - (0.337633 * (uValue-128)); |
| *b = yValue + (1.732446 * (uValue-128)); |
| |
| *r = clamp(*r, 0, 255); |
| *g = clamp(*g, 0, 255); |
| *b = clamp(*b, 0, 255); |
| } |
| |
| bool YUVImage::writeToPPM(const char *filename) const { |
| FILE *fp = fopen(filename, "w"); |
| if (fp == NULL) { |
| return false; |
| } |
| fprintf(fp, "P3\n"); |
| fprintf(fp, "%d %d\n", mWidth, mHeight); |
| fprintf(fp, "255\n"); |
| for (int32_t y = 0; y < mHeight; ++y) { |
| for (int32_t x = 0; x < mWidth; ++x) { |
| uint8_t yValue; |
| uint8_t uValue; |
| uint8_t vValue; |
| getPixelValue(x, y, &yValue, &uValue, & vValue); |
| |
| uint8_t rValue; |
| uint8_t gValue; |
| uint8_t bValue; |
| yuv2rgb(yValue, uValue, vValue, &rValue, &gValue, &bValue); |
| |
| fprintf(fp, "%d %d %d\n", (int32_t)rValue, (int32_t)gValue, (int32_t)bValue); |
| } |
| } |
| fclose(fp); |
| return true; |
| } |
| |
| } // namespace android |