From 420b91db29485df39fd6e724e782c449158811cb Mon Sep 17 00:00:00 2001 From: James Cook Date: Tue, 2 Jan 2007 08:33:20 +0000 Subject: Print done when done. --- indra/llimage/llimage.cpp | 1772 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1772 insertions(+) create mode 100644 indra/llimage/llimage.cpp (limited to 'indra/llimage/llimage.cpp') diff --git a/indra/llimage/llimage.cpp b/indra/llimage/llimage.cpp new file mode 100644 index 0000000000..89b4a6d1cc --- /dev/null +++ b/indra/llimage/llimage.cpp @@ -0,0 +1,1772 @@ +/** + * @file llimage.cpp + * @brief Base class for images. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include +#include +#include +#include +#include + +#include "llmath.h" +#include "stdtypes.h" +#include "v4coloru.h" +#include "llmemory.h" + +#include "llimage.h" +#include "llimagebmp.h" +#include "llimagetga.h" +#include "llimagej2c.h" +#if JPEG_SUPPORT +#include "llimagejpeg.h" +#endif +#include "llimagedxt.h" + +//--------------------------------------------------------------------------- +// LLImageBase +//--------------------------------------------------------------------------- + +LLImageBase::LLImageBase() + : mData(NULL), + mDataSize(0), + mWidth(0), + mHeight(0), + mComponents(0), + mMemType(LLMemType::MTYPE_IMAGEBASE) +{ +} + +// 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; + } +} + +LLString LLImageBase::sLastErrorMessage; +BOOL LLImageBase::sSizeOverride = FALSE; + +BOOL LLImageBase::setLastError(const LLString& message, const LLString& filename) +{ + sLastErrorMessage = message; + if (filename != "") + { + sLastErrorMessage += LLString(" FILE:"); + sLastErrorMessage += filename; + } + llwarns << sLastErrorMessage << llendl; + return FALSE; +} + +// virtual +void LLImageBase::deleteData() +{ + delete[] mData; + mData = NULL; + mDataSize = 0; +} + +// virtual +U8* LLImageBase::allocateData(S32 size) +{ + LLMemType mt1((LLMemType::EMemType)mMemType); + + if (size < 0) + { + size = mWidth * mHeight * mComponents; + if (size <= 0) + { + llerrs << llformat("LLImageBase::allocateData called with bad dimentions: %dx%dx%d",mWidth,mHeight,mComponents) << llendl; + } + } + else if ((size <= 0 || size > 4096*4096*16) && sSizeOverride == FALSE) + { + llerrs << "LLImageBase::allocateData: bad size: " << size << llendl; + } + + resetLastError(); + + if (!mData || size != mDataSize) + { + deleteData(); // virtual + mData = new U8[size]; + if (!mData) + { + llerrs << "allocate image data: " << size << llendl; + } + mDataSize = size; + } + + return mData; +} + +// virtual +U8* LLImageBase::reallocateData(S32 size) +{ + LLMemType mt1((LLMemType::EMemType)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); + delete[] mData; + } + mData = new_datap; + mDataSize = size; + return mData; +} + + +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; + copyData(data, width, height, components); + ++sRawImageCount; +} + +LLImageRaw::LLImageRaw(const LLString &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(); +} + +BOOL LLImageRaw::copyData(U8 *data, U16 width, U16 height, S8 components) +{ + if (!resize(width, height, components)) + { + return FALSE; + } + memcpy(getData(), data, width*height*components); + return TRUE; +} + +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((LLMemType::EMemType)mMemType); + U8 *data = new U8[width*height*getComponents()]; + + // Should do some simple bounds checking + + U32 i; + for (i = y_pos; i < y_pos+height; i++) + { + memcpy(data + i*width*getComponents(), + 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; + U32 to_offset; + U32 from_offset; + if (!reverse_y) + { + for (i = 0; i < height; i++) + { + to_offset = (y_pos + i)*getWidth() + x_pos; + if (stride != 0) + { + from_offset = i*stride; + } + else + { + from_offset = i*width*getComponents(); + } + memcpy(getData() + to_offset*getComponents(), + data + from_offset, getComponents()*width); + } + } + else + { + for (i = 0; i < height; i++) + { + to_offset = (y_pos + i)*getWidth() + x_pos; + if (stride != 0) + { + from_offset = (height - 1 - i)*stride; + } + else + { + from_offset = (height - 1 - i)*width*getComponents(); + } + memcpy(getData() + to_offset*getComponents(), + 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((LLMemType::EMemType)mMemType); + S32 row_bytes = getWidth() * getComponents(); + U8* line_buffer = new U8[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, row_a_data, row_bytes ); + memcpy( row_a_data, row_b_data, row_bytes ); + memcpy( row_b_data, line_buffer, row_bytes ); + } + delete[] line_buffer; +} + + +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()) || (4 == src->getComponents()) ); + llassert( (3 == dst->getComponents()) || (4 == 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 ); + } + } + } + else + { + // 4 == dst->mComponents + llassert(0); // not implemented yet. + } +} + +// Src and dst can be any size. Src has 4 components. Dst has 3 components. +void LLImageRaw::compositeScaled4onto3(LLImageRaw* src) +{ + LLMemType mt1((LLMemType::EMemType)mMemType); + llinfos << "compositeScaled4onto3" << llendl; + + LLImageRaw* dst = this; // Just for clarity. + + llassert( (4 == src->getComponents()) && (3 == dst->getComponents()) ); + + // Vertical: scale but no composite + S32 temp_data_size = src->getWidth() * dst->getHeight() * src->getComponents(); + U8* temp_buffer = new U8[ temp_data_size ]; + for( S32 col = 0; col < src->getWidth(); col++ ) + { + copyLineScaled( src->getData() + (src->getComponents() * col), temp_buffer + (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 + (src->getComponents() * src->getWidth() * row), dst->getData() + (dst->getComponents() * dst->getWidth() * row), src->getWidth(), dst->getWidth() ); + } + + // Clean up + delete[] temp_buffer; +} + + +// 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) +{ + LLImageRaw* dst = this; // Just for clarity. + + llassert( (3 == src->getComponents()) || (4 == src->getComponents()) ); + llassert( (3 == dst->getComponents()) || (4 == dst->getComponents()) ); + + 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( (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() ); +} + + +// 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()) || (4 == src->getComponents()) ); + llassert( src->getComponents() == dst->getComponents() ); + + if( (src->getWidth() == dst->getWidth()) && (src->getHeight() == dst->getHeight()) ) + { + memcpy( dst->getData(), src->getData(), getWidth() * getHeight() * getComponents() ); + return; + } + + // Vertical + S32 temp_data_size = src->getWidth() * dst->getHeight() * getComponents(); + U8* temp_buffer = new U8[ temp_data_size ]; + for( S32 col = 0; col < src->getWidth(); col++ ) + { + copyLineScaled( src->getData() + (getComponents() * col), temp_buffer + (getComponents() * col), src->getHeight(), dst->getHeight(), src->getWidth(), src->getWidth() ); + } + + // Horizontal + for( S32 row = 0; row < dst->getHeight(); row++ ) + { + copyLineScaled( temp_buffer + (getComponents() * src->getWidth() * row), dst->getData() + (getComponents() * dst->getWidth() * row), src->getWidth(), dst->getWidth(), 1, 1 ); + } + + // Clean up + delete[] temp_buffer; +} + + +void LLImageRaw::scale( S32 new_width, S32 new_height, BOOL scale_image_data ) +{ + LLMemType mt1((LLMemType::EMemType)mMemType); + llassert( (3 == getComponents()) || (4 == getComponents()) ); + + S32 old_width = getWidth(); + S32 old_height = getHeight(); + + if( (old_width == new_width) && (old_height == new_height) ) + { + return; // Nothing to do. + } + + // Reallocate the data buffer. + + if (scale_image_data) + { + // Vertical + S32 temp_data_size = old_width * new_height * getComponents(); + U8* temp_buffer = new U8[ temp_data_size ]; + for( S32 col = 0; col < old_width; col++ ) + { + copyLineScaled( getData() + (getComponents() * col), temp_buffer + (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 + (getComponents() * old_width * row), new_buffer + (getComponents() * new_width * row), old_width, new_width, 1, 1 ); + } + + // Clean up + delete[] temp_buffer; + } + else + { + // copy out existing image data + S32 temp_data_size = old_width * old_height * getComponents(); + U8* temp_buffer = new U8[ temp_data_size ]; + memcpy(temp_buffer, 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 + (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()); + } + } + + // Clean up + delete[] temp_buffer; + } +} + +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; + S8 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 } +}; +#define NUM_FILE_EXTENSIONS sizeof(file_extensions)/sizeof(file_extensions[0]) + +static LLString find_file(LLString &name, S8 *codec) +{ + LLString tname; + for (int i=0; i<(int)(NUM_FILE_EXTENSIONS); i++) + { + tname = name + "." + LLString(file_extensions[i].exten); + llifstream ifs(tname.c_str(), llifstream::binary); + if (ifs.is_open()) + { + ifs.close(); + if (codec) + *codec = file_extensions[i].codec; + return LLString(file_extensions[i].exten); + } + } + return LLString(""); +} + +static S8 get_codec(const LLString& 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 LLString &filename, bool j2c_lowest_mip_only) +{ + LLString name = filename; + size_t dotidx = name.rfind('.'); + S8 codec = IMG_CODEC_INVALID; + LLString exten; + + deleteData(); // delete any existing data + + if (dotidx != LLString::npos) + { + exten = name.substr(dotidx+1); + LLString::toLower(exten); + codec = get_codec(exten); + } + else + { + exten = find_file(name, &codec); + name = name + "." + exten; + } + if (codec == IMG_CODEC_INVALID) + { + return false; // format not recognized + } + + llifstream ifs(name.c_str(), 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; +#if JPEG_SUPPORT + case IMG_CODEC_JPEG: + image = new LLImageJPEG(); + break; +#endif + 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(); + + image->updateData(); + + 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); + } + + BOOL 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; + +//static +LLWorkerThread* LLImageFormatted::sWorkerThread = NULL; + +//static +void LLImageFormatted::initClass(bool threaded, bool run_always) +{ + sWorkerThread = new LLWorkerThread(threaded, run_always); +} + +//static +void LLImageFormatted::cleanupClass() +{ + delete sWorkerThread; + sWorkerThread = NULL; +} + + +LLImageFormatted::LLImageFormatted(S8 codec) + : LLImageBase(), LLWorkerClass(sWorkerThread, "ImageFormatted"), + 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(); + releaseDecodedData(); +} + +//---------------------------------------------------------------------------- + +//virtual +void LLImageFormatted::startWork(S32 param) +{ + if (mDecoding) llerrs << "WTF?" << llendl; +} + +bool LLImageFormatted::doWork(S32 param) +{ + if (!(isWorking())) llerrs << "WTF?" << llendl; + llassert(mDecodedImage.notNull()); + if (param == 0) + { + // Decode primary channels + mDecoded = decode(mDecodedImage, .001f); // 1ms + } + else + { + // Decode aux channel + mDecoded = decode(mDecodedImage, .001f, param, param); // 1ms + } + if (mDecoded) + { + return true; + } + else + { + return false; + } +} + +void LLImageFormatted::endWork(S32 param, bool aborted) +{ + if (mDecoding) llerrs << "WTF?" << llendl; + if (!mDecoded) llerrs << "WTF?" << llendl; +} + +//---------------------------------------------------------------------------- + +// static +LLImageFormatted* LLImageFormatted::createFromExtension(const LLString& instring) +{ + LLString exten; + size_t dotidx = instring.rfind('.'); + if (dotidx != LLString::npos) + { + exten = instring.substr(dotidx+1); + } + else + { + exten = instring; + } + S8 codec = get_codec(exten); + LLPointer image; + switch(codec) + { + case IMG_CODEC_BMP: + image = new LLImageBMP(); + break; + case IMG_CODEC_TGA: + image = new LLImageTGA(); + break; +#if JPEG_SUPPORT + case IMG_CODEC_JPEG: + image = new LLImageJPEG(); + break; +#endif + case IMG_CODEC_J2C: + image = new LLImageJ2C(); + break; + case IMG_CODEC_DXT: + image = new LLImageDXT(); + break; + default: + break; + } + return image; +} +//---------------------------------------------------------------------------- + +// virtual +void LLImageFormatted::dump() +{ + LLImageBase::dump(); + + llinfos << "LLImageFormatted" + << " mDecoding " << mDecoding + << " mCodec " << S32(mCodec) + << " mDecoded " << mDecoded + << llendl; +} + +//---------------------------------------------------------------------------- + +void LLImageFormatted::readHeader(U8* data, S32 size) +{ + if (size <= 0) + { + size = calcHeaderSize(); + } + copyData(data, size); // calls updateData() +} + +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::decode(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 +BOOL LLImageFormatted::requestDecodedData(LLPointer& raw, S32 discard, F32 decode_time) +{ + llassert(getData() && getDataSize()); + // For most codecs, only mDiscardLevel data is available. (see LLImageDXT for exception) + if (discard >= 0 && discard != mDiscardLevel) + { + llerrs << "Request for invalid discard level" << llendl; + } + if (haveWork()) + { + checkWork(); + } + if (!mDecoded) + { + if (!haveWork()) + { + llassert(!mDecoding); + mDecodedImage = new LLImageRaw(getWidth(), getHeight(), getComponents()); + addWork(0); + } + return FALSE; + } + else + { + llassert(mDecodedImage.notNull()); + llassert(!mDecoding); + raw = mDecodedImage; + return TRUE; + } +} + +BOOL LLImageFormatted::requestDecodedAuxData(LLPointer& raw, S32 channel, + S32 discard, F32 decode_time) +{ + llassert(getData() && getDataSize()); + // For most codecs, only mDiscardLevel data is available. (see LLImageDXT for exception) + if (discard >= 0 && discard != mDiscardLevel) + { + llerrs << "Request for invalid discard level" << llendl; + } + if (haveWork()) + { + checkWork(); + } + if (!mDecoded) + { + if (!haveWork()) + { + llassert(!mDecoding); + mDecodedImage = new LLImageRaw(getWidth(), getHeight(), 1); + addWork(channel); + } + return FALSE; + } + else + { + llassert(mDecodedImage.notNull()); + llassert(!mDecoding); + raw = mDecodedImage; + return TRUE; + } +} + + +// virtual +void LLImageFormatted::releaseDecodedData() +{ + if (mDecoded || mDecoding) + { + mDecodedImage = NULL; // deletes image + mDecoded = FALSE; + mDecoding = FALSE; + } +} + +//---------------------------------------------------------------------------- + +// 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()) + { + deleteData(); + allocateData(size); + memcpy(getData(), data, size); + } + updateData(); // virtual + + return TRUE; +} + +BOOL LLImageFormatted::appendData(U8 *data, S32 size) +{ + LLMemType mt1((LLMemType::EMemType)mMemType); + S32 old_size = getDataSize(); + U8* old_data = getData(); + S32 new_size = old_size + size; + U8* new_data = new U8[new_size]; + // resize the image + setDataAndSize(new_data, new_size); + // copy the old data and delete it + memcpy(new_data, old_data, old_size); + delete old_data; + // if we have new data, copy it and call updateData() + if (data) + { + memcpy(new_data + old_size, data, size); + updateData(); // virtual + } + return TRUE; +} + +BOOL LLImageFormatted::setData(U8 *data, S32 size) +{ + if (data && data != getData()) + { + deleteData(); + setDataAndSize(data, size); // Access private LLImageBase members + sGlobalFormattedMemory += getDataSize(); + } + return updateData(); // virtual +} + +//---------------------------------------------------------------------------- + +BOOL LLImageFormatted::load(const LLString &filename) +{ + resetLastError(); + + S32 file_size = 0; + apr_file_t* apr_file = ll_apr_file_open(filename, LL_APR_RB, &file_size); + if (!apr_file) + { + setLastError("Unable to open file for reading", filename); + return FALSE; + } + if (file_size == 0) + { + setLastError("File is empty",filename); + apr_file_close(apr_file); + 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(); + } + apr_file_close(apr_file); + + return res; +} + +BOOL LLImageFormatted::save(const LLString &filename) +{ + resetLastError(); + + apr_file_t* apr_file = ll_apr_file_open(filename, LL_APR_WB); + if (!apr_file) + { + setLastError("Unable to open file for reading", filename); + return FALSE; + } + + ll_apr_file_write(apr_file, getData(), getDataSize()); + apr_file_close(apr_file); + + 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; +} -- cgit v1.2.3