blob: fd933ccfe5cf65d59c4aeaf589a8730cb3300f4e [file] [log] [blame]
/*
* Copyright (C) 2009 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.
*/
#include <media/stagefright/ColorConverter.h>
#include <media/stagefright/MediaDebug.h>
#include <media/stagefright/MediaErrors.h>
namespace android {
ColorConverter::ColorConverter(
OMX_COLOR_FORMATTYPE from, OMX_COLOR_FORMATTYPE to)
: mSrcFormat(from),
mDstFormat(to),
mClip(NULL) {
}
ColorConverter::~ColorConverter() {
delete[] mClip;
mClip = NULL;
}
bool ColorConverter::isValid() const {
if (mDstFormat != OMX_COLOR_Format16bitRGB565) {
return false;
}
switch (mSrcFormat) {
case OMX_COLOR_FormatYUV420Planar:
case OMX_COLOR_FormatCbYCrY:
case OMX_QCOM_COLOR_FormatYVU420SemiPlanar:
case OMX_COLOR_FormatYUV420SemiPlanar:
case OMX_TI_COLOR_FormatYUV420PackedSemiPlanar:
return true;
default:
return false;
}
}
ColorConverter::BitmapParams::BitmapParams(
void *bits,
size_t width, size_t height,
size_t cropLeft, size_t cropTop,
size_t cropRight, size_t cropBottom)
: mBits(bits),
mWidth(width),
mHeight(height),
mCropLeft(cropLeft),
mCropTop(cropTop),
mCropRight(cropRight),
mCropBottom(cropBottom) {
}
size_t ColorConverter::BitmapParams::cropWidth() const {
return mCropRight - mCropLeft + 1;
}
size_t ColorConverter::BitmapParams::cropHeight() const {
return mCropBottom - mCropTop + 1;
}
status_t ColorConverter::convert(
const void *srcBits,
size_t srcWidth, size_t srcHeight,
size_t srcCropLeft, size_t srcCropTop,
size_t srcCropRight, size_t srcCropBottom,
void *dstBits,
size_t dstWidth, size_t dstHeight,
size_t dstCropLeft, size_t dstCropTop,
size_t dstCropRight, size_t dstCropBottom) {
if (mDstFormat != OMX_COLOR_Format16bitRGB565) {
return ERROR_UNSUPPORTED;
}
BitmapParams src(
const_cast<void *>(srcBits),
srcWidth, srcHeight,
srcCropLeft, srcCropTop, srcCropRight, srcCropBottom);
BitmapParams dst(
dstBits,
dstWidth, dstHeight,
dstCropLeft, dstCropTop, dstCropRight, dstCropBottom);
status_t err;
switch (mSrcFormat) {
case OMX_COLOR_FormatYUV420Planar:
err = convertYUV420Planar(src, dst);
break;
case OMX_COLOR_FormatCbYCrY:
err = convertCbYCrY(src, dst);
break;
case OMX_QCOM_COLOR_FormatYVU420SemiPlanar:
err = convertQCOMYUV420SemiPlanar(src, dst);
break;
case OMX_COLOR_FormatYUV420SemiPlanar:
err = convertYUV420SemiPlanar(src, dst);
break;
case OMX_TI_COLOR_FormatYUV420PackedSemiPlanar:
err = convertTIYUV420PackedSemiPlanar(src, dst);
break;
default:
{
CHECK(!"Should not be here. Unknown color conversion.");
break;
}
}
return err;
}
status_t ColorConverter::convertCbYCrY(
const BitmapParams &src, const BitmapParams &dst) {
// XXX Untested
uint8_t *kAdjustedClip = initClip();
if (!((src.mCropLeft & 1) == 0
&& src.cropWidth() == dst.cropWidth()
&& src.cropHeight() == dst.cropHeight())) {
return ERROR_UNSUPPORTED;
}
uint32_t *dst_ptr = (uint32_t *)dst.mBits
+ (dst.mCropTop * dst.mWidth + dst.mCropLeft) / 2;
const uint8_t *src_ptr = (const uint8_t *)src.mBits
+ (src.mCropTop * dst.mWidth + src.mCropLeft) * 2;
for (size_t y = 0; y < src.cropHeight(); ++y) {
for (size_t x = 0; x < src.cropWidth(); x += 2) {
signed y1 = (signed)src_ptr[2 * x + 1] - 16;
signed y2 = (signed)src_ptr[2 * x + 3] - 16;
signed u = (signed)src_ptr[2 * x] - 128;
signed v = (signed)src_ptr[2 * x + 2] - 128;
signed u_b = u * 517;
signed u_g = -u * 100;
signed v_g = -v * 208;
signed v_r = v * 409;
signed tmp1 = y1 * 298;
signed b1 = (tmp1 + u_b) / 256;
signed g1 = (tmp1 + v_g + u_g) / 256;
signed r1 = (tmp1 + v_r) / 256;
signed tmp2 = y2 * 298;
signed b2 = (tmp2 + u_b) / 256;
signed g2 = (tmp2 + v_g + u_g) / 256;
signed r2 = (tmp2 + v_r) / 256;
uint32_t rgb1 =
((kAdjustedClip[r1] >> 3) << 11)
| ((kAdjustedClip[g1] >> 2) << 5)
| (kAdjustedClip[b1] >> 3);
uint32_t rgb2 =
((kAdjustedClip[r2] >> 3) << 11)
| ((kAdjustedClip[g2] >> 2) << 5)
| (kAdjustedClip[b2] >> 3);
dst_ptr[x / 2] = (rgb2 << 16) | rgb1;
}
src_ptr += src.mWidth * 2;
dst_ptr += dst.mWidth / 2;
}
return OK;
}
status_t ColorConverter::convertYUV420Planar(
const BitmapParams &src, const BitmapParams &dst) {
if (!((src.mCropLeft & 1) == 0
&& src.cropWidth() == dst.cropWidth()
&& src.cropHeight() == dst.cropHeight())) {
return ERROR_UNSUPPORTED;
}
uint8_t *kAdjustedClip = initClip();
uint16_t *dst_ptr = (uint16_t *)dst.mBits
+ dst.mCropTop * dst.mWidth + dst.mCropLeft;
const uint8_t *src_y =
(const uint8_t *)src.mBits + src.mCropTop * src.mWidth + src.mCropLeft;
const uint8_t *src_u =
(const uint8_t *)src_y + src.mWidth * src.mHeight
+ src.mCropTop * (src.mWidth / 2) + src.mCropLeft / 2;
const uint8_t *src_v =
src_u + (src.mWidth / 2) * (src.mHeight / 2);
for (size_t y = 0; y < src.cropHeight(); ++y) {
for (size_t x = 0; x < src.cropWidth(); x += 2) {
// B = 1.164 * (Y - 16) + 2.018 * (U - 128)
// G = 1.164 * (Y - 16) - 0.813 * (V - 128) - 0.391 * (U - 128)
// R = 1.164 * (Y - 16) + 1.596 * (V - 128)
// B = 298/256 * (Y - 16) + 517/256 * (U - 128)
// G = .................. - 208/256 * (V - 128) - 100/256 * (U - 128)
// R = .................. + 409/256 * (V - 128)
// min_B = (298 * (- 16) + 517 * (- 128)) / 256 = -277
// min_G = (298 * (- 16) - 208 * (255 - 128) - 100 * (255 - 128)) / 256 = -172
// min_R = (298 * (- 16) + 409 * (- 128)) / 256 = -223
// max_B = (298 * (255 - 16) + 517 * (255 - 128)) / 256 = 534
// max_G = (298 * (255 - 16) - 208 * (- 128) - 100 * (- 128)) / 256 = 432
// max_R = (298 * (255 - 16) + 409 * (255 - 128)) / 256 = 481
// clip range -278 .. 535
signed y1 = (signed)src_y[x] - 16;
signed y2 = (signed)src_y[x + 1] - 16;
signed u = (signed)src_u[x / 2] - 128;
signed v = (signed)src_v[x / 2] - 128;
signed u_b = u * 517;
signed u_g = -u * 100;
signed v_g = -v * 208;
signed v_r = v * 409;
signed tmp1 = y1 * 298;
signed b1 = (tmp1 + u_b) / 256;
signed g1 = (tmp1 + v_g + u_g) / 256;
signed r1 = (tmp1 + v_r) / 256;
signed tmp2 = y2 * 298;
signed b2 = (tmp2 + u_b) / 256;
signed g2 = (tmp2 + v_g + u_g) / 256;
signed r2 = (tmp2 + v_r) / 256;
uint32_t rgb1 =
((kAdjustedClip[r1] >> 3) << 11)
| ((kAdjustedClip[g1] >> 2) << 5)
| (kAdjustedClip[b1] >> 3);
uint32_t rgb2 =
((kAdjustedClip[r2] >> 3) << 11)
| ((kAdjustedClip[g2] >> 2) << 5)
| (kAdjustedClip[b2] >> 3);
if (x + 1 < src.cropWidth()) {
*(uint32_t *)(&dst_ptr[x]) = (rgb2 << 16) | rgb1;
} else {
dst_ptr[x] = rgb1;
}
}
src_y += src.mWidth;
if (y & 1) {
src_u += src.mWidth / 2;
src_v += src.mWidth / 2;
}
dst_ptr += dst.mWidth;
}
return OK;
}
status_t ColorConverter::convertQCOMYUV420SemiPlanar(
const BitmapParams &src, const BitmapParams &dst) {
uint8_t *kAdjustedClip = initClip();
if (!((dst.mWidth & 3) == 0
&& (src.mCropLeft & 1) == 0
&& src.cropWidth() == dst.cropWidth()
&& src.cropHeight() == dst.cropHeight())) {
return ERROR_UNSUPPORTED;
}
uint32_t *dst_ptr = (uint32_t *)dst.mBits
+ (dst.mCropTop * dst.mWidth + dst.mCropLeft) / 2;
const uint8_t *src_y =
(const uint8_t *)src.mBits + src.mCropTop * src.mWidth + src.mCropLeft;
const uint8_t *src_u =
(const uint8_t *)src_y + src.mWidth * src.mHeight
+ src.mCropTop * src.mWidth + src.mCropLeft;
for (size_t y = 0; y < src.cropHeight(); ++y) {
for (size_t x = 0; x < src.cropWidth(); x += 2) {
signed y1 = (signed)src_y[x] - 16;
signed y2 = (signed)src_y[x + 1] - 16;
signed u = (signed)src_u[x & ~1] - 128;
signed v = (signed)src_u[(x & ~1) + 1] - 128;
signed u_b = u * 517;
signed u_g = -u * 100;
signed v_g = -v * 208;
signed v_r = v * 409;
signed tmp1 = y1 * 298;
signed b1 = (tmp1 + u_b) / 256;
signed g1 = (tmp1 + v_g + u_g) / 256;
signed r1 = (tmp1 + v_r) / 256;
signed tmp2 = y2 * 298;
signed b2 = (tmp2 + u_b) / 256;
signed g2 = (tmp2 + v_g + u_g) / 256;
signed r2 = (tmp2 + v_r) / 256;
uint32_t rgb1 =
((kAdjustedClip[b1] >> 3) << 11)
| ((kAdjustedClip[g1] >> 2) << 5)
| (kAdjustedClip[r1] >> 3);
uint32_t rgb2 =
((kAdjustedClip[b2] >> 3) << 11)
| ((kAdjustedClip[g2] >> 2) << 5)
| (kAdjustedClip[r2] >> 3);
dst_ptr[x / 2] = (rgb2 << 16) | rgb1;
}
src_y += src.mWidth;
if (y & 1) {
src_u += src.mWidth;
}
dst_ptr += dst.mWidth / 2;
}
return OK;
}
status_t ColorConverter::convertYUV420SemiPlanar(
const BitmapParams &src, const BitmapParams &dst) {
// XXX Untested
uint8_t *kAdjustedClip = initClip();
if (!((dst.mWidth & 3) == 0
&& (src.mCropLeft & 1) == 0
&& src.cropWidth() == dst.cropWidth()
&& src.cropHeight() == dst.cropHeight())) {
return ERROR_UNSUPPORTED;
}
uint32_t *dst_ptr = (uint32_t *)dst.mBits
+ (dst.mCropTop * dst.mWidth + dst.mCropLeft) / 2;
const uint8_t *src_y =
(const uint8_t *)src.mBits + src.mCropTop * src.mWidth + src.mCropLeft;
const uint8_t *src_u =
(const uint8_t *)src_y + src.mWidth * src.mHeight
+ src.mCropTop * src.mWidth + src.mCropLeft;
for (size_t y = 0; y < src.cropHeight(); ++y) {
for (size_t x = 0; x < src.cropWidth(); x += 2) {
signed y1 = (signed)src_y[x] - 16;
signed y2 = (signed)src_y[x + 1] - 16;
signed v = (signed)src_u[x & ~1] - 128;
signed u = (signed)src_u[(x & ~1) + 1] - 128;
signed u_b = u * 517;
signed u_g = -u * 100;
signed v_g = -v * 208;
signed v_r = v * 409;
signed tmp1 = y1 * 298;
signed b1 = (tmp1 + u_b) / 256;
signed g1 = (tmp1 + v_g + u_g) / 256;
signed r1 = (tmp1 + v_r) / 256;
signed tmp2 = y2 * 298;
signed b2 = (tmp2 + u_b) / 256;
signed g2 = (tmp2 + v_g + u_g) / 256;
signed r2 = (tmp2 + v_r) / 256;
uint32_t rgb1 =
((kAdjustedClip[b1] >> 3) << 11)
| ((kAdjustedClip[g1] >> 2) << 5)
| (kAdjustedClip[r1] >> 3);
uint32_t rgb2 =
((kAdjustedClip[b2] >> 3) << 11)
| ((kAdjustedClip[g2] >> 2) << 5)
| (kAdjustedClip[r2] >> 3);
dst_ptr[x / 2] = (rgb2 << 16) | rgb1;
}
src_y += src.mWidth;
if (y & 1) {
src_u += src.mWidth;
}
dst_ptr += dst.mWidth / 2;
}
return OK;
}
status_t ColorConverter::convertTIYUV420PackedSemiPlanar(
const BitmapParams &src, const BitmapParams &dst) {
/*
The TIYUV420PackedSemiPlanar format is same as YUV420PackedSemiPlanar but with
additional padding as shown in the diagram below. The padded width and padded
height is different for different compression formats and it is read from the
codec. In this color conversion routine, the padded resolution is obtained from
src bitmap.
------------------------------------
| |
| |
| ------------------------- |
| | | |
| | | |
| | Y DATA | |
| | | |
| | | |
| | | |
| ------------------------- |
| |
| ------------ |
| | | |
| | | |
| | UV DATA | |
| | | |
| | | |
| | | |
| ------------ |
| |
| |
------------------------------------
*/
LOGV("src.mCropLeft = %d src.mCropTop =%d src.mWidth = %d src.mHeight = %d"
" dst.mWidth = %d dst.mHeight = %d", src.mCropLeft , src.mCropTop,
src.mWidth, src.mHeight, dst.mWidth, dst.mHeight);
size_t offset = (src.mWidth * src.mCropTop) + src.mCropLeft;
uint8_t *kAdjustedClip = initClip();
uint32_t *dst_ptr = (uint32_t *)dst.mBits;
const uint8_t *src_y = (const uint8_t *)src.mBits;
const uint8_t *src_u = (const uint8_t *)(src_y-offset) + (src.mWidth * src.mHeight);
src_u += ( ( src.mWidth * (src.mCropTop/2) ) + src.mCropLeft );
const uint8_t *src_v = src_u + 1;
for (size_t y = 0; y < dst.mHeight; ++y) {
for (size_t x = 0; x < dst.mWidth; x += 2) {
signed y1 = (signed)src_y[x] - 16; //Y pixel
signed y2 = (signed)src_y[x + 1] - 16; //2nd Y pixel
signed u = (signed)src_u[x & ~1] - 128; //U component
signed v = (signed)src_u[(x & ~1) + 1] - 128; //V component
signed u_b = u * 517;
signed u_g = -u * 100;
signed v_g = -v * 208;
signed v_r = v * 409;
signed tmp1 = y1 * 298;
signed b1 = (tmp1 + u_b) / 256;
signed g1 = (tmp1 + v_g + u_g) / 256;
signed r1 = (tmp1 + v_r) / 256;
signed tmp2 = y2 * 298;
signed b2 = (tmp2 + u_b) / 256;
signed g2 = (tmp2 + v_g + u_g) / 256;
signed r2 = (tmp2 + v_r) / 256;
uint32_t rgb1 =
((kAdjustedClip[r1] >> 3) << 11)
| ((kAdjustedClip[g1] >> 2) << 5)
| (kAdjustedClip[b1] >> 3);
uint32_t rgb2 =
((kAdjustedClip[r2] >> 3) << 11)
| ((kAdjustedClip[g1] >> 2) << 5)
| (kAdjustedClip[b1] >> 3);
dst_ptr[x / 2] = (rgb2 << 16) | rgb1;
}
src_y += src.mWidth; //increment Y-pixel line
if (y&1) {
src_u += src.mWidth; //increment U-V line
}
dst_ptr += dst.mWidth / 2;
}
return OK;
}
uint8_t *ColorConverter::initClip() {
static const signed kClipMin = -278;
static const signed kClipMax = 535;
if (mClip == NULL) {
mClip = new uint8_t[kClipMax - kClipMin + 1];
for (signed i = kClipMin; i <= kClipMax; ++i) {
mClip[i - kClipMin] = (i < 0) ? 0 : (i > 255) ? 255 : (uint8_t)i;
}
}
return &mClip[-kClipMin];
}
} // namespace android