imagecore/image/yuv_semiplanar.cpp (321 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_semiplanar.h"
#include "imagecore/image/rgba.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;
}
ImageYUVSemiplanar* ImageYUVSemiplanar::create(ImagePlane8* planeY, ImagePlane16* planeUV)
{
if( planeY != NULL && planeUV != NULL) {
ImageYUVSemiplanar* image = new ImageYUVSemiplanar(planeY, planeUV);
if( image != NULL ) {
return image;
}
}
return NULL;
}
ImageYUVSemiplanar* ImageYUVSemiplanar::create(unsigned int width, unsigned int height)
{
return ImageYUVSemiplanar::create(width, height, 16, 16);
}
ImageYUVSemiplanar* ImageYUVSemiplanar::create(unsigned int width, unsigned int height, unsigned int padding, unsigned int alignment)
{
ImagePlane8* planeY = ImagePlane8::create(width, height, padding, alignment);
ImagePlane16* planeUV = ImagePlane16::create(computeSize(width), computeSize(height), padding, alignment);
if( planeY != NULL && planeUV != NULL) {
ImageYUVSemiplanar* image = new ImageYUVSemiplanar(planeY, planeUV);
if( image != NULL ) {
return image;
}
}
delete planeY;
delete planeUV;
return NULL;
}
ImageYUVSemiplanar* ImageYUVSemiplanar::create(ImagePlane8* planeY, ImagePlane16* planeUV, ImageRGBA* inImage)
{
uint32_t width = inImage->getWidth();
uint32_t height = inImage->getHeight();
uint32_t outputPitchY;
uint8_t* dstY = planeY->lockRect(planeY->getWidth(), planeY->getHeight(), outputPitchY);
uint32_t outputPitchUV;
uint8_t* dstUV = planeUV->lockRect(planeUV->getWidth(), planeUV->getHeight(), outputPitchUV);
uint32_t inputPitch;
const uint8_t* srcRGBA = inImage->lockRect(width, height, inputPitch);
Conversions<false>::rgba_to_yuv420(dstY, dstUV, srcRGBA, width, height, inputPitch, outputPitchY, outputPitchUV);
ImageYUVSemiplanar* image = new ImageYUVSemiplanar(planeY, planeUV);
if( image != NULL ) {
return image;
}
return NULL;
}
ImageYUVSemiplanar* ImageYUVSemiplanar::create(ImageRGBA* inImage, unsigned int padding, unsigned int alignment)
{
uint32_t width = inImage->getWidth();
uint32_t height = inImage->getHeight();
ImagePlane8* planeY = ImagePlane8::create(width, height, padding, alignment);
ImagePlane16* planeUV = ImagePlane16::create(computeSize(width), computeSize(height), padding, alignment);
if( planeY != NULL && planeUV != NULL) {
ImageYUVSemiplanar* image = new ImageYUVSemiplanar(planeY, planeUV);
if( image != NULL ) {
uint32_t outputPitchY;
uint8_t* dstY = planeY->lockRect(planeY->getWidth(), planeY->getHeight(), outputPitchY);
uint32_t outputPitchUV;
uint8_t* dstUV = planeUV->lockRect(planeUV->getWidth(), planeUV->getHeight(), outputPitchUV);
uint32_t inputPitch;
const uint8_t* srcRGBA = inImage->lockRect(width, height, inputPitch);
Conversions<false>::rgba_to_yuv420(dstY, dstUV, srcRGBA, width, height, inputPitch, outputPitchY, outputPitchUV);
return image;
}
}
delete planeY;
delete planeUV;
return NULL;
}
ImageYUVSemiplanar::ImageYUVSemiplanar(ImagePlane8* planeY, ImagePlane16* planeUV)
: m_PlaneY(planeY)
, m_PlaneUV(planeUV)
, m_Range(kYUVRange_Unknown)
, m_OwnsPlanes(true)
{
}
ImageYUVSemiplanar::~ImageYUVSemiplanar()
{
if (m_OwnsPlanes) {
delete m_PlaneY;
delete m_PlaneUV;
}
m_PlaneY = NULL;
m_PlaneUV = NULL;
}
void ImageYUVSemiplanar::setDimensions(unsigned int width, unsigned int height)
{
m_PlaneY->setDimensions(width, height);
m_PlaneUV->setDimensions(computeSize(width), computeSize(height));
}
void ImageYUVSemiplanar::setDimensions(unsigned int width, unsigned int height, unsigned int padding, unsigned int alignment)
{
m_PlaneY->setDimensions(width, height, padding, alignment);
m_PlaneUV->setDimensions(computeSize(width), computeSize(height), padding, alignment);
}
void ImageYUVSemiplanar::setPadding(unsigned int padding)
{
m_PlaneY->setPadding(padding);
m_PlaneUV->setPadding(padding);
}
bool ImageYUVSemiplanar::resize(Image* dest, EResizeQuality quality)
{
ImageYUVSemiplanar* destYUVSemiplanar = dest->asYUVSemiplanar();
if( destYUVSemiplanar ) {
if( m_PlaneY->resize(destYUVSemiplanar->getPlaneY(), quality) ) {
if( m_PlaneUV->resize(destYUVSemiplanar->getPlaneUV(), quality) ) {
return true;
}
}
}
return false;
}
void ImageYUVSemiplanar::reduceHalf(Image* dest)
{
ImageYUVSemiplanar* destYUVSemiplanar = dest->asYUVSemiplanar();
if( destYUVSemiplanar ) {
m_PlaneY->reduceHalf(destYUVSemiplanar->getPlaneY());
if( (m_PlaneUV->getWidth() & 1) == 1 ) {
destYUVSemiplanar->getPlaneUV()->setDimensions(computeSize(dest->getWidth()), computeSize(dest->getHeight()));
m_PlaneUV->resize(destYUVSemiplanar->getPlaneUV(), kResizeQuality_High);
} else {
m_PlaneUV->reduceHalf(destYUVSemiplanar->getPlaneUV());
}
}
}
bool ImageYUVSemiplanar::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_PlaneUV->crop(boxUV);
return true;
}
return false;
}
void ImageYUVSemiplanar::rotate(Image* dest, EImageOrientation direction)
{
ImageYUVSemiplanar* destYUVSemiplanar = dest->asYUVSemiplanar();
if( destYUVSemiplanar ) {
m_PlaneY->rotate(destYUVSemiplanar->getPlaneY(), direction);
m_PlaneUV->rotate(destYUVSemiplanar->getPlaneUV(), direction);
}
}
void ImageYUVSemiplanar::fillPadding()
{
m_PlaneY->fillPadding();
m_PlaneUV->fillPadding();
}
void ImageYUVSemiplanar::copyRect(Image* dest, unsigned int sourceX, unsigned int sourceY, unsigned int destX, unsigned int destY, unsigned int width, unsigned int height)
{
ASSERT(0);
}
void ImageYUVSemiplanar::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)
{
ASSERT(0);
}
void ImageYUVSemiplanar::copy(Image* dest)
{
ImageYUVSemiplanar* destYUVSemiplanar = dest->asYUVSemiplanar();
if( destYUVSemiplanar ) {
m_PlaneY->copy(destYUVSemiplanar->getPlaneY());
m_PlaneUV->copy(destYUVSemiplanar->getPlaneUV());
}
}
ImageYUVSemiplanar* ImageYUVSemiplanar::move()
{
ImageYUVSemiplanar* image = new ImageYUVSemiplanar(m_PlaneY, m_PlaneUV);
m_OwnsPlanes = false;
return image;
}
void ImageYUVSemiplanar::applyLookupTable(ImageYUVSemiplanar* 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_PlaneUV->getWidth();
unsigned int heightUV = m_PlaneUV->getHeight();
unsigned int pitchUV = m_PlaneUV->getPitch();
unsigned int destPitchUV = 0;
const uint8_t* bufferUV = getPlaneUV()->getBytes();
uint8_t* destBufferUV = destImage->getPlaneUV()->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;
destBufferUV[offset] = tableUV[bufferUV[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;
destBufferUV[outOffset] = tableUV[bufferUV[inOffset]];
}
}
}
}
void ImageYUVSemiplanar::compressRange(ImageYUVSemiplanar* 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 ImageYUVSemiplanar::expandRange(ImageYUVSemiplanar *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 ImageYUVSemiplanar::getRange()
{
return m_Range;
}
void ImageYUVSemiplanar::setRange(EYUVRange range)
{
m_Range = range;
}
unsigned int ImageYUVSemiplanar::getWidth() const
{
return m_PlaneY->getWidth();
}
unsigned int ImageYUVSemiplanar::getHeight() const
{
return m_PlaneY->getHeight();
}
unsigned int ImageYUVSemiplanar::getPadding()
{
return m_PlaneY->getPadding();
}
EImageColorModel ImageYUVSemiplanar::getColorModel() const
{
return kColorModel_YUV_420;
}
ImageRGBA* ImageYUVSemiplanar::asRGBA()
{
return NULL;
}
ImageGrayscale* ImageYUVSemiplanar::asGrayscale()
{
return NULL;
}
ImageInterleaved* ImageYUVSemiplanar::asInterleaved()
{
return NULL;
}
ImageYUV* ImageYUVSemiplanar::asYUV()
{
return NULL;
}
ImageYUVSemiplanar* ImageYUVSemiplanar::asYUVSemiplanar()
{
return this;
}
}