/** * @file llimage.cpp * @brief Base class for images. * * $LicenseInfo:firstyear=2001&license=viewergpl$ * * Copyright (c) 2001-2009, Linden Research, Inc. * * Second Life Viewer Source Code * The source code in this file ("Source Code") is provided by Linden Lab * to you under the terms of the GNU General Public License, version 2.0 * ("GPL"), unless you have obtained a separate licensing agreement * ("Other License"), formally executed by you and Linden Lab. Terms of * the GPL can be found in doc/GPL-license.txt in this distribution, or * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 * * There are special exceptions to the terms and conditions of the GPL as * it is applied to this Source Code. View the full text of the exception * in the file doc/FLOSS-exception.txt in this software distribution, or * online at * http://secondlifegrid.net/programs/open_source/licensing/flossexception * * By copying, modifying or distributing this software, you acknowledge * that you have read and understood your obligations described above, * and agree to abide by those obligations. * * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, * COMPLETENESS OR PERFORMANCE. * $/LicenseInfo$ */ #include "linden_common.h" #include "llimage.h" #include "llmath.h" #include "v4coloru.h" #include "llmemtype.h" #include "llimagebmp.h" #include "llimagetga.h" #include "llimagej2c.h" #include "llimagejpeg.h" #include "llimagepng.h" #include "llimagedxt.h" #include "llimageworker.h" //--------------------------------------------------------------------------- // LLImage //--------------------------------------------------------------------------- //static std::string LLImage::sLastErrorMessage; LLMutex* LLImage::sMutex = NULL; //static void LLImage::initClass() { sMutex = new LLMutex(NULL); LLImageJ2C::openDSO(); } //static void LLImage::cleanupClass() { LLImageJ2C::closeDSO(); delete sMutex; sMutex = NULL; } //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), mMemType(LLMemType::MTYPE_IMAGEBASE) { mBadBufferAllocation = FALSE ; } // virtual LLImageBase::~LLImageBase() { deleteData(); // virtual } // 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; } } BOOL LLImageBase::sSizeOverride = FALSE; // virtual void LLImageBase::deleteData() { delete[] mData; mData = NULL; mDataSize = 0; } // virtual U8* LLImageBase::allocateData(S32 size) { LLMemType mt1(mMemType); if (size < 0) { size = mWidth * mHeight * mComponents; if (size <= 0) { llerrs << llformat("LLImageBase::allocateData called with bad dimensions: %dx%dx%d",mWidth,mHeight,mComponents) << llendl; } } else if (size <= 0 || (size > 4096*4096*16 && sSizeOverride == FALSE)) { llerrs << "LLImageBase::allocateData: bad size: " << size << llendl; } if (!mData || size != mDataSize) { deleteData(); // virtual mBadBufferAllocation = FALSE ; mData = new U8[size]; if (!mData) { llwarns << "allocate image data: " << size << llendl; size = 0 ; mWidth = mHeight = 0 ; mBadBufferAllocation = TRUE ; } mDataSize = size; } return mData; } // virtual U8* LLImageBase::reallocateData(S32 size) { LLMemType mt1(mMemType); U8 *new_datap = new U8[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 */ delete[] 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() { mMemType = LLMemType::MTYPE_IMAGERAW; ++sRawImageCount; } LLImageRaw::LLImageRaw(U16 width, U16 height, S8 components) : LLImageBase() { mMemType = LLMemType::MTYPE_IMAGERAW; 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) : LLImageBase() { mMemType = LLMemType::MTYPE_IMAGERAW; 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; } U8 * LLImageRaw::getSubImage(U32 x_pos, U32 y_pos, U32 width, U32 height) const { LLMemType mt1(mMemType); U8 *data = new U8[width*height*getComponents()]; // Should do some simple bounds checking if (!data) { llerrs << "Out of memory in LLImageRaw::getSubImage" << llendl; return NULL; } U32 i; for (i = y_pos; i < y_pos+height; i++) { memcpy(data + i*width*getComponents(), /* Flawfinder: ignore */ getData() + ((y_pos + i)*getWidth() + x_pos)*getComponents(), getComponents()*width); } return data; } 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() { LLMemType mt1(mMemType); S32 row_bytes = getWidth() * getComponents(); llassert(row_bytes > 0); std::vector 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) { LLMemType mt1(mMemType); 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 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; } } // 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; } } } // 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; igetComponents() ); 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; igetComponents()) || (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 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 ); } } //scale down image by not blending a pixel with its neighbors. BOOL LLImageRaw::scaleDownWithoutBlending( S32 new_width, S32 new_height) { LLMemType mt1(mMemType); S8 c = getComponents() ; llassert((1 == c) || (3 == c) || (4 == c) ); S32 old_width = getWidth(); S32 old_height = getHeight(); S32 new_data_size = old_width * new_height * c ; llassert_always(new_data_size > 0); F32 ratio_x = (F32)old_width / new_width ; F32 ratio_y = (F32)old_height / new_height ; if( ratio_x < 1.0f || ratio_y < 1.0f ) { return TRUE; // Nothing to do. } ratio_x -= 1.0f ; ratio_y -= 1.0f ; U8* new_data = new U8[new_data_size] ; llassert_always(new_data != NULL) ; U8* old_data = getData() ; S32 i, j, k, s, t; for(i = 0, s = 0, t = 0 ; i < new_height ; i++) { for(j = 0 ; j < new_width ; j++) { for(k = 0 ; k < c ; k++) { new_data[s++] = old_data[t++] ; } t += (S32)(ratio_x * c + 0.1f) ; } t += (S32)(ratio_y * old_width * c + 0.1f) ; } setDataAndSize(new_data, new_width, new_height, c) ; return TRUE ; } BOOL LLImageRaw::scale( S32 new_width, S32 new_height, BOOL scale_image_data ) { LLMemType mt1(mMemType); 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 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 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 ; } 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) 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(""); } 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; } 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 image; switch(codec) { //case IMG_CODEC_RGB: 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_J2C: image = new LLImageJ2C(); break; case IMG_CODEC_DXT: image = new LLImageDXT(); break; default: return false; } 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; } //--------------------------------------------------------------------------- // LLImageFormatted //--------------------------------------------------------------------------- //static S32 LLImageFormatted::sGlobalFormattedMemory = 0; LLImageFormatted::LLImageFormatted(S8 codec) : LLImageBase(), mCodec(codec), mDecoding(0), mDecoded(0), mDiscardLevel(0) { mMemType = LLMemType::MTYPE_IMAGEFORMATTED; } // 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); } } } //---------------------------------------------------------------------------- BOOL LLImageFormatted::load(const std::string &filename) { 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; } BOOL res; U8 *data = allocateData(file_size); apr_size_t bytes_read = file_size; apr_status_t s = apr_file_read(apr_file, data, &bytes_read); // modifies bytes_read if (s != APR_SUCCESS || (S32) bytes_read != file_size) { deleteData(); setLastError("Unable to read entire 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); } //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>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; } //============================================================================