irrlicht/source/Irrlicht/CImage.cpp
cutealien 72b1522083 Add IImage::checkDataSizeLimit and make IImage getDataSizeFromFormat return size_t
It's to allow image loader to check for sane limits for image sizes.
Idea came from this patch from sfan5 for Minetest: dbd39120e7
Thought solution is a bit different. 
Image loader checks not yet added (will come soon).
Also note that limit is to s32. While u32 might work mostly it will run into some troubles with color converter for now (which maybe could be changes). Also 2GB ought to be enough for anybody, right?

git-svn-id: svn://svn.code.sf.net/p/irrlicht/code/trunk@6386 dfc29bdd-3216-0410-991c-e03cc46cb475
2022-05-06 19:47:38 +00:00

449 lines
11 KiB
C++

// Copyright (C) 2002-2012 Nikolaus Gebhardt / Thomas Alten
// This file is part of the "Irrlicht Engine".
// For conditions of distribution and use, see copyright notice in irrlicht.h
#include "CImage.h"
#include "irrString.h"
#include "CColorConverter.h"
#include "CBlit.h"
#include "os.h"
#include "SoftwareDriver2_helper.h"
namespace irr
{
namespace video
{
//! Constructor from raw data
CImage::CImage(ECOLOR_FORMAT format, const core::dimension2d<u32>& size, void* data,
bool ownForeignMemory, bool deleteMemory) : IImage(format, size, deleteMemory)
{
if (ownForeignMemory)
{
Data = (u8*)data;
}
else
{
const size_t dataSize = getDataSizeFromFormat(Format, Size.Width, Size.Height);
Data = new u8[align_next(dataSize,16)];
memcpy(Data, data, dataSize);
DeleteMemory = true;
}
}
//! Constructor of empty image
CImage::CImage(ECOLOR_FORMAT format, const core::dimension2d<u32>& size) : IImage(format, size, true)
{
const size_t dataSize = getDataSizeFromFormat(Format, Size.Width, Size.Height);
Data = new u8[align_next(dataSize,16)];
DeleteMemory = true;
}
//! sets a pixel
void CImage::setPixel(u32 x, u32 y, const SColor &color, bool blend)
{
if (x >= Size.Width || y >= Size.Height)
return;
switch(Format)
{
case ECF_A1R5G5B5:
{
u16 * dest = (u16*) (Data + ( y * Pitch ) + ( x << 1 ));
*dest = video::A8R8G8B8toA1R5G5B5( color.color );
} break;
case ECF_R5G6B5:
{
u16 * dest = (u16*) (Data + ( y * Pitch ) + ( x << 1 ));
*dest = video::A8R8G8B8toR5G6B5( color.color );
} break;
case ECF_R8G8B8:
{
u8* dest = Data + ( y * Pitch ) + ( x * 3 );
dest[0] = (u8)color.getRed();
dest[1] = (u8)color.getGreen();
dest[2] = (u8)color.getBlue();
} break;
case ECF_A8R8G8B8:
{
u32 * dest = (u32*) (Data + ( y * Pitch ) + ( x << 2 ));
*dest = blend ? PixelBlend32 ( *dest, color.color ) : color.color;
} break;
IRR_CASE_IIMAGE_COMPRESSED_FORMAT
os::Printer::log("IImage::setPixel method doesn't work with compressed images.", ELL_WARNING);
return;
case ECF_UNKNOWN:
os::Printer::log("IImage::setPixel unknown format.", ELL_WARNING);
return;
default:
break;
}
}
//! returns a pixel
SColor CImage::getPixel(u32 x, u32 y) const
{
if (x >= Size.Width || y >= Size.Height)
return SColor(0);
switch(Format)
{
case ECF_A1R5G5B5:
return A1R5G5B5toA8R8G8B8(((u16*)Data)[y*Size.Width + x]);
case ECF_R5G6B5:
return R5G6B5toA8R8G8B8(((u16*)Data)[y*Size.Width + x]);
case ECF_A8R8G8B8:
return ((u32*)Data)[y*Size.Width + x];
case ECF_R8G8B8:
{
u8* p = Data+(y*3)*Size.Width + (x*3);
return SColor(255,p[0],p[1],p[2]);
}
IRR_CASE_IIMAGE_COMPRESSED_FORMAT
os::Printer::log("IImage::getPixel method doesn't work with compressed images.", ELL_WARNING);
break;
case ECF_UNKNOWN:
os::Printer::log("IImage::getPixel unknown format.", ELL_WARNING);
break;
default:
break;
}
return SColor(0);
}
//! copies this surface into another at given position
void CImage::copyTo(IImage* target, const core::position2d<s32>& pos)
{
if (IImage::isCompressedFormat(Format))
{
os::Printer::log("IImage::copyTo method doesn't work with compressed images.", ELL_WARNING);
return;
}
if (!Blit(BLITTER_TEXTURE, target, 0, &pos, this, 0, 0)
&& target && pos.X == 0 && pos.Y == 0 &&
CColorConverter::canConvertFormat(Format, target->getColorFormat()))
{
// No fast blitting, but copyToScaling uses other color conversions and might work
irr::core::dimension2du dim(target->getDimension());
copyToScaling(target->getData(), dim.Width, dim.Height, target->getColorFormat(), target->getPitch());
}
}
//! copies this surface partially into another at given position
void CImage::copyTo(IImage* target, const core::position2d<s32>& pos, const core::rect<s32>& sourceRect, const core::rect<s32>* clipRect)
{
if (IImage::isCompressedFormat(Format))
{
os::Printer::log("IImage::copyTo method doesn't work with compressed images.", ELL_WARNING);
return;
}
Blit(BLITTER_TEXTURE, target, clipRect, &pos, this, &sourceRect, 0);
}
//! copies this surface into another, using the alpha mask, a cliprect and a color to add with
void CImage::copyToWithAlpha(IImage* target, const core::position2d<s32>& pos, const core::rect<s32>& sourceRect, const SColor &color, const core::rect<s32>* clipRect, bool combineAlpha)
{
if (IImage::isCompressedFormat(Format))
{
os::Printer::log("IImage::copyToWithAlpha method doesn't work with compressed images.", ELL_WARNING);
return;
}
eBlitter op = combineAlpha ? BLITTER_TEXTURE_COMBINE_ALPHA :
color.color == 0xFFFFFFFF ? BLITTER_TEXTURE_ALPHA_BLEND : BLITTER_TEXTURE_ALPHA_COLOR_BLEND;
Blit(op,target, clipRect, &pos, this, &sourceRect, color.color);
}
//! copies this surface into another, scaling it to the target image size
// note: this is very very slow.
void CImage::copyToScaling(void* target, u32 width, u32 height, ECOLOR_FORMAT format, u32 pitch)
{
if (IImage::isCompressedFormat(Format))
{
os::Printer::log("IImage::copyToScaling method doesn't work with compressed images.", ELL_WARNING);
return;
}
if (!target || !width || !height)
return;
const u32 bpp=getBitsPerPixelFromFormat(format)/8;
if (0==pitch)
pitch = width*bpp;
if (Format==format && Size.Width==width && Size.Height==height)
{
if (pitch==Pitch)
{
memcpy(target, Data, height*pitch);
return;
}
else
{
u8* tgtpos = (u8*) target;
u8* srcpos = Data;
const u32 bwidth = width*bpp;
const u32 rest = pitch-bwidth;
for (u32 y=0; y<height; ++y)
{
// copy scanline
memcpy(tgtpos, srcpos, bwidth);
// clear pitch
memset(tgtpos+bwidth, 0, rest);
tgtpos += pitch;
srcpos += Pitch;
}
return;
}
}
// NOTE: Scaling is coded to keep the border pixels intact.
// Alternatively we could for example work with first pixel being taken at half step-size.
// Then we have one more step here and it would be:
// sourceXStep = (f32)(Size.Width-1) / (f32)(width);
// And sx would start at 0.5f + sourceXStep / 2.f;
// Similar for y.
// As scaling is done without any antialiasing it doesn't matter too much which outermost pixels we use and keeping
// border pixels intact is probably mostly better (with AA the other solution would be more correct).
const f32 sourceXStep = width > 1 ? (f32)(Size.Width-1) / (f32)(width-1) : 0.f;
const f32 sourceYStep = height > 1 ? (f32)(Size.Height-1) / (f32)(height-1) : 0.f;
s32 yval=0, syval=0;
f32 sy = 0.5f; // for rounding to nearest pixel
for (u32 y=0; y<height; ++y)
{
f32 sx = 0.5f; // for rounding to nearest pixel
for (u32 x=0; x<width; ++x)
{
CColorConverter::convert_viaFormat(Data+ syval + ((s32)sx)*BytesPerPixel, Format, 1, ((u8*)target)+ yval + (x*bpp), format);
sx+=sourceXStep;
}
sy+=sourceYStep;
syval=(s32)(sy)*Pitch;
yval+=pitch;
}
}
//! copies this surface into another, scaling it to the target image size
// note: this is very very slow.
void CImage::copyToScaling(IImage* target)
{
if (IImage::isCompressedFormat(Format))
{
os::Printer::log("IImage::copyToScaling method doesn't work with compressed images.", ELL_WARNING);
return;
}
if (!target)
return;
const core::dimension2d<u32>& targetSize = target->getDimension();
if (targetSize==Size)
{
copyTo(target);
return;
}
copyToScaling(target->getData(), targetSize.Width, targetSize.Height, target->getColorFormat());
}
//! copies this surface into another, scaling it to fit it.
void CImage::copyToScalingBoxFilter(IImage* target, s32 bias, bool blend)
{
if (IImage::isCompressedFormat(Format))
{
os::Printer::log("IImage::copyToScalingBoxFilter method doesn't work with compressed images.", ELL_WARNING);
return;
}
const core::dimension2d<u32> destSize = target->getDimension();
const f32 sourceXStep = (f32) Size.Width / (f32) destSize.Width;
const f32 sourceYStep = (f32) Size.Height / (f32) destSize.Height;
target->getData();
const s32 fx = core::ceil32( sourceXStep );
const s32 fy = core::ceil32( sourceYStep );
f32 sx;
f32 sy;
sy = 0.f;
for ( u32 y = 0; y != destSize.Height; ++y )
{
sx = 0.f;
for ( u32 x = 0; x != destSize.Width; ++x )
{
target->setPixel( x, y,
getPixelBox( core::floor32(sx), core::floor32(sy), fx, fy, bias ), blend );
sx += sourceXStep;
}
sy += sourceYStep;
}
}
//! fills the surface with given color
void CImage::fill(const SColor &color)
{
if (IImage::isCompressedFormat(Format))
{
os::Printer::log("IImage::fill method doesn't work with compressed images.", ELL_WARNING);
return;
}
u32 c;
switch ( Format )
{
case ECF_A1R5G5B5:
c = color.toA1R5G5B5();
c |= c << 16;
break;
case ECF_R5G6B5:
c = video::A8R8G8B8toR5G6B5( color.color );
c |= c << 16;
break;
case ECF_A8R8G8B8:
c = color.color;
break;
case ECF_R8G8B8:
{
u8 rgb[3];
CColorConverter::convert_A8R8G8B8toR8G8B8(&color, 1, rgb);
const size_t size = getImageDataSizeInBytes();
for (size_t i=0; i<size; i+=3)
{
memcpy(Data+i, rgb, 3);
}
return;
}
break;
default:
// TODO: Handle other formats
return;
}
memset32( Data, c, getImageDataSizeInBytes() );
}
void CImage::flip(bool topBottom, bool leftRight)
{
if ( !topBottom && !leftRight)
return;
const core::dimension2du dim(getDimension());
if ( dim.Width == 0 || dim.Height == 0 )
return;
u8* data = (u8*)getData();
if (!data)
return;
const u32 bpp = getBytesPerPixel();
const u32 pitch = getPitch();
if ( topBottom )
{
for ( u32 i=0; i<dim.Height/2; ++i)
{
// Reverse bottom/top lines
u8* l1 = data+i*pitch;
u8* l2 = data+(dim.Height-1-i)*pitch;
for ( u32 b=0; b<pitch; ++b)
{
irr::u8 dummy = *l1;
*l1 = *l2;
*l2 = dummy;
++l1;
++l2;
}
}
}
if ( leftRight )
{
for ( u32 i=0; i<dim.Height; ++i)
{
// Reverse left/right for each line
u8* l1 = data+i*pitch;
u8* l2 = l1+(dim.Width-1)*bpp;
for ( u32 p=0; p<dim.Width/2; ++p)
{
for ( u32 b=0; b<bpp; ++b)
{
irr::u8 dummy = l1[b];
l1[b] = l2[b];
l2[b] = dummy;
}
l1 += bpp;
l2 -= bpp;
}
}
}
}
//! get a filtered pixel
inline SColor CImage::getPixelBox( const s32 x, const s32 y, const s32 fx, const s32 fy, const s32 bias ) const
{
/*
if (IImage::isCompressedFormat(Format))
{
os::Printer::log("IImage::getPixelBox method doesn't work with compressed images.", ELL_WARNING);
return SColor(0);
}
*/
SColor c;
s32 a = 0, r = 0, g = 0, b = 0;
for ( s32 dx = 0; dx != fx; ++dx )
{
for ( s32 dy = 0; dy != fy; ++dy )
{
c = getPixel( core::s32_min ( x + dx, Size.Width - 1 ) ,
core::s32_min ( y + dy, Size.Height - 1 )
);
a += c.getAlpha();
r += c.getRed();
g += c.getGreen();
b += c.getBlue();
}
}
const s32 sdiv = fx * fy; // s32_log2_s32(fx * fy);
a = core::s32_clamp( ( a / sdiv ) + bias, 0, 255 );
r = core::s32_clamp( ( r / sdiv ) + bias, 0, 255 );
g = core::s32_clamp( ( g / sdiv ) + bias, 0, 255 );
b = core::s32_clamp( ( b / sdiv ) + bias, 0, 255 );
c.set( a, r, g, b );
return c;
}
} // end namespace video
} // end namespace irr