imagecore/image/image.cpp (564 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/imagecore.h"
#include "imagecore/utils/mathutils.h"
#include "imagecore/utils/securemath.h"
#include "imagecore/image/image.h"
#include "imagecore/image/rgba.h"
#include "imagecore/image/grayscale.h"
#include "imagecore/image/yuv.h"
#include "internal/filters.h"
namespace imagecore {
ImageRegion* ImageRegion::fromString(const char* input)
{
unsigned int rwidth = 0;
unsigned int rheight = 0;
unsigned int rleft = 0;
unsigned int rtop = 0;
if( sscanf(input, "%ux%uT%u", &rwidth, &rheight, &rtop) == 3 ) {
return new ImageRegion(rwidth, rheight, 0, rtop);
} else if( sscanf(input, "%ux%uL%uT%u", &rwidth, &rheight, &rleft, &rtop) >= 2 ) {
return new ImageRegion(rwidth, rheight, rleft, rtop);
}
// the input is invalid at this point
return NULL;
}
ImageRegion* ImageRegion::fromGravity(
unsigned int width,
unsigned int height,
unsigned int targetWidth,
unsigned int targetHeight,
ECropGravity gravity)
{
unsigned int rwidth = targetWidth;
unsigned int rheight = targetHeight;
unsigned int rleft = 0;
unsigned int rtop = 0;
ECropGravity computed_gravity = gravity;
if( gravity == kGravityHeuristic ) {
if( height > width ) {
computed_gravity = kGravityTop;
} else {
computed_gravity = kGravityCenter;
}
}
switch( computed_gravity ) {
case kGravityCenter:
rleft = (width - rwidth) / 2;
rtop = (height - rheight) / 2;
break;
case kGravityRight:
rleft = (width - rwidth);
case kGravityLeft:
rtop = (height - rheight) / 2;
break;
case kGravityBottom:
rtop = (height - rheight);
case kGravityTop:
rleft = (width - rwidth) / 2;
break;
default: break;
}
// prevent invalid cropping
if( width < targetWidth ) {
rleft = 0;
rwidth = width;
}
if( height < targetHeight ) {
rtop = 0;
rheight = height;
}
return new ImageRegion(rwidth, rheight, rleft, rtop);
}
#define IMAGEPLANE(t) template <uint32_t Channels> t ImagePlane<Channels>
// These capacity calculations are critical to the safety/security of the program - proceed with extreme caution.
IMAGEPLANE(unsigned int)::paddingOffset(unsigned int pitch, unsigned int pad_amount)
{
// Return a pointer to the beginning of the real image data, skipping the left/top padding.
return SafeUAdd(SafeUMul(pitch, pad_amount), SafeUMul(pad_amount, Channels));
}
IMAGEPLANE(unsigned int)::paddedPitch(unsigned int width, unsigned int pad_amount, unsigned int alignment)
{
// We like being 16 byte aligned for SSE, so apply the padding (twice, once each for left/right), then align to 16.
return align(SafeUMul(SafeUAdd(width, SafeUMul(pad_amount, 2U)), Channels), alignment);
}
IMAGEPLANE(unsigned int)::totalImageSize(unsigned int width, unsigned int height, unsigned int padAmount, unsigned int alignment)
{
unsigned int pitch = paddedPitch(width, padAmount, alignment);
SECURE_ASSERT(pitch >= SafeUMul(width, Channels));
// The pitch is already padded, so add another 2*pad scanlines of padding to height.
return SafeUMul(pitch, SafeUAdd(height, SafeUMul(padAmount, 2U)));
}
IMAGEPLANE()::ImagePlane(uint8_t* buffer, unsigned int capacity, bool ownsBuffer)
{
m_Buffer = buffer;
m_Capacity = capacity;
m_Width = 0;
m_Height = 0;
m_Pitch = 0;
m_Padding = 0;
m_OffsetX = 0;
m_OffsetY = 0;
m_Alignment = 1;
m_PadRegionDirty = kEdge_All;
m_OwnsBuffer = ownsBuffer;
}
IMAGEPLANE(ImagePlane<Channels>*)::create(uint8_t* buffer, unsigned int capacity)
{
return new ImagePlane<Channels>(buffer, capacity, false);
}
IMAGEPLANE(ImagePlane<Channels>*)::create(unsigned int width, unsigned int height)
{
return ImagePlane<Channels>::create(width, height, 0, 1);
}
IMAGEPLANE(ImagePlane<Channels>*)::create(unsigned int width, unsigned int height, unsigned int padding, unsigned int alignment)
{
unsigned int totalSize = totalImageSize(width, height, padding, alignment);
uint8_t* imageBuffer = (uint8_t*)memalign(max(16U, alignment), totalSize);
if( imageBuffer == NULL ) {
return NULL;
}
ImagePlane<Channels>* image = new ImagePlane<Channels>(imageBuffer, totalSize, true);
if( image == NULL ) {
free(imageBuffer);
return NULL;
}
image->setDimensions(width, height, padding, alignment);
return image;
}
IMAGEPLANE()::~ImagePlane<Channels>()
{
if( m_OwnsBuffer && m_Buffer != NULL ) {
free(m_Buffer);
m_Buffer = NULL;
}
}
IMAGEPLANE(const uint8_t*)::getBytes()
{
unsigned int p;
return lockRect(m_Width, m_Height, p);
}
IMAGEPLANE(uint8_t*)::lockRect(unsigned int width, unsigned int height, unsigned int& pitch)
{
return lockRect(0, 0, width, height, pitch);
}
IMAGEPLANE(uint8_t*)::lockRect(unsigned int x, unsigned int y, unsigned int width, unsigned int height, unsigned int& pitch)
{
SECURE_ASSERT(height != 0 && width != 0);
pitch = m_Pitch;
unsigned int writeX = SafeUAdd(x, SafeUAdd(m_OffsetX, m_Padding));
unsigned int writeY = SafeUAdd(y, SafeUAdd(m_OffsetY, m_Padding));
unsigned int headOffset = SafeUAdd(SafeUMul(writeY, m_Pitch), SafeUMul(writeX, Channels));
unsigned int tailOffset = SafeUAdd(SafeUMul(m_Padding, m_Pitch), SafeUMul(m_Padding, Channels));
unsigned int remainingBytes = SafeUSub(m_Capacity, SafeUAdd(headOffset, tailOffset));
unsigned int writeBytes = SafeUSub(SafeUMul(m_Pitch, height), SafeUAdd(SafeUMul(m_Padding, Channels), SafeUMul(writeX, Channels)));
SECURE_ASSERT(writeBytes <= remainingBytes);
SECURE_ASSERT(SafeUMul(SafeUAdd(width, SafeUMul(2, m_Padding)), Channels) <= m_Pitch);
SECURE_ASSERT(Image::validateSize(width, height));
m_PadRegionDirty = kEdge_All;
return m_Buffer + headOffset;
}
IMAGEPLANE(void)::unlockRect()
{
}
IMAGEPLANE(void)::setDimensions(unsigned int width, unsigned int height)
{
m_Width = width;
m_Height = height;
m_Pitch = paddedPitch(m_Width, m_Padding, m_Alignment);
m_PadRegionDirty = kEdge_All;
SECURE_ASSERT(checkCapacity(m_Width, m_Height));
}
IMAGEPLANE(void)::setDimensions(unsigned int width, unsigned int height, unsigned int padding, unsigned int alignment)
{
m_Width = width;
m_Height = height;
m_Padding = padding;
m_Alignment = alignment;
m_Pitch = paddedPitch(m_Width, m_Padding, m_Alignment);
m_PadRegionDirty = kEdge_All;
SECURE_ASSERT(checkCapacity(m_Width, m_Height));
}
IMAGEPLANE(void)::setPadding(unsigned int padding)
{
m_Padding = padding;
m_Pitch = paddedPitch(m_Width, m_Padding, m_Alignment);
m_PadRegionDirty = kEdge_All;
SECURE_ASSERT(checkCapacity(m_Width, m_Height));
}
IMAGEPLANE(bool)::checkCapacity(unsigned int width, unsigned int height)
{
unsigned int requestedSize = totalImageSize(SafeUAdd(width, m_OffsetX), SafeUAdd(height, m_OffsetY), m_Padding, m_Alignment);
unsigned int requestedPitch = paddedPitch(width, m_Padding, m_Alignment);
SECURE_ASSERT(requestedPitch >= SafeUMul(width, Channels));
return requestedSize <= m_Capacity;
}
IMAGEPLANE(unsigned int)::getImageSize()
{
// The total user-available area inside the buffer (doesn't count the offset and padding).
// When you ask for image->getBytes(), this is how much you're allowed to write.
return SafeUMul(m_Pitch, m_Height);
}
IMAGEPLANE(void)::setOffset(unsigned int offsetX, unsigned int offsetY)
{
m_OffsetX = offsetX;
m_OffsetY = offsetY;
m_PadRegionDirty = kEdge_All;
SECURE_ASSERT(checkCapacity(m_Width, m_Height));
}
IMAGEPLANE(bool)::crop(const ImageRegion& boundingBox)
{
if( m_Width >= boundingBox.width() && m_Height >= boundingBox.height() ) {
SECURE_ASSERT(SafeUAdd(boundingBox.width(), boundingBox.left()) <= m_Width);
SECURE_ASSERT(SafeUAdd(boundingBox.height(), boundingBox.top()) <= m_Height);
m_OffsetX += boundingBox.left();
m_OffsetY += boundingBox.top();
m_Width = boundingBox.width();
m_Height = boundingBox.height();
m_PadRegionDirty = kEdge_All;
return true;
}
return false;
}
IMAGEPLANE(void)::rotate(ImagePlane<Channels>* dest, EImageOrientation direction)
{
unsigned int destPitch = 0;
if( direction == kImageOrientation_Left ) {
dest->setDimensions(m_Height, m_Width);
uint8_t* destBuffer = dest->lockRect(m_Height, m_Width, destPitch);
Filters<ComponentSIMD<Channels>>::rotateLeft(this->getBytes(), destBuffer, m_Width, m_Height, this->getPitch(), destPitch, dest->getImageSize());
} else if( direction == kImageOrientation_Right ) {
dest->setDimensions(m_Height, m_Width);
uint8_t* destBuffer = dest->lockRect(m_Height, m_Width, destPitch);
Filters<ComponentSIMD<Channels>>::rotateRight(this->getBytes(), destBuffer, m_Width, m_Height, this->getPitch(), destPitch, dest->getImageSize());
} else if( direction == kImageOrientation_Up || direction == kImageOrientation_Down ) {
dest->setDimensions(m_Width, m_Height);
uint8_t* destBuffer = dest->lockRect(m_Width, m_Height, destPitch);
Filters<ComponentSIMD<Channels>>::rotateUp(this->getBytes(), destBuffer, m_Width, m_Height, this->getPitch(), destPitch, dest->getImageSize());
}
dest->unlockRect();
}
IMAGEPLANE(void)::transpose(ImagePlane* dest)
{
SECURE_ASSERT(m_Width == dest->getHeight());
SECURE_ASSERT(m_Height == dest->getWidth());
SECURE_ASSERT((m_Pitch & 3) == 0); // multiple of 4 only
uint32_t destPitch;
uint8_t* destBuffer = dest->lockRect(dest->getWidth(), dest->getHeight(), destPitch);
SECURE_ASSERT((destPitch & 3) == 0); // multiple of 4 only
Filters<ComponentSIMD<Channels>>::transpose(this->getBytes(), destBuffer, m_Width, m_Height, this->getPitch(), destPitch, dest->getImageSize());
}
IMAGEPLANE(void)::fillPadding(EEdgeMask edgeMask)
{
if (m_PadRegionDirty != kEdge_None) {
unsigned int pitch;
// lockRect will validate that we have enough capacity to write m_Padding*2 bytes beyond m_Width and m_Height.
typename primType::asType* sample = (typename primType::asType*)lockRect(m_Width, m_Height, pitch);
int componentPitch = pitch / Channels;
if( (edgeMask & kEdge_Left) != 0 && (m_PadRegionDirty & kEdge_Left) != 0 ) {
for( int y = 0; y < m_Height; y++ ) {
for( int x = -m_Padding; x < 0; x++ ) {
sample[y * componentPitch + x] = sample[y * componentPitch + 0];
}
}
m_PadRegionDirty &= ~kEdge_Left;
}
if( (edgeMask & kEdge_Right) != 0 && (m_PadRegionDirty & kEdge_Right) != 0 ) {
unsigned int paddedWidth = m_Width + m_Padding;
for( int y = 0; y < m_Height; y++ ) {
for( int x = m_Width; x < paddedWidth; x++ ) {
sample[y * componentPitch + x] = sample[y * componentPitch + (m_Width - 1)];
}
}
m_PadRegionDirty &= ~kEdge_Right;
}
if( (edgeMask & kEdge_Top) != 0 && (m_PadRegionDirty & kEdge_Top) != 0 ) {
unsigned int twicePaddedWidth = m_Width + m_Padding * 2;
for( int y = -m_Padding; y < 0; y++ ) {
memcpy(sample + y * componentPitch - m_Padding, sample + 0 * componentPitch - m_Padding, twicePaddedWidth * Channels);
}
m_PadRegionDirty &= ~kEdge_Top;
}
if( (edgeMask & kEdge_Bottom) != 0 && (m_PadRegionDirty & kEdge_Bottom) != 0 ) {
unsigned int paddedHeight = m_Height + m_Padding;
unsigned int twicePaddedWidth = m_Width + m_Padding * 2;
for( int y = m_Height; y < paddedHeight; y++ ) {
memcpy(sample + y * componentPitch - m_Padding, sample + (m_Height - 1) * componentPitch - m_Padding, twicePaddedWidth * Channels);
}
m_PadRegionDirty &= ~kEdge_Bottom;
}
}
}
IMAGEPLANE(void)::reduceHalf(ImagePlane<Channels>* dest)
{
dest->setDimensions(m_Width / 2, m_Height / 2);
unsigned int destPitch = 0;
uint8_t* destBuffer = dest->lockRect(m_Width / 2, m_Height / 2, destPitch);
Filters<ComponentSIMD<Channels>>::reduceHalf(this->getBytes(), destBuffer, m_Width, m_Height, m_Pitch, destPitch, dest->getImageSize());
dest->unlockRect();
}
IMAGEPLANE(bool)::resize(ImagePlane<Channels>* dest, EResizeQuality quality)
{
if( dest->getWidth() == m_Width && dest->getHeight() == m_Height ) {
copy(dest);
return true;
} else if( dest->getWidth() > m_Width || dest->getHeight() > m_Height ) {
ImagePlane<Channels>* workBuffer = NULL;
ImagePlane<Channels>* sourceImage = this;
if( m_Padding < 4 ) {
workBuffer = ImagePlane<Channels>::create(m_Width, m_Height, 4, 16);
if( workBuffer == NULL ) {
return false;
}
copy(workBuffer);
sourceImage = workBuffer;
}
// Upsample.
EFilterType kernelType = Image::getUpsampleFilterKernelType(quality);
FilterKernelFixed filterKernelX(kernelType, this->getWidth(), dest->getWidth());
FilterKernelFixed filterKernelY(kernelType, this->getHeight(), dest->getHeight());
bool success = sourceImage->upsampleFilter4x4(dest, &filterKernelX, &filterKernelY);
delete workBuffer;
return success;
} else {
// Downsample.
ImagePlane<Channels>* whichImage = this;
EFilterType kernelType = Image::getDownsampleFilterKernelType(quality);
unsigned int kernelSize = Image::getDownsampleFilterKernelSize(quality);
unsigned int destWidth = dest->getWidth();
unsigned int destHeight = dest->getHeight();
ImagePlane<Channels>* workBuffer[2] = { NULL, NULL };
bool unpadded = false;
bool doneDownsampling = false;
if(Filters<ComponentSIMD<Channels>>::supportsUnpadded(kernelSize)) {
unpadded = Filters<ComponentSIMD<Channels>>::fasterUnpadded(kernelSize);
}
// Do we need to do iterative 2x2 reductions first?
if (whichImage->getWidth() / 2 >= destWidth && whichImage->getHeight() / 2 >= destHeight) {
workBuffer[0] = ImagePlane<Channels>::create(whichImage->getWidth() / 2, whichImage->getHeight() / 2, kernelSize, 16);
workBuffer[1] = ImagePlane<Channels>::create(whichImage->getWidth() / 2, whichImage->getHeight() / 2, kernelSize, 16);
if (workBuffer[0] == NULL || workBuffer[1] == NULL) {
delete workBuffer[0];
delete workBuffer[1];
return false;
}
unsigned int whichWorkBuffer = 0;
while (!doneDownsampling && whichImage->getWidth() / 2 >= destWidth && whichImage->getHeight() / 2 >= destHeight) {
if ((whichImage->getWidth() / 2 == destWidth) && (whichImage->getHeight() / 2 == destHeight)) {
whichImage->reduceHalf(dest);
doneDownsampling = true; // for the case where reducehalf gets us the correct size
} else {
whichImage->reduceHalf(workBuffer[whichWorkBuffer]);
whichImage = workBuffer[whichWorkBuffer];
whichWorkBuffer ^= 1;
}
}
} else if (m_Padding < kernelSize) {
if(Filters<ComponentSIMD<Channels>>::supportsUnpadded(kernelSize)) {
unpadded = true; // use unpadded code path to avoid copying for all bit depths
} else {
// TODO: this is wasteful, find another solution
workBuffer[0] = ImagePlane<Channels>::create(whichImage->getWidth(), whichImage->getHeight(), kernelSize, 16);
if (workBuffer[0] == NULL) {
return false;
}
copy(workBuffer[0]);
whichImage = workBuffer[0];
}
}
bool success = false;
if (doneDownsampling) {
success = true;
} else {
FilterKernelAdaptive filterKernelX(kernelType, kernelSize, whichImage->getWidth(), destWidth);
FilterKernelAdaptive filterKernelY(kernelType, kernelSize, whichImage->getHeight(), destHeight);
success = whichImage->downsampleFilter(dest, &filterKernelX, &filterKernelY, unpadded);
}
// Only allocated if iterative reduction was performed.
delete workBuffer[0];
delete workBuffer[1];
return success;
}
}
IMAGEPLANE(bool)::downsampleFilter(ImagePlane<Channels> *dest, const FilterKernelAdaptive *filterKernelX, const FilterKernelAdaptive *filterKernelY, bool unpadded)
{
if (filterKernelX->getKernelSize() == 2) {
// Special low quality but fast 2x2 bilinear filter, used for on device video transcoding
return downsampleFilter2x2(dest, filterKernelX, filterKernelY);
} else if (filterKernelX->getKernelSize() == 4) {
// Special 4x4 non-seperable filter.
return downsampleFilter4x4(dest, filterKernelX, filterKernelY);
} else {
return downsampleFilterSeperable(dest, filterKernelX, filterKernelY, unpadded);
}
}
IMAGEPLANE(bool)::downsampleFilterSeperable(ImagePlane<Channels>* dest, const FilterKernelAdaptive* filterKernelX, const FilterKernelAdaptive* filterKernelY, bool unpadded)
{
unsigned int padSize = max(filterKernelX->getKernelSize(), filterKernelY->getKernelSize());
SECURE_ASSERT((m_Padding >= padSize) || (unpadded));
ImagePlane<Channels>* temp = ImagePlane<Channels>::create(m_Height, dest->getWidth(), padSize, 16U);
if( temp == NULL ) {
return false;
}
unsigned int tempPitch = 0;
if( !unpadded ) {
fillPadding();
}
uint8_t* tempBuffer = temp->lockRect(temp->getWidth(), temp->getHeight(), tempPitch);
Filters<ComponentSIMD<Channels>>::adaptiveSeperable(filterKernelX, this->getBytes(), m_Width, m_Height, m_Pitch,
tempBuffer, temp->getHeight(), temp->getWidth(), tempPitch, temp->getImageSize(), unpadded);
temp->unlockRect();
if( !unpadded ) {
temp->fillPadding();
}
unsigned int destPitch = 0;
uint8_t* destBuffer = dest->lockRect(dest->getWidth(), dest->getHeight(), destPitch);
Filters<ComponentSIMD<Channels>>::adaptiveSeperable(filterKernelY, temp->getBytes(), temp->getWidth(), temp->getHeight(), temp->getPitch(),
destBuffer, dest->getHeight(), dest->getWidth(), destPitch, dest->getImageSize(), unpadded);
dest->unlockRect();
delete temp;
return true;
}
IMAGEPLANE(bool)::downsampleFilter2x2(ImagePlane<Channels>* dest, const FilterKernelAdaptive* filterKernelX, const FilterKernelAdaptive* filterKernelY)
{
unsigned int destPitch = 0;
fillPadding();
ImagePlane<Channels>* transposedDest = ImagePlane<Channels>::create(dest->getHeight(), dest->getWidth(), 0, 4);
uint8_t* destBuffer = transposedDest->lockRect(dest->getHeight(), dest->getWidth(), destPitch);
Filters<ComponentSIMD<Channels>>::adaptiveSeparable2x2(filterKernelX, filterKernelY, this->getBytes(), m_Width, m_Height, m_Pitch,
destBuffer, dest->getWidth(), dest->getHeight(), destPitch, dest->getImageSize());
transposedDest->unlockRect();
transposedDest->transpose(dest);
delete transposedDest;
return true;
}
IMAGEPLANE(bool)::downsampleFilter4x4(ImagePlane<Channels>* dest, const FilterKernelAdaptive* filterKernelX, const FilterKernelAdaptive* filterKernelY)
{
SECURE_ASSERT(m_Padding >= 4);
unsigned int destPitch = 0;
fillPadding();
uint8_t* destBuffer = dest->lockRect(dest->getWidth(), dest->getHeight(), destPitch);
Filters<ComponentSIMD<Channels>>::adaptive4x4(filterKernelX, filterKernelY, this->getBytes(), m_Width, m_Height, m_Pitch,
destBuffer, dest->getWidth(), dest->getHeight(), destPitch, dest->getImageSize());
dest->unlockRect();
return true;
}
IMAGEPLANE(bool)::upsampleFilter4x4(ImagePlane<Channels>* dest, const FilterKernelFixed* filterKernelX, const FilterKernelFixed* filterKernelY)
{
SECURE_ASSERT(m_Padding >= 4);
unsigned int destPitch = 0;
fillPadding();
uint8_t* destBuffer = dest->lockRect(dest->getWidth(), dest->getHeight(), destPitch);
Filters<ComponentSIMD<Channels>>::fixed4x4(filterKernelX, filterKernelY, this->getBytes(), m_Width, m_Height, m_Pitch,
destBuffer, dest->getWidth(), dest->getHeight(), destPitch, dest->getImageSize());
dest->unlockRect();
return true;
}
IMAGEPLANE(void)::clearRect(unsigned int sx, unsigned int sy, unsigned int w, unsigned int h, typename primType::asType component)
{
unsigned int destPitch = 0;
uint8_t* destBuffer = lockRect(sx, sy, w, h, destPitch);
for( unsigned int y = 0; y < h; y++ ) {
for( unsigned int x = 0; x < w; x++ ) {
unsigned int outIndex = y * destPitch + x * Channels;
*(typename primType::asType*)&destBuffer[outIndex + 0] = component;
}
}
unlockRect();
}
IMAGEPLANE(void)::clear(typename primType::asType component)
{
clearRect(0, 0, m_Width, m_Height, component);
}
IMAGEPLANE(void)::copyRect(ImagePlane<Channels>* dest, unsigned int sourceX, unsigned int sourceY, unsigned int destX, unsigned int destY, unsigned int width, unsigned int height)
{
if( this == dest && sourceX == destX && sourceY == destY && width == m_Width && height == m_Height ) {
return;
}
unsigned int sourcePitch = 0;
uint8_t* sourceBuffer = lockRect(sourceX, sourceY, width, height, sourcePitch);
unsigned int destPitch = 0;
uint8_t* destBuffer = dest->lockRect(destX, destY, width, height, destPitch);
for( unsigned int y = 0; y < height; y++ ) {
memcpy(destBuffer + y * destPitch, sourceBuffer + y * sourcePitch, width * Channels);
}
}
IMAGEPLANE(void)::copy(ImagePlane<Channels>* dest)
{
copyRect(dest, 0, 0, 0, 0, m_Width, m_Height);
}
template class ImagePlane<1>;
template class ImagePlane<2>;
template class ImagePlane<4>;
Image* Image::create(EImageColorModel colorSpace, unsigned int width, unsigned int height)
{
if( colorModelIsRGBA(colorSpace) ) {
return ImageRGBA::create(width, height, colorSpace == kColorModel_RGBA);
} else if( colorModelIsGrayscale(colorSpace) ) {
return ImageGrayscale::create(width, height);
} else if( colorModelIsYUV(colorSpace) ) {
return ImageYUV::create(width, height);
}
return NULL;
}
Image* Image::create(EImageColorModel colorSpace, unsigned int width, unsigned int height, unsigned int padding, unsigned int alignment)
{
if( colorModelIsRGBA(colorSpace) ) {
return ImageRGBA::create(width, height, padding, alignment, colorSpace == kColorModel_RGBA);
} else if( colorModelIsGrayscale(colorSpace) ) {
return ImageGrayscale::create(width, height, padding, alignment);
} else if( colorModelIsYUV(colorSpace) ) {
return ImageYUV::create(width, height, padding, alignment);
}
return NULL;
}
unsigned int Image::getDownsampleFilterKernelSize(EResizeQuality quality)
{
if( quality == kResizeQuality_Bilinear ) {
return 2;
} else if( quality == kResizeQuality_Low ) {
return 4;
} else if( quality == kResizeQuality_Medium ) {
return 8;
} else if( quality == kResizeQuality_High || quality == kResizeQuality_HighSharp ) {
return 12;
}
ASSERT(0);
return 0;
}
EFilterType Image::getDownsampleFilterKernelType(EResizeQuality quality)
{
if (quality == kResizeQuality_Bilinear) {
return kFilterType_Linear;
} else if( quality == kResizeQuality_Low ) {
return kFilterType_Kaiser;
} else if( quality == kResizeQuality_Medium || quality == kResizeQuality_High ) {
return kFilterType_Lanczos;
} else if( quality == kResizeQuality_HighSharp ) {
return kFilterType_LanczosSharper;
}
ASSERT(0);
return kFilterType_Lanczos;
}
unsigned int Image::getUpsampleFilterKernelSize(EResizeQuality quality)
{
return 4;
}
EFilterType Image::getUpsampleFilterKernelType(EResizeQuality quality)
{
if( quality == kResizeQuality_Low ) {
return kFilterType_MitchellNetravali;
} else if( quality == kResizeQuality_Medium ) {
return kFilterType_MitchellNetravali;
} else if( quality == kResizeQuality_High ) {
return kFilterType_Lanczos;
} else if( quality == kResizeQuality_HighSharp ) {
return kFilterType_LanczosSharper;
}
ASSERT(0);
return kFilterType_MitchellNetravali;
}
bool Image::validateSize(unsigned int width, unsigned int height)
{
if( width < 1 || height < 1 ) {
return false;
}
if( width > 16384 || height > 16384 ) {
return false;
}
if( SafeUMul(width, height) > 8192 * 8192 ) {
return false;
}
return true;
}
void Image::clear(uint8_t r, uint8_t g, uint8_t b, uint8_t a)
{
clearRect(0, 0, getWidth(), getHeight(), r, g, b, a);
}
void Image::copy(Image* dest)
{
copyRect(dest, 0, 0, 0, 0, getWidth(), getHeight());
}
}