diff options
author | James Cook <james@lindenlab.com> | 2007-01-02 08:33:20 +0000 |
---|---|---|
committer | James Cook <james@lindenlab.com> | 2007-01-02 08:33:20 +0000 |
commit | 420b91db29485df39fd6e724e782c449158811cb (patch) | |
tree | b471a94563af914d3ed3edd3e856d21cb1b69945 /indra/llimage |
Print done when done.
Diffstat (limited to 'indra/llimage')
-rw-r--r-- | indra/llimage/llimage.cpp | 1772 | ||||
-rw-r--r-- | indra/llimage/llimage.h | 298 | ||||
-rw-r--r-- | indra/llimage/llimagebmp.cpp | 624 | ||||
-rw-r--r-- | indra/llimage/llimagebmp.h | 45 | ||||
-rw-r--r-- | indra/llimage/llimagedxt.cpp | 475 | ||||
-rw-r--r-- | indra/llimage/llimagedxt.h | 120 | ||||
-rw-r--r-- | indra/llimage/llimagej2c.cpp | 249 | ||||
-rw-r--r-- | indra/llimage/llimagej2c.h | 77 | ||||
-rw-r--r-- | indra/llimage/llimagejpeg.cpp | 602 | ||||
-rw-r--r-- | indra/llimage/llimagejpeg.h | 63 | ||||
-rw-r--r-- | indra/llimage/llimagetga.cpp | 1090 | ||||
-rw-r--r-- | indra/llimage/llimagetga.h | 89 | ||||
-rw-r--r-- | indra/llimage/llmapimagetype.h | 22 |
13 files changed, 5526 insertions, 0 deletions
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 <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <algorithm> +#include <iostream> + +#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; i<pixels; i++ ) + { + dst_data[0] = src_data[0]; + dst_data[1] = src_data[1]; + dst_data[2] = src_data[2]; + src_data += 4; + dst_data += 3; + } +} + + +// Src and dst are same size. Src has 3 components. Dst has 4 components. +void LLImageRaw::copyUnscaled3onto4( LLImageRaw* src ) +{ + LLImageRaw* dst = this; // Just for clarity. + llassert( 3 == src->getComponents() ); + llassert( 4 == dst->getComponents() ); + llassert( (src->getWidth() == dst->getWidth()) && (src->getHeight() == dst->getHeight()) ); + + S32 pixels = getWidth() * getHeight(); + U8* src_data = src->getData(); + U8* dst_data = dst->getData(); + for( S32 i=0; i<pixels; i++ ) + { + dst_data[0] = src_data[0]; + dst_data[1] = src_data[1]; + dst_data[2] = src_data[2]; + dst_data[3] = 255; + src_data += 3; + dst_data += 4; + } +} + + +// Src and dst can be any size. Src and dst have same number of components. +void LLImageRaw::copyScaled( LLImageRaw* src ) +{ + LLMemType mt1((LLMemType::EMemType)mMemType); + LLImageRaw* dst = this; // Just for clarity. + + llassert( (3 == src->getComponents()) || (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<LLImageFormatted> 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<LLImageFormatted> 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<LLImageRaw>& 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<LLImageRaw>& 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<height; h++) + { + for (S32 w=0; w<width; w++) + { + switch(nchannels) + { + case 4: + avg4_colors4(indata, indata+4, indata+4*in_width, indata+4*in_width+4, data); + break; + case 3: + avg4_colors3(indata, indata+3, indata+3*in_width, indata+3*in_width+3, data); + break; + case 2: + avg4_colors2(indata, indata+2, indata+2*in_width, indata+2*in_width+2, data); + break; + case 1: + *(U8*)data = (U8)(((U32)(indata[0]) + indata[1] + indata[in_width] + indata[in_width+1])>>2); + break; + default: + llerrs << "generateMmip called with bad num channels" << llendl; + } + indata += nchannels*2; + data += nchannels; + } + indata += nchannels*in_width; // skip odd lines + } +} + + +//============================================================================ + +//static +F32 LLImageBase::calc_download_priority(F32 virtual_size, F32 visible_pixels, S32 bytes_sent) +{ + F32 w_priority; + + F32 bytes_weight = 1.f; + if (!bytes_sent) + { + bytes_weight = 20.f; + } + else if (bytes_sent < 1000) + { + bytes_weight = 1.f; + } + else if (bytes_sent < 2000) + { + bytes_weight = 1.f/1.5f; + } + else if (bytes_sent < 4000) + { + bytes_weight = 1.f/3.f; + } + else if (bytes_sent < 8000) + { + bytes_weight = 1.f/6.f; + } + else if (bytes_sent < 16000) + { + bytes_weight = 1.f/12.f; + } + else if (bytes_sent < 32000) + { + bytes_weight = 1.f/20.f; + } + else if (bytes_sent < 64000) + { + bytes_weight = 1.f/32.f; + } + else + { + bytes_weight = 1.f/64.f; + } + bytes_weight *= bytes_weight; + + + //llinfos << "VS: " << virtual_size << llendl; + F32 virtual_size_factor = virtual_size / (10.f*10.f); + + // The goal is for weighted priority to be <= 0 when we've reached a point where + // we've sent enough data. + //llinfos << "BytesSent: " << bytes_sent << llendl; + //llinfos << "BytesWeight: " << bytes_weight << llendl; + //llinfos << "PreLog: " << bytes_weight * virtual_size_factor << llendl; + w_priority = (F32)log10(bytes_weight * virtual_size_factor); + + //llinfos << "PreScale: " << w_priority << llendl; + + // We don't want to affect how MANY bytes we send based on the visible pixels, but the order + // in which they're sent. We post-multiply so we don't change the zero point. + if (w_priority > 0.f) + { + F32 pixel_weight = (F32)log10(visible_pixels + 1)*3.0f; + w_priority *= pixel_weight; + } + + return w_priority; +} diff --git a/indra/llimage/llimage.h b/indra/llimage/llimage.h new file mode 100644 index 0000000000..13ea916654 --- /dev/null +++ b/indra/llimage/llimage.h @@ -0,0 +1,298 @@ +/** + * @file llimage.h + * @brief Object for managing images and their textures. + * + * Copyright (c) 2000-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLIMAGE_H +#define LL_LLIMAGE_H + +#include "stdtypes.h" +#include "lluuid.h" +#include "llstring.h" +#include "llmemory.h" +#include "llworkerthread.h" + +const S32 MIN_IMAGE_MIP = 2; // 4x4, only used for expand/contract power of 2 +const S32 MAX_IMAGE_MIP = 11; // 2048x2048 +const S32 MAX_DISCARD_LEVEL = 5; + +const S32 MIN_IMAGE_SIZE = (1<<MIN_IMAGE_MIP); // 4, only used for expand/contract power of 2 +const S32 MAX_IMAGE_SIZE = (1<<MAX_IMAGE_MIP); // 2048 +const S32 MIN_IMAGE_AREA = MIN_IMAGE_SIZE * MIN_IMAGE_SIZE; +const S32 MAX_IMAGE_AREA = MAX_IMAGE_SIZE * MAX_IMAGE_SIZE; +const S32 MAX_IMAGE_COMPONENTS = 8; +const S32 MAX_IMAGE_DATA_SIZE = MAX_IMAGE_AREA * MAX_IMAGE_COMPONENTS; + +// Note! These CANNOT be changed without invalidating the viewer VFS files, I think? +const S32 FIRST_PACKET_SIZE = 600; +const S32 MAX_IMG_PACKET_SIZE = 1000; + +// Base classes for images. +// There are two major parts for the image: +// The compressed representation, and the decompressed representation. + +class LLImageFormatted; +class LLImageRaw; +class LLColor4U; + +enum +{ + IMG_CODEC_INVALID = 0, + IMG_CODEC_RGB = 1, + IMG_CODEC_J2C = 2, + IMG_CODEC_BMP = 3, + IMG_CODEC_TGA = 4, + IMG_CODEC_JPEG = 5, + IMG_CODEC_DXT = 6, + IMG_CODEC_EOF = 7 +}; + +//============================================================================ + +class LLImageBase : public LLThreadSafeRefCount +{ +protected: + virtual ~LLImageBase(); + +public: + LLImageBase(); + + enum + { + TYPE_NORMAL = 0, + TYPE_AVATAR_BAKE = 1, + }; + + virtual void deleteData(); + virtual U8* allocateData(S32 size = -1); + virtual U8* reallocateData(S32 size = -1); + + virtual void dump(); + virtual void sanityCheck(); + + U16 getWidth() const { return mWidth; } + U16 getHeight() const { return mHeight; } + S8 getComponents() const { return mComponents; } + S32 getDataSize() const { return mDataSize; } + + const U8 *getData() const { return mData; } // read only + U8 *getData() { return mData; } + + void setSize(S32 width, S32 height, S32 ncomponents); + U8* allocateDataSize(S32 width, S32 height, S32 ncomponents, S32 size = -1); // setSize() + allocateData() + +protected: + // special accessor to allow direct setting of mData and mDataSize by LLImageFormatted + void setDataAndSize(U8 *data, S32 size) { mData = data; mDataSize = size; }; + +public: + static const LLString& getLastError() {return sLastErrorMessage;}; + static void resetLastError() {sLastErrorMessage = LLString("No Error"); }; + static BOOL setLastError(const LLString& message, const LLString& filename = ""); // returns FALSE + + static void generateMip(const U8 *indata, U8* mipdata, int width, int height, S32 nchannels); + + // Function for calculating the download priority for textes + // <= 0 priority means that there's no need for more data. + static F32 calc_download_priority(F32 virtual_size, F32 visible_area, S32 bytes_sent); + + static void setSizeOverride(BOOL enabled) { sSizeOverride = enabled; } + +private: + U8 *mData; + S32 mDataSize; + + U16 mWidth; + U16 mHeight; + + S8 mComponents; + +public: + S16 mMemType; // debug + + static LLString sLastErrorMessage; + + static BOOL sSizeOverride; +}; + +// Raw representation of an image (used for textures, and other uncompressed formats +class LLImageRaw : public LLImageBase +{ +protected: + /*virtual*/ ~LLImageRaw(); + +public: + LLImageRaw(); + LLImageRaw(U16 width, U16 height, S8 components); + LLImageRaw(U8 *data, U16 width, U16 height, S8 components); + // Construct using createFromFile (used by tools) + LLImageRaw(const LLString& filename, bool j2c_lowest_mip_only = false); + + /*virtual*/ void deleteData(); + /*virtual*/ U8* allocateData(S32 size = -1); + /*virtual*/ U8* reallocateData(S32 size); + + BOOL copyData(U8 *data, U16 width, U16 height, S8 components); + + BOOL resize(U16 width, U16 height, S8 components); + + U8 * getSubImage(U32 x_pos, U32 y_pos, U32 width, U32 height) const; + BOOL setSubImage(U32 x_pos, U32 y_pos, U32 width, U32 height, + const U8 *data, U32 stride = 0, BOOL reverse_y = FALSE); + + void clear(U8 r=0, U8 g=0, U8 b=0, U8 a=255); + + void verticalFlip(); + + 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); + void scale( S32 new_width, S32 new_height, BOOL scale_image = TRUE ); + + // Fill the buffer with a constant color + void fill( const LLColor4U& color ); + + // Copy operations + + // Src and dst can be any size. Src and dst can each have 3 or 4 components. + void copy( LLImageRaw* src ); + + // Src and dst are same size. Src and dst have same number of components. + void copyUnscaled( LLImageRaw* src ); + + // Src and dst are same size. Src has 4 components. Dst has 3 components. + void copyUnscaled4onto3( LLImageRaw* src ); + + // Src and dst are same size. Src has 3 components. Dst has 4 components. + void copyUnscaled3onto4( LLImageRaw* src ); + + // Src and dst can be any size. Src and dst have same number of components. + void copyScaled( LLImageRaw* src ); + + // Src and dst can be any size. Src has 3 components. Dst has 4 components. + void copyScaled3onto4( LLImageRaw* src ); + + // Src and dst can be any size. Src has 4 components. Dst has 3 components. + void copyScaled4onto3( LLImageRaw* src ); + + + // Composite operations + + // Src and dst can be any size. Src and dst can each have 3 or 4 components. + void composite( LLImageRaw* src ); + + // Src and dst can be any size. Src has 4 components. Dst has 3 components. + void compositeScaled4onto3( LLImageRaw* src ); + + // Src and dst are same size. Src has 4 components. Dst has 3 components. + void compositeUnscaled4onto3( LLImageRaw* src ); + +protected: + // Create an image from a local file (generally used in tools) + bool createFromFile(const LLString& filename, bool j2c_lowest_mip_only = false); + + void copyLineScaled( U8* in, U8* out, S32 in_pixel_len, S32 out_pixel_len, S32 in_pixel_step, S32 out_pixel_step ); + void compositeRowScaled4onto3( U8* in, U8* out, S32 in_pixel_len, S32 out_pixel_len ); + + U8 fastFractionalMult(U8 a,U8 b); + +public: + static S32 sGlobalRawMemory; + static S32 sRawImageCount; +}; + +// Compressed representation of image. +// Subclass from this class for the different representations (J2C, bmp) +class LLImageFormatted : public LLImageBase, public LLWorkerClass +{ +public: + static void initClass(bool threaded = true, bool run_always = true); + static void cleanupClass(); + static LLImageFormatted* createFromExtension(const LLString& instring); + +protected: + /*virtual*/ ~LLImageFormatted(); + +public: + LLImageFormatted(S8 codec); + + // LLImageBase +public: + /*virtual*/ void deleteData(); + /*virtual*/ U8* allocateData(S32 size = -1); + /*virtual*/ U8* reallocateData(S32 size); + + /*virtual*/ void dump(); + /*virtual*/ void sanityCheck(); + + // LLWorkerThread +public: + // called from WORKER THREAD, returns TRUE if done + /*virtual*/ bool doWork(S32 param); +private: + // called from MAIN THREAD + /*virtual*/ void startWork(S32 param); // called from addWork() + /*virtual*/ void endWork(S32 param, bool aborted); // called from doWork() + + // New methods +public: + // calcHeaderSize() returns the maximum size of header; + // 0 indicates we don't know have a header and have to lead the entire file + virtual S32 calcHeaderSize() { return 0; }; + // readHeader() reads size bytes into mData, and sets width/height/ncomponents + virtual void readHeader(U8* data, S32 size); + // calcDataSize() returns how many bytes to read to load discard_level (including header) + virtual S32 calcDataSize(S32 discard_level); + // calcDiscardLevelBytes() returns the smallest valid discard level based on the number of input bytes + virtual S32 calcDiscardLevelBytes(S32 bytes); + // getRawDiscardLevel()by default returns mDiscardLevel, but may be overridden (LLImageJ2C) + virtual S8 getRawDiscardLevel() { return mDiscardLevel; } + + BOOL load(const LLString& filename); + BOOL save(const LLString& filename); +// BOOL save(LLVFS *vfs, const LLUUID &uuid, const LLAssetType::EType type); +// Depricated to remove VFS dependency (see .cpp for replacement): + + virtual BOOL updateData() = 0; // pure virtual + BOOL copyData(U8 *data, S32 size); // calls updateData() + BOOL setData(U8 *data, S32 size); // calls updateData() + BOOL appendData(U8 *data, S32 size); // use if some data (e.g header) is already loaded, calls updateData() + + // Loads first 4 channels. + virtual BOOL decode(LLImageRaw* raw_image, F32 decode_time=0.0) = 0; + // Subclasses that can handle more than 4 channels should override this function. + virtual BOOL decode(LLImageRaw* raw_image, F32 decode_time, S32 first_channel, S32 max_channel); + + // Decode methods to return a pointer to raw data for purposes of passing to + // opengl or such. This class tracks the decoded data and keeps it alive until + // destroyed or releaseDecodedData() is called. + virtual BOOL requestDecodedData(LLPointer<LLImageRaw>& raw, S32 discard = -1, F32 decode_time=0.0); + virtual BOOL requestDecodedAuxData(LLPointer<LLImageRaw>& raw, S32 channel, + S32 discard = -1, F32 decode_time=0.0); + virtual void releaseDecodedData(); + + virtual BOOL encode(const LLImageRaw* raw_image, F32 encode_time=0.0) = 0; + + S8 getCodec() const; + BOOL isDecoding() const { return mDecoding ? TRUE : FALSE; } + BOOL isDecoded() const { return mDecoded ? TRUE : FALSE; } + void setDiscardLevel(S8 discard_level) { mDiscardLevel = discard_level; } + S8 getDiscardLevel() const { return mDiscardLevel; } + +protected: + S8 mCodec; + S8 mDecoding; + S8 mDecoded; + S8 mDiscardLevel; + + LLPointer<LLImageRaw> mDecodedImage; + +public: + static S32 sGlobalFormattedMemory; + static LLWorkerThread* sWorkerThread; +}; + +#endif diff --git a/indra/llimage/llimagebmp.cpp b/indra/llimage/llimagebmp.cpp new file mode 100644 index 0000000000..94aaa5c19e --- /dev/null +++ b/indra/llimage/llimagebmp.cpp @@ -0,0 +1,624 @@ +/** + * @file llimagebmp.cpp + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llimagebmp.h" +#include "llerror.h" + +#include "llendianswizzle.h" + + +/** + * @struct LLBMPHeader + * + * This struct helps deal with bmp files. + */ +struct LLBMPHeader +{ + S32 mSize; + S32 mWidth; + S32 mHeight; + S16 mPlanes; + S16 mBitsPerPixel; + S16 mCompression; + S16 mAlignmentPadding; // pads out to next word boundary + S32 mImageSize; + S32 mHorzPelsPerMeter; + S32 mVertPelsPerMeter; + S32 mNumColors; + S32 mNumColorsImportant; +}; + +/** + * @struct Win95BmpHeaderExtension + */ +struct Win95BmpHeaderExtension +{ + U32 mReadMask; + U32 mGreenMask; + U32 mBlueMask; + U32 mAlphaMask; + U32 mColorSpaceType; + U16 mRed[3]; // Red CIE endpoint + U16 mGreen[3]; // Green CIE endpoint + U16 mBlue[3]; // Blue CIE endpoint + U32 mGamma[3]; // Gamma scale for r g and b +}; + +/** + * LLImageBMP + */ +LLImageBMP::LLImageBMP() + : + LLImageFormatted(IMG_CODEC_BMP), + mColorPaletteColors( 0 ), + mColorPalette( NULL ), + mBitmapOffset( 0 ), + mBitsPerPixel( 0 ), + mOriginAtTop( FALSE ) +{ + mBitfieldMask[0] = 0; + mBitfieldMask[1] = 0; + mBitfieldMask[2] = 0; + mBitfieldMask[3] = 0; +} + +LLImageBMP::~LLImageBMP() +{ + delete[] mColorPalette; +} + + +BOOL LLImageBMP::updateData() +{ + resetLastError(); + + // Check to make sure that this instance has been initialized with data + U8* mdata = getData(); + if (!mdata || (0 == getDataSize())) + { + setLastError("Uninitialized instance of LLImageBMP"); + return FALSE; + } + + // Read the bitmap headers in order to get all the useful info + // about this image + + //////////////////////////////////////////////////////////////////// + // Part 1: "File Header" + // 14 bytes consisting of + // 2 bytes: either BM or BA + // 4 bytes: file size in bytes + // 4 bytes: reserved (always 0) + // 4 bytes: bitmap offset (starting position of image data in bytes) + const S32 FILE_HEADER_SIZE = 14; + if ((mdata[0] != 'B') || (mdata[1] != 'M')) + { + if ((mdata[0] != 'B') || (mdata[1] != 'A')) + { + setLastError("OS/2 bitmap array BMP files are not supported"); + return FALSE; + } + else + { + setLastError("Does not appear to be a bitmap file"); + return FALSE; + } + } + + mBitmapOffset = mdata[13]; + mBitmapOffset <<= 8; mBitmapOffset += mdata[12]; + mBitmapOffset <<= 8; mBitmapOffset += mdata[11]; + mBitmapOffset <<= 8; mBitmapOffset += mdata[10]; + + + //////////////////////////////////////////////////////////////////// + // Part 2: "Bitmap Header" + const S32 BITMAP_HEADER_SIZE = 40; + LLBMPHeader header; + llassert( sizeof( header ) == BITMAP_HEADER_SIZE ); + + memcpy((void *)&header, mdata + FILE_HEADER_SIZE, BITMAP_HEADER_SIZE); + + // convert BMP header from little endian (no-op on little endian builds) + llendianswizzleone(header.mSize); + llendianswizzleone(header.mWidth); + llendianswizzleone(header.mHeight); + llendianswizzleone(header.mPlanes); + llendianswizzleone(header.mBitsPerPixel); + llendianswizzleone(header.mCompression); + llendianswizzleone(header.mAlignmentPadding); + llendianswizzleone(header.mImageSize); + llendianswizzleone(header.mHorzPelsPerMeter); + llendianswizzleone(header.mVertPelsPerMeter); + llendianswizzleone(header.mNumColors); + llendianswizzleone(header.mNumColorsImportant); + + BOOL windows_nt_version = FALSE; + BOOL windows_95_version = FALSE; + if( 12 == header.mSize ) + { + setLastError("Windows 2.x and OS/2 1.x BMP files are not supported"); + return FALSE; + } + else + if( 40 == header.mSize ) + { + if( 3 == header.mCompression ) + { + // Windows NT + windows_nt_version = TRUE; + } + else + { + // Windows 3.x + } + } + else + if( 12 <= header.mSize && 64 <= header.mSize ) + { + setLastError("OS/2 2.x BMP files are not supported"); + return FALSE; + } + else + if( 108 == header.mSize ) + { + // BITMAPV4HEADER + windows_95_version = TRUE; + } + else + if( 108 < header.mSize ) + { + // BITMAPV5HEADER or greater + // Should work as long at Microsoft maintained backwards compatibility (which they did in V4 and V5) + windows_95_version = TRUE; + } + + S32 width = header.mWidth; + S32 height = header.mHeight; + if (height < 0) + { + mOriginAtTop = TRUE; + height = -height; + } + else + { + mOriginAtTop = FALSE; + } + + mBitsPerPixel = header.mBitsPerPixel; + S32 components; + switch( mBitsPerPixel ) + { + case 8: + components = 1; + break; + case 24: + case 32: + components = 3; + break; + case 1: + case 4: + case 16: // Started work on 16, but doesn't work yet + // These are legal, but we don't support them yet. + setLastError("Unsupported bit depth"); + return FALSE; + default: + setLastError("Unrecognized bit depth"); + return FALSE; + } + + setSize(width, height, components); + + switch( header.mCompression ) + { + case 0: + // Uncompressed + break; + + case 1: + setLastError("8 bit RLE compression not supported."); + return FALSE; + + case 2: + setLastError("4 bit RLE compression not supported."); + return FALSE; + + case 3: + // Windows NT or Windows 95 + break; + + default: + setLastError("Unsupported compression format."); + return FALSE; + } + + //////////////////////////////////////////////////////////////////// + // Part 3: Bitfield Masks and other color data + S32 extension_size = 0; + if( windows_nt_version ) + { + if( (16 != header.mBitsPerPixel) && (32 != header.mBitsPerPixel) ) + { + setLastError("Bitfield encoding requires 16 or 32 bits per pixel."); + return FALSE; + } + + if( 0 != header.mNumColors ) + { + setLastError("Bitfield encoding is not compatible with a color table."); + return FALSE; + } + + + extension_size = 4 * 3; + memcpy( mBitfieldMask, mdata + FILE_HEADER_SIZE + BITMAP_HEADER_SIZE, extension_size); + } + else + if( windows_95_version ) + { + Win95BmpHeaderExtension win_95_extension; + extension_size = sizeof( win_95_extension ); + + llassert( sizeof( win_95_extension ) + BITMAP_HEADER_SIZE == 108 ); + memcpy( &win_95_extension, mdata + FILE_HEADER_SIZE + BITMAP_HEADER_SIZE, sizeof( win_95_extension ) ); + + if( 3 == header.mCompression ) + { + memcpy( mBitfieldMask, mdata + FILE_HEADER_SIZE + BITMAP_HEADER_SIZE, 4 * 4); + } + + // Color correction ignored for now + } + + + //////////////////////////////////////////////////////////////////// + // Part 4: Color Palette (optional) + // Note: There's no color palette if there are 16 or more bits per pixel + S32 color_palette_size = 0; + mColorPaletteColors = 0; + if( header.mBitsPerPixel < 16 ) + { + if( 0 == header.mNumColors ) + { + mColorPaletteColors = (1 << header.mBitsPerPixel); + } + else + { + mColorPaletteColors = header.mNumColors; + } + } + color_palette_size = mColorPaletteColors * 4; + + if( 0 != mColorPaletteColors ) + { + mColorPalette = new U8[color_palette_size]; + memcpy( mColorPalette, mdata + FILE_HEADER_SIZE + BITMAP_HEADER_SIZE + extension_size, color_palette_size ); + } + + return TRUE; +} + +BOOL LLImageBMP::decode(LLImageRaw* raw_image, F32 decode_time) +{ + llassert_always(raw_image); + + resetLastError(); + + // Check to make sure that this instance has been initialized with data + U8* mdata = getData(); + if (!mdata || (0 == getDataSize())) + { + setLastError("llimagebmp trying to decode an image with no data!"); + return FALSE; + } + + raw_image->resize(getWidth(), getHeight(), 3); + + U8* src = mdata + mBitmapOffset; + U8* dst = raw_image->getData(); + + BOOL success = FALSE; + + switch( mBitsPerPixel ) + { + case 8: + if( mColorPaletteColors >= 256 ) + { + success = decodeColorTable8( dst, src ); + } + break; + + case 16: + success = decodeColorMask16( dst, src ); + break; + + case 24: + success = decodeTruecolor24( dst, src ); + break; + + case 32: + success = decodeColorMask32( dst, src ); + break; + } + + if( success && mOriginAtTop ) + { + raw_image->verticalFlip(); + } + + return success; +} + +U32 LLImageBMP::countTrailingZeros( U32 m ) +{ + U32 shift_count = 0; + while( !(m & 1) ) + { + shift_count++; + m >>= 1; + } + return shift_count; +} + + +BOOL LLImageBMP::decodeColorMask16( U8* dst, U8* src ) +{ + llassert( 16 == mBitsPerPixel ); + + if( !mBitfieldMask[0] && !mBitfieldMask[1] && !mBitfieldMask[2] ) + { + // Use default values + mBitfieldMask[0] = 0x00007C00; + mBitfieldMask[1] = 0x000003E0; + mBitfieldMask[2] = 0x0000001F; + } + + S32 src_row_span = getWidth() * 2; + S32 alignment_bytes = (3 * src_row_span) % 4; // round up to nearest multiple of 4 + + U32 r_shift = countTrailingZeros( mBitfieldMask[2] ); + U32 g_shift = countTrailingZeros( mBitfieldMask[1] ); + U32 b_shift = countTrailingZeros( mBitfieldMask[0] ); + + for( S32 row = 0; row < getHeight(); row++ ) + { + for( S32 col = 0; col < getWidth(); col++ ) + { + U32 value = *((U16*)src); + dst[0] = U8((value & mBitfieldMask[2]) >> r_shift); // Red + dst[1] = U8((value & mBitfieldMask[1]) >> g_shift); // Green + dst[2] = U8((value & mBitfieldMask[0]) >> b_shift); // Blue + src += 2; + dst += 3; + } + src += alignment_bytes; + } + + return TRUE; +} + +BOOL LLImageBMP::decodeColorMask32( U8* dst, U8* src ) +{ + // Note: alpha is not supported + + llassert( 32 == mBitsPerPixel ); + + if( !mBitfieldMask[0] && !mBitfieldMask[1] && !mBitfieldMask[2] ) + { + // Use default values + mBitfieldMask[0] = 0x00FF0000; + mBitfieldMask[1] = 0x0000FF00; + mBitfieldMask[2] = 0x000000FF; + } + + + S32 src_row_span = getWidth() * 4; + S32 alignment_bytes = (3 * src_row_span) % 4; // round up to nearest multiple of 4 + + U32 r_shift = countTrailingZeros( mBitfieldMask[0] ); + U32 g_shift = countTrailingZeros( mBitfieldMask[1] ); + U32 b_shift = countTrailingZeros( mBitfieldMask[2] ); + + for( S32 row = 0; row < getHeight(); row++ ) + { + for( S32 col = 0; col < getWidth(); col++ ) + { + U32 value = *((U32*)src); + dst[0] = U8((value & mBitfieldMask[0]) >> r_shift); // Red + dst[1] = U8((value & mBitfieldMask[1]) >> g_shift); // Green + dst[2] = U8((value & mBitfieldMask[2]) >> b_shift); // Blue + src += 4; + dst += 3; + } + src += alignment_bytes; + } + + return TRUE; +} + + +BOOL LLImageBMP::decodeColorTable8( U8* dst, U8* src ) +{ + llassert( (8 == mBitsPerPixel) && (mColorPaletteColors >= 256) ); + + S32 src_row_span = getWidth() * 1; + S32 alignment_bytes = (3 * src_row_span) % 4; // round up to nearest multiple of 4 + + for( S32 row = 0; row < getHeight(); row++ ) + { + for( S32 col = 0; col < getWidth(); col++ ) + { + S32 index = 4 * src[0]; + dst[0] = mColorPalette[index + 2]; // Red + dst[1] = mColorPalette[index + 1]; // Green + dst[2] = mColorPalette[index + 0]; // Blue + src++; + dst += 3; + } + src += alignment_bytes; + } + + return TRUE; +} + + +BOOL LLImageBMP::decodeTruecolor24( U8* dst, U8* src ) +{ + llassert( 24 == mBitsPerPixel ); + llassert( 3 == getComponents() ); + S32 src_row_span = getWidth() * 3; + S32 alignment_bytes = (3 * src_row_span) % 4; // round up to nearest multiple of 4 + + for( S32 row = 0; row < getHeight(); row++ ) + { + for( S32 col = 0; col < getWidth(); col++ ) + { + dst[0] = src[2]; // Red + dst[1] = src[1]; // Green + dst[2] = src[0]; // Blue + src += 3; + dst += 3; + } + src += alignment_bytes; + } + + return TRUE; +} + +BOOL LLImageBMP::encode(const LLImageRaw* raw_image, F32 encode_time) +{ + llassert_always(raw_image); + + resetLastError(); + + S32 src_components = raw_image->getComponents(); + S32 dst_components = ( src_components < 3 ) ? 1 : 3; + + if( (2 == src_components) || (4 == src_components) ) + { + llinfos << "Dropping alpha information during BMP encoding" << llendl; + } + + setSize(raw_image->getWidth(), raw_image->getHeight(), dst_components); + + U8 magic[14]; + LLBMPHeader header; + int header_bytes = 14+sizeof(header); + llassert(header_bytes == 54); + if (getComponents() == 1) + { + header_bytes += 1024; // Need colour LUT. + } + int line_bytes = getComponents() * getWidth(); + int alignment_bytes = (3 * line_bytes) % 4; + line_bytes += alignment_bytes; + int file_bytes = line_bytes*getHeight() + header_bytes; + + // Allocate the new buffer for the data. + allocateData(file_bytes); + + magic[0] = 'B'; magic[1] = 'M'; + magic[2] = (U8) file_bytes; + magic[3] = (U8)(file_bytes>>8); + magic[4] = (U8)(file_bytes>>16); + magic[5] = (U8)(file_bytes>>24); + magic[6] = magic[7] = magic[8] = magic[9] = 0; + magic[10] = (U8) header_bytes; + magic[11] = (U8)(header_bytes>>8); + magic[12] = (U8)(header_bytes>>16); + magic[13] = (U8)(header_bytes>>24); + header.mSize = 40; + header.mWidth = getWidth(); + header.mHeight = getHeight(); + header.mPlanes = 1; + header.mBitsPerPixel = (getComponents()==1)?8:24; + header.mCompression = 0; + header.mAlignmentPadding = 0; + header.mImageSize = 0; +#if LL_DARWIN + header.mHorzPelsPerMeter = header.mVertPelsPerMeter = 2834; // 72dpi +#else + header.mHorzPelsPerMeter = header.mVertPelsPerMeter = 0; +#endif + header.mNumColors = header.mNumColorsImportant = 0; + + // convert BMP header to little endian (no-op on little endian builds) + llendianswizzleone(header.mSize); + llendianswizzleone(header.mWidth); + llendianswizzleone(header.mHeight); + llendianswizzleone(header.mPlanes); + llendianswizzleone(header.mBitsPerPixel); + llendianswizzleone(header.mCompression); + llendianswizzleone(header.mAlignmentPadding); + llendianswizzleone(header.mImageSize); + llendianswizzleone(header.mHorzPelsPerMeter); + llendianswizzleone(header.mVertPelsPerMeter); + llendianswizzleone(header.mNumColors); + llendianswizzleone(header.mNumColorsImportant); + + U8* mdata = getData(); + + // Output magic, then header, then the palette table, then the data. + U32 cur_pos = 0; + memcpy(mdata, magic, 14); + cur_pos += 14; + memcpy(mdata+cur_pos, &header, 40); + cur_pos += 40; + if (getComponents() == 1) + { + S32 n; + for (n=0; n < 256; n++) + { + mdata[cur_pos++] = (U8)n; + mdata[cur_pos++] = (U8)n; + mdata[cur_pos++] = (U8)n; + mdata[cur_pos++] = 0; + } + } + + // Need to iterate through, because we need to flip the RGB. + const U8* src = raw_image->getData(); + U8* dst = mdata + cur_pos; + + for( S32 row = 0; row < getHeight(); row++ ) + { + for( S32 col = 0; col < getWidth(); col++ ) + { + switch( src_components ) + { + case 1: + *dst++ = *src++; + break; + case 2: + { + U32 lum = src[0]; + U32 alpha = src[1]; + *dst++ = (U8)(lum * alpha / 255); + src += 2; + break; + } + case 3: + case 4: + dst[0] = src[2]; + dst[1] = src[1]; + dst[2] = src[0]; + src += src_components; + dst += 3; + break; + } + + for( S32 i = 0; i < alignment_bytes; i++ ) + { + *dst++ = 0; + } + } + } + + return TRUE; +} diff --git a/indra/llimage/llimagebmp.h b/indra/llimage/llimagebmp.h new file mode 100644 index 0000000000..0c0e51e2e7 --- /dev/null +++ b/indra/llimage/llimagebmp.h @@ -0,0 +1,45 @@ +/** + * @file llimagebmp.h + * @brief Image implementation for BMP. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLIMAGEBMP_H +#define LL_LLIMAGEBMP_H + +#include "llimage.h" + +// This class compresses and decompressed BMP files + +class LLImageBMP : public LLImageFormatted +{ +protected: + virtual ~LLImageBMP(); + +public: + LLImageBMP(); + + /*virtual*/ BOOL updateData(); + /*virtual*/ BOOL decode(LLImageRaw* raw_image, F32 time=0.0); + /*virtual*/ BOOL encode(const LLImageRaw* raw_image, F32 time=0.0); + +protected: + BOOL decodeColorTable8( U8* dst, U8* src ); + BOOL decodeColorMask16( U8* dst, U8* src ); + BOOL decodeTruecolor24( U8* dst, U8* src ); + BOOL decodeColorMask32( U8* dst, U8* src ); + + U32 countTrailingZeros( U32 m ); + +protected: + S32 mColorPaletteColors; + U8* mColorPalette; + S32 mBitmapOffset; + S32 mBitsPerPixel; + U32 mBitfieldMask[4]; // rgba + BOOL mOriginAtTop; +}; + +#endif diff --git a/indra/llimage/llimagedxt.cpp b/indra/llimage/llimagedxt.cpp new file mode 100644 index 0000000000..9ddd044007 --- /dev/null +++ b/indra/llimage/llimagedxt.cpp @@ -0,0 +1,475 @@ +/** + * @file llimagedxt.cpp + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llimagedxt.h" + +//static +void LLImageDXT::checkMinWidthHeight(EFileFormat format, S32& width, S32& height) +{ + S32 mindim = (format >= FORMAT_DXT1 && format <= FORMAT_DXR5) ? 4 : 1; + width = llmax(width, mindim); + height = llmax(height, mindim); +} + +//static +S32 LLImageDXT::formatBits(EFileFormat format) +{ + switch (format) + { + case FORMAT_DXT1: return 4; + case FORMAT_DXR1: return 4; + case FORMAT_I8: return 8; + case FORMAT_A8: return 8; + case FORMAT_DXT3: return 8; + case FORMAT_DXR3: return 8; + case FORMAT_DXR5: return 8; + case FORMAT_DXT5: return 8; + case FORMAT_RGB8: return 24; + case FORMAT_RGBA8: return 32; + default: + llerrs << "LLImageDXT::Unknown format: " << format << llendl; + return 0; + } +}; + +//static +S32 LLImageDXT::formatBytes(EFileFormat format, S32 width, S32 height) +{ + checkMinWidthHeight(format, width, height); + S32 bytes = ((width*height*formatBits(format)+7)>>3); + S32 aligned = (bytes+3)&~3; + return aligned; +} + +//static +S32 LLImageDXT::formatComponents(EFileFormat format) +{ + switch (format) + { + case FORMAT_DXT1: return 3; + case FORMAT_DXR1: return 3; + case FORMAT_I8: return 1; + case FORMAT_A8: return 1; + case FORMAT_DXT3: return 4; + case FORMAT_DXR3: return 4; + case FORMAT_DXT5: return 4; + case FORMAT_DXR5: return 4; + case FORMAT_RGB8: return 3; + case FORMAT_RGBA8: return 4; + default: + llerrs << "LLImageDXT::Unknown format: " << format << llendl; + return 0; + } +}; + +// static +LLImageDXT::EFileFormat LLImageDXT::getFormat(S32 fourcc) +{ + switch(fourcc) + { + case 0x20203849: return FORMAT_I8; + case 0x20203841: return FORMAT_A8; + case 0x20424752: return FORMAT_RGB8; + case 0x41424752: return FORMAT_RGBA8; + case 0x31525844: return FORMAT_DXR1; + case 0x32525844: return FORMAT_DXR2; + case 0x33525844: return FORMAT_DXR3; + case 0x34525844: return FORMAT_DXR4; + case 0x35525844: return FORMAT_DXR5; + case 0x31545844: return FORMAT_DXT1; + case 0x32545844: return FORMAT_DXT2; + case 0x33545844: return FORMAT_DXT3; + case 0x34545844: return FORMAT_DXT4; + case 0x35545844: return FORMAT_DXT5; + default: return FORMAT_UNKNOWN; + } +} + +//static +S32 LLImageDXT::getFourCC(EFileFormat format) +{ + switch(format) + { + case FORMAT_I8: return 0x20203849; + case FORMAT_A8: return 0x20203841; + case FORMAT_RGB8: return 0x20424752; + case FORMAT_RGBA8: return 0x41424752; + case FORMAT_DXR1: return 0x31525844; + case FORMAT_DXR2: return 0x32525844; + case FORMAT_DXR3: return 0x33525844; + case FORMAT_DXR4: return 0x34525844; + case FORMAT_DXR5: return 0x35525844; + case FORMAT_DXT1: return 0x31545844; + case FORMAT_DXT2: return 0x32545844; + case FORMAT_DXT3: return 0x33545844; + case FORMAT_DXT4: return 0x34545844; + case FORMAT_DXT5: return 0x35545844; + default: return 0x00000000; + } +} + +//static +void LLImageDXT::calcDiscardWidthHeight(S32 discard_level, EFileFormat format, S32& width, S32& height) +{ + while (discard_level > 0 && width > 1 && height > 1) + { + discard_level--; + width >>= 1; + height >>= 1; + } + checkMinWidthHeight(format, width, height); +} + +//static +S32 LLImageDXT::calcNumMips(S32 width, S32 height) +{ + S32 nmips = 0; + while (width > 0 && height > 0) + { + width >>= 1; + height >>= 1; + nmips++; + } + return nmips; +} + +//============================================================================ + +LLImageDXT::LLImageDXT() + : LLImageFormatted(IMG_CODEC_DXT), + mFileFormat(FORMAT_UNKNOWN), + mHeaderSize(0) +{ +} + +LLImageDXT::~LLImageDXT() +{ +} + +// virtual +BOOL LLImageDXT::updateData() +{ + resetLastError(); + + U8* data = getData(); + S32 data_size = getDataSize(); + + if (!data || !data_size) + { + setLastError("LLImageDXT uninitialized"); + return FALSE; + } + + S32 width, height, miplevelmax; + dxtfile_header_t* header = (dxtfile_header_t*)data; + if (header->fourcc != 0x20534444) + { + dxtfile_header_old_t* oldheader = (dxtfile_header_old_t*)header; + mHeaderSize = sizeof(dxtfile_header_old_t); + mFileFormat = EFileFormat(oldheader->format); + miplevelmax = llmin(oldheader->maxlevel,MAX_IMAGE_MIP); + width = oldheader->maxwidth; + height = oldheader->maxheight; + } + else + { + mHeaderSize = sizeof(dxtfile_header_t); + mFileFormat = getFormat(header->pixel_fmt.fourcc); + miplevelmax = llmin(header->num_mips-1,MAX_IMAGE_MIP); + width = header->maxwidth; + height = header->maxheight; + } + + if (data_size < mHeaderSize) + { + llerrs << "LLImageDXT: not enough data" << llendl; + } + S32 ncomponents = formatComponents(mFileFormat); + setSize(width, height, ncomponents); + + S32 discard = calcDiscardLevelBytes(data_size); + discard = llmin(discard, miplevelmax); + setDiscardLevel(discard); + + return TRUE; +} + +// discard: 0 = largest (last) mip +S32 LLImageDXT::getMipOffset(S32 discard) +{ + if (mFileFormat >= FORMAT_DXT1 && mFileFormat <= FORMAT_DXT5) + { + llerrs << "getMipOffset called with old (unsupported) format" << llendl; + } + S32 width = getWidth(), height = getHeight(); + S32 num_mips = calcNumMips(width, height); + discard = llclamp(discard, 0, num_mips-1); + S32 last_mip = num_mips-1-discard; + llassert(mHeaderSize > 0); + S32 offset = mHeaderSize; + for (S32 mipidx = num_mips-1; mipidx >= 0; mipidx--) + { + if (mipidx < last_mip) + { + offset += formatBytes(mFileFormat, width, height); + } + width >>= 1; + height >>= 1; + } + return offset; +} + +void LLImageDXT::setFormat() +{ + S32 ncomponents = getComponents(); + switch (ncomponents) + { + case 3: mFileFormat = FORMAT_DXR1; break; + case 4: mFileFormat = FORMAT_DXR3; break; + default: llerrs << "LLImageDXT::setFormat called with ncomponents = " << ncomponents << llendl; + } + mHeaderSize = calcHeaderSize(); +} + +// virtual +BOOL LLImageDXT::decode(LLImageRaw* raw_image, F32 time) +{ + llassert_always(raw_image); + + if (mFileFormat >= FORMAT_DXT1 && mFileFormat <= FORMAT_DXR5) + { + llwarns << "Attempt to decode compressed LLImageDXT to Raw (unsupported)" << llendl; + return FALSE; + } + + S32 width = getWidth(), height = getHeight(); + S32 ncomponents = getComponents(); + S32 image_size = formatBytes(mFileFormat, width, height); + U8* data = getData() + getMipOffset(0); + + if ((!getData()) || (data + image_size > getData() + getDataSize())) + { + setLastError("LLImageDXT trying to decode an image with not enough data!"); + return FALSE; + } + + raw_image->resize(width, height, ncomponents); + memcpy(raw_image->getData(), data, image_size); + + return TRUE; +} + +// virtual +BOOL LLImageDXT::requestDecodedData(LLPointer<LLImageRaw>& raw, S32 discard, F32 decode_time) +{ + if (discard < 0) + { + discard = mDiscardLevel; + } + else if (discard < mDiscardLevel) + { + llerrs << "Request for invalid discard level" << llendl; + } + U8* data = getData() + getMipOffset(discard); + S32 width, height; + calcDiscardWidthHeight(discard, mFileFormat, width, height); + raw = new LLImageRaw(data, width, height, getComponents()); + return TRUE; +} + +void LLImageDXT::releaseDecodedData() +{ + // nothing to do +} + +BOOL LLImageDXT::encode(const LLImageRaw* raw_image, F32 time, bool explicit_mips) +{ + llassert_always(raw_image); + + S32 ncomponents = raw_image->getComponents(); + EFileFormat format; + switch (ncomponents) + { + case 1: + format = FORMAT_A8; + break; + case 3: + format = FORMAT_RGB8; + break; + case 4: + format = FORMAT_RGBA8; + break; + default: + llerrs << "LLImageDXT::encode: Unhandled channel number: " << ncomponents << llendl; + return 0; + } + + S32 width = raw_image->getWidth(); + S32 height = raw_image->getHeight(); + + if (explicit_mips) + { + height = (height/3)*2; + } + + setSize(width, height, ncomponents); + mHeaderSize = sizeof(dxtfile_header_t); + mFileFormat = format; + + S32 nmips = calcNumMips(width, height); + S32 w = width; + S32 h = height; + + S32 totbytes = mHeaderSize; + for (S32 mip=0; mip<nmips; mip++) + { + totbytes += formatBytes(format,w,h); + w >>= 1; + h >>= 1; + } + + allocateData(totbytes); + + U8* data = getData(); + dxtfile_header_t* header = (dxtfile_header_t*)data; + llassert(mHeaderSize > 0); + memset(header, 0, mHeaderSize); + header->fourcc = 0x20534444; + header->pixel_fmt.fourcc = getFourCC(format); + header->num_mips = nmips; + header->maxwidth = width; + header->maxheight = height; + + U8* prev_mipdata = 0; + w = width, h = height; + for (S32 mip=0; mip<nmips; mip++) + { + U8* mipdata = data + getMipOffset(mip); + S32 bytes = formatBytes(format, w, h); + if (mip==0) + { + memcpy(mipdata, raw_image->getData(), bytes); + } + else if (explicit_mips) + { + extractMip(raw_image->getData(), mipdata, width, height, w, h, format); + } + else + { + generateMip(prev_mipdata, mipdata, w, h, ncomponents); + } + w >>= 1; + h >>= 1; + checkMinWidthHeight(format, w, h); + prev_mipdata = mipdata; + } + + return TRUE; +} + +// virtual +BOOL LLImageDXT::encode(const LLImageRaw* raw_image, F32 time) +{ + return encode(raw_image, time, false); +} + +// virtual +bool LLImageDXT::convertToDXR() +{ + EFileFormat newformat = FORMAT_UNKNOWN; + switch (mFileFormat) + { + case FORMAT_DXR1: + case FORMAT_DXR2: + case FORMAT_DXR3: + case FORMAT_DXR4: + case FORMAT_DXR5: + return false; // nothing to do + case FORMAT_DXT1: newformat = FORMAT_DXR1; break; + case FORMAT_DXT2: newformat = FORMAT_DXR2; break; + case FORMAT_DXT3: newformat = FORMAT_DXR3; break; + 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; + return false; + } + mFileFormat = newformat; + S32 width = getWidth(), height = getHeight(); + S32 nmips = calcNumMips(width,height); + S32 total_bytes = getDataSize(); + U8* olddata = getData(); + U8* newdata = new U8[total_bytes]; + llassert(total_bytes > 0); + memset(newdata, 0, total_bytes); + memcpy(newdata, olddata, mHeaderSize); + for (S32 mip=0; mip<nmips; mip++) + { + S32 bytes = formatBytes(mFileFormat, width, height); + S32 newoffset = getMipOffset(mip); + S32 oldoffset = mHeaderSize + (total_bytes - newoffset - bytes); + memcpy(newdata + newoffset, olddata + oldoffset, bytes); + width >>= 1; + height >>= 1; + } + dxtfile_header_t* header = (dxtfile_header_t*)newdata; + header->pixel_fmt.fourcc = getFourCC(newformat); + setData(newdata, total_bytes); + return true; +} + +// virtual +S32 LLImageDXT::calcHeaderSize() +{ + return llmax(sizeof(dxtfile_header_old_t), sizeof(dxtfile_header_t)); +} + +// virtual +S32 LLImageDXT::calcDataSize(S32 discard_level) +{ + if (mFileFormat == FORMAT_UNKNOWN) + { + llerrs << "calcDataSize called with unloaded LLImageDXT" << llendl; + return 0; + } + if (discard_level < 0) + { + discard_level = mDiscardLevel; + } + S32 bytes = getMipOffset(discard_level); // size of header + previous mips + S32 w = getWidth() >> discard_level; + S32 h = getHeight() >> discard_level; + bytes += formatBytes(mFileFormat,w,h); + return bytes; +} + +//============================================================================ + +//static +void LLImageDXT::extractMip(const U8 *indata, U8* mipdata, int width, int height, + int mip_width, int mip_height, EFileFormat format) +{ + int initial_offset = formatBytes(format, width, height); + int line_width = formatBytes(format, width, 1); + int mip_line_width = formatBytes(format, mip_width, 1); + int line_offset = 0; + + for (int ww=width>>1; ww>mip_width; ww>>=1) + { + line_offset += formatBytes(format, ww, 1); + } + + for (int h=0;h<mip_height;++h) + { + int start_offset = initial_offset + line_width * h + line_offset; + memcpy(mipdata + mip_line_width*h, indata + start_offset, mip_line_width); + } +} + +//============================================================================ diff --git a/indra/llimage/llimagedxt.h b/indra/llimage/llimagedxt.h new file mode 100644 index 0000000000..88d28a2958 --- /dev/null +++ b/indra/llimage/llimagedxt.h @@ -0,0 +1,120 @@ +/** + * @file llimagedxt.h + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLIMAGEDXT_H +#define LL_LLIMAGEDXT_H + +#include "llimage.h" + +// This class decodes and encodes LL DXT files (which may unclude uncompressed RGB or RGBA mipped data) + +class LLImageDXT : public LLImageFormatted +{ +public: + enum EFileFormat + { + FORMAT_UNKNOWN = 0, + FORMAT_I8 = 1, + FORMAT_A8, + FORMAT_RGB8, + FORMAT_RGBA8, + FORMAT_DXT1, + FORMAT_DXT2, + FORMAT_DXT3, + FORMAT_DXT4, + FORMAT_DXT5, + FORMAT_DXR1, + FORMAT_DXR2, + FORMAT_DXR3, + FORMAT_DXR4, + FORMAT_DXR5, + FORMAT_NOFILE = 0xff, + }; + + struct dxtfile_header_old_t + { + S32 format; + S32 maxlevel; + S32 maxwidth; + S32 maxheight; + }; + + struct dxtfile_header_t + { + S32 fourcc; + // begin DDSURFACEDESC2 struct + S32 header_size; // size of the header + S32 flags; // flags - unused + S32 maxheight; + S32 maxwidth; + S32 image_size; // size of the compressed image + S32 depth; + S32 num_mips; + S32 reserved[11]; + struct pixel_format + { + S32 struct_size; // size of this structure + S32 flags; + S32 fourcc; + S32 bit_count; + S32 r_mask; + S32 g_mask; + S32 b_mask; + S32 a_mask; + } pixel_fmt; + S32 caps[4]; + S32 reserved2; + }; + +protected: + /*virtual*/ ~LLImageDXT(); + +public: + LLImageDXT(); + + /*virtual*/ BOOL updateData(); + + /*virtual*/ BOOL decode(LLImageRaw* raw_image, F32 time=0.0); + BOOL encode(const LLImageRaw* raw_image, F32 time, bool explicit_mips); + /*virtual*/ BOOL encode(const LLImageRaw* raw_image, F32 time=0.0); + + /*virtual*/ BOOL requestDecodedData(LLPointer<LLImageRaw>& raw, S32 discard=-1, F32 decode_time=0.0); + /*virtual*/ void releaseDecodedData(); + + /*virtual*/ S32 calcHeaderSize(); + /*virtual*/ S32 calcDataSize(S32 discard_level = 0); + + void setFormat(); + S32 getMipOffset(S32 discard); + + EFileFormat getFileFormat() { return mFileFormat; } + bool isCompressed() { return (mFileFormat >= FORMAT_DXT1 && mFileFormat <= FORMAT_DXR5); } + + bool convertToDXR(); // convert from DXT to DXR + + static void checkMinWidthHeight(EFileFormat format, S32& width, S32& height); + static S32 formatBits(EFileFormat format); + static S32 formatBytes(EFileFormat format, S32 width, S32 height); + static S32 formatOffset(EFileFormat format, S32 width, S32 height, S32 max_width, S32 max_height); + static S32 formatComponents(EFileFormat format); + + static EFileFormat getFormat(S32 fourcc); + static S32 getFourCC(EFileFormat format); + + static void calcDiscardWidthHeight(S32 discard_level, EFileFormat format, S32& width, S32& height); + static S32 calcNumMips(S32 width, S32 height); + +private: + static void extractMip(const U8 *indata, U8* mipdata, int width, int height, + int mip_width, int mip_height, EFileFormat format); + +private: + EFileFormat mFileFormat; + S32 mHeaderSize; +}; + +#endif diff --git a/indra/llimage/llimagej2c.cpp b/indra/llimage/llimagej2c.cpp new file mode 100644 index 0000000000..ad07700a37 --- /dev/null +++ b/indra/llimage/llimagej2c.cpp @@ -0,0 +1,249 @@ +/** + * @file llimagej2c.cpp + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#ifndef LL_USE_KDU +#define LL_USE_KDU 1 +#endif // LL_USE_KDU + +#include "llimagej2c.h" +#include "llmemory.h" +#if LL_USE_KDU +#include "llimagej2ckdu.h" +#endif + +#include "llimagej2coj.h" + + +LLImageJ2C::LLImageJ2C() : LLImageFormatted(IMG_CODEC_J2C), + mMaxBytes(0), + mRawDiscardLevel(-1), + mRate(0.0f) +{ +#if LL_USE_KDU + mImpl = new LLImageJ2CKDU(); +#else + mImpl = new LLImageJ2COJ(); +#endif +} + +// virtual +LLImageJ2C::~LLImageJ2C() +{ + delete mImpl; +} + +// virtual +S8 LLImageJ2C::getRawDiscardLevel() +{ + return mRawDiscardLevel; +} + +BOOL LLImageJ2C::updateData() +{ + resetLastError(); + + // Check to make sure that this instance has been initialized with data + if (!getData() || (getDataSize() < 16)) + { + setLastError("LLImageJ2C uninitialized"); + return FALSE; + } + + if (!mImpl->getMetadata(*this)) + { + return FALSE; + } + // SJB: override discard based on mMaxBytes elsewhere + S32 max_bytes = getDataSize(); // mMaxBytes ? mMaxBytes : getDataSize(); + S32 discard = calcDiscardLevelBytes(max_bytes); + setDiscardLevel(discard); + + return TRUE; +} + + +BOOL LLImageJ2C::decode(LLImageRaw *raw_imagep, F32 decode_time) +{ + return decode(raw_imagep, decode_time, 0, 4); +} + + +BOOL LLImageJ2C::decode(LLImageRaw *raw_imagep, F32 decode_time, S32 first_channel, S32 max_channel_count ) +{ + LLMemType mt1((LLMemType::EMemType)mMemType); + + resetLastError(); + + // Check to make sure that this instance has been initialized with data + if (!getData() || (getDataSize() < 16)) + { + setLastError("LLImageJ2C uninitialized"); + return FALSE; + } + + // Update the raw discard level + updateRawDiscardLevel(); + + return mImpl->decodeImpl(*this, *raw_imagep, decode_time, 0, 4); +} + + +BOOL LLImageJ2C::encode(const LLImageRaw *raw_imagep, F32 encode_time) +{ + return encode(raw_imagep, NULL, encode_time); +} + + +BOOL LLImageJ2C::encode(const LLImageRaw *raw_imagep, const char* comment_text, F32 encode_time) +{ + LLMemType mt1((LLMemType::EMemType)mMemType); + return mImpl->encodeImpl(*this, *raw_imagep, comment_text, encode_time); +} + +//static +S32 LLImageJ2C::calcHeaderSizeJ2C() +{ + return 600; //2048; // ??? hack... just needs to be >= actual header size... +} + +//static +S32 LLImageJ2C::calcDataSizeJ2C(S32 w, S32 h, S32 comp, S32 discard_level, F32 rate) +{ + if (rate <= 0.f) rate = .125f; + while (discard_level > 0) + { + if (w < 1 || h < 1) + break; + w >>= 1; + h >>= 1; + discard_level--; + } + S32 bytes = (S32)((F32)(w*h*comp)*rate); + bytes = llmax(bytes, calcHeaderSizeJ2C()); + return bytes; +} + +S32 LLImageJ2C::calcHeaderSize() +{ + return calcHeaderSizeJ2C(); +} + +S32 LLImageJ2C::calcDataSize(S32 discard_level) +{ + return calcDataSizeJ2C(getWidth(), getHeight(), getComponents(), discard_level, mRate); +} + +S32 LLImageJ2C::calcDiscardLevelBytes(S32 bytes) +{ + llassert(bytes >= 0); + S32 discard_level = 0; + if (bytes == 0) + { + return MAX_DISCARD_LEVEL; + } + while (1) + { + S32 bytes_needed = calcDataSize(discard_level); // virtual + if (bytes >= bytes_needed - (bytes_needed>>2)) // For J2c, up the res at 75% of the optimal number of bytes + { + break; + } + discard_level++; + if (discard_level >= MAX_DISCARD_LEVEL) + { + break; + } + } + return discard_level; +} + +void LLImageJ2C::setRate(F32 rate) +{ + mRate = rate; +} + +void LLImageJ2C::setMaxBytes(S32 max_bytes) +{ + mMaxBytes = max_bytes; +} +// NOT USED +// void LLImageJ2C::setReversible(const BOOL reversible) +// { +// mReversible = reversible; +// } + + +BOOL LLImageJ2C::loadAndValidate(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; + } + + U8 *data = new U8[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 || bytes_read != file_size) + { + delete[] data; + setLastError("Unable to read entire file"); + return FALSE; + } + apr_file_close(apr_file); + + return validate(data, file_size); +} + + +BOOL LLImageJ2C::validate(U8 *data, U32 file_size) +{ + LLMemType mt1((LLMemType::EMemType)mMemType); + // Taken from setData() + + BOOL res = LLImageFormatted::setData(data, file_size); + if ( !res ) + { + return FALSE; + } + + // Check to make sure that this instance has been initialized with data + if (!getData() || (0 == getDataSize())) + { + setLastError("LLImageJ2C uninitialized"); + return FALSE; + } + + return mImpl->getMetadata(*this); +} + +void LLImageJ2C::setDecodingDone(BOOL complete) +{ + mDecoding = FALSE; + mDecoded = complete; +} + +void LLImageJ2C::updateRawDiscardLevel() +{ + mRawDiscardLevel = mMaxBytes ? calcDiscardLevelBytes(mMaxBytes) : mDiscardLevel; +} + +LLImageJ2CImpl::~LLImageJ2CImpl() +{ +} diff --git a/indra/llimage/llimagej2c.h b/indra/llimage/llimagej2c.h new file mode 100644 index 0000000000..ee73612bc7 --- /dev/null +++ b/indra/llimage/llimagej2c.h @@ -0,0 +1,77 @@ +/** + * @file llimagej2c.h + * @brief Image implmenation for jpeg2000. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLIMAGEJ2C_H +#define LL_LLIMAGEJ2C_H + +#include "llimage.h" +#include "llassettype.h" + +class LLImageJ2CImpl; + +class LLImageJ2C : public LLImageFormatted +{ +protected: + virtual ~LLImageJ2C(); + +public: + LLImageJ2C(); + + // Base class overrides + /*virtual*/ BOOL updateData(); + /*virtual*/ BOOL decode(LLImageRaw *raw_imagep, F32 decode_time=0.0); + /*virtual*/ BOOL decode(LLImageRaw *raw_imagep, F32 decode_time, S32 first_channel, S32 max_channel_count); + /*virtual*/ BOOL encode(const LLImageRaw *raw_imagep, F32 encode_time=0.0); + /*virtual*/ S32 calcHeaderSize(); + /*virtual*/ S32 calcDataSize(S32 discard_level = 0); + /*virtual*/ S32 calcDiscardLevelBytes(S32 bytes); + /*virtual*/ S8 getRawDiscardLevel(); + + // Encode with comment text + BOOL encode(const LLImageRaw *raw_imagep, const char* comment_text, F32 encode_time=0.0); + + BOOL validate(U8 *data, U32 file_size); + BOOL loadAndValidate(const LLString &filename); + + // Encode accessors + void setReversible(const BOOL reversible); // Use non-lossy? + void setRate(F32 rate); + void setMaxBytes(S32 max_bytes); + S32 getMaxBytes() const { return mMaxBytes; } + + static S32 calcHeaderSizeJ2C(); + static S32 calcDataSizeJ2C(S32 w, S32 h, S32 comp, S32 discard_level, F32 rate = 0.f); + +protected: + friend class LLImageJ2CImpl; + friend class LLImageJ2COJ; + friend class LLImageJ2CKDU; + void setDecodingDone(BOOL complete = TRUE); + void updateRawDiscardLevel(); + + S32 mMaxBytes; // Maximum number of bytes of data to use... + S8 mRawDiscardLevel; + F32 mRate; + LLImageJ2CImpl *mImpl; +}; + +// Derive from this class to implement JPEG2000 decoding +class LLImageJ2CImpl +{ +protected: + virtual ~LLImageJ2CImpl(); + virtual BOOL getMetadata(LLImageJ2C &base) = 0; + virtual BOOL decodeImpl(LLImageJ2C &base, LLImageRaw &raw_image, F32 decode_time, S32 first_channel, S32 max_channel_count) = 0; + virtual BOOL encodeImpl(LLImageJ2C &base, const LLImageRaw &raw_image, const char* comment_text, F32 encode_time=0.0) = 0; + + friend class LLImageJ2C; +}; + +#define LINDEN_J2C_COMMENT_PREFIX "LL_" + +#endif diff --git a/indra/llimage/llimagejpeg.cpp b/indra/llimage/llimagejpeg.cpp new file mode 100644 index 0000000000..c75e449db5 --- /dev/null +++ b/indra/llimage/llimagejpeg.cpp @@ -0,0 +1,602 @@ +/** + * @file llimagejpeg.cpp + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" +#include "stdtypes.h" + +#include "llimagejpeg.h" + +#include "llerror.h" + +LLImageJPEG::LLImageJPEG() + : + LLImageFormatted(IMG_CODEC_JPEG), + mOutputBuffer( NULL ), + mOutputBufferSize( 0 ), + mEncodeQuality( 75 ) // on a scale from 1 to 100 +{ +} + +LLImageJPEG::~LLImageJPEG() +{ + llassert( !mOutputBuffer ); // Should already be deleted at end of encode. + delete[] mOutputBuffer; +} + +BOOL LLImageJPEG::updateData() +{ + resetLastError(); + + // Check to make sure that this instance has been initialized with data + if (!getData() || (0 == getDataSize())) + { + setLastError("Uninitialized instance of LLImageJPEG"); + return FALSE; + } + + //////////////////////////////////////// + // Step 1: allocate and initialize JPEG decompression object + + // This struct contains the JPEG decompression parameters and pointers to + // working space (which is allocated as needed by the JPEG library). + struct jpeg_decompress_struct cinfo; + cinfo.client_data = this; + + struct jpeg_error_mgr jerr; + cinfo.err = jpeg_std_error(&jerr); + + // Customize with our own callbacks + jerr.error_exit = &LLImageJPEG::errorExit; // Error exit handler: does not return to caller + jerr.emit_message = &LLImageJPEG::errorEmitMessage; // Conditionally emit a trace or warning message + jerr.output_message = &LLImageJPEG::errorOutputMessage; // Routine that actually outputs a trace or error message + + try + { + // Now we can initialize the JPEG decompression object. + jpeg_create_decompress(&cinfo); + + //////////////////////////////////////// + // Step 2: specify data source + // (Code is modified version of jpeg_stdio_src(); + if (cinfo.src == NULL) + { + cinfo.src = (struct jpeg_source_mgr *) + (*cinfo.mem->alloc_small) ((j_common_ptr) &cinfo, JPOOL_PERMANENT, + sizeof(struct jpeg_source_mgr)); + } + cinfo.src->init_source = &LLImageJPEG::decodeInitSource; + cinfo.src->fill_input_buffer = &LLImageJPEG::decodeFillInputBuffer; + cinfo.src->skip_input_data = &LLImageJPEG::decodeSkipInputData; + cinfo.src->resync_to_restart = jpeg_resync_to_restart; // For now, use default method, but we should be able to do better. + cinfo.src->term_source = &LLImageJPEG::decodeTermSource; + + cinfo.src->bytes_in_buffer = getDataSize(); + cinfo.src->next_input_byte = getData(); + + //////////////////////////////////////// + // Step 3: read file parameters with jpeg_read_header() + jpeg_read_header( &cinfo, TRUE ); + + // Data set by jpeg_read_header + setSize(cinfo.image_width, cinfo.image_height, 3); // Force to 3 components (RGB) + + /* + // More data set by jpeg_read_header + cinfo.num_components; + cinfo.jpeg_color_space; // Colorspace of image + cinfo.saw_JFIF_marker; // TRUE if a JFIF APP0 marker was seen + cinfo.JFIF_major_version; // Version information from JFIF marker + cinfo.JFIF_minor_version; // + cinfo.density_unit; // Resolution data from JFIF marker + cinfo.X_density; + cinfo.Y_density; + cinfo.saw_Adobe_marker; // TRUE if an Adobe APP14 marker was seen + cinfo.Adobe_transform; // Color transform code from Adobe marker + */ + } + catch (int) + { + jpeg_destroy_decompress(&cinfo); + + return FALSE; + } + //////////////////////////////////////// + // Step 4: Release JPEG decompression object + jpeg_destroy_decompress(&cinfo); + + return TRUE; +} + +// Initialize source --- called by jpeg_read_header +// before any data is actually read. +void LLImageJPEG::decodeInitSource( j_decompress_ptr cinfo ) +{ + // no work necessary here +} + +// Fill the input buffer --- called whenever buffer is emptied. +boolean LLImageJPEG::decodeFillInputBuffer( j_decompress_ptr cinfo ) +{ +// jpeg_source_mgr* src = cinfo->src; +// LLImageJPEG* self = (LLImageJPEG*) cinfo->client_data; + + // Should never get here, since we provide the entire buffer up front. + ERREXIT(cinfo, JERR_INPUT_EMPTY); + + return TRUE; +} + +// Skip data --- used to skip over a potentially large amount of +// uninteresting data (such as an APPn marker). +// +// Writers of suspendable-input applications must note that skip_input_data +// is not granted the right to give a suspension return. If the skip extends +// beyond the data currently in the buffer, the buffer can be marked empty so +// that the next read will cause a fill_input_buffer call that can suspend. +// Arranging for additional bytes to be discarded before reloading the input +// buffer is the application writer's problem. +void LLImageJPEG::decodeSkipInputData (j_decompress_ptr cinfo, long num_bytes) +{ + jpeg_source_mgr* src = cinfo->src; +// LLImageJPEG* self = (LLImageJPEG*) cinfo->client_data; + + src->next_input_byte += (size_t) num_bytes; + src->bytes_in_buffer -= (size_t) num_bytes; +} + +void LLImageJPEG::decodeTermSource (j_decompress_ptr cinfo) +{ + // no work necessary here +} + + +BOOL LLImageJPEG::decode(LLImageRaw* raw_image, F32 decode_time) +{ + llassert_always(raw_image); + + resetLastError(); + + // Check to make sure that this instance has been initialized with data + if (!getData() || (0 == getDataSize())) + { + setLastError("LLImageJPEG trying to decode an image with no data!"); + return FALSE; + } + + S32 row_stride = 0; + U8* raw_image_data = NULL; + + //////////////////////////////////////// + // Step 1: allocate and initialize JPEG decompression object + + // This struct contains the JPEG decompression parameters and pointers to + // working space (which is allocated as needed by the JPEG library). + struct jpeg_decompress_struct cinfo; + + struct jpeg_error_mgr jerr; + cinfo.err = jpeg_std_error(&jerr); + + // Customize with our own callbacks + jerr.error_exit = &LLImageJPEG::errorExit; // Error exit handler: does not return to caller + jerr.emit_message = &LLImageJPEG::errorEmitMessage; // Conditionally emit a trace or warning message + jerr.output_message = &LLImageJPEG::errorOutputMessage; // Routine that actually outputs a trace or error message + + + try + { + // Now we can initialize the JPEG decompression object. + jpeg_create_decompress(&cinfo); + + //////////////////////////////////////// + // Step 2: specify data source + // (Code is modified version of jpeg_stdio_src(); + if (cinfo.src == NULL) + { + cinfo.src = (struct jpeg_source_mgr *) + (*cinfo.mem->alloc_small) ((j_common_ptr) &cinfo, JPOOL_PERMANENT, + sizeof(struct jpeg_source_mgr)); + } + cinfo.src->init_source = &LLImageJPEG::decodeInitSource; + cinfo.src->fill_input_buffer = &LLImageJPEG::decodeFillInputBuffer; + cinfo.src->skip_input_data = &LLImageJPEG::decodeSkipInputData; + cinfo.src->resync_to_restart = jpeg_resync_to_restart; // For now, use default method, but we should be able to do better. + cinfo.src->term_source = &LLImageJPEG::decodeTermSource; + cinfo.src->bytes_in_buffer = getDataSize(); + cinfo.src->next_input_byte = getData(); + + //////////////////////////////////////// + // Step 3: read file parameters with jpeg_read_header() + + jpeg_read_header(&cinfo, TRUE); + + // We can ignore the return value from jpeg_read_header since + // (a) suspension is not possible with our data source, and + // (b) we passed TRUE to reject a tables-only JPEG file as an error. + // See libjpeg.doc for more info. + + setSize(cinfo.image_width, cinfo.image_height, 3); // Force to 3 components (RGB) + + raw_image->resize(getWidth(), getHeight(), getComponents()); + raw_image_data = raw_image->getData(); + + + //////////////////////////////////////// + // Step 4: set parameters for decompression + cinfo.out_color_components = 3; + cinfo.out_color_space = JCS_RGB; + + + //////////////////////////////////////// + // Step 5: Start decompressor + + jpeg_start_decompress(&cinfo); + // We can ignore the return value since suspension is not possible + // with our data source. + + // We may need to do some setup of our own at this point before reading + // the data. After jpeg_start_decompress() we have the correct scaled + // output image dimensions available, as well as the output colormap + // if we asked for color quantization. + // In this example, we need to make an output work buffer of the right size. + + // JSAMPLEs per row in output buffer + row_stride = cinfo.output_width * cinfo.output_components; + + //////////////////////////////////////// + // Step 6: while (scan lines remain to be read) + // jpeg_read_scanlines(...); + + // Here we use the library's state variable cinfo.output_scanline as the + // loop counter, so that we don't have to keep track ourselves. + + // Move pointer to last line + raw_image_data += row_stride * (cinfo.output_height - 1); + + while (cinfo.output_scanline < cinfo.output_height) + { + // jpeg_read_scanlines expects an array of pointers to scanlines. + // Here the array is only one element long, but you could ask for + // more than one scanline at a time if that's more convenient. + + jpeg_read_scanlines(&cinfo, &raw_image_data, 1); + raw_image_data -= row_stride; // move pointer up a line + } + + //////////////////////////////////////// + // Step 7: Finish decompression + jpeg_finish_decompress(&cinfo); + + //////////////////////////////////////// + // Step 8: Release JPEG decompression object + jpeg_destroy_decompress(&cinfo); + } + + catch (int) + { + jpeg_destroy_decompress(&cinfo); + return FALSE; + } + + // Check to see whether any corrupt-data warnings occurred + if( jerr.num_warnings != 0 ) + { + // TODO: extract the warning to find out what went wrong. + setLastError( "Unable to decode JPEG image."); + return FALSE; + } + + return TRUE; +} + + +// Initialize destination --- called by jpeg_start_compress before any data is actually written. +// static +void LLImageJPEG::encodeInitDestination ( j_compress_ptr cinfo ) +{ + LLImageJPEG* self = (LLImageJPEG*) cinfo->client_data; + + cinfo->dest->next_output_byte = self->mOutputBuffer; + cinfo->dest->free_in_buffer = self->mOutputBufferSize; +} + + +// Empty the output buffer --- called whenever buffer fills up. +// +// In typical applications, this should write the entire output buffer +// (ignoring the current state of next_output_byte & free_in_buffer), +// reset the pointer & count to the start of the buffer, and return TRUE +// indicating that the buffer has been dumped. +// +// In applications that need to be able to suspend compression due to output +// overrun, a FALSE return indicates that the buffer cannot be emptied now. +// In this situation, the compressor will return to its caller (possibly with +// an indication that it has not accepted all the supplied scanlines). The +// application should resume compression after it has made more room in the +// output buffer. Note that there are substantial restrictions on the use of +// suspension --- see the documentation. +// +// When suspending, the compressor will back up to a convenient restart point +// (typically the start of the current MCU). next_output_byte & free_in_buffer +// indicate where the restart point will be if the current call returns FALSE. +// Data beyond this point will be regenerated after resumption, so do not +// write it out when emptying the buffer externally. + +boolean LLImageJPEG::encodeEmptyOutputBuffer( j_compress_ptr cinfo ) +{ + LLImageJPEG* self = (LLImageJPEG*) cinfo->client_data; + + // Should very rarely happen, since our output buffer is + // as large as the input to start out with. + + // Double the buffer size; + S32 new_buffer_size = self->mOutputBufferSize * 2; + U8* new_buffer = new U8[ new_buffer_size ]; + memcpy( new_buffer, self->mOutputBuffer, self->mOutputBufferSize ); + delete[] self->mOutputBuffer; + self->mOutputBuffer = new_buffer; + + cinfo->dest->next_output_byte = self->mOutputBuffer + self->mOutputBufferSize; + cinfo->dest->free_in_buffer = self->mOutputBufferSize; + self->mOutputBufferSize = new_buffer_size; + + return TRUE; +} + +// Terminate destination --- called by jpeg_finish_compress +// after all data has been written. Usually needs to flush buffer. +// +// NB: *not* called by jpeg_abort or jpeg_destroy; surrounding +// application must deal with any cleanup that should happen even +// for error exit. +void LLImageJPEG::encodeTermDestination( j_compress_ptr cinfo ) +{ + LLImageJPEG* self = (LLImageJPEG*) cinfo->client_data; + + S32 file_bytes = (S32)(self->mOutputBufferSize - cinfo->dest->free_in_buffer); + self->allocateData(file_bytes); + + memcpy( self->getData(), self->mOutputBuffer, file_bytes ); +} + +// static +void LLImageJPEG::errorExit( j_common_ptr cinfo ) +{ + //LLImageJPEG* self = (LLImageJPEG*) cinfo->client_data; + + // Always display the message + (*cinfo->err->output_message)(cinfo); + + // Let the memory manager delete any temp files + jpeg_destroy(cinfo); + + // Return control to the setjmp point + throw 1; +} + +// Decide whether to emit a trace or warning message. +// msg_level is one of: +// -1: recoverable corrupt-data warning, may want to abort. +// 0: important advisory messages (always display to user). +// 1: first level of tracing detail. +// 2,3,...: successively more detailed tracing messages. +// An application might override this method if it wanted to abort on warnings +// or change the policy about which messages to display. +// static +void LLImageJPEG::errorEmitMessage( j_common_ptr cinfo, int msg_level ) +{ + struct jpeg_error_mgr * err = cinfo->err; + + if (msg_level < 0) + { + // It's a warning message. Since corrupt files may generate many warnings, + // the policy implemented here is to show only the first warning, + // unless trace_level >= 3. + if (err->num_warnings == 0 || err->trace_level >= 3) + { + (*err->output_message) (cinfo); + } + // Always count warnings in num_warnings. + err->num_warnings++; + } + else + { + // It's a trace message. Show it if trace_level >= msg_level. + if (err->trace_level >= msg_level) + { + (*err->output_message) (cinfo); + } + } +} + +// static +void LLImageJPEG::errorOutputMessage( j_common_ptr cinfo ) +{ + // Create the message + char buffer[JMSG_LENGTH_MAX]; + (*cinfo->err->format_message) (cinfo, buffer); + + ((LLImageJPEG*) cinfo->client_data)->setLastError( buffer ); + + BOOL is_decode = (cinfo->is_decompressor != 0); + llwarns << "LLImageJPEG " << (is_decode ? "decode " : "encode ") << " failed: " << buffer << llendl; +} + +BOOL LLImageJPEG::encode( const LLImageRaw* raw_image, F32 encode_time ) +{ + llassert_always(raw_image); + + resetLastError(); + + switch( raw_image->getComponents() ) + { + case 1: + case 3: + break; + default: + setLastError("Unable to encode a JPEG image that doesn't have 1 or 3 components."); + return FALSE; + } + + setSize(raw_image->getWidth(), raw_image->getHeight(), raw_image->getComponents()); + + // 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; + mOutputBufferSize = getWidth() * getHeight() * getComponents() + 1024; + mOutputBuffer = new U8[ mOutputBufferSize ]; + + const U8* raw_image_data = NULL; + S32 row_stride = 0; + + //////////////////////////////////////// + // Step 1: allocate and initialize JPEG compression object + + // This struct contains the JPEG compression parameters and pointers to + // working space (which is allocated as needed by the JPEG library). + struct jpeg_compress_struct cinfo; + cinfo.client_data = this; + + // We have to set up the error handler first, in case the initialization + // step fails. (Unlikely, but it could happen if you are out of memory.) + // This routine fills in the contents of struct jerr, and returns jerr's + // address which we place into the link field in cinfo. + struct jpeg_error_mgr jerr; + cinfo.err = jpeg_std_error(&jerr); + + // Customize with our own callbacks + jerr.error_exit = &LLImageJPEG::errorExit; // Error exit handler: does not return to caller + jerr.emit_message = &LLImageJPEG::errorEmitMessage; // Conditionally emit a trace or warning message + jerr.output_message = &LLImageJPEG::errorOutputMessage; // Routine that actually outputs a trace or error message + + // Establish the setjmp return context mSetjmpBuffer. Used by library to abort. + if( setjmp(mSetjmpBuffer) ) + { + // If we get here, the JPEG code has signaled an error. + // We need to clean up the JPEG object, close the input file, and return. + jpeg_destroy_compress(&cinfo); + delete[] mOutputBuffer; + mOutputBuffer = NULL; + mOutputBufferSize = 0; + return FALSE; + } + + try + { + + // Now we can initialize the JPEG compression object. + jpeg_create_compress(&cinfo); + + //////////////////////////////////////// + // Step 2: specify data destination + // (code is a modified form of jpeg_stdio_dest() ) + if( cinfo.dest == NULL) + { + cinfo.dest = (struct jpeg_destination_mgr *) + (*cinfo.mem->alloc_small) ((j_common_ptr) &cinfo, JPOOL_PERMANENT, + sizeof(struct jpeg_destination_mgr)); + } + cinfo.dest->next_output_byte = mOutputBuffer; // => next byte to write in buffer + cinfo.dest->free_in_buffer = mOutputBufferSize; // # of byte spaces remaining in buffer + cinfo.dest->init_destination = &LLImageJPEG::encodeInitDestination; + cinfo.dest->empty_output_buffer = &LLImageJPEG::encodeEmptyOutputBuffer; + cinfo.dest->term_destination = &LLImageJPEG::encodeTermDestination; + + //////////////////////////////////////// + // Step 3: set parameters for compression + // + // First we supply a description of the input image. + // Four fields of the cinfo struct must be filled in: + + cinfo.image_width = getWidth(); // image width and height, in pixels + cinfo.image_height = getHeight(); + + switch( getComponents() ) + { + case 1: + cinfo.input_components = 1; // # of color components per pixel + cinfo.in_color_space = JCS_GRAYSCALE; // colorspace of input image + break; + case 3: + cinfo.input_components = 3; // # of color components per pixel + cinfo.in_color_space = JCS_RGB; // colorspace of input image + break; + default: + setLastError("Unable to encode a JPEG image that doesn't have 1 or 3 components."); + return FALSE; + } + + // Now use the library's routine to set default compression parameters. + // (You must set at least cinfo.in_color_space before calling this, + // since the defaults depend on the source color space.) + jpeg_set_defaults(&cinfo); + + // Now you can set any non-default parameters you wish to. + jpeg_set_quality(&cinfo, mEncodeQuality, TRUE ); // limit to baseline-JPEG values + + //////////////////////////////////////// + // Step 4: Start compressor + // + // TRUE ensures that we will write a complete interchange-JPEG file. + // Pass TRUE unless you are very sure of what you're doing. + + jpeg_start_compress(&cinfo, TRUE); + + //////////////////////////////////////// + // Step 5: while (scan lines remain to be written) + // jpeg_write_scanlines(...); + + // Here we use the library's state variable cinfo.next_scanline as the + // loop counter, so that we don't have to keep track ourselves. + // To keep things simple, we pass one scanline per call; you can pass + // more if you wish, though. + + row_stride = getWidth() * getComponents(); // JSAMPLEs per row in image_buffer + + // NOTE: For compatibility with LLImage, we need to invert the rows. + raw_image_data = raw_image->getData(); + + const U8* last_row_data = raw_image_data + (getHeight()-1) * row_stride; + + JSAMPROW row_pointer[1]; // pointer to JSAMPLE row[s] + while (cinfo.next_scanline < cinfo.image_height) + { + // jpeg_write_scanlines expects an array of pointers to scanlines. + // Here the array is only one element long, but you could pass + // more than one scanline at a time if that's more convenient. + + //Ugly const uncast here (jpeg_write_scanlines should take a const* but doesn't) + //row_pointer[0] = (JSAMPROW)(raw_image_data + (cinfo.next_scanline * row_stride)); + row_pointer[0] = (JSAMPROW)(last_row_data - (cinfo.next_scanline * row_stride)); + + jpeg_write_scanlines(&cinfo, row_pointer, 1); + } + + //////////////////////////////////////// + // Step 6: Finish compression + jpeg_finish_compress(&cinfo); + + // After finish_compress, we can release the temp output buffer. + delete[] mOutputBuffer; + mOutputBuffer = NULL; + mOutputBufferSize = 0; + + //////////////////////////////////////// + // Step 7: release JPEG compression object + jpeg_destroy_compress(&cinfo); + } + + catch(int) + { + jpeg_destroy_compress(&cinfo); + delete[] mOutputBuffer; + mOutputBuffer = NULL; + mOutputBufferSize = 0; + return FALSE; + } + + return TRUE; +} diff --git a/indra/llimage/llimagejpeg.h b/indra/llimage/llimagejpeg.h new file mode 100644 index 0000000000..5684e3720f --- /dev/null +++ b/indra/llimage/llimagejpeg.h @@ -0,0 +1,63 @@ +/** + * @file llimagejpeg.h + * @brief This class compresses and decompresses JPEG files + * + * Copyright (c) 2002-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLIMAGEJPEG_H +#define LL_LLIMAGEJPEG_H + +#include <setjmp.h> + +#include "llimage.h" + +extern "C" { +#include "jpeglib/jinclude.h" +#include "jpeglib/jpeglib.h" +#include "jpeglib/jerror.h" +} + +class LLImageJPEG : public LLImageFormatted +{ +protected: + virtual ~LLImageJPEG(); + +public: + LLImageJPEG(); + + /*virtual*/ BOOL updateData(); + /*virtual*/ BOOL decode(LLImageRaw* raw_image, F32 time=0.0); + /*virtual*/ BOOL encode(const LLImageRaw* raw_image, F32 time=0.0); + + void setEncodeQuality( S32 q ) { mEncodeQuality = q; } // on a scale from 1 to 100 + S32 getEncodeQuality() { return mEncodeQuality; } + + // Callbacks registered with jpeglib + static void encodeInitDestination ( j_compress_ptr cinfo ); + static boolean encodeEmptyOutputBuffer(j_compress_ptr cinfo); + static void encodeTermDestination(j_compress_ptr cinfo); + + static void decodeInitSource(j_decompress_ptr cinfo); + static boolean decodeFillInputBuffer(j_decompress_ptr cinfo); + static void decodeSkipInputData(j_decompress_ptr cinfo, long num_bytes); + static void decodeTermSource(j_decompress_ptr cinfo); + + + static void errorExit(j_common_ptr cinfo); + static void errorEmitMessage(j_common_ptr cinfo, int msg_level); + static void errorOutputMessage(j_common_ptr cinfo); + + static BOOL decompress(LLImageJPEG* imagep); + +protected: + U8* mOutputBuffer; // temp buffer used during encoding + S32 mOutputBufferSize; // bytes in mOuputBuffer + + S32 mEncodeQuality; // on a scale from 1 to 100 + + jmp_buf mSetjmpBuffer; // To allow the library to abort. +}; + +#endif // LL_LLIMAGEJPEG_H diff --git a/indra/llimage/llimagetga.cpp b/indra/llimage/llimagetga.cpp new file mode 100644 index 0000000000..1007f8e2bb --- /dev/null +++ b/indra/llimage/llimagetga.cpp @@ -0,0 +1,1090 @@ +/** + * @file llimagetga.cpp + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include "llimagetga.h" +#include "llerror.h" +#include "llmath.h" + +// For expanding 5-bit pixel values to 8-bit with best rounding +// static +const U8 LLImageTGA::s5to8bits[32] = + { + 0, 8, 16, 25, 33, 41, 49, 58, + 66, 74, 82, 90, 99, 107, 115, 123, + 132, 140, 148, 156, 165, 173, 181, 189, + 197, 206, 214, 222, 230, 239, 247, 255 + }; + +inline void LLImageTGA::decodeTruecolorPixel15( U8* dst, const U8* src ) +{ + // We expand 5 bit data to 8 bit sample width. + // The format of the 16-bit (LSB first) input word is + // xRRRRRGGGGGBBBBB + U32 t = U32(src[0]) + (U32(src[1]) << 8); + dst[2] = s5to8bits[t & 0x1F]; // blue + t >>= 5; + dst[1] = s5to8bits[t & 0x1F]; // green + t >>= 5; + dst[0] = s5to8bits[t & 0x1F]; // red +} + +LLImageTGA::LLImageTGA() + : LLImageFormatted(IMG_CODEC_TGA), + mColorMap( NULL ), + mColorMapStart( 0 ), + mColorMapLength( 0 ), + mColorMapBytesPerEntry( 0 ), + mIs15Bit( FALSE ) +{ +} + +LLImageTGA::LLImageTGA(const LLString& file_name) + : LLImageFormatted(IMG_CODEC_TGA), + mColorMap( NULL ), + mColorMapStart( 0 ), + mColorMapLength( 0 ), + mColorMapBytesPerEntry( 0 ), + mIs15Bit( FALSE ) +{ + loadFile(file_name); +} + +LLImageTGA::~LLImageTGA() +{ + delete mColorMap; +} + +BOOL LLImageTGA::updateData() +{ + resetLastError(); + + // Check to make sure that this instance has been initialized with data + if (!getData() || (0 == getDataSize())) + { + setLastError("LLImageTGA uninitialized"); + return FALSE; + } + + // Pull image information from the header... + U8 flags; + U8 junk[256]; + + /**************************************************************************** + ** + ** For more information about the original Truevision TGA(tm) file format, + ** or for additional information about the new extensions to the + ** Truevision TGA file, refer to the "Truevision TGA File Format + ** Specification Version 2.0" available from Truevision or your + ** Truevision dealer. + ** + ** FILE STRUCTURE FOR THE ORIGINAL TRUEVISION TGA FILE + ** FIELD 1 : NUMBER OF CHARACTERS IN ID FIELD (1 BYTES) + ** 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 + ** FIELD 4 : COLOR MAP SPECIFICATION (5 BYTES) + ** 4.1 : COLOR MAP ORIGIN (2 BYTES) + ** 4.2 : COLOR MAP LENGTH (2 BYTES) + ** 4.3 : COLOR MAP ENTRY SIZE (2 BYTES) + ** FIELD 5 : IMAGE SPECIFICATION (10 BYTES) + ** 5.1 : X-ORIGIN OF IMAGE (2 BYTES) + ** 5.2 : Y-ORIGIN OF IMAGE (2 BYTES) + ** 5.3 : WIDTH OF IMAGE (2 BYTES) + ** 5.4 : HEIGHT OF IMAGE (2 BYTES) + ** 5.5 : IMAGE PIXEL SIZE (1 BYTE) + ** 5.6 : IMAGE DESCRIPTOR BYTE (1 BYTE) + ** FIELD 6 : IMAGE ID FIELD (LENGTH SPECIFIED BY FIELD 1) + ** FIELD 7 : COLOR MAP DATA (BIT WIDTH SPECIFIED BY FIELD 4.3 AND + ** NUMBER OF COLOR MAP ENTRIES SPECIFIED IN FIELD 4.2) + ** FIELD 8 : IMAGE DATA FIELD (WIDTH AND HEIGHT SPECIFIED IN + ** FIELD 5.3 AND 5.4) + ****************************************************************************/ + + mDataOffset = 0; + mIDLength = *(getData()+mDataOffset++); + mColorMapType = *(getData()+mDataOffset++); + mImageType = *(getData()+mDataOffset++); + mColorMapIndexLo = *(getData()+mDataOffset++); + mColorMapIndexHi = *(getData()+mDataOffset++); + mColorMapLengthLo = *(getData()+mDataOffset++); + mColorMapLengthHi = *(getData()+mDataOffset++); + mColorMapDepth = *(getData()+mDataOffset++); + mXOffsetLo = *(getData()+mDataOffset++); + mXOffsetHi = *(getData()+mDataOffset++); + mYOffsetLo = *(getData()+mDataOffset++); + mYOffsetHi = *(getData()+mDataOffset++); + mWidthLo = *(getData()+mDataOffset++); + mWidthHi = *(getData()+mDataOffset++); + mHeightLo = *(getData()+mDataOffset++); + mHeightHi = *(getData()+mDataOffset++); + mPixelSize = *(getData()+mDataOffset++); + flags = *(getData()+mDataOffset++); + mAttributeBits = flags & 0xf; + mOriginRightBit = (flags & 0x10) >> 4; + mOriginTopBit = (flags & 0x20) >> 5; + mInterleave = (flags & 0xc0) >> 6; + + switch( mImageType ) + { + case 0: + // No image data included in file + setLastError("Unable to load file. TGA file contains no image data."); + return FALSE; + case 1: + // Colormapped uncompressed + if( 8 != mPixelSize ) + { + setLastError("Unable to load file. Colormapped images must have 8 bits per pixel."); + return FALSE; + } + break; + case 2: + // Truecolor uncompressed + break; + case 3: + // Monochrome uncompressed + if( 8 != mPixelSize ) + { + setLastError("Unable to load file. Monochrome images must have 8 bits per pixel."); + return FALSE; + } + break; + case 9: + // Colormapped, RLE + break; + case 10: + // Truecolor, RLE + break; + case 11: + // Monochrome, RLE + if( 8 != mPixelSize ) + { + setLastError("Unable to load file. Monochrome images must have 8 bits per pixel."); + return FALSE; + } + break; + default: + setLastError("Unable to load file. Unrecoginzed TGA image type."); + return FALSE; + } + + // discard the ID field, if any + if (mIDLength) + { + memcpy(junk, getData()+mDataOffset, mIDLength); + mDataOffset += mIDLength; + } + + // check to see if there's a colormap since even rgb files can have them + S32 color_map_bytes = 0; + if( (1 == mColorMapType) && (mColorMapDepth > 0) ) + { + mColorMapStart = (S32(mColorMapIndexHi) << 8) + mColorMapIndexLo; + mColorMapLength = (S32(mColorMapLengthHi) << 8) + mColorMapLengthLo; + + if( mColorMapDepth > 24 ) + { + mColorMapBytesPerEntry = 4; + } + else + if( mColorMapDepth > 16 ) + { + mColorMapBytesPerEntry = 3; + } + else + if( mColorMapDepth > 8 ) + { + mColorMapBytesPerEntry = 2; + } + else + { + mColorMapBytesPerEntry = 1; + } + color_map_bytes = mColorMapLength * mColorMapBytesPerEntry; + + // Note: although it's legal for TGA files to have color maps and not use them + // (some programs actually do this and use the color map for other ends), we'll + // only allocate memory for one if _we_ intend to use it. + if ( (1 == mImageType) || (9 == mImageType) ) + { + mColorMap = new U8[ color_map_bytes ]; + memcpy( mColorMap, getData() + mDataOffset, color_map_bytes ); + } + + mDataOffset += color_map_bytes; + } + + // heights are read as bytes to prevent endian problems + S32 height = (S32(mHeightHi) << 8) + mHeightLo; + S32 width = (S32(mWidthHi) << 8) + mWidthLo; + + // make sure that it's a pixel format that we understand + S32 bits_per_pixel; + if( mColorMap ) + { + bits_per_pixel = mColorMapDepth; + } + else + { + bits_per_pixel = mPixelSize; + } + + S32 components; + switch(bits_per_pixel) + { + case 24: + components = 3; + break; + case 32: + components = 4; +// Don't enforce this. ACDSee doesn't bother to set the attributes bits correctly. Arrgh! +// if( mAttributeBits != 8 ) +// { +// setLastError("Unable to load file. 32 bit TGA image does not have 8 bits of alpha."); +// return FALSE; +// } + mAttributeBits = 8; + break; + case 15: + case 16: + components = 3; + mIs15Bit = TRUE; // 16th bit is used for Targa hardware interupts and is ignored. + break; + case 8: + components = 1; + break; + default: + setLastError("Unable to load file. Unknown pixel size."); + return FALSE; + } + setSize(width, height, components); + + return TRUE; +} + +BOOL LLImageTGA::decode(LLImageRaw* raw_image, F32 decode_time) +{ + llassert_always(raw_image); + + // Check to make sure that this instance has been initialized with data + if (!getData() || (0 == getDataSize())) + { + setLastError("LLImageTGA trying to decode an image with no data!"); + return FALSE; + } + + // Copy everything after the header. + + raw_image->resize(getWidth(), getHeight(), getComponents()); + + if( (getComponents() != 1) && + (getComponents() != 3) && + (getComponents() != 4) ) + { + setLastError("TGA images with a number of components other than 1, 3, and 4 are not supported."); + return FALSE; + } + + + if( mOriginRightBit ) + { + setLastError("TGA images with origin on right side are not supported."); + return FALSE; + } + + BOOL flipped = (mOriginTopBit != 0); + BOOL rle_compressed = ((mImageType & 0x08) != 0); + + if( mColorMap ) + { + return decodeColorMap( raw_image, rle_compressed, flipped ); + } + else + { + return decodeTruecolor( raw_image, rle_compressed, flipped ); + } +} + +BOOL LLImageTGA::decodeTruecolor( LLImageRaw* raw_image, BOOL rle, BOOL flipped ) +{ + BOOL success = FALSE; + BOOL alpha_opaque = FALSE; + if( rle ) + { + + switch( getComponents() ) + { + case 1: + success = decodeTruecolorRle8( raw_image ); + break; + case 3: + if( mIs15Bit ) + { + success = decodeTruecolorRle15( raw_image ); + } + else + { + success = decodeTruecolorRle24( raw_image ); + } + break; + case 4: + success = decodeTruecolorRle32( raw_image, alpha_opaque ); + if (alpha_opaque) + { + // alpha was entirely opaque + // convert to 24 bit image + LLPointer<LLImageRaw> compacted_image = new LLImageRaw(raw_image->getWidth(), raw_image->getHeight(), 3); + compacted_image->copy(raw_image); + raw_image->resize(raw_image->getWidth(), raw_image->getHeight(), 3); + raw_image->copy(compacted_image); + } + break; + } + } + else + { + BOOL alpha_opaque; + success = decodeTruecolorNonRle( raw_image, alpha_opaque ); + if (alpha_opaque && raw_image->getComponents() == 4) + { + // alpha was entirely opaque + // convert to 24 bit image + LLPointer<LLImageRaw> compacted_image = new LLImageRaw(raw_image->getWidth(), raw_image->getHeight(), 3); + compacted_image->copy(raw_image); + raw_image->resize(raw_image->getWidth(), raw_image->getHeight(), 3); + raw_image->copy(compacted_image); + } + } + + if( success && flipped ) + { + // This works because the Targa definition requires that RLE blocks never + // encode pixels from more than one scanline. + // (On the other hand, it's not as fast as writing separate flipped versions as + // we did with TruecolorNonRle.) + raw_image->verticalFlip(); + } + + return success; +} + + +BOOL LLImageTGA::decodeTruecolorNonRle( LLImageRaw* raw_image, BOOL &alpha_opaque ) +{ + alpha_opaque = TRUE; + + // Origin is the bottom left + U8* dst = raw_image->getData(); + U8* src = getData() + mDataOffset; + S32 pixels = getWidth() * getHeight(); + + if (getComponents() == 4) + { + while( pixels-- ) + { + // Our data is stored in RGBA. TGA stores them as BGRA (little-endian ARGB) + dst[0] = src[2]; // Red + dst[1] = src[1]; // Green + dst[2] = src[0]; // Blue + dst[3] = src[3]; // Alpha + if (dst[3] != 255) + { + alpha_opaque = FALSE; + } + dst += 4; + src += 4; + } + } + else if (getComponents() == 3) + { + if( mIs15Bit ) + { + while( pixels-- ) + { + decodeTruecolorPixel15( dst, src ); + dst += 3; + src += 2; + } + } + else + { + while( pixels-- ) + { + dst[0] = src[2]; // Red + dst[1] = src[1]; // Green + dst[2] = src[0]; // Blue + dst += 3; + src += 3; + } + } + } + else if (getComponents() == 1) + { + memcpy(dst, src, pixels); + } + + return TRUE; +} + +void LLImageTGA::decodeColorMapPixel8( U8* dst, const U8* src ) +{ + S32 index = llclamp( *src - mColorMapStart, 0, mColorMapLength - 1 ); + dst[0] = mColorMap[ index ]; +} + +void LLImageTGA::decodeColorMapPixel15( U8* dst, const U8* src ) +{ + S32 index = llclamp( *src - mColorMapStart, 0, mColorMapLength - 1 ); + decodeTruecolorPixel15( dst, mColorMap + 2 * index ); +} + +void LLImageTGA::decodeColorMapPixel24( U8* dst, const U8* src ) +{ + S32 index = 3 * llclamp( *src - mColorMapStart, 0, mColorMapLength - 1 ); + dst[0] = mColorMap[ index + 2 ]; // Red + dst[1] = mColorMap[ index + 1 ]; // Green + dst[2] = mColorMap[ index + 0 ]; // Blue +} + +void LLImageTGA::decodeColorMapPixel32( U8* dst, const U8* src ) +{ + S32 index = 4 * llclamp( *src - mColorMapStart, 0, mColorMapLength - 1 ); + dst[0] = mColorMap[ index + 2 ]; // Red + dst[1] = mColorMap[ index + 1 ]; // Green + dst[2] = mColorMap[ index + 0 ]; // Blue + dst[3] = mColorMap[ index + 3 ]; // Alpha +} + + +BOOL LLImageTGA::decodeColorMap( LLImageRaw* raw_image, BOOL rle, BOOL flipped ) +{ + // If flipped, origin is the top left. Need to reverse the order of the rows. + // Otherwise the origin is the bottom left. + + if( 8 != mPixelSize ) + { + return FALSE; + } + + U8* src = getData() + mDataOffset; + U8* dst = raw_image->getData(); // start from the top + + void (LLImageTGA::*pixel_decoder)( U8*, const U8* ); + + switch( mColorMapBytesPerEntry ) + { + case 1: pixel_decoder = &LLImageTGA::decodeColorMapPixel8; break; + case 2: pixel_decoder = &LLImageTGA::decodeColorMapPixel15; break; + case 3: pixel_decoder = &LLImageTGA::decodeColorMapPixel24; break; + case 4: pixel_decoder = &LLImageTGA::decodeColorMapPixel32; break; + default: llassert(0); return FALSE; + } + + if( rle ) + { + U8* last_dst = dst + getComponents() * (getHeight() * getWidth() - 1); + while( dst <= last_dst ) + { + // Read RLE block header + U8 block_header_byte = *src; + src++; + + U8 block_pixel_count = (block_header_byte & 0x7F) + 1; + if( block_header_byte & 0x80 ) + { + // Encoded (duplicate-pixel) block + do + { + (this->*pixel_decoder)( dst, src ); + dst += getComponents(); + block_pixel_count--; + } + while( block_pixel_count > 0 ); + src++; + } + else + { + // Unencoded block + do + { + (this->*pixel_decoder)( dst, src ); + dst += getComponents(); + src++; + block_pixel_count--; + } + while( block_pixel_count > 0 ); + } + } + + raw_image->verticalFlip(); + } + else + { + S32 src_row_bytes = getWidth(); + S32 dst_row_bytes = getWidth() * getComponents(); + + if( flipped ) + { + U8* src_last_row_start = src + (getHeight() - 1) * src_row_bytes; + src = src_last_row_start; // start from the bottom + src_row_bytes *= -1; + } + + + S32 i; + S32 j; + + for( S32 row = 0; row < getHeight(); row++ ) + { + for( i = 0, j = 0; j < getWidth(); i += getComponents(), j++ ) + { + (this->*pixel_decoder)( dst + i, src + j ); + } + + dst += dst_row_bytes; + src += src_row_bytes; + } + } + + return TRUE; +} + + + +BOOL LLImageTGA::encode(const LLImageRaw* raw_image, F32 encode_time) +{ + llassert_always(raw_image); + + deleteData(); + + setSize(raw_image->getWidth(), raw_image->getHeight(), raw_image->getComponents()); + + // Data from header + mIDLength = 0; // Length of identifier string + mColorMapType = 0; // 0 = No Map + + // Supported: 2 = Uncompressed true color, 3 = uncompressed monochrome without colormap + switch( getComponents() ) + { + case 1: + mImageType = 3; + break; + case 2: // Interpret as intensity plus alpha + case 3: + case 4: + mImageType = 2; + break; + default: + return FALSE; + } + + // Color map stuff (unsupported) + mColorMapIndexLo = 0; // First color map entry (low order byte) + mColorMapIndexHi = 0; // First color map entry (high order byte) + mColorMapLengthLo = 0; // Color map length (low order byte) + mColorMapLengthHi = 0; // Color map length (high order byte) + mColorMapDepth = 0; // Size of color map entry (15, 16, 24, or 32 bits) + + // Image offset relative to origin. + mXOffsetLo = 0; // X offset from origin (low order byte) + mXOffsetHi = 0; // X offset from origin (hi order byte) + mYOffsetLo = 0; // Y offset from origin (low order byte) + mYOffsetHi = 0; // Y offset from origin (hi order byte) + + // Height and width + mWidthLo = U8(getWidth() & 0xFF); // Width (low order byte) + mWidthHi = U8((getWidth() >> 8) & 0xFF); // Width (hi order byte) + mHeightLo = U8(getHeight() & 0xFF); // Height (low order byte) + mHeightHi = U8((getHeight() >> 8) & 0xFF); // Height (hi order byte) + + S32 bytes_per_pixel; + switch( getComponents() ) + { + case 1: + bytes_per_pixel = 1; + break; + case 3: + bytes_per_pixel = 3; + break; + case 2: // Interpret as intensity plus alpha. Store as RGBA. + case 4: + bytes_per_pixel = 4; + break; + default: + return FALSE; + } + mPixelSize = U8(bytes_per_pixel * 8); // 8, 16, 24, 32 bits per pixel + + mAttributeBits = (4 == bytes_per_pixel) ? 8 : 0; // 4 bits: number of attribute bits (alpha) per pixel + mOriginRightBit = 0; // 1 bit: origin, 0 = left, 1 = right + mOriginTopBit = 0; // 1 bit: origin, 0 = bottom, 1 = top + mInterleave = 0; // 2 bits: interleaved flag, 0 = none, 1 = interleaved 2, 2 = interleaved 4 + + + const S32 TGA_HEADER_SIZE = 18; + const S32 COLOR_MAP_SIZE = 0; + mDataOffset = TGA_HEADER_SIZE + mIDLength + COLOR_MAP_SIZE; // Offset from start of data to the actual header. + + S32 pixels = getWidth() * getHeight(); + S32 datasize = mDataOffset + bytes_per_pixel * pixels; + U8* dst = allocateData(datasize); + + // Write header + *(dst++) = mIDLength; + *(dst++) = mColorMapType; + *(dst++) = mImageType; + *(dst++) = mColorMapIndexLo; + *(dst++) = mColorMapIndexHi; + *(dst++) = mColorMapLengthLo; + *(dst++) = mColorMapLengthHi; + *(dst++) = mColorMapDepth; + *(dst++) = mXOffsetLo; + *(dst++) = mXOffsetHi; + *(dst++) = mYOffsetLo; + *(dst++) = mYOffsetHi; + *(dst++) = mWidthLo; + *(dst++) = mWidthHi; + *(dst++) = mHeightLo; + *(dst++) = mHeightHi; + *(dst++) = mPixelSize; + *(dst++) = + ((mInterleave & 3) << 5) | + ((mOriginTopBit & 1) << 4) | + ((mOriginRightBit & 1) << 3) | + ((mAttributeBits & 0xF) << 0); + + // Write pixels + const U8* src = raw_image->getData(); + llassert( dst == getData() + mDataOffset ); + S32 i = 0; + S32 j = 0; + switch( getComponents() ) + { + case 1: + memcpy( dst, src, bytes_per_pixel * pixels ); + break; + + case 2: + while( pixels-- ) + { + dst[i + 0] = src[j + 0]; // intensity + dst[i + 1] = src[j + 0]; // intensity + dst[i + 2] = src[j + 0]; // intensity + dst[i + 3] = src[j + 1]; // alpha + i += 4; + j += 2; + } + break; + + case 3: + while( pixels-- ) + { + dst[i + 0] = src[i + 2]; // blue + dst[i + 1] = src[i + 1]; // green + dst[i + 2] = src[i + 0]; // red + i += 3; + } + break; + + case 4: + while( pixels-- ) + { + dst[i + 0] = src[i + 2]; // blue + dst[i + 1] = src[i + 1]; // green + dst[i + 2] = src[i + 0]; // red + dst[i + 3] = src[i + 3]; // alpha + i += 4; + } + break; + } + + return TRUE; +} + +BOOL LLImageTGA::decodeTruecolorRle32( LLImageRaw* raw_image, BOOL &alpha_opaque ) +{ + llassert( getComponents() == 4 ); + alpha_opaque = TRUE; + + U8* dst = raw_image->getData(); + U32* dst_pixels = (U32*) dst; + + U8* src = getData() + mDataOffset; + + U32 rgba; + U8* rgba_byte_p = (U8*) &rgba; + + U32* last_dst_pixel = dst_pixels + getHeight() * getWidth() - 1; + while( dst_pixels <= last_dst_pixel ) + { + // Read RLE block header + U8 block_header_byte = *src; + src++; + + U32 block_pixel_count = (block_header_byte & 0x7F) + 1; + if( block_header_byte & 0x80 ) + { + // Encoded (duplicate-pixel) block + rgba_byte_p[0] = src[2]; + rgba_byte_p[1] = src[1]; + rgba_byte_p[2] = src[0]; + rgba_byte_p[3] = src[3]; + if (rgba_byte_p[3] != 255) + { + alpha_opaque = FALSE; + } + + src += 4; + register U32 value = rgba; + do + { + *dst_pixels = value; + dst_pixels++; + block_pixel_count--; + } + while( block_pixel_count > 0 ); + } + else + { + // Unencoded block + do + { + ((U8*)dst_pixels)[0] = src[2]; + ((U8*)dst_pixels)[1] = src[1]; + ((U8*)dst_pixels)[2] = src[0]; + ((U8*)dst_pixels)[3] = src[3]; + if (src[3] != 255) + { + alpha_opaque = FALSE; + } + src += 4; + dst_pixels++; + block_pixel_count--; + } + while( block_pixel_count > 0 ); + } + } + + return TRUE; +} + +BOOL LLImageTGA::decodeTruecolorRle15( LLImageRaw* raw_image ) +{ + llassert( getComponents() == 3 ); + llassert( mIs15Bit ); + + U8* dst = raw_image->getData(); + U8* src = getData() + mDataOffset; + + U8* last_dst = dst + getComponents() * (getHeight() * getWidth() - 1); + while( dst <= last_dst ) + { + // Read RLE block header + U8 block_header_byte = *src; + src++; + + U8 block_pixel_count = (block_header_byte & 0x7F) + 1; + if( block_header_byte & 0x80 ) + { + // Encoded (duplicate-pixel) block + do + { + decodeTruecolorPixel15( dst, src ); // slow + dst += 3; + block_pixel_count--; + } + while( block_pixel_count > 0 ); + src += 2; + } + else + { + // Unencoded block + do + { + decodeTruecolorPixel15( dst, src ); + dst += 3; + src += 2; + block_pixel_count--; + } + while( block_pixel_count > 0 ); + } + } + + return TRUE; +} + + + +BOOL LLImageTGA::decodeTruecolorRle24( LLImageRaw* raw_image ) +{ + llassert( getComponents() == 3 ); + + U8* dst = raw_image->getData(); + U8* src = getData() + mDataOffset; + + U8* last_dst = dst + getComponents() * (getHeight() * getWidth() - 1); + while( dst <= last_dst ) + { + // Read RLE block header + U8 block_header_byte = *src; + src++; + + U8 block_pixel_count = (block_header_byte & 0x7F) + 1; + if( block_header_byte & 0x80 ) + { + // Encoded (duplicate-pixel) block + do + { + dst[0] = src[2]; + dst[1] = src[1]; + dst[2] = src[0]; + dst += 3; + block_pixel_count--; + } + while( block_pixel_count > 0 ); + src += 3; + } + else + { + // Unencoded block + do + { + dst[0] = src[2]; + dst[1] = src[1]; + dst[2] = src[0]; + dst += 3; + src += 3; + block_pixel_count--; + } + while( block_pixel_count > 0 ); + } + } + + return TRUE; +} + + +BOOL LLImageTGA::decodeTruecolorRle8( LLImageRaw* raw_image ) +{ + llassert( getComponents() == 1 ); + + U8* dst = raw_image->getData(); + U8* src = getData() + mDataOffset; + + U8* last_dst = dst + getHeight() * getWidth() - 1; + while( dst <= last_dst ) + { + // Read RLE block header + U8 block_header_byte = *src; + src++; + + U8 block_pixel_count = (block_header_byte & 0x7F) + 1; + if( block_header_byte & 0x80 ) + { + // Encoded (duplicate-pixel) block + memset( dst, *src, block_pixel_count ); + dst += block_pixel_count; + src++; + } + else + { + // Unencoded block + do + { + *dst = *src; + dst++; + src++; + block_pixel_count--; + } + while( block_pixel_count > 0 ); + } + } + + return TRUE; +} + + +// Decoded and process the image for use in avatar gradient masks. +// Processing happens during the decode for speed. +BOOL LLImageTGA::decodeAndProcess( LLImageRaw* raw_image, F32 domain, F32 weight ) +{ + llassert_always(raw_image); + + // "Domain" isn't really the right word. It refers to the width of the + // ramp portion of the function that relates input and output pixel values. + // A domain of 0 gives a step function. + // + // | /---------------- + // O| / | + // u| / | + // t| / | + // p|------------------/ | + // u| | | + // t|<---------------->|<-->| + // | "offset" "domain" + // | + // --+---Input-------------------------------- + // | + + if (!getData() || (0 == getDataSize())) + { + setLastError("LLImageTGA trying to decode an image with no data!"); + return FALSE; + } + + // 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; + return FALSE; + } + + raw_image->resize(getWidth(), getHeight(), getComponents()); + + U8* dst = raw_image->getData(); + U8* src = getData() + mDataOffset; + U8* last_dst = dst + getHeight() * getWidth() - 1; + + if( domain > 0 ) + { + // Process using a look-up table (lut) + const S32 LUT_LEN = 256; + U8 lut[LUT_LEN]; + S32 i; + + F32 scale = 1.f / domain; + F32 offset = (1.f - domain) * llclampf( 1.f - weight ); + F32 bias = -(scale * offset); + + for( i = 0; i < LUT_LEN; i++ ) + { + lut[i] = (U8)llclampb( 255.f * ( i/255.f * scale + bias ) ); + } + + while( dst <= last_dst ) + { + // Read RLE block header + U8 block_header_byte = *src; + src++; + + U8 block_pixel_count = (block_header_byte & 0x7F) + 1; + if( block_header_byte & 0x80 ) + { + // Encoded (duplicate-pixel) block + memset( dst, lut[ *src ], block_pixel_count ); + dst += block_pixel_count; + src++; + } + else + { + // Unencoded block + do + { + *dst = lut[ *src ]; + dst++; + src++; + block_pixel_count--; + } + while( block_pixel_count > 0 ); + } + } + } + else + { + // Process using a simple comparison agains a threshold + const U8 threshold = (U8)(0xFF * llclampf( 1.f - weight )); + + while( dst <= last_dst ) + { + // Read RLE block header + U8 block_header_byte = *src; + src++; + + U8 block_pixel_count = (block_header_byte & 0x7F) + 1; + if( block_header_byte & 0x80 ) + { + // Encoded (duplicate-pixel) block + memset( dst, ((*src >= threshold) ? 0xFF : 0), block_pixel_count ); + dst += block_pixel_count; + src++; + } + else + { + // Unencoded block + do + { + *dst = (*src >= threshold) ? 0xFF : 0; + dst++; + src++; + block_pixel_count--; + } + while( block_pixel_count > 0 ); + } + } + } + return TRUE; +} + +// Reads a .tga file and creates an LLImageTGA with its data. +bool LLImageTGA::loadFile( const LLString& path ) +{ + S32 len = path.size(); + if( len < 5 ) + { + return false; + } + + LLString extension = path.substr( len - 4, 4 ); + LLString::toLower(extension); + if( ".tga" != extension ) + { + return false; + } + + FILE *file = LLFile::fopen(path.c_str(), "rb"); + if( !file ) + { + llwarns << "Couldn't open file " << path << llendl; + return false; + } + + S32 file_size = 0; + if (!fseek(file, 0, SEEK_END)) + { + file_size = ftell(file); + fseek(file, 0, SEEK_SET); + } + + U8* buffer = allocateData(file_size); + S32 bytes_read = fread(buffer, 1, file_size, file); + if( bytes_read != file_size ) + { + deleteData(); + llwarns << "Couldn't read file " << path << llendl; + return false; + } + + fclose( file ); + + if( !updateData() ) + { + llwarns << "Couldn't decode file " << path << llendl; + deleteData(); + return false; + } + return true; +} + + diff --git a/indra/llimage/llimagetga.h b/indra/llimage/llimagetga.h new file mode 100644 index 0000000000..376d6dc269 --- /dev/null +++ b/indra/llimage/llimagetga.h @@ -0,0 +1,89 @@ +/** + * @file llimagetga.h + * @brief Image implementation to compresses and decompressed TGA files. + * + * Copyright (c) 2001-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLIMAGETGA_H +#define LL_LLIMAGETGA_H + +#include "llimage.h" + +// This class compresses and decompressed TGA (targa) files + +class LLImageTGA : public LLImageFormatted +{ +protected: + virtual ~LLImageTGA(); + +public: + LLImageTGA(); + LLImageTGA(const LLString& file_name); + + /*virtual*/ BOOL updateData(); + /*virtual*/ BOOL decode(LLImageRaw* raw_image, F32 decode_time=0.0); + /*virtual*/ BOOL encode(const LLImageRaw* raw_image, F32 encode_time=0.0); + + BOOL decodeAndProcess(LLImageRaw* raw_image, F32 domain, F32 weight); + +private: + BOOL decodeTruecolor( LLImageRaw* raw_image, BOOL rle, BOOL flipped ); + + BOOL decodeTruecolorRle8( LLImageRaw* raw_image ); + BOOL decodeTruecolorRle15( LLImageRaw* raw_image ); + BOOL decodeTruecolorRle24( LLImageRaw* raw_image ); + BOOL decodeTruecolorRle32( LLImageRaw* raw_image, BOOL &alpha_opaque ); + + void decodeTruecolorPixel15( U8* dst, const U8* src ); + + BOOL decodeTruecolorNonRle( LLImageRaw* raw_image, BOOL &alpha_opaque ); + + BOOL decodeColorMap( LLImageRaw* raw_image, BOOL rle, BOOL flipped ); + + void decodeColorMapPixel8(U8* dst, const U8* src); + void decodeColorMapPixel15(U8* dst, const U8* src); + void decodeColorMapPixel24(U8* dst, const U8* src); + void decodeColorMapPixel32(U8* dst, const U8* src); + + bool loadFile(const LLString& file_name); + +private: + // Class specific data + U32 mDataOffset; // Offset from start of data to the actual header. + + // Data from header + U8 mIDLength; // Length of identifier string + U8 mColorMapType; // 0 = No Map + U8 mImageType; // Supported: 2 = Uncompressed true color, 3 = uncompressed monochrome without colormap + U8 mColorMapIndexLo; // First color map entry (low order byte) + U8 mColorMapIndexHi; // First color map entry (high order byte) + U8 mColorMapLengthLo; // Color map length (low order byte) + U8 mColorMapLengthHi; // Color map length (high order byte) + U8 mColorMapDepth; // Size of color map entry (15, 16, 24, or 32 bits) + U8 mXOffsetLo; // X offset of image (low order byte) + U8 mXOffsetHi; // X offset of image (hi order byte) + U8 mYOffsetLo; // Y offset of image (low order byte) + U8 mYOffsetHi; // Y offset of image (hi order byte) + U8 mWidthLo; // Width (low order byte) + U8 mWidthHi; // Width (hi order byte) + U8 mHeightLo; // Height (low order byte) + U8 mHeightHi; // Height (hi order byte) + U8 mPixelSize; // 8, 16, 24, 32 bits per pixel + U8 mAttributeBits; // 4 bits: number of attributes per pixel + U8 mOriginRightBit; // 1 bit: origin, 0 = left, 1 = right + U8 mOriginTopBit; // 1 bit: origin, 0 = bottom, 1 = top + U8 mInterleave; // 2 bits: interleaved flag, 0 = none, 1 = interleaved 2, 2 = interleaved 4 + + U8* mColorMap; + S32 mColorMapStart; + S32 mColorMapLength; + S32 mColorMapBytesPerEntry; + + BOOL mIs15Bit; + + static const U8 s5to8bits[32]; +}; + +#endif diff --git a/indra/llimage/llmapimagetype.h b/indra/llimage/llmapimagetype.h new file mode 100644 index 0000000000..6b66506a28 --- /dev/null +++ b/indra/llimage/llmapimagetype.h @@ -0,0 +1,22 @@ +/** + * @file llmapimagetype.h + * + * Copyright (c) 2003-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#ifndef LL_LLMAPIMAGETYPE_H +#define LL_LLMAPIMAGETYPE_H + +typedef enum e_map_image_type +{ + MIT_TERRAIN = 0, + MIT_POPULAR = 1, + MIT_OBJECTS = 2, + MIT_OBJECTS_FOR_SALE = 3, + MIT_LAND_TO_BUY = 4, + MIT_OBJECT_NEW = 5, + MIT_EOF = 6 +} EMapImageType; + +#endif |