diff options
Diffstat (limited to 'indra/llimage')
25 files changed, 1310 insertions, 145 deletions
diff --git a/indra/llimage/CMakeLists.txt b/indra/llimage/CMakeLists.txt index ea8c1a1107..293ada7548 100644..100755 --- a/indra/llimage/CMakeLists.txt +++ b/indra/llimage/CMakeLists.txt @@ -7,12 +7,15 @@ include(LLCommon) include(LLImage) include(LLMath) include(LLVFS) +include(LLKDU) +include(LLImageJ2COJ) include(ZLIB) include(LLAddBuildTest) include(Tut) include_directories( ${LLCOMMON_INCLUDE_DIRS} + ${LLCOMMON_SYSTEM_INCLUDE_DIRS} ${LLMATH_INCLUDE_DIRS} ${LLVFS_INCLUDE_DIRS} ${PNG_INCLUDE_DIRS} @@ -24,6 +27,7 @@ set(llimage_SOURCE_FILES llimage.cpp llimagedimensionsinfo.cpp llimagedxt.cpp + llimagefilter.cpp llimagej2c.cpp llimagejpeg.cpp llimagepng.cpp @@ -39,6 +43,7 @@ set(llimage_HEADER_FILES llimagebmp.h llimagedimensionsinfo.h llimagedxt.h + llimagefilter.h llimagej2c.h llimagejpeg.h llimagepng.h @@ -56,8 +61,16 @@ list(APPEND llimage_SOURCE_FILES ${llimage_HEADER_FILES}) add_library (llimage ${llimage_SOURCE_FILES}) # Libraries on which this library depends, needed for Linux builds # Sort by high-level to low-level +if (USE_KDU) + target_link_libraries(llimage ${LLKDU_LIBRARIES}) +else (USE_KDU) + target_link_libraries(llimage ${LLIMAGEJ2COJ_LIBRARIES}) +endif (USE_KDU) + target_link_libraries(llimage - llcommon + ${LLVFS_LIBRARIES} + ${LLMATH_LIBRARIES} + ${LLCOMMON_LIBRARIES} ${JPEG_LIBRARIES} ${PNG_LIBRARIES} ${ZLIB_LIBRARIES} diff --git a/indra/llimage/llimage.cpp b/indra/llimage/llimage.cpp index a88ac148ef..16df27bb8e 100644..100755 --- a/indra/llimage/llimage.cpp +++ b/indra/llimage/llimage.cpp @@ -26,6 +26,7 @@ #include "linden_common.h" +#include "llimageworker.h" #include "llimage.h" #include "llmath.h" @@ -37,7 +38,6 @@ #include "llimagejpeg.h" #include "llimagepng.h" #include "llimagedxt.h" -#include "llimageworker.h" #include "llmemory.h" //--------------------------------------------------------------------------- @@ -89,15 +89,15 @@ void LLImage::setLastError(const std::string& message) //--------------------------------------------------------------------------- LLImageBase::LLImageBase() - : mData(NULL), - mDataSize(0), - mWidth(0), - mHeight(0), - mComponents(0), - mBadBufferAllocation(false), - mAllowOverSize(false) -{ -} +: LLTrace::MemTrackable<LLImageBase>("LLImage"), + mData(NULL), + mDataSize(0), + mWidth(0), + mHeight(0), + mComponents(0), + mBadBufferAllocation(false), + mAllowOverSize(false) +{} // virtual LLImageBase::~LLImageBase() @@ -127,12 +127,12 @@ void LLImageBase::destroyPrivatePool() // virtual void LLImageBase::dump() { - llinfos << "LLImageBase mComponents " << mComponents + LL_INFOS() << "LLImageBase mComponents " << mComponents << " mData " << mData << " mDataSize " << mDataSize << " mWidth " << mWidth << " mHeight " << mHeight - << llendl; + << LL_ENDL; } // virtual @@ -144,13 +144,13 @@ void LLImageBase::sanityCheck() || mComponents > (S8)MAX_IMAGE_COMPONENTS ) { - llerrs << "Failed LLImageBase::sanityCheck " + LL_ERRS() << "Failed LLImageBase::sanityCheck " << "width " << mWidth << "height " << mHeight << "datasize " << mDataSize << "components " << mComponents << "data " << mData - << llendl; + << LL_ENDL; } } @@ -158,8 +158,9 @@ void LLImageBase::sanityCheck() void LLImageBase::deleteData() { FREE_MEM(sPrivatePoolp, mData) ; - mData = NULL; + disclaimMem(mDataSize); mDataSize = 0; + mData = NULL; } // virtual @@ -170,7 +171,7 @@ U8* LLImageBase::allocateData(S32 size) size = mWidth * mHeight * mComponents; if (size <= 0) { - llerrs << llformat("LLImageBase::allocateData called with bad dimensions: %dx%dx%d",mWidth,mHeight,(S32)mComponents) << llendl; + LL_ERRS() << llformat("LLImageBase::allocateData called with bad dimensions: %dx%dx%d",mWidth,mHeight,(S32)mComponents) << LL_ENDL; } } @@ -178,14 +179,14 @@ U8* LLImageBase::allocateData(S32 size) 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 ; + LL_INFOS() << "width: " << mWidth << " height: " << mHeight << " components: " << mComponents << LL_ENDL ; if(mAllowOverSize) { - llinfos << "Oversize: " << size << llendl ; + LL_INFOS() << "Oversize: " << size << LL_ENDL ; } else { - llerrs << "LLImageBase::allocateData: bad size: " << size << llendl; + LL_ERRS() << "LLImageBase::allocateData: bad size: " << size << LL_ENDL; } } if (!mData || size != mDataSize) @@ -195,12 +196,13 @@ U8* LLImageBase::allocateData(S32 size) mData = (U8*)ALLOCATE_MEM(sPrivatePoolp, size); if (!mData) { - llwarns << "Failed to allocate image data size [" << size << "]" << llendl; + LL_WARNS() << "Failed to allocate image data size [" << size << "]" << LL_ENDL; size = 0 ; mWidth = mHeight = 0 ; mBadBufferAllocation = true ; } mDataSize = size; + claimMem(mDataSize); } return mData; @@ -212,7 +214,7 @@ U8* LLImageBase::reallocateData(S32 size) U8 *new_datap = (U8*)ALLOCATE_MEM(sPrivatePoolp, size); if (!new_datap) { - llwarns << "Out of memory in LLImageBase::reallocateData" << llendl; + LL_WARNS() << "Out of memory in LLImageBase::reallocateData" << LL_ENDL; return 0; } if (mData) @@ -222,7 +224,9 @@ U8* LLImageBase::reallocateData(S32 size) FREE_MEM(sPrivatePoolp, mData) ; } mData = new_datap; + disclaimMem(mDataSize); mDataSize = size; + claimMem(mDataSize); return mData; } @@ -230,7 +234,7 @@ const U8* LLImageBase::getData() const { if(mBadBufferAllocation) { - llerrs << "Bad memory allocation for the image buffer!" << llendl ; + LL_ERRS() << "Bad memory allocation for the image buffer!" << LL_ENDL ; } return mData; @@ -240,7 +244,7 @@ U8* LLImageBase::getData() { if(mBadBufferAllocation) { - llerrs << "Bad memory allocation for the image buffer!" << llendl ; + LL_ERRS() << "Bad memory allocation for the image buffer!" << LL_ENDL ; } return mData; @@ -288,7 +292,6 @@ LLImageRaw::LLImageRaw(U16 width, U16 height, S8 components) LLImageRaw::LLImageRaw(U8 *data, U16 width, U16 height, S8 components, bool no_copy) : LLImageBase() { - if(no_copy) { setDataAndSize(data, width, height, components); @@ -449,18 +452,8 @@ void LLImageRaw::verticalFlip() 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; - } + S32 new_width = expandDimToPowerOfTwo(getWidth(), max_dim); + S32 new_height = expandDimToPowerOfTwo(getHeight(), max_dim); scale( new_width, new_height, scale_image ); } @@ -468,55 +461,61 @@ void LLImageRaw::expandToPowerOfTwo(S32 max_dim, BOOL 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; - } + S32 new_width = contractDimToPowerOfTwo(getWidth(), MIN_IMAGE_SIZE); + S32 new_height = contractDimToPowerOfTwo(getHeight(), MIN_IMAGE_SIZE); scale( new_width, new_height, scale_image ); } -void LLImageRaw::biasedScaleToPowerOfTwo(S32 max_dim) +// static +S32 LLImageRaw::biasedDimToPowerOfTwo(S32 curr_dim, S32 max_dim) { // Strong bias towards rounding down (to save bandwidth) // No bias would mean THRESHOLD == 1.5f; - const F32 THRESHOLD = 1.75f; - + 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) ) + S32 larger_dim = max_dim; // 2^n >= curr_dim + S32 smaller_dim = max_dim; // 2^(n-1) <= curr_dim + while( (smaller_dim > curr_dim) && (smaller_dim > MIN_IMAGE_SIZE) ) { - larger_w = smaller_w; - smaller_w >>= 1; + larger_dim = smaller_dim; + smaller_dim >>= 1; } - S32 new_width = ( (F32)getWidth() / smaller_w > THRESHOLD ) ? larger_w : smaller_w; + return ( ((F32)curr_dim / (F32)smaller_dim) > THRESHOLD ) ? larger_dim : smaller_dim; +} +// static +S32 LLImageRaw::expandDimToPowerOfTwo(S32 curr_dim, S32 max_dim) +{ + S32 new_dim = MIN_IMAGE_SIZE; + while( (new_dim < curr_dim) && (new_dim < max_dim) ) + { + new_dim <<= 1; + } + return new_dim; +} - 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) ) +// static +S32 LLImageRaw::contractDimToPowerOfTwo(S32 curr_dim, S32 min_dim) +{ + S32 new_dim = MAX_IMAGE_SIZE; + while( (new_dim > curr_dim) && (new_dim > min_dim) ) { - larger_h = smaller_h; - smaller_h >>= 1; + new_dim >>= 1; } - S32 new_height = ( (F32)getHeight() / smaller_h > THRESHOLD ) ? larger_h : smaller_h; + return new_dim; +} +void LLImageRaw::biasedScaleToPowerOfTwo(S32 max_dim) +{ + // Find new sizes + S32 new_width = biasedDimToPowerOfTwo(getWidth(),max_dim); + S32 new_height = biasedDimToPowerOfTwo(getHeight(),max_dim); 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 ) { @@ -563,7 +562,7 @@ void LLImageRaw::composite( LLImageRaw* src ) // Src and dst can be any size. Src has 4 components. Dst has 3 components. void LLImageRaw::compositeScaled4onto3(LLImageRaw* src) { - llinfos << "compositeScaled4onto3" << llendl; + LL_INFOS() << "compositeScaled4onto3" << LL_ENDL; LLImageRaw* dst = this; // Just for clarity. @@ -640,6 +639,29 @@ void LLImageRaw::compositeUnscaled4onto3( LLImageRaw* src ) } } +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 ) { @@ -666,15 +688,24 @@ void LLImageRaw::fill( const LLColor4U& color ) } } +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; + LL_WARNS() << "LLImageRaw::copy called with a null src pointer" << LL_ENDL; return; } @@ -1005,13 +1036,13 @@ void LLImageRaw::copyLineScaled( U8* in, U8* out, S32 in_pixel_len, S32 out_pixe a *= norm_factor; // skip conditional S32 t4 = x * out_pixel_step * components; - out[t4 + 0] = U8(llround(r)); + out[t4 + 0] = U8(ll_round(r)); if (components >= 2) - out[t4 + 1] = U8(llround(g)); + out[t4 + 1] = U8(ll_round(g)); if (components >= 3) - out[t4 + 2] = U8(llround(b)); + out[t4 + 2] = U8(ll_round(b)); if( components == 4) - out[t4 + 3] = U8(llround(a)); + out[t4 + 3] = U8(ll_round(a)); } } } @@ -1086,10 +1117,10 @@ void LLImageRaw::compositeRowScaled4onto3( U8* in, U8* out, S32 in_pixel_len, S3 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)); + in_scaled_r = U8(ll_round(r)); + in_scaled_g = U8(ll_round(g)); + in_scaled_b = U8(ll_round(b)); + in_scaled_a = U8(ll_round(a)); } if( in_scaled_a ) @@ -1141,7 +1172,7 @@ static std::string find_file(std::string &name, S8 *codec) for (int i=0; i<(int)(NUM_FILE_EXTENSIONS); i++) { tname = name + "." + std::string(file_extensions[i].exten); - llifstream ifs(tname, llifstream::binary); + llifstream ifs(tname.c_str(), llifstream::binary); if (ifs.is_open()) { ifs.close(); @@ -1188,11 +1219,11 @@ bool LLImageRaw::createFromFile(const std::string &filename, bool j2c_lowest_mip return false; // format not recognized } - llifstream ifs(name, llifstream::binary); + 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; + // SJB: changed from LL_INFOS() to LL_DEBUGS() to reduce spam + LL_DEBUGS() << "Unable to open image file: " << name << LL_ENDL; return false; } @@ -1206,7 +1237,7 @@ bool LLImageRaw::createFromFile(const std::string &filename, bool j2c_lowest_mip if (!length) { - llinfos << "Zero length file file: " << name << llendl; + LL_INFOS() << "Zero length file file: " << name << LL_ENDL; return false; } @@ -1242,7 +1273,7 @@ bool LLImageRaw::createFromFile(const std::string &filename, bool j2c_lowest_mip if (!success) { deleteData(); - llwarns << "Unable to decode image" << name << llendl; + LL_WARNS() << "Unable to decode image" << name << LL_ENDL; return false; } @@ -1347,11 +1378,11 @@ void LLImageFormatted::dump() { LLImageBase::dump(); - llinfos << "LLImageFormatted" + LL_INFOS() << "LLImageFormatted" << " mDecoding " << mDecoding << " mCodec " << S32(mCodec) << " mDecoded " << mDecoded - << llendl; + << LL_ENDL; } //---------------------------------------------------------------------------- @@ -1434,11 +1465,11 @@ void LLImageFormatted::sanityCheck() if (mCodec >= IMG_CODEC_EOF) { - llerrs << "Failed LLImageFormatted::sanityCheck " + LL_ERRS() << "Failed LLImageFormatted::sanityCheck " << "decoding " << S32(mDecoding) << "decoded " << S32(mDecoded) << "codec " << S32(mCodec) - << llendl; + << LL_ENDL; } } @@ -1585,7 +1616,10 @@ static void avg4_colors2(const U8* a, const U8* b, const U8* c, const U8* d, U8* void LLImageBase::setDataAndSize(U8 *data, S32 size) { ll_assert_aligned(data, 16); - mData = data; mDataSize = size; + mData = data; + disclaimMem(mDataSize); + mDataSize = size; + claimMem(mDataSize); } //static @@ -1613,7 +1647,7 @@ void LLImageBase::generateMip(const U8* indata, U8* mipdata, S32 width, S32 heig *(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; + LL_ERRS() << "generateMmip called with bad num channels" << LL_ENDL; } indata += nchannels*2; data += nchannels; @@ -1670,17 +1704,17 @@ F32 LLImageBase::calc_download_priority(F32 virtual_size, F32 visible_pixels, S3 bytes_weight *= bytes_weight; - //llinfos << "VS: " << virtual_size << llendl; + //LL_INFOS() << "VS: " << virtual_size << LL_ENDL; 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; + //LL_INFOS() << "BytesSent: " << bytes_sent << LL_ENDL; + //LL_INFOS() << "BytesWeight: " << bytes_weight << LL_ENDL; + //LL_INFOS() << "PreLog: " << bytes_weight * virtual_size_factor << LL_ENDL; w_priority = (F32)log10(bytes_weight * virtual_size_factor); - //llinfos << "PreScale: " << w_priority << llendl; + //LL_INFOS() << "PreScale: " << w_priority << LL_ENDL; // 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. diff --git a/indra/llimage/llimage.h b/indra/llimage/llimage.h index 6cb1226da0..cd3f76f1fd 100644..100755 --- a/indra/llimage/llimage.h +++ b/indra/llimage/llimage.h @@ -29,7 +29,8 @@ #include "lluuid.h" #include "llstring.h" -#include "llthread.h" +#include "llpointer.h" +#include "lltrace.h" const S32 MIN_IMAGE_MIP = 2; // 4x4, only used for expand/contract power of 2 const S32 MAX_IMAGE_MIP = 11; // 2048x2048 @@ -110,7 +111,9 @@ protected: //============================================================================ // Image base class -class LLImageBase : public LLThreadSafeRefCount +class LLImageBase +: public LLThreadSafeRefCount, + public LLTrace::MemTrackable<LLImageBase> { protected: virtual ~LLImageBase(); @@ -162,6 +165,8 @@ public: static void destroyPrivatePool() ; static LLPrivateMemoryPool* getPrivatePool() {return sPrivatePoolp;} + //static LLTrace::MemStatHandle sMemStat; + private: U8 *mData; S32 mDataSize; @@ -204,6 +209,9 @@ public: void verticalFlip(); + static S32 biasedDimToPowerOfTwo(S32 curr_dim, S32 max_dim = MAX_IMAGE_SIZE); + static S32 expandDimToPowerOfTwo(S32 curr_dim, S32 max_dim = MAX_IMAGE_SIZE); + static S32 contractDimToPowerOfTwo(S32 curr_dim, S32 min_dim = MIN_IMAGE_SIZE); void expandToPowerOfTwo(S32 max_dim = MAX_IMAGE_SIZE, BOOL scale_image = TRUE); void contractToPowerOfTwo(S32 max_dim = MAX_IMAGE_SIZE, BOOL scale_image = TRUE); void biasedScaleToPowerOfTwo(S32 max_dim = MAX_IMAGE_SIZE); @@ -214,6 +222,9 @@ public: // Copy operations + //duplicate this raw image if refCount > 1. + LLPointer<LLImageRaw> duplicate(); + // Src and dst can be any size. Src and dst can each have 3 or 4 components. void copy( LLImageRaw* src ); @@ -226,6 +237,11 @@ public: // Src and dst are same size. Src has 3 components. Dst has 4 components. void copyUnscaled3onto4( LLImageRaw* src ); + // Src and dst are same size. Src has 1 component. Dst has 4 components. + // Alpha component is set to source alpha mask component. + // RGB components are set to fill color. + void copyUnscaledAlphaMask( LLImageRaw* src, const LLColor4U& fill); + // Src and dst can be any size. Src and dst have same number of components. void copyScaled( LLImageRaw* src ); diff --git a/indra/llimage/llimagebmp.cpp b/indra/llimage/llimagebmp.cpp index 60b1c628d7..8573fe0d91 100644..100755 --- a/indra/llimage/llimagebmp.cpp +++ b/indra/llimage/llimagebmp.cpp @@ -321,7 +321,7 @@ BOOL LLImageBMP::updateData() mColorPalette = new U8[color_palette_size]; if (!mColorPalette) { - llerrs << "Out of memory in LLImageBMP::updateData()" << llendl; + LL_ERRS() << "Out of memory in LLImageBMP::updateData()" << LL_ENDL; return FALSE; } memcpy( mColorPalette, mdata + FILE_HEADER_SIZE + BITMAP_HEADER_SIZE + extension_size, color_palette_size ); /* Flawfinder: ignore */ @@ -528,7 +528,7 @@ BOOL LLImageBMP::encode(const LLImageRaw* raw_image, F32 encode_time) if( (2 == src_components) || (4 == src_components) ) { - llinfos << "Dropping alpha information during BMP encoding" << llendl; + LL_INFOS() << "Dropping alpha information during BMP encoding" << LL_ENDL; } setSize(raw_image->getWidth(), raw_image->getHeight(), dst_components); diff --git a/indra/llimage/llimagebmp.h b/indra/llimage/llimagebmp.h index db0b45def0..db0b45def0 100644..100755 --- a/indra/llimage/llimagebmp.h +++ b/indra/llimage/llimagebmp.h diff --git a/indra/llimage/llimagedimensionsinfo.cpp b/indra/llimage/llimagedimensionsinfo.cpp index c6bfa50b40..5bf3f29b3c 100644..100755 --- a/indra/llimage/llimagedimensionsinfo.cpp +++ b/indra/llimage/llimagedimensionsinfo.cpp @@ -77,7 +77,7 @@ bool LLImageDimensionsInfo::getImageDimensionsBmp() const S32 DATA_LEN = 26; // BMP header (14) + DIB header size (4) + width (4) + height (4) if (!checkFileLength(DATA_LEN)) { - llwarns << "Premature end of file" << llendl; + LL_WARNS() << "Premature end of file" << LL_ENDL; return false; } @@ -89,7 +89,7 @@ bool LLImageDimensionsInfo::getImageDimensionsBmp() // We only support Windows bitmaps (BM), according to LLImageBMP::updateData(). if (signature[0] != 'B' || signature[1] != 'M') { - llwarns << "Not a BMP" << llendl; + LL_WARNS() << "Not a BMP" << LL_ENDL; return false; } @@ -108,7 +108,7 @@ bool LLImageDimensionsInfo::getImageDimensionsTga() // Make sure the file is long enough. if (!checkFileLength(TGA_FILE_HEADER_SIZE + 1 /* width */ + 1 /* height */)) { - llwarns << "Premature end of file" << llendl; + LL_WARNS() << "Premature end of file" << LL_ENDL; return false; } @@ -127,7 +127,7 @@ bool LLImageDimensionsInfo::getImageDimensionsPng() // Make sure the file is long enough. if (!checkFileLength(PNG_MAGIC_SIZE + 8 + sizeof(S32) * 2 /* width, height */)) { - llwarns << "Premature end of file" << llendl; + LL_WARNS() << "Premature end of file" << LL_ENDL; return false; } @@ -139,7 +139,7 @@ bool LLImageDimensionsInfo::getImageDimensionsPng() // Make sure it's a PNG file. if (memcmp(signature, png_magic, PNG_MAGIC_SIZE) != 0) { - llwarns << "Not a PNG" << llendl; + LL_WARNS() << "Not a PNG" << LL_ENDL; return false; } @@ -156,7 +156,7 @@ void on_jpeg_error(j_common_ptr cinfo) { (void) cinfo; sJpegErrorEncountered = true; - llwarns << "Libjpeg has encountered an error!" << llendl; + LL_WARNS() << "Libjpeg has encountered an error!" << LL_ENDL; } bool LLImageDimensionsInfo::getImageDimensionsJpeg() @@ -172,17 +172,17 @@ bool LLImageDimensionsInfo::getImageDimensionsJpeg() /* Make sure this is a JPEG file. */ const size_t JPEG_MAGIC_SIZE = 2; - const uint8_t jpeg_magic[JPEG_MAGIC_SIZE] = {0xFF, 0xD8}; - uint8_t signature[JPEG_MAGIC_SIZE]; + const U8 jpeg_magic[JPEG_MAGIC_SIZE] = {0xFF, 0xD8}; + U8 signature[JPEG_MAGIC_SIZE]; if (fread(signature, sizeof(signature), 1, fp) != 1) { - llwarns << "Premature end of file" << llendl; + LL_WARNS() << "Premature end of file" << LL_ENDL; return false; } if (memcmp(signature, jpeg_magic, JPEG_MAGIC_SIZE) != 0) { - llwarns << "Not a JPEG" << llendl; + LL_WARNS() << "Not a JPEG" << LL_ENDL; return false; } fseek(fp, 0, SEEK_SET); // go back to start of the file diff --git a/indra/llimage/llimagedimensionsinfo.h b/indra/llimage/llimagedimensionsinfo.h index 382fdb2a0e..8f716c5d02 100644..100755 --- a/indra/llimage/llimagedimensionsinfo.h +++ b/indra/llimage/llimagedimensionsinfo.h @@ -27,6 +27,8 @@ #ifndef LL_LLIMAGEDIMENSIONSINFO_H #define LL_LLIMAGEDIMENSIONSINFO_H +#include "llapr.h" + //----------------------------------------------------------------------------- // LLImageDimensionsInfo // helper class to get image dimensions WITHOUT loading image to memore diff --git a/indra/llimage/llimagedxt.cpp b/indra/llimage/llimagedxt.cpp index 34c6793522..04e0e752eb 100644..100755 --- a/indra/llimage/llimagedxt.cpp +++ b/indra/llimage/llimagedxt.cpp @@ -52,7 +52,7 @@ S32 LLImageDXT::formatBits(EFileFormat format) case FORMAT_RGB8: return 24; case FORMAT_RGBA8: return 32; default: - llerrs << "LLImageDXT::Unknown format: " << format << llendl; + LL_ERRS() << "LLImageDXT::Unknown format: " << format << LL_ENDL; return 0; } }; @@ -82,7 +82,7 @@ S32 LLImageDXT::formatComponents(EFileFormat format) case FORMAT_RGB8: return 3; case FORMAT_RGBA8: return 4; default: - llerrs << "LLImageDXT::Unknown format: " << format << llendl; + LL_ERRS() << "LLImageDXT::Unknown format: " << format << LL_ENDL; return 0; } }; @@ -207,7 +207,7 @@ BOOL LLImageDXT::updateData() if (data_size < mHeaderSize) { - llerrs << "LLImageDXT: not enough data" << llendl; + LL_ERRS() << "LLImageDXT: not enough data" << LL_ENDL; } S32 ncomponents = formatComponents(mFileFormat); setSize(width, height, ncomponents); @@ -224,7 +224,7 @@ S32 LLImageDXT::getMipOffset(S32 discard) { if (mFileFormat >= FORMAT_DXT1 && mFileFormat <= FORMAT_DXT5) { - llerrs << "getMipOffset called with old (unsupported) format" << llendl; + LL_ERRS() << "getMipOffset called with old (unsupported) format" << LL_ENDL; } S32 width = getWidth(), height = getHeight(); S32 num_mips = calcNumMips(width, height); @@ -251,7 +251,7 @@ void LLImageDXT::setFormat() { case 3: mFileFormat = FORMAT_DXR1; break; case 4: mFileFormat = FORMAT_DXR3; break; - default: llerrs << "LLImageDXT::setFormat called with ncomponents = " << ncomponents << llendl; + default: LL_ERRS() << "LLImageDXT::setFormat called with ncomponents = " << ncomponents << LL_ENDL; } mHeaderSize = calcHeaderSize(); } @@ -265,7 +265,7 @@ BOOL LLImageDXT::decode(LLImageRaw* raw_image, F32 time) if (mFileFormat >= FORMAT_DXT1 && mFileFormat <= FORMAT_DXR5) { - llwarns << "Attempt to decode compressed LLImageDXT to Raw (unsupported)" << llendl; + LL_WARNS() << "Attempt to decode compressed LLImageDXT to Raw (unsupported)" << LL_ENDL; return FALSE; } @@ -303,7 +303,7 @@ BOOL LLImageDXT::getMipData(LLPointer<LLImageRaw>& raw, S32 discard) } else if (discard < mDiscardLevel) { - llerrs << "Request for invalid discard level" << llendl; + LL_ERRS() << "Request for invalid discard level" << LL_ENDL; } U8* data = getData() + getMipOffset(discard); S32 width = 0; @@ -331,7 +331,7 @@ BOOL LLImageDXT::encodeDXT(const LLImageRaw* raw_image, F32 time, bool explicit_ format = FORMAT_RGBA8; break; default: - llerrs << "LLImageDXT::encode: Unhandled channel number: " << ncomponents << llendl; + LL_ERRS() << "LLImageDXT::encode: Unhandled channel number: " << ncomponents << LL_ENDL; return 0; } @@ -422,7 +422,7 @@ bool LLImageDXT::convertToDXR() case FORMAT_DXT4: newformat = FORMAT_DXR4; break; case FORMAT_DXT5: newformat = FORMAT_DXR5; break; default: - llwarns << "convertToDXR: can not convert format: " << llformat("0x%08x",getFourCC(mFileFormat)) << llendl; + LL_WARNS() << "convertToDXR: can not convert format: " << llformat("0x%08x",getFourCC(mFileFormat)) << LL_ENDL; return false; } mFileFormat = newformat; @@ -433,7 +433,7 @@ bool LLImageDXT::convertToDXR() U8* newdata = (U8*)ALLOCATE_MEM(LLImageBase::getPrivatePool(), total_bytes); if (!newdata) { - llerrs << "Out of memory in LLImageDXT::convertToDXR()" << llendl; + LL_ERRS() << "Out of memory in LLImageDXT::convertToDXR()" << LL_ENDL; return false; } llassert(total_bytes > 0); @@ -466,7 +466,7 @@ S32 LLImageDXT::calcDataSize(S32 discard_level) { if (mFileFormat == FORMAT_UNKNOWN) { - llerrs << "calcDataSize called with unloaded LLImageDXT" << llendl; + LL_ERRS() << "calcDataSize called with unloaded LLImageDXT" << LL_ENDL; return 0; } if (discard_level < 0) diff --git a/indra/llimage/llimagedxt.h b/indra/llimage/llimagedxt.h index a8756ba8ed..a8756ba8ed 100644..100755 --- a/indra/llimage/llimagedxt.h +++ b/indra/llimage/llimagedxt.h diff --git a/indra/llimage/llimagefilter.cpp b/indra/llimage/llimagefilter.cpp new file mode 100755 index 0000000000..41adc7be9a --- /dev/null +++ b/indra/llimage/llimagefilter.cpp @@ -0,0 +1,939 @@ +/** + * @file llimagefilter.cpp + * @brief Simple Image Filtering. See https://wiki.lindenlab.com/wiki/SL_Viewer_Image_Filters for complete documentation. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2014, 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 "llimagefilter.h" + +#include "llmath.h" +#include "v3color.h" +#include "v4coloru.h" +#include "m3math.h" +#include "v3math.h" +#include "llsdserialize.h" +#include "llstring.h" + +//--------------------------------------------------------------------------- +// LLImageFilter +//--------------------------------------------------------------------------- + +LLImageFilter::LLImageFilter(const std::string& file_path) : + mFilterData(LLSD::emptyArray()), + mImage(NULL), + mHistoRed(NULL), + mHistoGreen(NULL), + mHistoBlue(NULL), + mHistoBrightness(NULL), + mStencilBlendMode(STENCIL_BLEND_MODE_BLEND), + mStencilShape(STENCIL_SHAPE_UNIFORM), + mStencilGamma(1.0), + mStencilMin(0.0), + mStencilMax(1.0) +{ + // Load filter description from file + llifstream filter_xml(file_path.c_str()); + if (filter_xml.is_open()) + { + // Load and parse the file + LLPointer<LLSDParser> parser = new LLSDXMLParser(); + parser->parse(filter_xml, mFilterData, LLSDSerialize::SIZE_UNLIMITED); + filter_xml.close(); + } +} + +LLImageFilter::~LLImageFilter() +{ + mImage = NULL; + ll_aligned_free_16(mHistoRed); + ll_aligned_free_16(mHistoGreen); + ll_aligned_free_16(mHistoBlue); + ll_aligned_free_16(mHistoBrightness); +} + +/* + *TODO + * Rename stencil to mask + * Improve perf: use LUT for alpha blending in uniform case + * Add gradient coloring as a filter + */ + +//============================================================================ +// Apply the filter data to the image passed as parameter +//============================================================================ + +void LLImageFilter::executeFilter(LLPointer<LLImageRaw> raw_image) +{ + mImage = raw_image; + + //std::cout << "Filter : size = " << mFilterData.size() << std::endl; + for (S32 i = 0; i < mFilterData.size(); ++i) + { + std::string filter_name = mFilterData[i][0].asString(); + // Dump out the filter values (for debug) + //std::cout << "Filter : name = " << mFilterData[i][0].asString() << ", params = "; + //for (S32 j = 1; j < mFilterData[i].size(); ++j) + //{ + // std::cout << mFilterData[i][j].asString() << ", "; + //} + //std::cout << std::endl; + + if (filter_name == "stencil") + { + // Get the shape of the stencil, that is how the procedural alpha is computed geometrically + std::string filter_shape = mFilterData[i][1].asString(); + EStencilShape shape = STENCIL_SHAPE_UNIFORM; + if (filter_shape == "uniform") + { + shape = STENCIL_SHAPE_UNIFORM; + } + else if (filter_shape == "gradient") + { + shape = STENCIL_SHAPE_GRADIENT; + } + else if (filter_shape == "vignette") + { + shape = STENCIL_SHAPE_VIGNETTE; + } + else if (filter_shape == "scanlines") + { + shape = STENCIL_SHAPE_SCAN_LINES; + } + // Get the blend mode of the stencil, that is how the effect is blended in the background through the stencil + std::string filter_mode = mFilterData[i][2].asString(); + EStencilBlendMode mode = STENCIL_BLEND_MODE_BLEND; + if (filter_mode == "blend") + { + mode = STENCIL_BLEND_MODE_BLEND; + } + else if (filter_mode == "add") + { + mode = STENCIL_BLEND_MODE_ADD; + } + else if (filter_mode == "add_back") + { + mode = STENCIL_BLEND_MODE_ABACK; + } + else if (filter_mode == "fade") + { + mode = STENCIL_BLEND_MODE_FADE; + } + // Get the float params: mandatory min, max then the optional parameters (4 max) + F32 min = (F32)(mFilterData[i][3].asReal()); + F32 max = (F32)(mFilterData[i][4].asReal()); + F32 params[4] = {0.0, 0.0, 0.0, 0.0}; + for (S32 j = 5; (j < mFilterData[i].size()) && (j < 9); j++) + { + params[j-5] = (F32)(mFilterData[i][j].asReal()); + } + // Set the stencil + setStencil(shape,mode,min,max,params); + } + else if (filter_name == "sepia") + { + filterSepia(); + } + else if (filter_name == "grayscale") + { + filterGrayScale(); + } + else if (filter_name == "saturate") + { + filterSaturate((float)(mFilterData[i][1].asReal())); + } + else if (filter_name == "rotate") + { + filterRotate((float)(mFilterData[i][1].asReal())); + } + else if (filter_name == "gamma") + { + LLColor3 color((float)(mFilterData[i][2].asReal()),(float)(mFilterData[i][3].asReal()),(float)(mFilterData[i][4].asReal())); + filterGamma((float)(mFilterData[i][1].asReal()),color); + } + else if (filter_name == "colorize") + { + LLColor3 color((float)(mFilterData[i][1].asReal()),(float)(mFilterData[i][2].asReal()),(float)(mFilterData[i][3].asReal())); + LLColor3 alpha((F32)(mFilterData[i][4].asReal()),(float)(mFilterData[i][5].asReal()),(float)(mFilterData[i][6].asReal())); + filterColorize(color,alpha); + } + else if (filter_name == "contrast") + { + LLColor3 color((float)(mFilterData[i][2].asReal()),(float)(mFilterData[i][3].asReal()),(float)(mFilterData[i][4].asReal())); + filterContrast((float)(mFilterData[i][1].asReal()),color); + } + else if (filter_name == "brighten") + { + LLColor3 color((float)(mFilterData[i][2].asReal()),(float)(mFilterData[i][3].asReal()),(float)(mFilterData[i][4].asReal())); + filterBrightness((float)(mFilterData[i][1].asReal()),color); + } + else if (filter_name == "darken") + { + LLColor3 color((float)(mFilterData[i][2].asReal()),(float)(mFilterData[i][3].asReal()),(float)(mFilterData[i][4].asReal())); + filterBrightness((float)(-mFilterData[i][1].asReal()),color); + } + else if (filter_name == "linearize") + { + LLColor3 color((float)(mFilterData[i][2].asReal()),(float)(mFilterData[i][3].asReal()),(float)(mFilterData[i][4].asReal())); + filterLinearize((float)(mFilterData[i][1].asReal()),color); + } + else if (filter_name == "posterize") + { + LLColor3 color((float)(mFilterData[i][2].asReal()),(float)(mFilterData[i][3].asReal()),(float)(mFilterData[i][4].asReal())); + filterEqualize((S32)(mFilterData[i][1].asReal()),color); + } + else if (filter_name == "screen") + { + std::string screen_name = mFilterData[i][1].asString(); + EScreenMode mode = SCREEN_MODE_2DSINE; + if (screen_name == "2Dsine") + { + mode = SCREEN_MODE_2DSINE; + } + else if (screen_name == "line") + { + mode = SCREEN_MODE_LINE; + } + filterScreen(mode,(F32)(mFilterData[i][2].asReal()),(F32)(mFilterData[i][3].asReal())); + } + else if (filter_name == "blur") + { + LLMatrix3 kernel; + for (S32 i = 0; i < NUM_VALUES_IN_MAT3; i++) + for (S32 j = 0; j < NUM_VALUES_IN_MAT3; j++) + kernel.mMatrix[i][j] = 1.0; + convolve(kernel,true,false); + } + else if (filter_name == "sharpen") + { + LLMatrix3 kernel; + for (S32 k = 0; k < NUM_VALUES_IN_MAT3; k++) + for (S32 j = 0; j < NUM_VALUES_IN_MAT3; j++) + kernel.mMatrix[k][j] = -1.0; + kernel.mMatrix[1][1] = 9.0; + convolve(kernel,false,false); + } + else if (filter_name == "gradient") + { + LLMatrix3 kernel; + for (S32 k = 0; k < NUM_VALUES_IN_MAT3; k++) + for (S32 j = 0; j < NUM_VALUES_IN_MAT3; j++) + kernel.mMatrix[k][j] = -1.0; + kernel.mMatrix[1][1] = 8.0; + convolve(kernel,false,true); + } + else if (filter_name == "convolve") + { + LLMatrix3 kernel; + S32 index = 1; + bool normalize = (mFilterData[i][index++].asReal() > 0.0); + bool abs_value = (mFilterData[i][index++].asReal() > 0.0); + for (S32 k = 0; k < NUM_VALUES_IN_MAT3; k++) + for (S32 j = 0; j < NUM_VALUES_IN_MAT3; j++) + kernel.mMatrix[k][j] = mFilterData[i][index++].asReal(); + convolve(kernel,normalize,abs_value); + } + else if (filter_name == "colortransform") + { + LLMatrix3 transform; + S32 index = 1; + for (S32 k = 0; k < NUM_VALUES_IN_MAT3; k++) + for (S32 j = 0; j < NUM_VALUES_IN_MAT3; j++) + transform.mMatrix[k][j] = mFilterData[i][index++].asReal(); + transform.transpose(); + colorTransform(transform); + } + else + { + LL_WARNS() << "Filter unknown, cannot execute filter command : " << filter_name << LL_ENDL; + } + } +} + +//============================================================================ +// Filter Primitives +//============================================================================ + +void LLImageFilter::blendStencil(F32 alpha, U8* pixel, U8 red, U8 green, U8 blue) +{ + F32 inv_alpha = 1.0 - alpha; + switch (mStencilBlendMode) + { + case STENCIL_BLEND_MODE_BLEND: + // Classic blend of incoming color with the background image + pixel[VRED] = inv_alpha * pixel[VRED] + alpha * red; + pixel[VGREEN] = inv_alpha * pixel[VGREEN] + alpha * green; + pixel[VBLUE] = inv_alpha * pixel[VBLUE] + alpha * blue; + break; + case STENCIL_BLEND_MODE_ADD: + // Add incoming color to the background image + pixel[VRED] = llclampb(pixel[VRED] + alpha * red); + pixel[VGREEN] = llclampb(pixel[VGREEN] + alpha * green); + pixel[VBLUE] = llclampb(pixel[VBLUE] + alpha * blue); + break; + case STENCIL_BLEND_MODE_ABACK: + // Add back background image to the incoming color + pixel[VRED] = llclampb(inv_alpha * pixel[VRED] + red); + pixel[VGREEN] = llclampb(inv_alpha * pixel[VGREEN] + green); + pixel[VBLUE] = llclampb(inv_alpha * pixel[VBLUE] + blue); + break; + case STENCIL_BLEND_MODE_FADE: + // Fade incoming color to black + pixel[VRED] = alpha * red; + pixel[VGREEN] = alpha * green; + pixel[VBLUE] = alpha * blue; + break; + } +} + +void LLImageFilter::colorCorrect(const U8* lut_red, const U8* lut_green, const U8* lut_blue) +{ + const S32 components = mImage->getComponents(); + llassert( components >= 1 && components <= 4 ); + + S32 width = mImage->getWidth(); + S32 height = mImage->getHeight(); + + U8* dst_data = mImage->getData(); + for (S32 j = 0; j < height; j++) + { + for (S32 i = 0; i < width; i++) + { + // Blend LUT value + blendStencil(getStencilAlpha(i,j), dst_data, lut_red[dst_data[VRED]], lut_green[dst_data[VGREEN]], lut_blue[dst_data[VBLUE]]); + dst_data += components; + } + } +} + +void LLImageFilter::colorTransform(const LLMatrix3 &transform) +{ + const S32 components = mImage->getComponents(); + llassert( components >= 1 && components <= 4 ); + + S32 width = mImage->getWidth(); + S32 height = mImage->getHeight(); + + U8* dst_data = mImage->getData(); + for (S32 j = 0; j < height; j++) + { + for (S32 i = 0; i < width; i++) + { + // Compute transform + LLVector3 src((F32)(dst_data[VRED]),(F32)(dst_data[VGREEN]),(F32)(dst_data[VBLUE])); + LLVector3 dst = src * transform; + dst.clamp(0.0f,255.0f); + + // Blend result + blendStencil(getStencilAlpha(i,j), dst_data, dst.mV[VRED], dst.mV[VGREEN], dst.mV[VBLUE]); + dst_data += components; + } + } +} + +void LLImageFilter::convolve(const LLMatrix3 &kernel, bool normalize, bool abs_value) +{ + const S32 components = mImage->getComponents(); + llassert( components >= 1 && components <= 4 ); + + // Compute normalization factors + F32 kernel_min = 0.0; + F32 kernel_max = 0.0; + for (S32 i = 0; i < NUM_VALUES_IN_MAT3; i++) + { + for (S32 j = 0; j < NUM_VALUES_IN_MAT3; j++) + { + if (kernel.mMatrix[i][j] >= 0.0) + kernel_max += kernel.mMatrix[i][j]; + else + kernel_min += kernel.mMatrix[i][j]; + } + } + if (abs_value) + { + kernel_max = llabs(kernel_max); + kernel_min = llabs(kernel_min); + kernel_max = llmax(kernel_max,kernel_min); + kernel_min = 0.0; + } + F32 kernel_range = kernel_max - kernel_min; + + // Allocate temporary buffers and initialize algorithm's data + S32 width = mImage->getWidth(); + S32 height = mImage->getHeight(); + + U8* dst_data = mImage->getData(); + + S32 buffer_size = width * components; + llassert_always(buffer_size > 0); + std::vector<U8> even_buffer(buffer_size); + std::vector<U8> odd_buffer(buffer_size); + + U8* south_data = dst_data + buffer_size; + U8* east_west_data; + U8* north_data; + + // Line 0 : we set the line to 0 (debatable) + memcpy( &even_buffer[0], dst_data, buffer_size ); /* Flawfinder: ignore */ + for (S32 i = 0; i < width; i++) + { + blendStencil(getStencilAlpha(i,0), dst_data, 0, 0, 0); + dst_data += components; + } + south_data += buffer_size; + + // All other lines + for (S32 j = 1; j < (height-1); j++) + { + // We need to buffer 2 lines. We flip north and east-west (current) to avoid moving too much memory around + if (j % 2) + { + memcpy( &odd_buffer[0], dst_data, buffer_size ); /* Flawfinder: ignore */ + east_west_data = &odd_buffer[0]; + north_data = &even_buffer[0]; + } + else + { + memcpy( &even_buffer[0], dst_data, buffer_size ); /* Flawfinder: ignore */ + east_west_data = &even_buffer[0]; + north_data = &odd_buffer[0]; + } + // First pixel : set to 0 + blendStencil(getStencilAlpha(0,j), dst_data, 0, 0, 0); + dst_data += components; + // Set pointers to kernel + U8* NW = north_data; + U8* N = NW+components; + U8* NE = N+components; + U8* W = east_west_data; + U8* C = W+components; + U8* E = C+components; + U8* SW = south_data; + U8* S = SW+components; + U8* SE = S+components; + // All other pixels + for (S32 i = 1; i < (width-1); i++) + { + // Compute convolution + LLVector3 dst; + dst.mV[VRED] = (kernel.mMatrix[0][0]*NW[VRED] + kernel.mMatrix[0][1]*N[VRED] + kernel.mMatrix[0][2]*NE[VRED] + + kernel.mMatrix[1][0]*W[VRED] + kernel.mMatrix[1][1]*C[VRED] + kernel.mMatrix[1][2]*E[VRED] + + kernel.mMatrix[2][0]*SW[VRED] + kernel.mMatrix[2][1]*S[VRED] + kernel.mMatrix[2][2]*SE[VRED]); + dst.mV[VGREEN] = (kernel.mMatrix[0][0]*NW[VGREEN] + kernel.mMatrix[0][1]*N[VGREEN] + kernel.mMatrix[0][2]*NE[VGREEN] + + kernel.mMatrix[1][0]*W[VGREEN] + kernel.mMatrix[1][1]*C[VGREEN] + kernel.mMatrix[1][2]*E[VGREEN] + + kernel.mMatrix[2][0]*SW[VGREEN] + kernel.mMatrix[2][1]*S[VGREEN] + kernel.mMatrix[2][2]*SE[VGREEN]); + dst.mV[VBLUE] = (kernel.mMatrix[0][0]*NW[VBLUE] + kernel.mMatrix[0][1]*N[VBLUE] + kernel.mMatrix[0][2]*NE[VBLUE] + + kernel.mMatrix[1][0]*W[VBLUE] + kernel.mMatrix[1][1]*C[VBLUE] + kernel.mMatrix[1][2]*E[VBLUE] + + kernel.mMatrix[2][0]*SW[VBLUE] + kernel.mMatrix[2][1]*S[VBLUE] + kernel.mMatrix[2][2]*SE[VBLUE]); + if (abs_value) + { + dst.mV[VRED] = llabs(dst.mV[VRED]); + dst.mV[VGREEN] = llabs(dst.mV[VGREEN]); + dst.mV[VBLUE] = llabs(dst.mV[VBLUE]); + } + if (normalize) + { + dst.mV[VRED] = (dst.mV[VRED] - kernel_min)/kernel_range; + dst.mV[VGREEN] = (dst.mV[VGREEN] - kernel_min)/kernel_range; + dst.mV[VBLUE] = (dst.mV[VBLUE] - kernel_min)/kernel_range; + } + dst.clamp(0.0f,255.0f); + + // Blend result + blendStencil(getStencilAlpha(i,j), dst_data, dst.mV[VRED], dst.mV[VGREEN], dst.mV[VBLUE]); + + // Next pixel + dst_data += components; + NW += components; + N += components; + NE += components; + W += components; + C += components; + E += components; + SW += components; + S += components; + SE += components; + } + // Last pixel : set to 0 + blendStencil(getStencilAlpha(width-1,j), dst_data, 0, 0, 0); + dst_data += components; + south_data += buffer_size; + } + + // Last line + for (S32 i = 0; i < width; i++) + { + blendStencil(getStencilAlpha(i,0), dst_data, 0, 0, 0); + dst_data += components; + } +} + +void LLImageFilter::filterScreen(EScreenMode mode, const F32 wave_length, const F32 angle) +{ + const S32 components = mImage->getComponents(); + llassert( components >= 1 && components <= 4 ); + + S32 width = mImage->getWidth(); + S32 height = mImage->getHeight(); + + F32 wave_length_pixels = wave_length * (F32)(height) / 2.0; + F32 sin = sinf(angle*DEG_TO_RAD); + F32 cos = cosf(angle*DEG_TO_RAD); + + // Precompute the gamma table : gives us the gray level to use when cutting outside the screen (prevents strong aliasing on the screen) + U8 gamma[256]; + for (S32 i = 0; i < 256; i++) + { + F32 gamma_i = llclampf((float)(powf((float)(i)/255.0,1.0/4.0))); + gamma[i] = (U8)(255.0 * gamma_i); + } + + U8* dst_data = mImage->getData(); + for (S32 j = 0; j < height; j++) + { + for (S32 i = 0; i < width; i++) + { + // Compute screen value + F32 value = 0.0; + F32 di = 0.0; + F32 dj = 0.0; + switch (mode) + { + case SCREEN_MODE_2DSINE: + di = cos*i + sin*j; + dj = -sin*i + cos*j; + value = (sinf(2*F_PI*di/wave_length_pixels)*sinf(2*F_PI*dj/wave_length_pixels)+1.0)*255.0/2.0; + break; + case SCREEN_MODE_LINE: + dj = sin*i - cos*j; + value = (sinf(2*F_PI*dj/wave_length_pixels)+1.0)*255.0/2.0; + break; + } + U8 dst_value = (dst_data[VRED] >= (U8)(value) ? gamma[dst_data[VRED] - (U8)(value)] : 0); + + // Blend result + blendStencil(getStencilAlpha(i,j), dst_data, dst_value, dst_value, dst_value); + dst_data += components; + } + } +} + +//============================================================================ +// Procedural Stencils +//============================================================================ +void LLImageFilter::setStencil(EStencilShape shape, EStencilBlendMode mode, F32 min, F32 max, F32* params) +{ + mStencilShape = shape; + mStencilBlendMode = mode; + mStencilMin = llmin(llmax(min, -1.0f), 1.0f); + mStencilMax = llmin(llmax(max, -1.0f), 1.0f); + + // Each shape will interpret the 4 params differenly. + // We compute each systematically, though, clearly, values are meaningless when the shape doesn't correspond to the parameters + mStencilCenterX = (S32)(mImage->getWidth() + params[0] * (F32)(mImage->getHeight()))/2; + mStencilCenterY = (S32)(mImage->getHeight() + params[1] * (F32)(mImage->getHeight()))/2; + mStencilWidth = (S32)(params[2] * (F32)(mImage->getHeight()))/2; + mStencilGamma = (params[3] <= 0.0 ? 1.0 : params[3]); + + mStencilWavelength = (params[0] <= 0.0 ? 10.0 : params[0] * (F32)(mImage->getHeight()) / 2.0); + mStencilSine = sinf(params[1]*DEG_TO_RAD); + mStencilCosine = cosf(params[1]*DEG_TO_RAD); + + mStencilStartX = ((F32)(mImage->getWidth()) + params[0] * (F32)(mImage->getHeight()))/2.0; + mStencilStartY = ((F32)(mImage->getHeight()) + params[1] * (F32)(mImage->getHeight()))/2.0; + F32 end_x = ((F32)(mImage->getWidth()) + params[2] * (F32)(mImage->getHeight()))/2.0; + F32 end_y = ((F32)(mImage->getHeight()) + params[3] * (F32)(mImage->getHeight()))/2.0; + mStencilGradX = end_x - mStencilStartX; + mStencilGradY = end_y - mStencilStartY; + mStencilGradN = mStencilGradX*mStencilGradX + mStencilGradY*mStencilGradY; +} + +F32 LLImageFilter::getStencilAlpha(S32 i, S32 j) +{ + F32 alpha = 1.0; // That init actually takes care of the STENCIL_SHAPE_UNIFORM case... + if (mStencilShape == STENCIL_SHAPE_VIGNETTE) + { + // 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 - mStencilCenterX)*(i - mStencilCenterX) + (j - mStencilCenterY)*(j - mStencilCenterY); + alpha = powf(F_E, -(powf((d_center_square/(mStencilWidth*mStencilWidth)),mStencilGamma)/2.0f)); + } + else if (mStencilShape == STENCIL_SHAPE_SCAN_LINES) + { + // alpha varies according to a squared sine function. + F32 d = mStencilSine*i - mStencilCosine*j; + alpha = (sinf(2*F_PI*d/mStencilWavelength) > 0.0 ? 1.0 : 0.0); + } + else if (mStencilShape == STENCIL_SHAPE_GRADIENT) + { + alpha = (((F32)(i) - mStencilStartX)*mStencilGradX + ((F32)(j) - mStencilStartY)*mStencilGradY) / mStencilGradN; + alpha = llclampf(alpha); + } + + // We rescale alpha between min and max + return (mStencilMin + alpha * (mStencilMax - mStencilMin)); +} + +//============================================================================ +// Histograms +//============================================================================ + +U32* LLImageFilter::getBrightnessHistogram() +{ + if (!mHistoBrightness) + { + computeHistograms(); + } + return mHistoBrightness; +} + +void LLImageFilter::computeHistograms() +{ + const S32 components = mImage->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 = mImage->getWidth() * mImage->getHeight(); + U8* dst_data = mImage->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; + } +} + +//============================================================================ +// Secondary Filters +//============================================================================ + +void LLImageFilter::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 LLImageFilter::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 LLImageFilter::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 LLImageFilter::filterRotate(F32 angle) +{ + // 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; + angle *= DEG_TO_RAD; + r.setRows(LLVector3( cosf(angle), sinf(angle), 0.0), + LLVector3(-sinf(angle), cosf(angle), 0.0), + LLVector3( 0.0, 0.0, 1.0)); + + // Global color rotation transform + LLMatrix3 transfo = Lij_inv * r * Lij; + colorTransform(transfo); +} + +void LLImageFilter::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)(powf((float)(i)/255.0,1.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 LLImageFilter::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 LLImageFilter::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 LLImageFilter::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 LLImageFilter::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 LLImageFilter::filterBrightness(F32 add, const LLColor3& alpha) +{ + U8 brightness_red_lut[256]; + U8 brightness_green_lut[256]; + U8 brightness_blue_lut[256]; + + S32 add_value = (S32)(add * 255.0); + + for (S32 i = 0; i < 256; i++) + { + U8 value_i = (U8)(llclampb(i + add_value)); + // 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); +} + +//============================================================================ diff --git a/indra/llimage/llimagefilter.h b/indra/llimage/llimagefilter.h new file mode 100755 index 0000000000..16ec395f76 --- /dev/null +++ b/indra/llimage/llimagefilter.h @@ -0,0 +1,137 @@ +/** + * @file llimagefilter.h + * @brief Simple Image Filtering. See https://wiki.lindenlab.com/wiki/SL_Viewer_Image_Filters for complete documentation. + * + * $LicenseInfo:firstyear=2000&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2014, 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$ + */ + +#ifndef LL_LLIMAGEFILTER_H +#define LL_LLIMAGEFILTER_H + +#include "llsd.h" +#include "llimage.h" + +class LLImageRaw; +class LLColor4U; +class LLColor3; +class LLMatrix3; + +typedef enum e_stencil_blend_mode +{ + STENCIL_BLEND_MODE_BLEND = 0, + STENCIL_BLEND_MODE_ADD = 1, + STENCIL_BLEND_MODE_ABACK = 2, + STENCIL_BLEND_MODE_FADE = 3 +} EStencilBlendMode; + +typedef enum e_stencil_shape +{ + STENCIL_SHAPE_UNIFORM = 0, + STENCIL_SHAPE_GRADIENT = 1, + STENCIL_SHAPE_VIGNETTE = 2, + STENCIL_SHAPE_SCAN_LINES = 3 +} EStencilShape; + +typedef enum e_screen_mode +{ + SCREEN_MODE_2DSINE = 0, + SCREEN_MODE_LINE = 1 +} EScreenMode; + +//============================================================================ +// LLImageFilter +//============================================================================ + +class LLImageFilter +{ +public: + LLImageFilter(const std::string& file_path); + ~LLImageFilter(); + + void executeFilter(LLPointer<LLImageRaw> raw_image); + +private: + // Filter Operations : Transforms + void filterGrayScale(); // Convert to grayscale + void filterSepia(); // Convert to sepia + void filterSaturate(F32 saturation); // < 1.0 desaturates, > 1.0 saturates + void filterRotate(F32 angle); // Rotates hue according to angle, angle in degrees + + // Filter Operations : Color Corrections + // When specified, the LLColor3 alpha parameter indicates the intensity of the effect for each color channel + // acting in effect as an alpha blending factor different for each channel. For instance (1.0,0.0,0.0) will apply + // the effect only to the Red channel. Intermediate values blends the effect with the source color. + void filterGamma(F32 gamma, const LLColor3& alpha); // Apply gamma to each channel + void filterLinearize(F32 tail, const LLColor3& alpha); // Use histogram to linearize constrast between min and max values minus tail + void filterEqualize(S32 nb_classes, const LLColor3& alpha); // Use histogram to equalize constrast between nb_classes throughout the image + void filterColorize(const LLColor3& color, const LLColor3& alpha); // Colorize with color and alpha per channel + void filterContrast(F32 slope, const LLColor3& alpha); // Change contrast according to slope: > 1.0 more contrast, < 1.0 less contrast + void filterBrightness(F32 add, const LLColor3& alpha); // Change brightness according to add: > 0 brighter, < 0 darker + + // Filter Primitives + void colorTransform(const LLMatrix3 &transform); + void colorCorrect(const U8* lut_red, const U8* lut_green, const U8* lut_blue); + void filterScreen(EScreenMode mode, const F32 wave_length, const F32 angle); + void blendStencil(F32 alpha, U8* pixel, U8 red, U8 green, U8 blue); + void convolve(const LLMatrix3 &kernel, bool normalize, bool abs_value); + + // Procedural Stencils + void setStencil(EStencilShape shape, EStencilBlendMode mode, F32 min, F32 max, F32* params); + F32 getStencilAlpha(S32 i, S32 j); + + // Histograms + U32* getBrightnessHistogram(); + void computeHistograms(); + + LLSD mFilterData; + LLPointer<LLImageRaw> mImage; + + // Histograms (if we ever happen to need them) + U32 *mHistoRed; + U32 *mHistoGreen; + U32 *mHistoBlue; + U32 *mHistoBrightness; + + // Current Stencil Settings + EStencilBlendMode mStencilBlendMode; + EStencilShape mStencilShape; + F32 mStencilMin; + F32 mStencilMax; + + S32 mStencilCenterX; + S32 mStencilCenterY; + S32 mStencilWidth; + F32 mStencilGamma; + + F32 mStencilWavelength; + F32 mStencilSine; + F32 mStencilCosine; + + F32 mStencilStartX; + F32 mStencilStartY; + F32 mStencilGradX; + F32 mStencilGradY; + F32 mStencilGradN; +}; + + +#endif diff --git a/indra/llimage/llimagej2c.cpp b/indra/llimage/llimagej2c.cpp index 5412f98ee5..7cd59a2983 100644..100755 --- a/indra/llimage/llimagej2c.cpp +++ b/indra/llimage/llimagej2c.cpp @@ -24,11 +24,13 @@ */ #include "linden_common.h" +#include "llapr.h" #include "lldir.h" #include "llimagej2c.h" #include "lltimer.h" #include "llmath.h" #include "llmemory.h" +#include "llsd.h" typedef LLImageJ2CImpl* (*CreateLLImageJ2CFunction)(); typedef void (*DestroyLLImageJ2CFunction)(LLImageJ2CImpl*); @@ -60,6 +62,7 @@ LLImageJ2C::LLImageJ2C() : LLImageFormatted(IMG_CODEC_J2C), mAreaUsedForDataSizeCalcs(0) { mImpl = fallbackCreateLLImageJ2CImpl(); + claimMem(mImpl); // Clear data size table for( S32 i = 0; i <= MAX_DISCARD_LEVEL; i++) diff --git a/indra/llimage/llimagej2c.h b/indra/llimage/llimagej2c.h index ce8195940d..ce8195940d 100644..100755 --- a/indra/llimage/llimagej2c.h +++ b/indra/llimage/llimagej2c.h diff --git a/indra/llimage/llimagejpeg.cpp b/indra/llimage/llimagejpeg.cpp index b70f84efc8..e419c77ff2 100644..100755 --- a/indra/llimage/llimagejpeg.cpp +++ b/indra/llimage/llimagejpeg.cpp @@ -32,8 +32,7 @@ jmp_buf LLImageJPEG::sSetjmpBuffer ; LLImageJPEG::LLImageJPEG(S32 quality) - : - LLImageFormatted(IMG_CODEC_JPEG), +: LLImageFormatted(IMG_CODEC_JPEG), mOutputBuffer( NULL ), mOutputBufferSize( 0 ), mEncodeQuality( quality ) // on a scale from 1 to 100 @@ -374,7 +373,7 @@ boolean LLImageJPEG::encodeEmptyOutputBuffer( j_compress_ptr cinfo ) U8* new_buffer = new U8[ new_buffer_size ]; if (!new_buffer) { - llerrs << "Out of memory in LLImageJPEG::encodeEmptyOutputBuffer( j_compress_ptr cinfo )" << llendl; + LL_ERRS() << "Out of memory in LLImageJPEG::encodeEmptyOutputBuffer( j_compress_ptr cinfo )" << LL_ENDL; return FALSE; } memcpy( new_buffer, self->mOutputBuffer, self->mOutputBufferSize ); /* Flawfinder: ignore */ @@ -383,7 +382,9 @@ boolean LLImageJPEG::encodeEmptyOutputBuffer( j_compress_ptr cinfo ) cinfo->dest->next_output_byte = self->mOutputBuffer + self->mOutputBufferSize; cinfo->dest->free_in_buffer = self->mOutputBufferSize; + self->disclaimMem(self->mOutputBufferSize); self->mOutputBufferSize = new_buffer_size; + self->claimMem(new_buffer_size); return TRUE; } @@ -465,7 +466,7 @@ void LLImageJPEG::errorOutputMessage( j_common_ptr cinfo ) LLImage::setLastError(error); BOOL is_decode = (cinfo->is_decompressor != 0); - llwarns << "LLImageJPEG " << (is_decode ? "decode " : "encode ") << " failed: " << buffer << llendl; + LL_WARNS() << "LLImageJPEG " << (is_decode ? "decode " : "encode ") << " failed: " << buffer << LL_ENDL; } BOOL LLImageJPEG::encode( const LLImageRaw* raw_image, F32 encode_time ) @@ -489,7 +490,9 @@ BOOL LLImageJPEG::encode( const LLImageRaw* raw_image, F32 encode_time ) // Allocate a temporary buffer big enough to hold the entire compressed image (and then some) // (Note: we make it bigger in emptyOutputBuffer() if we need to) delete[] mOutputBuffer; + disclaimMem(mOutputBufferSize); mOutputBufferSize = getWidth() * getHeight() * getComponents() + 1024; + claimMem(mOutputBufferSize); mOutputBuffer = new U8[ mOutputBufferSize ]; const U8* raw_image_data = NULL; @@ -526,6 +529,7 @@ BOOL LLImageJPEG::encode( const LLImageRaw* raw_image, F32 encode_time ) jpeg_destroy_compress(&cinfo); delete[] mOutputBuffer; mOutputBuffer = NULL; + disclaimMem(mOutputBufferSize); mOutputBufferSize = 0; return FALSE; } @@ -628,6 +632,7 @@ BOOL LLImageJPEG::encode( const LLImageRaw* raw_image, F32 encode_time ) // After finish_compress, we can release the temp output buffer. delete[] mOutputBuffer; mOutputBuffer = NULL; + disclaimMem(mOutputBufferSize); mOutputBufferSize = 0; //////////////////////////////////////// @@ -640,6 +645,7 @@ BOOL LLImageJPEG::encode( const LLImageRaw* raw_image, F32 encode_time ) jpeg_destroy_compress(&cinfo); delete[] mOutputBuffer; mOutputBuffer = NULL; + disclaimMem(mOutputBufferSize); mOutputBufferSize = 0; return FALSE; } diff --git a/indra/llimage/llimagejpeg.h b/indra/llimage/llimagejpeg.h index 7ac7f5d2e0..2142660c81 100644..100755 --- a/indra/llimage/llimagejpeg.h +++ b/indra/llimage/llimagejpeg.h @@ -31,8 +31,9 @@ #include "llimage.h" +#include "llwin32headerslean.h" extern "C" { -#ifdef LL_STANDALONE +#ifdef LL_USESYSTEMLIBS # include <jpeglib.h> # include <jerror.h> #else diff --git a/indra/llimage/llimagepng.cpp b/indra/llimage/llimagepng.cpp index 294f68b122..7735dc1379 100644..100755 --- a/indra/llimage/llimagepng.cpp +++ b/indra/llimage/llimagepng.cpp @@ -67,7 +67,7 @@ BOOL LLImagePNG::updateData() } LLPngWrapper::ImageInfo infop; - if (! pngWrapper.readPng(getData(), NULL, &infop)) + if (! pngWrapper.readPng(getData(), getDataSize(), NULL, &infop)) { setLastError(pngWrapper.getErrorMessage()); return FALSE; @@ -102,7 +102,7 @@ BOOL LLImagePNG::decode(LLImageRaw* raw_image, F32 decode_time) return FALSE; } - if (! pngWrapper.readPng(getData(), raw_image)) + if (! pngWrapper.readPng(getData(), getDataSize(), raw_image)) { setLastError(pngWrapper.getErrorMessage()); return FALSE; diff --git a/indra/llimage/llimagepng.h b/indra/llimage/llimagepng.h index 1fbd850a2e..1fbd850a2e 100644..100755 --- a/indra/llimage/llimagepng.h +++ b/indra/llimage/llimagepng.h diff --git a/indra/llimage/llimagetga.cpp b/indra/llimage/llimagetga.cpp index 58426d31fa..4eb8dc7440 100644..100755 --- a/indra/llimage/llimagetga.cpp +++ b/indra/llimage/llimagetga.cpp @@ -132,12 +132,12 @@ BOOL LLImageTGA::updateData() ** FIELD 2 : COLOR MAP TYPE (1 BYTES) ** FIELD 3 : IMAGE TYPE CODE (1 BYTES) ** = 0 NO IMAGE DATA INCLUDED - ** = 1 UNCOMPRESSED, COLOR-MAPPED IMAGE - ** = 2 UNCOMPRESSED, TRUE-COLOR IMAGE - ** = 3 UNCOMPRESSED, BLACK AND WHITE IMAGE - ** = 9 RUN-LENGTH ENCODED COLOR-MAPPED IMAGE - ** = 10 RUN-LENGTH ENCODED TRUE-COLOR IMAGE - ** = 11 RUN-LENGTH ENCODED BLACK AND WHITE IMAGE + ** = (0001) 1 UNCOMPRESSED, COLOR-MAPPED IMAGE + ** = (0010) 2 UNCOMPRESSED, TRUE-COLOR IMAGE + ** = (0011) 3 UNCOMPRESSED, BLACK AND WHITE IMAGE + ** = (1001) 9 RUN-LENGTH ENCODED COLOR-MAPPED IMAGE + ** = (1010) 10 RUN-LENGTH ENCODED TRUE-COLOR IMAGE + ** = (1011) 11 RUN-LENGTH ENCODED BLACK AND WHITE IMAGE ** FIELD 4 : COLOR MAP SPECIFICATION (5 BYTES) ** 4.1 : COLOR MAP ORIGIN (2 BYTES) ** 4.2 : COLOR MAP LENGTH (2 BYTES) @@ -266,7 +266,7 @@ BOOL LLImageTGA::updateData() mColorMap = new U8[ color_map_bytes ]; if (!mColorMap) { - llerrs << "Out of Memory in BOOL LLImageTGA::updateData()" << llendl; + LL_ERRS() << "Out of Memory in BOOL LLImageTGA::updateData()" << LL_ENDL; return FALSE; } memcpy( mColorMap, getData() + mDataOffset, color_map_bytes ); /* Flawfinder: ignore */ @@ -1043,7 +1043,7 @@ BOOL LLImageTGA::decodeAndProcess( LLImageRaw* raw_image, F32 domain, F32 weight // Only works for unflipped monochrome RLE images if( (getComponents() != 1) || (mImageType != 11) || mOriginTopBit || mOriginRightBit ) { - llerrs << "LLImageTGA trying to alpha-gradient process an image that's not a standard RLE, one component image" << llendl; + LL_ERRS() << "LLImageTGA trying to alpha-gradient process an image that's not a standard RLE, one component image" << LL_ENDL; return FALSE; } @@ -1151,7 +1151,7 @@ bool LLImageTGA::loadFile( const std::string& path ) LLFILE* file = LLFile::fopen(path, "rb"); /* Flawfinder: ignore */ if( !file ) { - llwarns << "Couldn't open file " << path << llendl; + LL_WARNS() << "Couldn't open file " << path << LL_ENDL; return false; } @@ -1167,7 +1167,7 @@ bool LLImageTGA::loadFile( const std::string& path ) if( bytes_read != file_size ) { deleteData(); - llwarns << "Couldn't read file " << path << llendl; + LL_WARNS() << "Couldn't read file " << path << LL_ENDL; return false; } @@ -1175,7 +1175,7 @@ bool LLImageTGA::loadFile( const std::string& path ) if( !updateData() ) { - llwarns << "Couldn't decode file " << path << llendl; + LL_WARNS() << "Couldn't decode file " << path << LL_ENDL; deleteData(); return false; } diff --git a/indra/llimage/llimagetga.h b/indra/llimage/llimagetga.h index 5da3525149..5da3525149 100644..100755 --- a/indra/llimage/llimagetga.h +++ b/indra/llimage/llimagetga.h diff --git a/indra/llimage/llimageworker.cpp b/indra/llimage/llimageworker.cpp index ad2eb0f69c..4875fe7001 100644..100755 --- a/indra/llimage/llimageworker.cpp +++ b/indra/llimage/llimageworker.cpp @@ -60,7 +60,7 @@ S32 LLImageDecodeThread::update(F32 max_time_ms) bool res = addRequest(req); if (!res) { - llerrs << "request added after LLLFSThread::cleanupClass()" << llendl; + LL_ERRS() << "request added after LLLFSThread::cleanupClass()" << LL_ENDL; } } mCreationList.clear(); @@ -143,7 +143,8 @@ bool LLImageDecodeThread::ImageRequest::processRequest() mFormattedImage->getComponents()); } done = mFormattedImage->decode(mDecodedImageRaw, decode_time_slice); // 1ms - mDecodedRaw = done; + // some decoders are removing data when task is complete and there were errors + mDecodedRaw = done && mDecodedImageRaw->getData(); } if (done && mNeedsAux && !mDecodedAux && mFormattedImage.notNull()) { @@ -155,7 +156,7 @@ bool LLImageDecodeThread::ImageRequest::processRequest() 1); } done = mFormattedImage->decodeChannels(mDecodedImageAux, decode_time_slice, 4, 4); // 1ms - mDecodedAux = done; + mDecodedAux = done && mDecodedImageAux->getData(); } return done; diff --git a/indra/llimage/llimageworker.h b/indra/llimage/llimageworker.h index 1bfb0ddfd3..1bfb0ddfd3 100644..100755 --- a/indra/llimage/llimageworker.h +++ b/indra/llimage/llimageworker.h diff --git a/indra/llimage/llmapimagetype.h b/indra/llimage/llmapimagetype.h index 0a040d3db9..0a040d3db9 100644..100755 --- a/indra/llimage/llmapimagetype.h +++ b/indra/llimage/llmapimagetype.h diff --git a/indra/llimage/llpngwrapper.cpp b/indra/llimage/llpngwrapper.cpp index 2cc7d3c460..aad139f570 100644..100755 --- a/indra/llimage/llpngwrapper.cpp +++ b/indra/llimage/llpngwrapper.cpp @@ -87,6 +87,12 @@ void LLPngWrapper::errorHandler(png_structp png_ptr, png_const_charp msg) void LLPngWrapper::readDataCallback(png_structp png_ptr, png_bytep dest, png_size_t length) { PngDataInfo *dataInfo = (PngDataInfo *) png_get_io_ptr(png_ptr); + if(dataInfo->mOffset + length > dataInfo->mDataSize) + { + png_error(png_ptr, "Data read error. Requested data size exceeds available data size."); + return; + } + U8 *src = &dataInfo->mData[dataInfo->mOffset]; memcpy(dest, src, length); dataInfo->mOffset += static_cast<U32>(length); @@ -114,7 +120,7 @@ void LLPngWrapper::writeFlush(png_structp png_ptr) // The scanline also begins at the bottom of // the image (per SecondLife conventions) instead of at the top, so we // must assign row-pointers in "reverse" order. -BOOL LLPngWrapper::readPng(U8* src, LLImageRaw* rawImage, ImageInfo *infop) +BOOL LLPngWrapper::readPng(U8* src, S32 dataSize, LLImageRaw* rawImage, ImageInfo *infop) { try { @@ -133,6 +139,7 @@ BOOL LLPngWrapper::readPng(U8* src, LLImageRaw* rawImage, ImageInfo *infop) PngDataInfo dataPtr; dataPtr.mData = src; dataPtr.mOffset = 0; + dataPtr.mDataSize = dataSize; png_set_read_fn(mReadPngPtr, &dataPtr, &readDataCallback); png_set_sig_bytes(mReadPngPtr, 0); diff --git a/indra/llimage/llpngwrapper.h b/indra/llimage/llpngwrapper.h index 739f435996..27d7df3bef 100644..100755 --- a/indra/llimage/llpngwrapper.h +++ b/indra/llimage/llpngwrapper.h @@ -44,7 +44,7 @@ public: }; BOOL isValidPng(U8* src); - BOOL readPng(U8* src, LLImageRaw* rawImage, ImageInfo *infop = NULL); + BOOL readPng(U8* src, S32 dataSize, LLImageRaw* rawImage, ImageInfo *infop = NULL); BOOL writePng(const LLImageRaw* rawImage, U8* dst); U32 getFinalSize(); const std::string& getErrorMessage(); @@ -61,6 +61,7 @@ private: { U8 *mData; U32 mOffset; + S32 mDataSize; }; static void writeFlush(png_structp png_ptr); diff --git a/indra/llimage/tests/llimageworker_test.cpp b/indra/llimage/tests/llimageworker_test.cpp index e255d65b43..51c5c63556 100644..100755 --- a/indra/llimage/tests/llimageworker_test.cpp +++ b/indra/llimage/tests/llimageworker_test.cpp @@ -31,6 +31,8 @@ #include "../llimageworker.h" // For timer class #include "../llcommon/lltimer.h" +// for lltrace class +#include "../llcommon/lltrace.h" // Tut header #include "../test/lltut.h" @@ -43,7 +45,8 @@ // * A simulator for a class can be implemented here. Please comment and document thoroughly. LLImageBase::LLImageBase() -: mData(NULL), +: LLTrace::MemTrackable<LLImageBase>("LLImageBase"), +mData(NULL), mDataSize(0), mWidth(0), mHeight(0), @@ -64,6 +67,8 @@ LLImageRaw::~LLImageRaw() { } void LLImageRaw::deleteData() { } U8* LLImageRaw::allocateData(S32 size) { return NULL; } U8* LLImageRaw::reallocateData(S32 size) { return NULL; } +const U8* LLImageBase::getData() const { return NULL; } +U8* LLImageBase::getData() { return NULL; } // End Stubbing // ------------------------------------------------------------------------------------------- @@ -110,7 +115,6 @@ namespace tut { // Instance to be tested LLImageDecodeThread* mThread; - // Constructor and destructor of the test wrapper imagedecodethread_test() { @@ -136,6 +140,7 @@ namespace tut imagerequest_test() { done = false; + mRequest = new LLImageDecodeThread::ImageRequest(0, 0, LLQueuedThread::PRIORITY_NORMAL, 0, FALSE, new responder_test(&done)); |