summaryrefslogtreecommitdiff
path: root/indra/llimage
diff options
context:
space:
mode:
authorJames Cook <james@lindenlab.com>2007-01-02 08:33:20 +0000
committerJames Cook <james@lindenlab.com>2007-01-02 08:33:20 +0000
commit420b91db29485df39fd6e724e782c449158811cb (patch)
treeb471a94563af914d3ed3edd3e856d21cb1b69945 /indra/llimage
Print done when done.
Diffstat (limited to 'indra/llimage')
-rw-r--r--indra/llimage/llimage.cpp1772
-rw-r--r--indra/llimage/llimage.h298
-rw-r--r--indra/llimage/llimagebmp.cpp624
-rw-r--r--indra/llimage/llimagebmp.h45
-rw-r--r--indra/llimage/llimagedxt.cpp475
-rw-r--r--indra/llimage/llimagedxt.h120
-rw-r--r--indra/llimage/llimagej2c.cpp249
-rw-r--r--indra/llimage/llimagej2c.h77
-rw-r--r--indra/llimage/llimagejpeg.cpp602
-rw-r--r--indra/llimage/llimagejpeg.h63
-rw-r--r--indra/llimage/llimagetga.cpp1090
-rw-r--r--indra/llimage/llimagetga.h89
-rw-r--r--indra/llimage/llmapimagetype.h22
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