mirror of
https://github.com/minetest/irrlicht.git
synced 2024-11-14 19:53:46 +01:00
72b1522083
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
449 lines
11 KiB
C++
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
|