2232 lines
58 KiB
C++
Executable File
2232 lines
58 KiB
C++
Executable File
/**
|
|
* @file llimage.cpp
|
|
* @brief Base class for images.
|
|
*
|
|
* $LicenseInfo:firstyear=2001&license=viewerlgpl$
|
|
* Second Life Viewer Source Code
|
|
* Copyright (C) 2010, Linden Research, Inc.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation;
|
|
* version 2.1 of the License only.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*
|
|
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
|
|
* $/LicenseInfo$
|
|
*/
|
|
|
|
#include "linden_common.h"
|
|
|
|
#include "llimage.h"
|
|
|
|
#include "llmath.h"
|
|
#include "v3color.h"
|
|
#include "v4coloru.h"
|
|
#include "m3math.h"
|
|
#include "v3math.h"
|
|
|
|
#include "llimagebmp.h"
|
|
#include "llimagetga.h"
|
|
#include "llimagej2c.h"
|
|
#include "llimagejpeg.h"
|
|
#include "llimagepng.h"
|
|
#include "llimagedxt.h"
|
|
#include "llimageworker.h"
|
|
#include "llmemory.h"
|
|
|
|
//---------------------------------------------------------------------------
|
|
// LLImage
|
|
//---------------------------------------------------------------------------
|
|
|
|
//static
|
|
std::string LLImage::sLastErrorMessage;
|
|
LLMutex* LLImage::sMutex = NULL;
|
|
bool LLImage::sUseNewByteRange = false;
|
|
S32 LLImage::sMinimalReverseByteRangePercent = 75;
|
|
LLPrivateMemoryPool* LLImageBase::sPrivatePoolp = NULL ;
|
|
|
|
//static
|
|
void LLImage::initClass(bool use_new_byte_range, S32 minimal_reverse_byte_range_percent)
|
|
{
|
|
sUseNewByteRange = use_new_byte_range;
|
|
sMinimalReverseByteRangePercent = minimal_reverse_byte_range_percent;
|
|
sMutex = new LLMutex(NULL);
|
|
|
|
LLImageBase::createPrivatePool() ;
|
|
}
|
|
|
|
//static
|
|
void LLImage::cleanupClass()
|
|
{
|
|
delete sMutex;
|
|
sMutex = NULL;
|
|
|
|
LLImageBase::destroyPrivatePool() ;
|
|
}
|
|
|
|
//static
|
|
const std::string& LLImage::getLastError()
|
|
{
|
|
static const std::string noerr("No Error");
|
|
return sLastErrorMessage.empty() ? noerr : sLastErrorMessage;
|
|
}
|
|
|
|
//static
|
|
void LLImage::setLastError(const std::string& message)
|
|
{
|
|
LLMutexLock m(sMutex);
|
|
sLastErrorMessage = message;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// LLImageBase
|
|
//---------------------------------------------------------------------------
|
|
|
|
LLImageBase::LLImageBase()
|
|
: mData(NULL),
|
|
mDataSize(0),
|
|
mWidth(0),
|
|
mHeight(0),
|
|
mComponents(0),
|
|
mBadBufferAllocation(false),
|
|
mAllowOverSize(false),
|
|
mHistoRed(NULL),
|
|
mHistoGreen(NULL),
|
|
mHistoBlue(NULL),
|
|
mHistoBrightness(NULL),
|
|
mVignetteMode(VIGNETTE_MODE_NONE),
|
|
mVignetteGamma(1.0),
|
|
mVignetteMin(0.0)
|
|
{
|
|
}
|
|
|
|
// virtual
|
|
LLImageBase::~LLImageBase()
|
|
{
|
|
deleteData(); // virtual
|
|
ll_aligned_free_16(mHistoRed);
|
|
ll_aligned_free_16(mHistoGreen);
|
|
ll_aligned_free_16(mHistoBlue);
|
|
ll_aligned_free_16(mHistoBrightness);
|
|
}
|
|
|
|
//static
|
|
void LLImageBase::createPrivatePool()
|
|
{
|
|
if(!sPrivatePoolp)
|
|
{
|
|
sPrivatePoolp = LLPrivateMemoryPoolManager::getInstance()->newPool(LLPrivateMemoryPool::STATIC_THREADED) ;
|
|
}
|
|
}
|
|
|
|
//static
|
|
void LLImageBase::destroyPrivatePool()
|
|
{
|
|
if(sPrivatePoolp)
|
|
{
|
|
LLPrivateMemoryPoolManager::getInstance()->deletePool(sPrivatePoolp) ;
|
|
sPrivatePoolp = NULL ;
|
|
}
|
|
}
|
|
|
|
// virtual
|
|
void LLImageBase::dump()
|
|
{
|
|
llinfos << "LLImageBase mComponents " << mComponents
|
|
<< " mData " << mData
|
|
<< " mDataSize " << mDataSize
|
|
<< " mWidth " << mWidth
|
|
<< " mHeight " << mHeight
|
|
<< llendl;
|
|
}
|
|
|
|
// virtual
|
|
void LLImageBase::sanityCheck()
|
|
{
|
|
if (mWidth > MAX_IMAGE_SIZE
|
|
|| mHeight > MAX_IMAGE_SIZE
|
|
|| mDataSize > (S32)MAX_IMAGE_DATA_SIZE
|
|
|| mComponents > (S8)MAX_IMAGE_COMPONENTS
|
|
)
|
|
{
|
|
llerrs << "Failed LLImageBase::sanityCheck "
|
|
<< "width " << mWidth
|
|
<< "height " << mHeight
|
|
<< "datasize " << mDataSize
|
|
<< "components " << mComponents
|
|
<< "data " << mData
|
|
<< llendl;
|
|
}
|
|
}
|
|
|
|
// virtual
|
|
void LLImageBase::deleteData()
|
|
{
|
|
FREE_MEM(sPrivatePoolp, mData) ;
|
|
mData = NULL;
|
|
mDataSize = 0;
|
|
}
|
|
|
|
// virtual
|
|
U8* LLImageBase::allocateData(S32 size)
|
|
{
|
|
if (size < 0)
|
|
{
|
|
size = mWidth * mHeight * mComponents;
|
|
if (size <= 0)
|
|
{
|
|
llerrs << llformat("LLImageBase::allocateData called with bad dimensions: %dx%dx%d",mWidth,mHeight,(S32)mComponents) << llendl;
|
|
}
|
|
}
|
|
|
|
//make this function thread-safe.
|
|
static const U32 MAX_BUFFER_SIZE = 4096 * 4096 * 16 ; //256 MB
|
|
if (size < 1 || size > MAX_BUFFER_SIZE)
|
|
{
|
|
llinfos << "width: " << mWidth << " height: " << mHeight << " components: " << mComponents << llendl ;
|
|
if(mAllowOverSize)
|
|
{
|
|
llinfos << "Oversize: " << size << llendl ;
|
|
}
|
|
else
|
|
{
|
|
llerrs << "LLImageBase::allocateData: bad size: " << size << llendl;
|
|
}
|
|
}
|
|
if (!mData || size != mDataSize)
|
|
{
|
|
deleteData(); // virtual
|
|
mBadBufferAllocation = false ;
|
|
mData = (U8*)ALLOCATE_MEM(sPrivatePoolp, size);
|
|
if (!mData)
|
|
{
|
|
llwarns << "Failed to allocate image data size [" << size << "]" << llendl;
|
|
size = 0 ;
|
|
mWidth = mHeight = 0 ;
|
|
mBadBufferAllocation = true ;
|
|
}
|
|
mDataSize = size;
|
|
}
|
|
|
|
return mData;
|
|
}
|
|
|
|
// virtual
|
|
U8* LLImageBase::reallocateData(S32 size)
|
|
{
|
|
U8 *new_datap = (U8*)ALLOCATE_MEM(sPrivatePoolp, size);
|
|
if (!new_datap)
|
|
{
|
|
llwarns << "Out of memory in LLImageBase::reallocateData" << llendl;
|
|
return 0;
|
|
}
|
|
if (mData)
|
|
{
|
|
S32 bytes = llmin(mDataSize, size);
|
|
memcpy(new_datap, mData, bytes); /* Flawfinder: ignore */
|
|
FREE_MEM(sPrivatePoolp, mData) ;
|
|
}
|
|
mData = new_datap;
|
|
mDataSize = size;
|
|
return mData;
|
|
}
|
|
|
|
const U8* LLImageBase::getData() const
|
|
{
|
|
if(mBadBufferAllocation)
|
|
{
|
|
llerrs << "Bad memory allocation for the image buffer!" << llendl ;
|
|
}
|
|
|
|
return mData;
|
|
} // read only
|
|
|
|
U8* LLImageBase::getData()
|
|
{
|
|
if(mBadBufferAllocation)
|
|
{
|
|
llerrs << "Bad memory allocation for the image buffer!" << llendl ;
|
|
}
|
|
|
|
return mData;
|
|
}
|
|
|
|
bool LLImageBase::isBufferInvalid()
|
|
{
|
|
return mBadBufferAllocation || mData == NULL ;
|
|
}
|
|
|
|
void LLImageBase::setSize(S32 width, S32 height, S32 ncomponents)
|
|
{
|
|
mWidth = width;
|
|
mHeight = height;
|
|
mComponents = ncomponents;
|
|
}
|
|
|
|
U8* LLImageBase::allocateDataSize(S32 width, S32 height, S32 ncomponents, S32 size)
|
|
{
|
|
setSize(width, height, ncomponents);
|
|
return allocateData(size); // virtual
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
// LLImageRaw
|
|
//---------------------------------------------------------------------------
|
|
|
|
S32 LLImageRaw::sGlobalRawMemory = 0;
|
|
S32 LLImageRaw::sRawImageCount = 0;
|
|
|
|
LLImageRaw::LLImageRaw()
|
|
: LLImageBase()
|
|
{
|
|
++sRawImageCount;
|
|
}
|
|
|
|
LLImageRaw::LLImageRaw(U16 width, U16 height, S8 components)
|
|
: LLImageBase()
|
|
{
|
|
//llassert( S32(width) * S32(height) * S32(components) <= MAX_IMAGE_DATA_SIZE );
|
|
allocateDataSize(width, height, components);
|
|
++sRawImageCount;
|
|
}
|
|
|
|
LLImageRaw::LLImageRaw(U8 *data, U16 width, U16 height, S8 components, bool no_copy)
|
|
: LLImageBase()
|
|
{
|
|
|
|
if(no_copy)
|
|
{
|
|
setDataAndSize(data, width, height, components);
|
|
}
|
|
else if(allocateDataSize(width, height, components))
|
|
{
|
|
memcpy(getData(), data, width*height*components);
|
|
}
|
|
++sRawImageCount;
|
|
}
|
|
|
|
//LLImageRaw::LLImageRaw(const std::string& filename, bool j2c_lowest_mip_only)
|
|
// : LLImageBase()
|
|
//{
|
|
// createFromFile(filename, j2c_lowest_mip_only);
|
|
//}
|
|
|
|
LLImageRaw::~LLImageRaw()
|
|
{
|
|
// NOTE: ~LLimageBase() call to deleteData() calls LLImageBase::deleteData()
|
|
// NOT LLImageRaw::deleteData()
|
|
deleteData();
|
|
--sRawImageCount;
|
|
}
|
|
|
|
// virtual
|
|
U8* LLImageRaw::allocateData(S32 size)
|
|
{
|
|
U8* res = LLImageBase::allocateData(size);
|
|
sGlobalRawMemory += getDataSize();
|
|
return res;
|
|
}
|
|
|
|
// virtual
|
|
U8* LLImageRaw::reallocateData(S32 size)
|
|
{
|
|
sGlobalRawMemory -= getDataSize();
|
|
U8* res = LLImageBase::reallocateData(size);
|
|
sGlobalRawMemory += getDataSize();
|
|
return res;
|
|
}
|
|
|
|
// virtual
|
|
void LLImageRaw::deleteData()
|
|
{
|
|
sGlobalRawMemory -= getDataSize();
|
|
LLImageBase::deleteData();
|
|
}
|
|
|
|
void LLImageRaw::setDataAndSize(U8 *data, S32 width, S32 height, S8 components)
|
|
{
|
|
if(data == getData())
|
|
{
|
|
return ;
|
|
}
|
|
|
|
deleteData();
|
|
|
|
LLImageBase::setSize(width, height, components) ;
|
|
LLImageBase::setDataAndSize(data, width * height * components) ;
|
|
|
|
sGlobalRawMemory += getDataSize();
|
|
}
|
|
|
|
BOOL LLImageRaw::resize(U16 width, U16 height, S8 components)
|
|
{
|
|
if ((getWidth() == width) && (getHeight() == height) && (getComponents() == components))
|
|
{
|
|
return TRUE;
|
|
}
|
|
// Reallocate the data buffer.
|
|
deleteData();
|
|
|
|
allocateDataSize(width,height,components);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL LLImageRaw::setSubImage(U32 x_pos, U32 y_pos, U32 width, U32 height,
|
|
const U8 *data, U32 stride, BOOL reverse_y)
|
|
{
|
|
if (!getData())
|
|
{
|
|
return FALSE;
|
|
}
|
|
if (!data)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
// Should do some simple bounds checking
|
|
|
|
U32 i;
|
|
for (i = 0; i < height; i++)
|
|
{
|
|
const U32 row = reverse_y ? height - 1 - i : i;
|
|
const U32 from_offset = row * ((stride == 0) ? width*getComponents() : stride);
|
|
const U32 to_offset = (y_pos + i)*getWidth() + x_pos;
|
|
memcpy(getData() + to_offset*getComponents(), /* Flawfinder: ignore */
|
|
data + from_offset, getComponents()*width);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
void LLImageRaw::clear(U8 r, U8 g, U8 b, U8 a)
|
|
{
|
|
llassert( getComponents() <= 4 );
|
|
// This is fairly bogus, but it'll do for now.
|
|
U8 *pos = getData();
|
|
U32 x, y;
|
|
for (x = 0; x < getWidth(); x++)
|
|
{
|
|
for (y = 0; y < getHeight(); y++)
|
|
{
|
|
*pos = r;
|
|
pos++;
|
|
if (getComponents() == 1)
|
|
{
|
|
continue;
|
|
}
|
|
*pos = g;
|
|
pos++;
|
|
if (getComponents() == 2)
|
|
{
|
|
continue;
|
|
}
|
|
*pos = b;
|
|
pos++;
|
|
if (getComponents() == 3)
|
|
{
|
|
continue;
|
|
}
|
|
*pos = a;
|
|
pos++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Reverses the order of the rows in the image
|
|
void LLImageRaw::verticalFlip()
|
|
{
|
|
S32 row_bytes = getWidth() * getComponents();
|
|
llassert(row_bytes > 0);
|
|
std::vector<U8> line_buffer(row_bytes);
|
|
S32 mid_row = getHeight() / 2;
|
|
for( S32 row = 0; row < mid_row; row++ )
|
|
{
|
|
U8* row_a_data = getData() + row * row_bytes;
|
|
U8* row_b_data = getData() + (getHeight() - 1 - row) * row_bytes;
|
|
memcpy( &line_buffer[0], row_a_data, row_bytes );
|
|
memcpy( row_a_data, row_b_data, row_bytes );
|
|
memcpy( row_b_data, &line_buffer[0], row_bytes );
|
|
}
|
|
}
|
|
|
|
|
|
void LLImageRaw::expandToPowerOfTwo(S32 max_dim, BOOL scale_image)
|
|
{
|
|
// Find new sizes
|
|
S32 new_width = MIN_IMAGE_SIZE;
|
|
S32 new_height = MIN_IMAGE_SIZE;
|
|
|
|
while( (new_width < getWidth()) && (new_width < max_dim) )
|
|
{
|
|
new_width <<= 1;
|
|
}
|
|
|
|
while( (new_height < getHeight()) && (new_height < max_dim) )
|
|
{
|
|
new_height <<= 1;
|
|
}
|
|
|
|
scale( new_width, new_height, scale_image );
|
|
}
|
|
|
|
void LLImageRaw::contractToPowerOfTwo(S32 max_dim, BOOL scale_image)
|
|
{
|
|
// Find new sizes
|
|
S32 new_width = max_dim;
|
|
S32 new_height = max_dim;
|
|
|
|
while( (new_width > getWidth()) && (new_width > MIN_IMAGE_SIZE) )
|
|
{
|
|
new_width >>= 1;
|
|
}
|
|
|
|
while( (new_height > getHeight()) && (new_height > MIN_IMAGE_SIZE) )
|
|
{
|
|
new_height >>= 1;
|
|
}
|
|
|
|
scale( new_width, new_height, scale_image );
|
|
}
|
|
|
|
void LLImageRaw::biasedScaleToPowerOfTwo(S32 max_dim)
|
|
{
|
|
// Strong bias towards rounding down (to save bandwidth)
|
|
// No bias would mean THRESHOLD == 1.5f;
|
|
const F32 THRESHOLD = 1.75f;
|
|
|
|
// Find new sizes
|
|
S32 larger_w = max_dim; // 2^n >= mWidth
|
|
S32 smaller_w = max_dim; // 2^(n-1) <= mWidth
|
|
while( (smaller_w > getWidth()) && (smaller_w > MIN_IMAGE_SIZE) )
|
|
{
|
|
larger_w = smaller_w;
|
|
smaller_w >>= 1;
|
|
}
|
|
S32 new_width = ( (F32)getWidth() / smaller_w > THRESHOLD ) ? larger_w : smaller_w;
|
|
|
|
|
|
S32 larger_h = max_dim; // 2^m >= mHeight
|
|
S32 smaller_h = max_dim; // 2^(m-1) <= mHeight
|
|
while( (smaller_h > getHeight()) && (smaller_h > MIN_IMAGE_SIZE) )
|
|
{
|
|
larger_h = smaller_h;
|
|
smaller_h >>= 1;
|
|
}
|
|
S32 new_height = ( (F32)getHeight() / smaller_h > THRESHOLD ) ? larger_h : smaller_h;
|
|
|
|
|
|
scale( new_width, new_height );
|
|
}
|
|
|
|
|
|
|
|
|
|
// Calculates (U8)(255*(a/255.f)*(b/255.f) + 0.5f). Thanks, Jim Blinn!
|
|
inline U8 LLImageRaw::fastFractionalMult( U8 a, U8 b )
|
|
{
|
|
U32 i = a * b + 128;
|
|
return U8((i + (i>>8)) >> 8);
|
|
}
|
|
|
|
|
|
void LLImageRaw::composite( LLImageRaw* src )
|
|
{
|
|
LLImageRaw* dst = this; // Just for clarity.
|
|
|
|
llassert(3 == src->getComponents());
|
|
llassert(3 == dst->getComponents());
|
|
|
|
if( 3 == dst->getComponents() )
|
|
{
|
|
if( (src->getWidth() == dst->getWidth()) && (src->getHeight() == dst->getHeight()) )
|
|
{
|
|
// No scaling needed
|
|
if( 3 == src->getComponents() )
|
|
{
|
|
copyUnscaled( src ); // alpha is one so just copy the data.
|
|
}
|
|
else
|
|
{
|
|
compositeUnscaled4onto3( src );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( 3 == src->getComponents() )
|
|
{
|
|
copyScaled( src ); // alpha is one so just copy the data.
|
|
}
|
|
else
|
|
{
|
|
compositeScaled4onto3( src );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Src and dst can be any size. Src has 4 components. Dst has 3 components.
|
|
void LLImageRaw::compositeScaled4onto3(LLImageRaw* src)
|
|
{
|
|
llinfos << "compositeScaled4onto3" << llendl;
|
|
|
|
LLImageRaw* dst = this; // Just for clarity.
|
|
|
|
llassert( (4 == src->getComponents()) && (3 == dst->getComponents()) );
|
|
|
|
S32 temp_data_size = src->getWidth() * dst->getHeight() * src->getComponents();
|
|
llassert_always(temp_data_size > 0);
|
|
std::vector<U8> temp_buffer(temp_data_size);
|
|
|
|
// Vertical: scale but no composite
|
|
for( S32 col = 0; col < src->getWidth(); col++ )
|
|
{
|
|
copyLineScaled( src->getData() + (src->getComponents() * col), &temp_buffer[0] + (src->getComponents() * col), src->getHeight(), dst->getHeight(), src->getWidth(), src->getWidth() );
|
|
}
|
|
|
|
// Horizontal: scale and composite
|
|
for( S32 row = 0; row < dst->getHeight(); row++ )
|
|
{
|
|
compositeRowScaled4onto3( &temp_buffer[0] + (src->getComponents() * src->getWidth() * row), dst->getData() + (dst->getComponents() * dst->getWidth() * row), src->getWidth(), dst->getWidth() );
|
|
}
|
|
}
|
|
|
|
|
|
// Src and dst are same size. Src has 4 components. Dst has 3 components.
|
|
void LLImageRaw::compositeUnscaled4onto3( LLImageRaw* src )
|
|
{
|
|
/*
|
|
//test fastFractionalMult()
|
|
{
|
|
U8 i = 255;
|
|
U8 j = 255;
|
|
do
|
|
{
|
|
do
|
|
{
|
|
llassert( fastFractionalMult(i, j) == (U8)(255*(i/255.f)*(j/255.f) + 0.5f) );
|
|
} while( j-- );
|
|
} while( i-- );
|
|
}
|
|
*/
|
|
|
|
LLImageRaw* dst = this; // Just for clarity.
|
|
|
|
llassert( (3 == src->getComponents()) || (4 == src->getComponents()) );
|
|
llassert( (src->getWidth() == dst->getWidth()) && (src->getHeight() == dst->getHeight()) );
|
|
|
|
|
|
U8* src_data = src->getData();
|
|
U8* dst_data = dst->getData();
|
|
S32 pixels = getWidth() * getHeight();
|
|
while( pixels-- )
|
|
{
|
|
U8 alpha = src_data[3];
|
|
if( alpha )
|
|
{
|
|
if( 255 == alpha )
|
|
{
|
|
dst_data[0] = src_data[0];
|
|
dst_data[1] = src_data[1];
|
|
dst_data[2] = src_data[2];
|
|
}
|
|
else
|
|
{
|
|
|
|
U8 transparency = 255 - alpha;
|
|
dst_data[0] = fastFractionalMult( dst_data[0], transparency ) + fastFractionalMult( src_data[0], alpha );
|
|
dst_data[1] = fastFractionalMult( dst_data[1], transparency ) + fastFractionalMult( src_data[1], alpha );
|
|
dst_data[2] = fastFractionalMult( dst_data[2], transparency ) + fastFractionalMult( src_data[2], alpha );
|
|
}
|
|
}
|
|
|
|
src_data += 4;
|
|
dst_data += 3;
|
|
}
|
|
}
|
|
|
|
void LLImageRaw::copyUnscaledAlphaMask( LLImageRaw* src, const LLColor4U& fill)
|
|
{
|
|
LLImageRaw* dst = this; // Just for clarity.
|
|
|
|
llassert( 1 == src->getComponents() );
|
|
llassert( 4 == dst->getComponents() );
|
|
llassert( (src->getWidth() == dst->getWidth()) && (src->getHeight() == dst->getHeight()) );
|
|
|
|
S32 pixels = getWidth() * getHeight();
|
|
U8* src_data = src->getData();
|
|
U8* dst_data = dst->getData();
|
|
for ( S32 i = 0; i < pixels; i++ )
|
|
{
|
|
dst_data[0] = fill.mV[0];
|
|
dst_data[1] = fill.mV[1];
|
|
dst_data[2] = fill.mV[2];
|
|
dst_data[3] = src_data[0];
|
|
src_data += 1;
|
|
dst_data += 4;
|
|
}
|
|
}
|
|
|
|
|
|
// Fill the buffer with a constant color
|
|
void LLImageRaw::fill( const LLColor4U& color )
|
|
{
|
|
S32 pixels = getWidth() * getHeight();
|
|
if( 4 == getComponents() )
|
|
{
|
|
U32* data = (U32*) getData();
|
|
for( S32 i = 0; i < pixels; i++ )
|
|
{
|
|
data[i] = color.mAll;
|
|
}
|
|
}
|
|
else
|
|
if( 3 == getComponents() )
|
|
{
|
|
U8* data = getData();
|
|
for( S32 i = 0; i < pixels; i++ )
|
|
{
|
|
data[0] = color.mV[0];
|
|
data[1] = color.mV[1];
|
|
data[2] = color.mV[2];
|
|
data += 3;
|
|
}
|
|
}
|
|
}
|
|
|
|
LLPointer<LLImageRaw> LLImageRaw::duplicate()
|
|
{
|
|
if(getNumRefs() < 2)
|
|
{
|
|
return this; //nobody else refences to this image, no need to duplicate.
|
|
}
|
|
|
|
//make a duplicate
|
|
LLPointer<LLImageRaw> dup = new LLImageRaw(getData(), getWidth(), getHeight(), getComponents());
|
|
return dup;
|
|
}
|
|
|
|
// Src and dst can be any size. Src and dst can each have 3 or 4 components.
|
|
void LLImageRaw::copy(LLImageRaw* src)
|
|
{
|
|
if (!src)
|
|
{
|
|
llwarns << "LLImageRaw::copy called with a null src pointer" << llendl;
|
|
return;
|
|
}
|
|
|
|
LLImageRaw* dst = this; // Just for clarity.
|
|
|
|
if( (src->getWidth() == dst->getWidth()) && (src->getHeight() == dst->getHeight()) )
|
|
{
|
|
// No scaling needed
|
|
if( src->getComponents() == dst->getComponents() )
|
|
{
|
|
copyUnscaled( src );
|
|
}
|
|
else
|
|
if( 3 == src->getComponents() )
|
|
{
|
|
copyUnscaled3onto4( src );
|
|
}
|
|
else
|
|
{
|
|
// 4 == src->getComponents()
|
|
copyUnscaled4onto3( src );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Scaling needed
|
|
// No scaling needed
|
|
if( src->getComponents() == dst->getComponents() )
|
|
{
|
|
copyScaled( src );
|
|
}
|
|
else
|
|
if( 3 == src->getComponents() )
|
|
{
|
|
copyScaled3onto4( src );
|
|
}
|
|
else
|
|
{
|
|
// 4 == src->getComponents()
|
|
copyScaled4onto3( src );
|
|
}
|
|
}
|
|
}
|
|
|
|
// Src and dst are same size. Src and dst have same number of components.
|
|
void LLImageRaw::copyUnscaled(LLImageRaw* src)
|
|
{
|
|
LLImageRaw* dst = this; // Just for clarity.
|
|
|
|
llassert( (1 == src->getComponents()) || (3 == src->getComponents()) || (4 == src->getComponents()) );
|
|
llassert( src->getComponents() == dst->getComponents() );
|
|
llassert( (src->getWidth() == dst->getWidth()) && (src->getHeight() == dst->getHeight()) );
|
|
|
|
memcpy( dst->getData(), src->getData(), getWidth() * getHeight() * getComponents() ); /* Flawfinder: ignore */
|
|
}
|
|
|
|
|
|
// Src and dst can be any size. Src has 3 components. Dst has 4 components.
|
|
void LLImageRaw::copyScaled3onto4(LLImageRaw* src)
|
|
{
|
|
llassert( (3 == src->getComponents()) && (4 == getComponents()) );
|
|
|
|
// Slow, but simple. Optimize later if needed.
|
|
LLImageRaw temp( src->getWidth(), src->getHeight(), 4);
|
|
temp.copyUnscaled3onto4( src );
|
|
copyScaled( &temp );
|
|
}
|
|
|
|
|
|
// Src and dst can be any size. Src has 4 components. Dst has 3 components.
|
|
void LLImageRaw::copyScaled4onto3(LLImageRaw* src)
|
|
{
|
|
llassert( (4 == src->getComponents()) && (3 == getComponents()) );
|
|
|
|
// Slow, but simple. Optimize later if needed.
|
|
LLImageRaw temp( src->getWidth(), src->getHeight(), 3);
|
|
temp.copyUnscaled4onto3( src );
|
|
copyScaled( &temp );
|
|
}
|
|
|
|
|
|
// Src and dst are same size. Src has 4 components. Dst has 3 components.
|
|
void LLImageRaw::copyUnscaled4onto3( LLImageRaw* src )
|
|
{
|
|
LLImageRaw* dst = this; // Just for clarity.
|
|
|
|
llassert( (3 == dst->getComponents()) && (4 == src->getComponents()) );
|
|
llassert( (src->getWidth() == dst->getWidth()) && (src->getHeight() == dst->getHeight()) );
|
|
|
|
S32 pixels = getWidth() * getHeight();
|
|
U8* src_data = src->getData();
|
|
U8* dst_data = dst->getData();
|
|
for( S32 i=0; i<pixels; i++ )
|
|
{
|
|
dst_data[0] = src_data[0];
|
|
dst_data[1] = src_data[1];
|
|
dst_data[2] = src_data[2];
|
|
src_data += 4;
|
|
dst_data += 3;
|
|
}
|
|
}
|
|
|
|
|
|
// Src and dst are same size. Src has 3 components. Dst has 4 components.
|
|
void LLImageRaw::copyUnscaled3onto4( LLImageRaw* src )
|
|
{
|
|
LLImageRaw* dst = this; // Just for clarity.
|
|
llassert( 3 == src->getComponents() );
|
|
llassert( 4 == dst->getComponents() );
|
|
llassert( (src->getWidth() == dst->getWidth()) && (src->getHeight() == dst->getHeight()) );
|
|
|
|
S32 pixels = getWidth() * getHeight();
|
|
U8* src_data = src->getData();
|
|
U8* dst_data = dst->getData();
|
|
for( S32 i=0; i<pixels; i++ )
|
|
{
|
|
dst_data[0] = src_data[0];
|
|
dst_data[1] = src_data[1];
|
|
dst_data[2] = src_data[2];
|
|
dst_data[3] = 255;
|
|
src_data += 3;
|
|
dst_data += 4;
|
|
}
|
|
}
|
|
|
|
|
|
// Src and dst can be any size. Src and dst have same number of components.
|
|
void LLImageRaw::copyScaled( LLImageRaw* src )
|
|
{
|
|
LLImageRaw* dst = this; // Just for clarity.
|
|
|
|
llassert_always( (1 == src->getComponents()) || (3 == src->getComponents()) || (4 == src->getComponents()) );
|
|
llassert_always( src->getComponents() == dst->getComponents() );
|
|
|
|
if( (src->getWidth() == dst->getWidth()) && (src->getHeight() == dst->getHeight()) )
|
|
{
|
|
memcpy( dst->getData(), src->getData(), getWidth() * getHeight() * getComponents() ); /* Flawfinder: ignore */
|
|
return;
|
|
}
|
|
|
|
S32 temp_data_size = src->getWidth() * dst->getHeight() * getComponents();
|
|
llassert_always(temp_data_size > 0);
|
|
std::vector<U8> temp_buffer(temp_data_size);
|
|
|
|
// Vertical
|
|
for( S32 col = 0; col < src->getWidth(); col++ )
|
|
{
|
|
copyLineScaled( src->getData() + (getComponents() * col), &temp_buffer[0] + (getComponents() * col), src->getHeight(), dst->getHeight(), src->getWidth(), src->getWidth() );
|
|
}
|
|
|
|
// Horizontal
|
|
for( S32 row = 0; row < dst->getHeight(); row++ )
|
|
{
|
|
copyLineScaled( &temp_buffer[0] + (getComponents() * src->getWidth() * row), dst->getData() + (getComponents() * dst->getWidth() * row), src->getWidth(), dst->getWidth(), 1, 1 );
|
|
}
|
|
}
|
|
|
|
|
|
BOOL LLImageRaw::scale( S32 new_width, S32 new_height, BOOL scale_image_data )
|
|
{
|
|
llassert((1 == getComponents()) || (3 == getComponents()) || (4 == getComponents()) );
|
|
|
|
S32 old_width = getWidth();
|
|
S32 old_height = getHeight();
|
|
|
|
if( (old_width == new_width) && (old_height == new_height) )
|
|
{
|
|
return TRUE; // Nothing to do.
|
|
}
|
|
|
|
// Reallocate the data buffer.
|
|
|
|
if (scale_image_data)
|
|
{
|
|
S32 temp_data_size = old_width * new_height * getComponents();
|
|
llassert_always(temp_data_size > 0);
|
|
std::vector<U8> temp_buffer(temp_data_size);
|
|
|
|
// Vertical
|
|
for( S32 col = 0; col < old_width; col++ )
|
|
{
|
|
copyLineScaled( getData() + (getComponents() * col), &temp_buffer[0] + (getComponents() * col), old_height, new_height, old_width, old_width );
|
|
}
|
|
|
|
deleteData();
|
|
|
|
U8* new_buffer = allocateDataSize(new_width, new_height, getComponents());
|
|
|
|
// Horizontal
|
|
for( S32 row = 0; row < new_height; row++ )
|
|
{
|
|
copyLineScaled( &temp_buffer[0] + (getComponents() * old_width * row), new_buffer + (getComponents() * new_width * row), old_width, new_width, 1, 1 );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// copy out existing image data
|
|
S32 temp_data_size = old_width * old_height * getComponents();
|
|
std::vector<U8> temp_buffer(temp_data_size);
|
|
memcpy(&temp_buffer[0], getData(), temp_data_size);
|
|
|
|
// allocate new image data, will delete old data
|
|
U8* new_buffer = allocateDataSize(new_width, new_height, getComponents());
|
|
|
|
for( S32 row = 0; row < new_height; row++ )
|
|
{
|
|
if (row < old_height)
|
|
{
|
|
memcpy(new_buffer + (new_width * row * getComponents()), &temp_buffer[0] + (old_width * row * getComponents()), getComponents() * llmin(old_width, new_width));
|
|
if (old_width < new_width)
|
|
{
|
|
// pad out rest of row with black
|
|
memset(new_buffer + (getComponents() * ((new_width * row) + old_width)), 0, getComponents() * (new_width - old_width));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// pad remaining rows with black
|
|
memset(new_buffer + (new_width * row * getComponents()), 0, new_width * getComponents());
|
|
}
|
|
}
|
|
}
|
|
|
|
return TRUE ;
|
|
}
|
|
|
|
// Filter Operations
|
|
void LLImageRaw::filterGrayScale()
|
|
{
|
|
LLMatrix3 gray_scale;
|
|
LLVector3 luminosity(0.2125, 0.7154, 0.0721);
|
|
gray_scale.setRows(luminosity, luminosity, luminosity);
|
|
gray_scale.transpose();
|
|
colorTransform(gray_scale);
|
|
}
|
|
|
|
void LLImageRaw::filterSepia()
|
|
{
|
|
LLMatrix3 sepia;
|
|
sepia.setRows(LLVector3(0.3588, 0.7044, 0.1368),
|
|
LLVector3(0.2990, 0.5870, 0.1140),
|
|
LLVector3(0.2392, 0.4696, 0.0912));
|
|
sepia.transpose();
|
|
colorTransform(sepia);
|
|
}
|
|
|
|
void LLImageRaw::filterSaturate(F32 saturation)
|
|
{
|
|
// Matrix to Lij
|
|
LLMatrix3 r_a;
|
|
LLMatrix3 r_b;
|
|
|
|
// 45 degre rotation around z
|
|
r_a.setRows(LLVector3( OO_SQRT2, OO_SQRT2, 0.0),
|
|
LLVector3(-OO_SQRT2, OO_SQRT2, 0.0),
|
|
LLVector3( 0.0, 0.0, 1.0));
|
|
// 54.73 degre rotation around y
|
|
float oo_sqrt3 = 1.0f / F_SQRT3;
|
|
float sin_54 = F_SQRT2 * oo_sqrt3;
|
|
r_b.setRows(LLVector3(oo_sqrt3, 0.0, -sin_54),
|
|
LLVector3(0.0, 1.0, 0.0),
|
|
LLVector3(sin_54, 0.0, oo_sqrt3));
|
|
|
|
// Coordinate conversion
|
|
LLMatrix3 Lij = r_b * r_a;
|
|
LLMatrix3 Lij_inv = Lij;
|
|
Lij_inv.transpose();
|
|
|
|
// Local saturation transform
|
|
LLMatrix3 s;
|
|
s.setRows(LLVector3(saturation, 0.0, 0.0),
|
|
LLVector3(0.0, saturation, 0.0),
|
|
LLVector3(0.0, 0.0, 1.0));
|
|
|
|
// Global saturation transform
|
|
LLMatrix3 transfo = Lij_inv * s * Lij;
|
|
colorTransform(transfo);
|
|
}
|
|
|
|
void LLImageRaw::filterRotate(F32 alpha)
|
|
{
|
|
// Matrix to Lij
|
|
LLMatrix3 r_a;
|
|
LLMatrix3 r_b;
|
|
|
|
// 45 degre rotation around z
|
|
r_a.setRows(LLVector3( OO_SQRT2, OO_SQRT2, 0.0),
|
|
LLVector3(-OO_SQRT2, OO_SQRT2, 0.0),
|
|
LLVector3( 0.0, 0.0, 1.0));
|
|
// 54.73 degre rotation around y
|
|
float oo_sqrt3 = 1.0f / F_SQRT3;
|
|
float sin_54 = F_SQRT2 * oo_sqrt3;
|
|
r_b.setRows(LLVector3(oo_sqrt3, 0.0, -sin_54),
|
|
LLVector3(0.0, 1.0, 0.0),
|
|
LLVector3(sin_54, 0.0, oo_sqrt3));
|
|
|
|
// Coordinate conversion
|
|
LLMatrix3 Lij = r_b * r_a;
|
|
LLMatrix3 Lij_inv = Lij;
|
|
Lij_inv.transpose();
|
|
|
|
// Local color rotation transform
|
|
LLMatrix3 r;
|
|
alpha *= DEG_TO_RAD;
|
|
r.setRows(LLVector3( cosf(alpha), sinf(alpha), 0.0),
|
|
LLVector3(-sinf(alpha), cosf(alpha), 0.0),
|
|
LLVector3( 0.0, 0.0, 1.0));
|
|
|
|
// Global color rotation transform
|
|
LLMatrix3 transfo = Lij_inv * r * Lij;
|
|
colorTransform(transfo);
|
|
}
|
|
|
|
void LLImageRaw::filterGamma(F32 gamma, const LLColor3& alpha)
|
|
{
|
|
U8 gamma_red_lut[256];
|
|
U8 gamma_green_lut[256];
|
|
U8 gamma_blue_lut[256];
|
|
|
|
for (S32 i = 0; i < 256; i++)
|
|
{
|
|
F32 gamma_i = llclampf((float)(pow((float)(i)/255.0,gamma)));
|
|
// Blend in with alpha values
|
|
gamma_red_lut[i] = (U8)((1.0 - alpha.mV[0]) * (float)(i) + alpha.mV[0] * 255.0 * gamma_i);
|
|
gamma_green_lut[i] = (U8)((1.0 - alpha.mV[1]) * (float)(i) + alpha.mV[1] * 255.0 * gamma_i);
|
|
gamma_blue_lut[i] = (U8)((1.0 - alpha.mV[2]) * (float)(i) + alpha.mV[2] * 255.0 * gamma_i);
|
|
}
|
|
|
|
colorCorrect(gamma_red_lut,gamma_green_lut,gamma_blue_lut);
|
|
}
|
|
|
|
void LLImageRaw::filterLinearize(F32 tail, const LLColor3& alpha)
|
|
{
|
|
// Get the histogram
|
|
U32* histo = getBrightnessHistogram();
|
|
|
|
// Compute cumulated histogram
|
|
U32 cumulated_histo[256];
|
|
cumulated_histo[0] = histo[0];
|
|
for (S32 i = 1; i < 256; i++)
|
|
{
|
|
cumulated_histo[i] = cumulated_histo[i-1] + histo[i];
|
|
}
|
|
|
|
// Compute min and max counts minus tail
|
|
tail = llclampf(tail);
|
|
S32 total = cumulated_histo[255];
|
|
S32 min_c = (S32)((F32)(total) * tail);
|
|
S32 max_c = (S32)((F32)(total) * (1.0 - tail));
|
|
|
|
// Find min and max values
|
|
S32 min_v = 0;
|
|
while (cumulated_histo[min_v] < min_c)
|
|
{
|
|
min_v++;
|
|
}
|
|
S32 max_v = 255;
|
|
while (cumulated_histo[max_v] > max_c)
|
|
{
|
|
max_v--;
|
|
}
|
|
|
|
// Compute linear lookup table
|
|
U8 linear_red_lut[256];
|
|
U8 linear_green_lut[256];
|
|
U8 linear_blue_lut[256];
|
|
if (max_v == min_v)
|
|
{
|
|
// Degenerated binary split case
|
|
for (S32 i = 0; i < 256; i++)
|
|
{
|
|
U8 value_i = (i < min_v ? 0 : 255);
|
|
// Blend in with alpha values
|
|
linear_red_lut[i] = (U8)((1.0 - alpha.mV[0]) * (float)(i) + alpha.mV[0] * value_i);
|
|
linear_green_lut[i] = (U8)((1.0 - alpha.mV[1]) * (float)(i) + alpha.mV[1] * value_i);
|
|
linear_blue_lut[i] = (U8)((1.0 - alpha.mV[2]) * (float)(i) + alpha.mV[2] * value_i);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Linearize between min and max
|
|
F32 slope = 255.0 / (F32)(max_v - min_v);
|
|
F32 translate = -min_v * slope;
|
|
for (S32 i = 0; i < 256; i++)
|
|
{
|
|
U8 value_i = (U8)(llclampb((S32)(slope*i + translate)));
|
|
// Blend in with alpha values
|
|
linear_red_lut[i] = (U8)((1.0 - alpha.mV[0]) * (float)(i) + alpha.mV[0] * value_i);
|
|
linear_green_lut[i] = (U8)((1.0 - alpha.mV[1]) * (float)(i) + alpha.mV[1] * value_i);
|
|
linear_blue_lut[i] = (U8)((1.0 - alpha.mV[2]) * (float)(i) + alpha.mV[2] * value_i);
|
|
}
|
|
}
|
|
|
|
// Apply lookup table
|
|
colorCorrect(linear_red_lut,linear_green_lut,linear_blue_lut);
|
|
}
|
|
|
|
void LLImageRaw::filterEqualize(S32 nb_classes, const LLColor3& alpha)
|
|
{
|
|
// Regularize the parameter: must be between 2 and 255
|
|
nb_classes = llmax(nb_classes,2);
|
|
nb_classes = llclampb(nb_classes);
|
|
|
|
// Get the histogram
|
|
U32* histo = getBrightnessHistogram();
|
|
|
|
// Compute cumulated histogram
|
|
U32 cumulated_histo[256];
|
|
cumulated_histo[0] = histo[0];
|
|
for (S32 i = 1; i < 256; i++)
|
|
{
|
|
cumulated_histo[i] = cumulated_histo[i-1] + histo[i];
|
|
}
|
|
|
|
// Compute deltas
|
|
S32 total = cumulated_histo[255];
|
|
S32 delta_count = total / nb_classes;
|
|
S32 current_count = delta_count;
|
|
S32 delta_value = 256 / (nb_classes - 1);
|
|
S32 current_value = 0;
|
|
|
|
// Compute equalized lookup table
|
|
U8 equalize_red_lut[256];
|
|
U8 equalize_green_lut[256];
|
|
U8 equalize_blue_lut[256];
|
|
for (S32 i = 0; i < 256; i++)
|
|
{
|
|
// Blend in current_value with alpha values
|
|
equalize_red_lut[i] = (U8)((1.0 - alpha.mV[0]) * (float)(i) + alpha.mV[0] * current_value);
|
|
equalize_green_lut[i] = (U8)((1.0 - alpha.mV[1]) * (float)(i) + alpha.mV[1] * current_value);
|
|
equalize_blue_lut[i] = (U8)((1.0 - alpha.mV[2]) * (float)(i) + alpha.mV[2] * current_value);
|
|
if (cumulated_histo[i] >= current_count)
|
|
{
|
|
current_count += delta_count;
|
|
current_value += delta_value;
|
|
current_value = llclampb(current_value);
|
|
}
|
|
}
|
|
|
|
// Apply lookup table
|
|
colorCorrect(equalize_red_lut,equalize_green_lut,equalize_blue_lut);
|
|
}
|
|
|
|
void LLImageRaw::filterColorize(const LLColor3& color, const LLColor3& alpha)
|
|
{
|
|
U8 red_lut[256];
|
|
U8 green_lut[256];
|
|
U8 blue_lut[256];
|
|
|
|
F32 red_composite = 255.0 * alpha.mV[0] * color.mV[0];
|
|
F32 green_composite = 255.0 * alpha.mV[1] * color.mV[1];
|
|
F32 blue_composite = 255.0 * alpha.mV[2] * color.mV[2];
|
|
|
|
for (S32 i = 0; i < 256; i++)
|
|
{
|
|
red_lut[i] = (U8)(llclampb((S32)((1.0 - alpha.mV[0]) * (F32)(i) + red_composite)));
|
|
green_lut[i] = (U8)(llclampb((S32)((1.0 - alpha.mV[1]) * (F32)(i) + green_composite)));
|
|
blue_lut[i] = (U8)(llclampb((S32)((1.0 - alpha.mV[2]) * (F32)(i) + blue_composite)));
|
|
}
|
|
|
|
colorCorrect(red_lut,green_lut,blue_lut);
|
|
}
|
|
|
|
void LLImageRaw::filterContrast(F32 slope, const LLColor3& alpha)
|
|
{
|
|
U8 contrast_red_lut[256];
|
|
U8 contrast_green_lut[256];
|
|
U8 contrast_blue_lut[256];
|
|
|
|
F32 translate = 128.0 * (1.0 - slope);
|
|
|
|
for (S32 i = 0; i < 256; i++)
|
|
{
|
|
U8 value_i = (U8)(llclampb((S32)(slope*i + translate)));
|
|
// Blend in with alpha values
|
|
contrast_red_lut[i] = (U8)((1.0 - alpha.mV[0]) * (float)(i) + alpha.mV[0] * value_i);
|
|
contrast_green_lut[i] = (U8)((1.0 - alpha.mV[1]) * (float)(i) + alpha.mV[1] * value_i);
|
|
contrast_blue_lut[i] = (U8)((1.0 - alpha.mV[2]) * (float)(i) + alpha.mV[2] * value_i);
|
|
}
|
|
|
|
colorCorrect(contrast_red_lut,contrast_green_lut,contrast_blue_lut);
|
|
}
|
|
|
|
void LLImageRaw::filterBrightness(S32 add, const LLColor3& alpha)
|
|
{
|
|
U8 brightness_red_lut[256];
|
|
U8 brightness_green_lut[256];
|
|
U8 brightness_blue_lut[256];
|
|
|
|
for (S32 i = 0; i < 256; i++)
|
|
{
|
|
U8 value_i = (U8)(llclampb((S32)((S32)(i) + add)));
|
|
// Blend in with alpha values
|
|
brightness_red_lut[i] = (U8)((1.0 - alpha.mV[0]) * (float)(i) + alpha.mV[0] * value_i);
|
|
brightness_green_lut[i] = (U8)((1.0 - alpha.mV[1]) * (float)(i) + alpha.mV[1] * value_i);
|
|
brightness_blue_lut[i] = (U8)((1.0 - alpha.mV[2]) * (float)(i) + alpha.mV[2] * value_i);
|
|
}
|
|
|
|
colorCorrect(brightness_red_lut,brightness_green_lut,brightness_blue_lut);
|
|
}
|
|
|
|
// Filter Primitives
|
|
void LLImageRaw::colorTransform(const LLMatrix3 &transform)
|
|
{
|
|
const S32 components = getComponents();
|
|
llassert( components >= 1 && components <= 4 );
|
|
|
|
S32 width = getWidth();
|
|
S32 height = getHeight();
|
|
|
|
U8* dst_data = getData();
|
|
for (S32 j = 0; j < height; j++)
|
|
{
|
|
for (S32 i = 0; i < width; i++)
|
|
{
|
|
LLVector3 src((F32)(dst_data[VRED]),(F32)(dst_data[VGREEN]),(F32)(dst_data[VBLUE]));
|
|
LLVector3 dst = src * transform;
|
|
dst.clamp(0.0f,255.0f);
|
|
if (mVignetteMode == VIGNETTE_MODE_NONE)
|
|
{
|
|
dst_data[VRED] = dst.mV[VRED];
|
|
dst_data[VGREEN] = dst.mV[VGREEN];
|
|
dst_data[VBLUE] = dst.mV[VBLUE];
|
|
}
|
|
else
|
|
{
|
|
F32 alpha = getVignetteAlpha(i,j);
|
|
if (mVignetteMode == VIGNETTE_MODE_BLEND)
|
|
{
|
|
// Blends with the source image on the edges
|
|
F32 inv_alpha = 1.0 - alpha;
|
|
dst_data[VRED] = inv_alpha * src.mV[VRED] + alpha * dst.mV[VRED];
|
|
dst_data[VGREEN] = inv_alpha * src.mV[VGREEN] + alpha * dst.mV[VGREEN];
|
|
dst_data[VBLUE] = inv_alpha * src.mV[VBLUE] + alpha * dst.mV[VBLUE];
|
|
}
|
|
else // VIGNETTE_MODE_FADE
|
|
{
|
|
// Fade to black on the edges
|
|
dst_data[VRED] = alpha * dst.mV[VRED];
|
|
dst_data[VGREEN] = alpha * dst.mV[VGREEN];
|
|
dst_data[VBLUE] = alpha * dst.mV[VBLUE];
|
|
}
|
|
}
|
|
dst_data += components;
|
|
}
|
|
}
|
|
}
|
|
|
|
void LLImageRaw::colorCorrect(const U8* lut_red, const U8* lut_green, const U8* lut_blue)
|
|
{
|
|
const S32 components = getComponents();
|
|
llassert( components >= 1 && components <= 4 );
|
|
|
|
S32 width = getWidth();
|
|
S32 height = getHeight();
|
|
|
|
U8* dst_data = getData();
|
|
for (S32 j = 0; j < height; j++)
|
|
{
|
|
for (S32 i = 0; i < width; i++)
|
|
{
|
|
if (mVignetteMode == VIGNETTE_MODE_NONE)
|
|
{
|
|
dst_data[VRED] = lut_red[dst_data[VRED]];
|
|
dst_data[VGREEN] = lut_green[dst_data[VGREEN]];
|
|
dst_data[VBLUE] = lut_blue[dst_data[VBLUE]];
|
|
}
|
|
else
|
|
{
|
|
F32 alpha = getVignetteAlpha(i,j);
|
|
if (mVignetteMode == VIGNETTE_MODE_BLEND)
|
|
{
|
|
// Blends with the source image on the edges
|
|
F32 inv_alpha = 1.0 - alpha;
|
|
dst_data[VRED] = inv_alpha * dst_data[VRED] + alpha * lut_red[dst_data[VRED]];
|
|
dst_data[VGREEN] = inv_alpha * dst_data[VGREEN] + alpha * lut_green[dst_data[VGREEN]];
|
|
dst_data[VBLUE] = inv_alpha * dst_data[VBLUE] + alpha * lut_blue[dst_data[VBLUE]];
|
|
}
|
|
else // VIGNETTE_MODE_FADE
|
|
{
|
|
// Fade to black on the edges
|
|
dst_data[VRED] = alpha * lut_red[dst_data[VRED]];
|
|
dst_data[VGREEN] = alpha * lut_green[dst_data[VGREEN]];
|
|
dst_data[VBLUE] = alpha * lut_blue[dst_data[VBLUE]];
|
|
}
|
|
}
|
|
dst_data += components;
|
|
}
|
|
}
|
|
}
|
|
|
|
void LLImageRaw::screenFilter(const S32 wave_length)
|
|
{
|
|
const S32 components = getComponents();
|
|
llassert( components >= 1 && components <= 4 );
|
|
|
|
S32 width = getWidth();
|
|
S32 height = getHeight();
|
|
|
|
U8* dst_data = getData();
|
|
for (S32 j = 0; j < height; j++)
|
|
{
|
|
for (S32 i = 0; i < width; i++)
|
|
{
|
|
F32 value = (sinf(2*F_PI*i/wave_length)*sinf(2*F_PI*j/wave_length)+1.0)*255.0/2.0;
|
|
//F32 value = (sinf(2*F_PI*i/wave_length)+1.0)*255.0/2.0; // will do line
|
|
U8 dst_value = (dst_data[VRED] >= (U8)(value) ? 255 : 0);
|
|
if (mVignetteMode == VIGNETTE_MODE_NONE)
|
|
{
|
|
dst_data[VRED] = dst_value;
|
|
dst_data[VGREEN] = dst_value;
|
|
dst_data[VBLUE] = dst_value;
|
|
}
|
|
else
|
|
{
|
|
F32 alpha = getVignetteAlpha(i,j);
|
|
if (mVignetteMode == VIGNETTE_MODE_BLEND)
|
|
{
|
|
// Blends with the source image on the edges
|
|
F32 inv_alpha = 1.0 - alpha;
|
|
dst_data[VRED] = inv_alpha * dst_data[VRED] + alpha * dst_value;
|
|
dst_data[VGREEN] = inv_alpha * dst_data[VGREEN] + alpha * dst_value;
|
|
dst_data[VBLUE] = inv_alpha * dst_data[VBLUE] + alpha * dst_value;
|
|
}
|
|
else // VIGNETTE_MODE_FADE
|
|
{
|
|
// Fade to black on the edges
|
|
dst_data[VRED] = alpha * dst_value;
|
|
dst_data[VGREEN] = alpha * dst_value;
|
|
dst_data[VBLUE] = alpha * dst_value;
|
|
}
|
|
}
|
|
dst_data += components;
|
|
}
|
|
}
|
|
}
|
|
|
|
void LLImageRaw::setVignette(EVignetteMode mode, F32 gamma, F32 min)
|
|
{
|
|
mVignetteMode = mode;
|
|
mVignetteGamma = gamma;
|
|
mVignetteMin = llclampf(min);
|
|
// We always center the vignette on the image and fits it in the image smallest dimension
|
|
mVignetteCenterX = getWidth()/2;
|
|
mVignetteCenterY = getHeight()/2;
|
|
mVignetteWidth = llmin(getWidth()/2,getHeight()/2);
|
|
}
|
|
|
|
F32 LLImageRaw::getVignetteAlpha(S32 i, S32 j)
|
|
{
|
|
// alpha is a modified gaussian value, with a center and fading in a circular pattern toward the edges
|
|
// The gamma parameter controls the intensity of the drop down from alpha 1.0 (center) to 0.0
|
|
F32 d_center_square = (i - mVignetteCenterX)*(i - mVignetteCenterX) + (j - mVignetteCenterY)*(j - mVignetteCenterY);
|
|
F32 alpha = powf(F_E, -(powf((d_center_square/(mVignetteWidth*mVignetteWidth)),mVignetteGamma)/2.0f));
|
|
// We rescale alpha between min and 1.0 so to avoid complete fading if so desired.
|
|
return (mVignetteMin + alpha * (1.0 - mVignetteMin));
|
|
}
|
|
|
|
U32* LLImageRaw::getBrightnessHistogram()
|
|
{
|
|
if (!mHistoBrightness)
|
|
{
|
|
computeHistograms();
|
|
}
|
|
return mHistoBrightness;
|
|
}
|
|
|
|
void LLImageRaw::computeHistograms()
|
|
{
|
|
const S32 components = getComponents();
|
|
llassert( components >= 1 && components <= 4 );
|
|
|
|
// Allocate memory for the histograms
|
|
if (!mHistoRed)
|
|
{
|
|
mHistoRed = (U32*) ll_aligned_malloc_16(256*sizeof(U32));
|
|
}
|
|
if (!mHistoGreen)
|
|
{
|
|
mHistoGreen = (U32*) ll_aligned_malloc_16(256*sizeof(U32));
|
|
}
|
|
if (!mHistoBlue)
|
|
{
|
|
mHistoBlue = (U32*) ll_aligned_malloc_16(256*sizeof(U32));
|
|
}
|
|
if (!mHistoBrightness)
|
|
{
|
|
mHistoBrightness = (U32*) ll_aligned_malloc_16(256*sizeof(U32));
|
|
}
|
|
|
|
// Initialize them
|
|
for (S32 i = 0; i < 256; i++)
|
|
{
|
|
mHistoRed[i] = 0;
|
|
mHistoGreen[i] = 0;
|
|
mHistoBlue[i] = 0;
|
|
mHistoBrightness[i] = 0;
|
|
}
|
|
|
|
// Compute them
|
|
S32 pixels = getWidth() * getHeight();
|
|
U8* dst_data = getData();
|
|
for (S32 i = 0; i < pixels; i++)
|
|
{
|
|
mHistoRed[dst_data[VRED]]++;
|
|
mHistoGreen[dst_data[VGREEN]]++;
|
|
mHistoBlue[dst_data[VBLUE]]++;
|
|
// Note: this is a very simple shorthand for brightness but it's OK for our use
|
|
S32 brightness = ((S32)(dst_data[VRED]) + (S32)(dst_data[VGREEN]) + (S32)(dst_data[VBLUE])) / 3;
|
|
mHistoBrightness[brightness]++;
|
|
// next pixel...
|
|
dst_data += components;
|
|
}
|
|
}
|
|
|
|
void LLImageRaw::copyLineScaled( U8* in, U8* out, S32 in_pixel_len, S32 out_pixel_len, S32 in_pixel_step, S32 out_pixel_step )
|
|
{
|
|
const S32 components = getComponents();
|
|
llassert( components >= 1 && components <= 4 );
|
|
|
|
const F32 ratio = F32(in_pixel_len) / out_pixel_len; // ratio of old to new
|
|
const F32 norm_factor = 1.f / ratio;
|
|
|
|
S32 goff = components >= 2 ? 1 : 0;
|
|
S32 boff = components >= 3 ? 2 : 0;
|
|
for( S32 x = 0; x < out_pixel_len; x++ )
|
|
{
|
|
// Sample input pixels in range from sample0 to sample1.
|
|
// Avoid floating point accumulation error... don't just add ratio each time. JC
|
|
const F32 sample0 = x * ratio;
|
|
const F32 sample1 = (x+1) * ratio;
|
|
const S32 index0 = llfloor(sample0); // left integer (floor)
|
|
const S32 index1 = llfloor(sample1); // right integer (floor)
|
|
const F32 fract0 = 1.f - (sample0 - F32(index0)); // spill over on left
|
|
const F32 fract1 = sample1 - F32(index1); // spill-over on right
|
|
|
|
if( index0 == index1 )
|
|
{
|
|
// Interval is embedded in one input pixel
|
|
S32 t0 = x * out_pixel_step * components;
|
|
S32 t1 = index0 * in_pixel_step * components;
|
|
U8* outp = out + t0;
|
|
U8* inp = in + t1;
|
|
for (S32 i = 0; i < components; ++i)
|
|
{
|
|
*outp = *inp;
|
|
++outp;
|
|
++inp;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Left straddle
|
|
S32 t1 = index0 * in_pixel_step * components;
|
|
F32 r = in[t1 + 0] * fract0;
|
|
F32 g = in[t1 + goff] * fract0;
|
|
F32 b = in[t1 + boff] * fract0;
|
|
F32 a = 0;
|
|
if( components == 4)
|
|
{
|
|
a = in[t1 + 3] * fract0;
|
|
}
|
|
|
|
// Central interval
|
|
if (components < 4)
|
|
{
|
|
for( S32 u = index0 + 1; u < index1; u++ )
|
|
{
|
|
S32 t2 = u * in_pixel_step * components;
|
|
r += in[t2 + 0];
|
|
g += in[t2 + goff];
|
|
b += in[t2 + boff];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for( S32 u = index0 + 1; u < index1; u++ )
|
|
{
|
|
S32 t2 = u * in_pixel_step * components;
|
|
r += in[t2 + 0];
|
|
g += in[t2 + 1];
|
|
b += in[t2 + 2];
|
|
a += in[t2 + 3];
|
|
}
|
|
}
|
|
|
|
// right straddle
|
|
// Watch out for reading off of end of input array.
|
|
if( fract1 && index1 < in_pixel_len )
|
|
{
|
|
S32 t3 = index1 * in_pixel_step * components;
|
|
if (components < 4)
|
|
{
|
|
U8 in0 = in[t3 + 0];
|
|
U8 in1 = in[t3 + goff];
|
|
U8 in2 = in[t3 + boff];
|
|
r += in0 * fract1;
|
|
g += in1 * fract1;
|
|
b += in2 * fract1;
|
|
}
|
|
else
|
|
{
|
|
U8 in0 = in[t3 + 0];
|
|
U8 in1 = in[t3 + 1];
|
|
U8 in2 = in[t3 + 2];
|
|
U8 in3 = in[t3 + 3];
|
|
r += in0 * fract1;
|
|
g += in1 * fract1;
|
|
b += in2 * fract1;
|
|
a += in3 * fract1;
|
|
}
|
|
}
|
|
|
|
r *= norm_factor;
|
|
g *= norm_factor;
|
|
b *= norm_factor;
|
|
a *= norm_factor; // skip conditional
|
|
|
|
S32 t4 = x * out_pixel_step * components;
|
|
out[t4 + 0] = U8(llround(r));
|
|
if (components >= 2)
|
|
out[t4 + 1] = U8(llround(g));
|
|
if (components >= 3)
|
|
out[t4 + 2] = U8(llround(b));
|
|
if( components == 4)
|
|
out[t4 + 3] = U8(llround(a));
|
|
}
|
|
}
|
|
}
|
|
|
|
void LLImageRaw::compositeRowScaled4onto3( U8* in, U8* out, S32 in_pixel_len, S32 out_pixel_len )
|
|
{
|
|
llassert( getComponents() == 3 );
|
|
|
|
const S32 IN_COMPONENTS = 4;
|
|
const S32 OUT_COMPONENTS = 3;
|
|
|
|
const F32 ratio = F32(in_pixel_len) / out_pixel_len; // ratio of old to new
|
|
const F32 norm_factor = 1.f / ratio;
|
|
|
|
for( S32 x = 0; x < out_pixel_len; x++ )
|
|
{
|
|
// Sample input pixels in range from sample0 to sample1.
|
|
// Avoid floating point accumulation error... don't just add ratio each time. JC
|
|
const F32 sample0 = x * ratio;
|
|
const F32 sample1 = (x+1) * ratio;
|
|
const S32 index0 = S32(sample0); // left integer (floor)
|
|
const S32 index1 = S32(sample1); // right integer (floor)
|
|
const F32 fract0 = 1.f - (sample0 - F32(index0)); // spill over on left
|
|
const F32 fract1 = sample1 - F32(index1); // spill-over on right
|
|
|
|
U8 in_scaled_r;
|
|
U8 in_scaled_g;
|
|
U8 in_scaled_b;
|
|
U8 in_scaled_a;
|
|
|
|
if( index0 == index1 )
|
|
{
|
|
// Interval is embedded in one input pixel
|
|
S32 t1 = index0 * IN_COMPONENTS;
|
|
in_scaled_r = in[t1 + 0];
|
|
in_scaled_g = in[t1 + 0];
|
|
in_scaled_b = in[t1 + 0];
|
|
in_scaled_a = in[t1 + 0];
|
|
}
|
|
else
|
|
{
|
|
// Left straddle
|
|
S32 t1 = index0 * IN_COMPONENTS;
|
|
F32 r = in[t1 + 0] * fract0;
|
|
F32 g = in[t1 + 1] * fract0;
|
|
F32 b = in[t1 + 2] * fract0;
|
|
F32 a = in[t1 + 3] * fract0;
|
|
|
|
// Central interval
|
|
for( S32 u = index0 + 1; u < index1; u++ )
|
|
{
|
|
S32 t2 = u * IN_COMPONENTS;
|
|
r += in[t2 + 0];
|
|
g += in[t2 + 1];
|
|
b += in[t2 + 2];
|
|
a += in[t2 + 3];
|
|
}
|
|
|
|
// right straddle
|
|
// Watch out for reading off of end of input array.
|
|
if( fract1 && index1 < in_pixel_len )
|
|
{
|
|
S32 t3 = index1 * IN_COMPONENTS;
|
|
r += in[t3 + 0] * fract1;
|
|
g += in[t3 + 1] * fract1;
|
|
b += in[t3 + 2] * fract1;
|
|
a += in[t3 + 3] * fract1;
|
|
}
|
|
|
|
r *= norm_factor;
|
|
g *= norm_factor;
|
|
b *= norm_factor;
|
|
a *= norm_factor;
|
|
|
|
in_scaled_r = U8(llround(r));
|
|
in_scaled_g = U8(llround(g));
|
|
in_scaled_b = U8(llround(b));
|
|
in_scaled_a = U8(llround(a));
|
|
}
|
|
|
|
if( in_scaled_a )
|
|
{
|
|
if( 255 == in_scaled_a )
|
|
{
|
|
out[0] = in_scaled_r;
|
|
out[1] = in_scaled_g;
|
|
out[2] = in_scaled_b;
|
|
}
|
|
else
|
|
{
|
|
U8 transparency = 255 - in_scaled_a;
|
|
out[0] = fastFractionalMult( out[0], transparency ) + fastFractionalMult( in_scaled_r, in_scaled_a );
|
|
out[1] = fastFractionalMult( out[1], transparency ) + fastFractionalMult( in_scaled_g, in_scaled_a );
|
|
out[2] = fastFractionalMult( out[2], transparency ) + fastFractionalMult( in_scaled_b, in_scaled_a );
|
|
}
|
|
}
|
|
out += OUT_COMPONENTS;
|
|
}
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
static struct
|
|
{
|
|
const char* exten;
|
|
EImageCodec codec;
|
|
}
|
|
file_extensions[] =
|
|
{
|
|
{ "bmp", IMG_CODEC_BMP },
|
|
{ "tga", IMG_CODEC_TGA },
|
|
{ "j2c", IMG_CODEC_J2C },
|
|
{ "jp2", IMG_CODEC_J2C },
|
|
{ "texture", IMG_CODEC_J2C },
|
|
{ "jpg", IMG_CODEC_JPEG },
|
|
{ "jpeg", IMG_CODEC_JPEG },
|
|
{ "mip", IMG_CODEC_DXT },
|
|
{ "dxt", IMG_CODEC_DXT },
|
|
{ "png", IMG_CODEC_PNG }
|
|
};
|
|
#define NUM_FILE_EXTENSIONS LL_ARRAY_SIZE(file_extensions)
|
|
#if 0
|
|
static std::string find_file(std::string &name, S8 *codec)
|
|
{
|
|
std::string tname;
|
|
for (int i=0; i<(int)(NUM_FILE_EXTENSIONS); i++)
|
|
{
|
|
tname = name + "." + std::string(file_extensions[i].exten);
|
|
llifstream ifs(tname, llifstream::binary);
|
|
if (ifs.is_open())
|
|
{
|
|
ifs.close();
|
|
if (codec)
|
|
*codec = file_extensions[i].codec;
|
|
return std::string(file_extensions[i].exten);
|
|
}
|
|
}
|
|
return std::string("");
|
|
}
|
|
#endif
|
|
EImageCodec LLImageBase::getCodecFromExtension(const std::string& exten)
|
|
{
|
|
for (int i=0; i<(int)(NUM_FILE_EXTENSIONS); i++)
|
|
{
|
|
if (exten == file_extensions[i].exten)
|
|
return file_extensions[i].codec;
|
|
}
|
|
return IMG_CODEC_INVALID;
|
|
}
|
|
#if 0
|
|
bool LLImageRaw::createFromFile(const std::string &filename, bool j2c_lowest_mip_only)
|
|
{
|
|
std::string name = filename;
|
|
size_t dotidx = name.rfind('.');
|
|
S8 codec = IMG_CODEC_INVALID;
|
|
std::string exten;
|
|
|
|
deleteData(); // delete any existing data
|
|
|
|
if (dotidx != std::string::npos)
|
|
{
|
|
exten = name.substr(dotidx+1);
|
|
LLStringUtil::toLower(exten);
|
|
codec = getCodecFromExtension(exten);
|
|
}
|
|
else
|
|
{
|
|
exten = find_file(name, &codec);
|
|
name = name + "." + exten;
|
|
}
|
|
if (codec == IMG_CODEC_INVALID)
|
|
{
|
|
return false; // format not recognized
|
|
}
|
|
|
|
llifstream ifs(name, llifstream::binary);
|
|
if (!ifs.is_open())
|
|
{
|
|
// SJB: changed from llinfos to lldebugs to reduce spam
|
|
lldebugs << "Unable to open image file: " << name << llendl;
|
|
return false;
|
|
}
|
|
|
|
ifs.seekg (0, std::ios::end);
|
|
int length = ifs.tellg();
|
|
if (j2c_lowest_mip_only && length > 2048)
|
|
{
|
|
length = 2048;
|
|
}
|
|
ifs.seekg (0, std::ios::beg);
|
|
|
|
if (!length)
|
|
{
|
|
llinfos << "Zero length file file: " << name << llendl;
|
|
return false;
|
|
}
|
|
|
|
LLPointer<LLImageFormatted> image = LLImageFormatted::createFromType(codec);
|
|
llassert(image.notNull());
|
|
|
|
U8 *buffer = image->allocateData(length);
|
|
ifs.read ((char*)buffer, length);
|
|
ifs.close();
|
|
|
|
BOOL success;
|
|
|
|
success = image->updateData();
|
|
if (success)
|
|
{
|
|
if (j2c_lowest_mip_only && codec == IMG_CODEC_J2C)
|
|
{
|
|
S32 width = image->getWidth();
|
|
S32 height = image->getHeight();
|
|
S32 discard_level = 0;
|
|
while (width > 1 && height > 1 && discard_level < MAX_DISCARD_LEVEL)
|
|
{
|
|
width >>= 1;
|
|
height >>= 1;
|
|
discard_level++;
|
|
}
|
|
((LLImageJ2C *)((LLImageFormatted*)image))->setDiscardLevel(discard_level);
|
|
}
|
|
success = image->decode(this, 100000.0f);
|
|
}
|
|
|
|
image = NULL; // deletes image
|
|
if (!success)
|
|
{
|
|
deleteData();
|
|
llwarns << "Unable to decode image" << name << llendl;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
#endif
|
|
//---------------------------------------------------------------------------
|
|
// LLImageFormatted
|
|
//---------------------------------------------------------------------------
|
|
|
|
//static
|
|
S32 LLImageFormatted::sGlobalFormattedMemory = 0;
|
|
|
|
LLImageFormatted::LLImageFormatted(S8 codec)
|
|
: LLImageBase(),
|
|
mCodec(codec),
|
|
mDecoding(0),
|
|
mDecoded(0),
|
|
mDiscardLevel(-1),
|
|
mLevels(0)
|
|
{
|
|
}
|
|
|
|
// virtual
|
|
LLImageFormatted::~LLImageFormatted()
|
|
{
|
|
// NOTE: ~LLimageBase() call to deleteData() calls LLImageBase::deleteData()
|
|
// NOT LLImageFormatted::deleteData()
|
|
deleteData();
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
//virtual
|
|
void LLImageFormatted::resetLastError()
|
|
{
|
|
LLImage::setLastError("");
|
|
}
|
|
|
|
//virtual
|
|
void LLImageFormatted::setLastError(const std::string& message, const std::string& filename)
|
|
{
|
|
std::string error = message;
|
|
if (!filename.empty())
|
|
error += std::string(" FILE: ") + filename;
|
|
LLImage::setLastError(error);
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
// static
|
|
LLImageFormatted* LLImageFormatted::createFromType(S8 codec)
|
|
{
|
|
LLImageFormatted* image;
|
|
switch(codec)
|
|
{
|
|
case IMG_CODEC_BMP:
|
|
image = new LLImageBMP();
|
|
break;
|
|
case IMG_CODEC_TGA:
|
|
image = new LLImageTGA();
|
|
break;
|
|
case IMG_CODEC_JPEG:
|
|
image = new LLImageJPEG();
|
|
break;
|
|
case IMG_CODEC_PNG:
|
|
image = new LLImagePNG();
|
|
break;
|
|
case IMG_CODEC_J2C:
|
|
image = new LLImageJ2C();
|
|
break;
|
|
case IMG_CODEC_DXT:
|
|
image = new LLImageDXT();
|
|
break;
|
|
default:
|
|
image = NULL;
|
|
break;
|
|
}
|
|
return image;
|
|
}
|
|
|
|
// static
|
|
LLImageFormatted* LLImageFormatted::createFromExtension(const std::string& instring)
|
|
{
|
|
std::string exten;
|
|
size_t dotidx = instring.rfind('.');
|
|
if (dotidx != std::string::npos)
|
|
{
|
|
exten = instring.substr(dotidx+1);
|
|
}
|
|
else
|
|
{
|
|
exten = instring;
|
|
}
|
|
S8 codec = getCodecFromExtension(exten);
|
|
return createFromType(codec);
|
|
}
|
|
//----------------------------------------------------------------------------
|
|
|
|
// virtual
|
|
void LLImageFormatted::dump()
|
|
{
|
|
LLImageBase::dump();
|
|
|
|
llinfos << "LLImageFormatted"
|
|
<< " mDecoding " << mDecoding
|
|
<< " mCodec " << S32(mCodec)
|
|
<< " mDecoded " << mDecoded
|
|
<< llendl;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
S32 LLImageFormatted::calcDataSize(S32 discard_level)
|
|
{
|
|
if (discard_level < 0)
|
|
{
|
|
discard_level = mDiscardLevel;
|
|
}
|
|
S32 w = getWidth() >> discard_level;
|
|
S32 h = getHeight() >> discard_level;
|
|
w = llmax(w, 1);
|
|
h = llmax(h, 1);
|
|
return w * h * getComponents();
|
|
}
|
|
|
|
S32 LLImageFormatted::calcDiscardLevelBytes(S32 bytes)
|
|
{
|
|
llassert(bytes >= 0);
|
|
S32 discard_level = 0;
|
|
while (1)
|
|
{
|
|
S32 bytes_needed = calcDataSize(discard_level); // virtual
|
|
if (bytes_needed <= bytes)
|
|
{
|
|
break;
|
|
}
|
|
discard_level++;
|
|
if (discard_level > MAX_IMAGE_MIP)
|
|
{
|
|
return -1;
|
|
}
|
|
}
|
|
return discard_level;
|
|
}
|
|
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
// Subclasses that can handle more than 4 channels should override this function.
|
|
BOOL LLImageFormatted::decodeChannels(LLImageRaw* raw_image,F32 decode_time, S32 first_channel, S32 max_channel)
|
|
{
|
|
llassert( (first_channel == 0) && (max_channel == 4) );
|
|
return decode( raw_image, decode_time ); // Loads first 4 channels by default.
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
// virtual
|
|
U8* LLImageFormatted::allocateData(S32 size)
|
|
{
|
|
U8* res = LLImageBase::allocateData(size); // calls deleteData()
|
|
sGlobalFormattedMemory += getDataSize();
|
|
return res;
|
|
}
|
|
|
|
// virtual
|
|
U8* LLImageFormatted::reallocateData(S32 size)
|
|
{
|
|
sGlobalFormattedMemory -= getDataSize();
|
|
U8* res = LLImageBase::reallocateData(size);
|
|
sGlobalFormattedMemory += getDataSize();
|
|
return res;
|
|
}
|
|
|
|
// virtual
|
|
void LLImageFormatted::deleteData()
|
|
{
|
|
sGlobalFormattedMemory -= getDataSize();
|
|
LLImageBase::deleteData();
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
// virtual
|
|
void LLImageFormatted::sanityCheck()
|
|
{
|
|
LLImageBase::sanityCheck();
|
|
|
|
if (mCodec >= IMG_CODEC_EOF)
|
|
{
|
|
llerrs << "Failed LLImageFormatted::sanityCheck "
|
|
<< "decoding " << S32(mDecoding)
|
|
<< "decoded " << S32(mDecoded)
|
|
<< "codec " << S32(mCodec)
|
|
<< llendl;
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
BOOL LLImageFormatted::copyData(U8 *data, S32 size)
|
|
{
|
|
if ( data && ((data != getData()) || (size != getDataSize())) )
|
|
{
|
|
deleteData();
|
|
allocateData(size);
|
|
memcpy(getData(), data, size); /* Flawfinder: ignore */
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
// LLImageFormatted becomes the owner of data
|
|
void LLImageFormatted::setData(U8 *data, S32 size)
|
|
{
|
|
if (data && data != getData())
|
|
{
|
|
deleteData();
|
|
setDataAndSize(data, size); // Access private LLImageBase members
|
|
|
|
sGlobalFormattedMemory += getDataSize();
|
|
}
|
|
}
|
|
|
|
void LLImageFormatted::appendData(U8 *data, S32 size)
|
|
{
|
|
if (data)
|
|
{
|
|
if (!getData())
|
|
{
|
|
setData(data, size);
|
|
}
|
|
else
|
|
{
|
|
S32 cursize = getDataSize();
|
|
S32 newsize = cursize + size;
|
|
reallocateData(newsize);
|
|
memcpy(getData() + cursize, data, size);
|
|
FREE_MEM(LLImageBase::getPrivatePool(), data);
|
|
}
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
BOOL LLImageFormatted::load(const std::string &filename, int load_size)
|
|
{
|
|
resetLastError();
|
|
|
|
S32 file_size = 0;
|
|
LLAPRFile infile ;
|
|
infile.open(filename, LL_APR_RB, NULL, &file_size);
|
|
apr_file_t* apr_file = infile.getFileHandle();
|
|
if (!apr_file)
|
|
{
|
|
setLastError("Unable to open file for reading", filename);
|
|
return FALSE;
|
|
}
|
|
if (file_size == 0)
|
|
{
|
|
setLastError("File is empty",filename);
|
|
return FALSE;
|
|
}
|
|
|
|
// Constrain the load size to acceptable values
|
|
if ((load_size == 0) || (load_size > file_size))
|
|
{
|
|
load_size = file_size;
|
|
}
|
|
BOOL res;
|
|
U8 *data = allocateData(load_size);
|
|
apr_size_t bytes_read = load_size;
|
|
apr_status_t s = apr_file_read(apr_file, data, &bytes_read); // modifies bytes_read
|
|
if (s != APR_SUCCESS || (S32) bytes_read != load_size)
|
|
{
|
|
deleteData();
|
|
setLastError("Unable to read file",filename);
|
|
res = FALSE;
|
|
}
|
|
else
|
|
{
|
|
res = updateData();
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
BOOL LLImageFormatted::save(const std::string &filename)
|
|
{
|
|
resetLastError();
|
|
|
|
LLAPRFile outfile ;
|
|
outfile.open(filename, LL_APR_WB);
|
|
if (!outfile.getFileHandle())
|
|
{
|
|
setLastError("Unable to open file for writing", filename);
|
|
return FALSE;
|
|
}
|
|
|
|
outfile.write(getData(), getDataSize());
|
|
outfile.close() ;
|
|
return TRUE;
|
|
}
|
|
|
|
// BOOL LLImageFormatted::save(LLVFS *vfs, const LLUUID &uuid, LLAssetType::EType type)
|
|
// Depricated to remove VFS dependency.
|
|
// Use:
|
|
// LLVFile::writeFile(image->getData(), image->getDataSize(), vfs, uuid, type);
|
|
|
|
//----------------------------------------------------------------------------
|
|
|
|
S8 LLImageFormatted::getCodec() const
|
|
{
|
|
return mCodec;
|
|
}
|
|
|
|
//============================================================================
|
|
|
|
static void avg4_colors4(const U8* a, const U8* b, const U8* c, const U8* d, U8* dst)
|
|
{
|
|
dst[0] = (U8)(((U32)(a[0]) + b[0] + c[0] + d[0])>>2);
|
|
dst[1] = (U8)(((U32)(a[1]) + b[1] + c[1] + d[1])>>2);
|
|
dst[2] = (U8)(((U32)(a[2]) + b[2] + c[2] + d[2])>>2);
|
|
dst[3] = (U8)(((U32)(a[3]) + b[3] + c[3] + d[3])>>2);
|
|
}
|
|
|
|
static void avg4_colors3(const U8* a, const U8* b, const U8* c, const U8* d, U8* dst)
|
|
{
|
|
dst[0] = (U8)(((U32)(a[0]) + b[0] + c[0] + d[0])>>2);
|
|
dst[1] = (U8)(((U32)(a[1]) + b[1] + c[1] + d[1])>>2);
|
|
dst[2] = (U8)(((U32)(a[2]) + b[2] + c[2] + d[2])>>2);
|
|
}
|
|
|
|
static void avg4_colors2(const U8* a, const U8* b, const U8* c, const U8* d, U8* dst)
|
|
{
|
|
dst[0] = (U8)(((U32)(a[0]) + b[0] + c[0] + d[0])>>2);
|
|
dst[1] = (U8)(((U32)(a[1]) + b[1] + c[1] + d[1])>>2);
|
|
}
|
|
|
|
void LLImageBase::setDataAndSize(U8 *data, S32 size)
|
|
{
|
|
ll_assert_aligned(data, 16);
|
|
mData = data; mDataSize = size;
|
|
}
|
|
|
|
//static
|
|
void LLImageBase::generateMip(const U8* indata, U8* mipdata, S32 width, S32 height, S32 nchannels)
|
|
{
|
|
llassert(width > 0 && height > 0);
|
|
U8* data = mipdata;
|
|
S32 in_width = width*2;
|
|
for (S32 h=0; h<height; h++)
|
|
{
|
|
for (S32 w=0; w<width; w++)
|
|
{
|
|
switch(nchannels)
|
|
{
|
|
case 4:
|
|
avg4_colors4(indata, indata+4, indata+4*in_width, indata+4*in_width+4, data);
|
|
break;
|
|
case 3:
|
|
avg4_colors3(indata, indata+3, indata+3*in_width, indata+3*in_width+3, data);
|
|
break;
|
|
case 2:
|
|
avg4_colors2(indata, indata+2, indata+2*in_width, indata+2*in_width+2, data);
|
|
break;
|
|
case 1:
|
|
*(U8*)data = (U8)(((U32)(indata[0]) + indata[1] + indata[in_width] + indata[in_width+1])>>2);
|
|
break;
|
|
default:
|
|
llerrs << "generateMmip called with bad num channels" << llendl;
|
|
}
|
|
indata += nchannels*2;
|
|
data += nchannels;
|
|
}
|
|
indata += nchannels*in_width; // skip odd lines
|
|
}
|
|
}
|
|
|
|
|
|
//============================================================================
|
|
|
|
//static
|
|
F32 LLImageBase::calc_download_priority(F32 virtual_size, F32 visible_pixels, S32 bytes_sent)
|
|
{
|
|
F32 w_priority;
|
|
|
|
F32 bytes_weight = 1.f;
|
|
if (!bytes_sent)
|
|
{
|
|
bytes_weight = 20.f;
|
|
}
|
|
else if (bytes_sent < 1000)
|
|
{
|
|
bytes_weight = 1.f;
|
|
}
|
|
else if (bytes_sent < 2000)
|
|
{
|
|
bytes_weight = 1.f/1.5f;
|
|
}
|
|
else if (bytes_sent < 4000)
|
|
{
|
|
bytes_weight = 1.f/3.f;
|
|
}
|
|
else if (bytes_sent < 8000)
|
|
{
|
|
bytes_weight = 1.f/6.f;
|
|
}
|
|
else if (bytes_sent < 16000)
|
|
{
|
|
bytes_weight = 1.f/12.f;
|
|
}
|
|
else if (bytes_sent < 32000)
|
|
{
|
|
bytes_weight = 1.f/20.f;
|
|
}
|
|
else if (bytes_sent < 64000)
|
|
{
|
|
bytes_weight = 1.f/32.f;
|
|
}
|
|
else
|
|
{
|
|
bytes_weight = 1.f/64.f;
|
|
}
|
|
bytes_weight *= bytes_weight;
|
|
|
|
|
|
//llinfos << "VS: " << virtual_size << llendl;
|
|
F32 virtual_size_factor = virtual_size / (10.f*10.f);
|
|
|
|
// The goal is for weighted priority to be <= 0 when we've reached a point where
|
|
// we've sent enough data.
|
|
//llinfos << "BytesSent: " << bytes_sent << llendl;
|
|
//llinfos << "BytesWeight: " << bytes_weight << llendl;
|
|
//llinfos << "PreLog: " << bytes_weight * virtual_size_factor << llendl;
|
|
w_priority = (F32)log10(bytes_weight * virtual_size_factor);
|
|
|
|
//llinfos << "PreScale: " << w_priority << llendl;
|
|
|
|
// We don't want to affect how MANY bytes we send based on the visible pixels, but the order
|
|
// in which they're sent. We post-multiply so we don't change the zero point.
|
|
if (w_priority > 0.f)
|
|
{
|
|
F32 pixel_weight = (F32)log10(visible_pixels + 1)*3.0f;
|
|
w_priority *= pixel_weight;
|
|
}
|
|
|
|
return w_priority;
|
|
}
|
|
|
|
//============================================================================
|