/* * 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 #include #include 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 { ALOGE("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 { ALOGE("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 { ALOGE("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 { ALOGE("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 = 0; int32_t vDestOffsetIncrement = 0; 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 = 0; 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 = 0u; uint8_t uValue = 0u; uint8_t vValue = 0u; 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