imagecore/image/yuv.cpp (326 lines of code) (raw):
/*
* MIT License
*
* Copyright (c) 2017 Twitter
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include "imagecore/utils/mathutils.h"
#include "imagecore/image/yuv.h"
#include "imagecore/formats/writer.h"
#include "imagecore/image/internal/conversions.h"
namespace imagecore {
static unsigned int computeSize(unsigned int in)
{
return (in + 1) / 2;
}
ImageYUV* ImageYUV::create(ImagePlane8* planeY, ImagePlane8* planeU, ImagePlane8* planeV)
{
if( planeY != NULL && planeU != NULL && planeV != NULL ) {
ImageYUV* image = new ImageYUV(planeY, planeU, planeV);
if( image != NULL ) {
return image;
}
}
return NULL;
}
ImageYUV* ImageYUV::create(unsigned int width, unsigned int height)
{
return ImageYUV::create(width, height, 16, 16);
}
ImageYUV* ImageYUV::create(unsigned int width, unsigned int height, unsigned int padding, unsigned int alignment)
{
ImagePlane8* planeY = ImagePlane8::create(width, height, padding, alignment);
ImagePlane8* planeU = ImagePlane8::create(computeSize(width), computeSize(height), padding, alignment);
ImagePlane8* planeV = ImagePlane8::create(computeSize(width), computeSize(height), padding, alignment);
if( planeY != NULL && planeU != NULL && planeV != NULL ) {
ImageYUV* image = new ImageYUV(planeY, planeU, planeV);
if( image != NULL ) {
return image;
}
}
delete planeY;
delete planeU;
delete planeV;
return NULL;
}
ImageYUV::ImageYUV(ImagePlaneGrayscale* planeY, ImagePlaneGrayscale* planeU, ImagePlaneGrayscale* planeV)
: m_PlaneY(planeY)
, m_PlaneU(planeU)
, m_PlaneV(planeV)
, m_Range(kYUVRange_Unknown)
, m_OwnsPlanes(true)
{
}
ImageYUV::~ImageYUV()
{
if( m_OwnsPlanes ) {
delete m_PlaneY;
delete m_PlaneU;
delete m_PlaneV;
}
m_PlaneY = NULL;
m_PlaneU = NULL;
m_PlaneV = NULL;
}
void ImageYUV::setDimensions(unsigned int width, unsigned int height)
{
m_PlaneY->setDimensions(width, height);
m_PlaneU->setDimensions(computeSize(width), computeSize(height));
m_PlaneV->setDimensions(computeSize(width), computeSize(height));
}
void ImageYUV::setDimensions(unsigned int width, unsigned int height, unsigned int padding, unsigned int alignment)
{
m_PlaneY->setDimensions(width, height, padding, alignment);
m_PlaneU->setDimensions(computeSize(width), computeSize(height), padding, alignment);
m_PlaneV->setDimensions(computeSize(width), computeSize(height), padding, alignment);
}
void ImageYUV::setPadding(unsigned int padding)
{
m_PlaneY->setPadding(padding);
m_PlaneU->setPadding(padding);
m_PlaneV->setPadding(padding);
}
bool ImageYUV::resize(Image* dest, EResizeQuality quality)
{
ImageYUV* destYUV = dest->asYUV();
if( destYUV ) {
if( m_PlaneY->resize(destYUV->getPlaneY(), quality) ) {
if( m_PlaneU->resize(destYUV->getPlaneU(), quality) ) {
if( m_PlaneV->resize(destYUV->getPlaneV(), quality) ) {
destYUV->setRange(m_Range);
return true;
}
}
}
}
return false;
}
void ImageYUV::reduceHalf(Image* dest)
{
ImageYUV* destYUV = dest->asYUV();
if( destYUV ) {
m_PlaneY->reduceHalf(destYUV->getPlaneY());
if( (m_PlaneU->getWidth() & 1) == 1 ) {
destYUV->getPlaneU()->setDimensions(computeSize(dest->getWidth()), computeSize(dest->getHeight()));
m_PlaneU->resize(destYUV->getPlaneU(), kResizeQuality_High);
} else {
m_PlaneU->reduceHalf(destYUV->getPlaneU());
}
if( (m_PlaneV->getWidth() & 1) == 1 ) {
destYUV->getPlaneV()->setDimensions(computeSize(dest->getWidth()), computeSize(dest->getHeight()));
m_PlaneV->resize(destYUV->getPlaneV(), kResizeQuality_High);
} else {
m_PlaneV->reduceHalf(destYUV->getPlaneV());
}
}
}
bool ImageYUV::crop(const ImageRegion& boundingBox)
{
if( boundingBox.right() <= getWidth() && boundingBox.bottom() <= getHeight() ) {
ImageRegion boxY = boundingBox;
// Since the U/V planes are half the size, we can only crop on even pixel boundaries.
if( (boxY.left() & 1) == 1 ) {
boxY.left(boxY.left() - 1);
}
if( (boxY.top() & 1) == 1 ) {
boxY.top(boxY.top() - 1);
}
ImageRegion boxUV = boxY;
boxUV.left(boxUV.left() / 2);
boxUV.top(boxUV.top() / 2);
boxUV.width(computeSize(boxUV.width()));
boxUV.height(computeSize(boxUV.height()));
m_PlaneY->crop(boxY);
m_PlaneU->crop(boxUV);
m_PlaneV->crop(boxUV);
return true;
}
return false;
}
void ImageYUV::rotate(Image* dest, EImageOrientation direction)
{
ImageYUV* destYUV = dest->asYUV();
if( destYUV ) {
m_PlaneY->rotate(destYUV->getPlaneY(), direction);
m_PlaneU->rotate(destYUV->getPlaneU(), direction);
m_PlaneV->rotate(destYUV->getPlaneV(), direction);
}
}
void ImageYUV::fillPadding()
{
m_PlaneY->fillPadding();
m_PlaneU->fillPadding();
m_PlaneV->fillPadding();
}
ImageYUV* ImageYUV::move()
{
ImageYUV* image = new ImageYUV(m_PlaneY, m_PlaneU, m_PlaneV);
m_OwnsPlanes = false;
return image;
}
void ImageYUV::applyLookupTable(ImageYUV* destImage, uint8_t* tableY, uint8_t* tableUV)
{
destImage->setDimensions(m_PlaneY->getWidth(), m_PlaneY->getHeight());
unsigned int widthY = m_PlaneY->getWidth();
unsigned int heightY = m_PlaneY->getHeight();
unsigned int pitchY = m_PlaneY->getPitch();
unsigned int destPitchY = m_PlaneY->getPitch();
const uint8_t* bufferY = getPlaneY()->getBytes();
uint8_t* destBufferY = destImage->getPlaneY()->lockRect(widthY, heightY, destPitchY);
if( destPitchY == pitchY ) {
for( unsigned int y = 0; y < heightY; y++ ) {
for( unsigned int x = 0; x < widthY; x++ ) {
unsigned int offset = (y * pitchY) + x;
destBufferY[offset] = tableY[bufferY[offset]];
}
}
} else {
for( unsigned int y = 0; y < heightY; y++ ) {
for( unsigned int x = 0; x < widthY; x++ ) {
destBufferY[(y * destPitchY) + x] = tableY[bufferY[(y * pitchY) + x]];
}
}
}
unsigned int widthUV = m_PlaneU->getWidth();
unsigned int heightUV = m_PlaneU->getHeight();
unsigned int pitchUV = m_PlaneU->getPitch();
unsigned int destPitchUV = 0;
const uint8_t* bufferU = getPlaneU()->getBytes();
const uint8_t* bufferV = getPlaneV()->getBytes();
uint8_t* destBufferU = destImage->getPlaneU()->lockRect(widthUV, heightUV, destPitchUV);
uint8_t* destBufferV = destImage->getPlaneV()->lockRect(widthUV, heightUV, destPitchUV);
if( destPitchY == pitchY ) {
for( unsigned int y = 0; y < heightUV; y++ ) {
for( unsigned int x = 0; x < widthUV; x++ ) {
unsigned int offset = (y * pitchUV) + x;
destBufferU[offset] = tableUV[bufferU[offset]];
destBufferV[offset] = tableUV[bufferV[offset]];
}
}
} else {
for( unsigned int y = 0; y < heightUV; y++ ) {
for( unsigned int x = 0; x < widthUV; x++ ) {
unsigned int inOffset = (y * pitchUV) + x;
unsigned int outOffset = (y * destPitchUV) + x;
destBufferU[outOffset] = tableUV[bufferU[inOffset]];
destBufferV[outOffset] = tableUV[bufferV[inOffset]];
}
}
}
}
void ImageYUV::compressRange(ImageYUV* destImage)
{
if( m_Range == kYUVRange_Compressed ) {
this->copy(destImage);
destImage->setRange(kYUVRange_Compressed);
return;
}
static bool didInitTable = false;
static uint8_t invTableY[255];
static uint8_t invTableUV[255];
if( !didInitTable ) {
for( int i = 0; i < 256; i++ ) {
invTableY[i] = clamp(0, 255, floor(0.5f + lerp(16.0f, 235.0f, i / 255.0f)));
invTableUV[i] = clamp(0, 255, floor(0.5f + lerp(16.0f, 240.0f, i / 255.0f)));
}
didInitTable = true;
}
applyLookupTable(destImage, invTableY, invTableUV);
destImage->setRange(kYUVRange_Compressed);
}
void ImageYUV::expandRange(ImageYUV *destImage)
{
if( m_Range == kYUVRange_Full ) {
this->copy(destImage);
destImage->setRange(kYUVRange_Full);
return;
}
static bool didInitTable = false;
static uint8_t tableY[255];
static uint8_t tableUV[255];
if( !didInitTable ) {
for( int i = 0; i < 256; i++ ) {
tableY[i] = clamp(0, 255, floor(0.5f + step(16.0f, 235.0f, i) * 255.0f));
tableUV[i] = clamp(0, 255, floor(0.5f + step(16.0f, 240.0f, i) * 255.0f));
}
didInitTable = true;
}
applyLookupTable(destImage, tableY, tableUV);
destImage->setRange(kYUVRange_Full);
}
EYUVRange ImageYUV::getRange()
{
return m_Range;
}
void ImageYUV::setRange(EYUVRange range)
{
m_Range = range;
}
unsigned int ImageYUV::getWidth() const
{
return m_PlaneY->getWidth();
}
unsigned int ImageYUV::getHeight() const
{
return m_PlaneY->getHeight();
}
unsigned int ImageYUV::getPadding()
{
return m_PlaneY->getPadding();
}
EImageColorModel ImageYUV::getColorModel() const
{
return kColorModel_YUV_420;
}
ImageRGBA* ImageYUV::asRGBA()
{
return NULL;
}
ImageGrayscale* ImageYUV::asGrayscale()
{
return NULL;
}
ImageInterleaved* ImageYUV::asInterleaved()
{
return NULL;
}
ImageYUV* ImageYUV::asYUV()
{
return this;
}
ImageYUVSemiplanar* ImageYUV::asYUVSemiplanar()
{
return NULL;
}
void ImageYUV::copyRect(Image* dest, unsigned int sourceX, unsigned int sourceY, unsigned int destX, unsigned int destY, unsigned int width, unsigned int height)
{
ImageYUV* destYUV = dest->asYUV();
SECURE_ASSERT(destYUV != NULL);
const uint32_t sourceX_Y = sourceX;
const uint32_t sourceY_Y = sourceY;
const uint32_t destX_Y = destX;
const uint32_t destY_Y = destY;
const uint32_t width_Y = width;
const uint32_t height_Y = height;
const uint32_t sourceX_UV = sourceX / 2;
const uint32_t sourceY_UV = sourceY / 2;
const uint32_t destX_UV = destX / 2;
const uint32_t destY_UV = destY / 2;
const uint32_t width_UV = computeSize(width);
const uint32_t height_UV = computeSize(height);
m_PlaneY->copyRect(destYUV->m_PlaneY, sourceX_Y, sourceY_Y, destX_Y, destY_Y, width_Y, height_Y);
m_PlaneU->copyRect(destYUV->m_PlaneU, sourceX_UV, sourceY_UV, destX_UV, destY_UV, width_UV, height_UV);
m_PlaneV->copyRect(destYUV->m_PlaneV, sourceX_UV, sourceY_UV, destX_UV, destY_UV, width_UV, height_UV);
}
void ImageYUV::clearRect(unsigned int x, unsigned int y, unsigned int w, unsigned int h, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
{
// convert to YUV
int16_t Y;
int16_t U;
int16_t V;
Conversions<false>::rgb_to_yuv(Y, U, V, r, g, b);
const uint32_t sourceX_Y = x;
const uint32_t sourceY_Y = y;
const uint32_t width_Y = w;
const uint32_t height_Y = h;
const uint32_t sourceX_UV = x / 2;
const uint32_t sourceY_UV = y / 2;
const uint32_t width_UV = computeSize(w);
const uint32_t height_UV = computeSize(h);
m_PlaneY->clearRect(sourceX_Y, sourceY_Y, width_Y, height_Y, Y);
m_PlaneU->clearRect(sourceX_UV, sourceY_UV, width_UV, height_UV, U);
m_PlaneV->clearRect(sourceX_UV, sourceY_UV, width_UV, height_UV, V);
}
}