Apertura del repositorio

This commit is contained in:
2025-05-24 18:09:39 -03:00
parent 76e6359dad
commit d883ddd0d0
35253 changed files with 2891973 additions and 2 deletions
+75
View File
@@ -0,0 +1,75 @@
# -*- cmake -*-
project(llimage)
include(00-Common)
include(LLCommon)
include(LLImage)
include(JPEG)
include(LLKDU)
include(ZLIBNG)
include(LLAddBuildTest)
include(bugsplat)
include(Tut)
set(llimage_SOURCE_FILES
llimagebmp.cpp
llimage.cpp
llimagedimensionsinfo.cpp
llimagedxt.cpp
llimagefilter.cpp
llimagej2c.cpp
llimagejpeg.cpp
llimagepng.cpp
llimagetga.cpp
llimageworker.cpp
llpngwrapper.cpp
)
set(llimage_HEADER_FILES
CMakeLists.txt
llimage.h
llimagebmp.h
llimagedimensionsinfo.h
llimagedxt.h
llimagefilter.h
llimagej2c.h
llimagejpeg.h
llimagepng.h
llimagetga.h
llimageworker.h
llmapimagetype.h
llpngwrapper.h
)
list(APPEND llimage_SOURCE_FILES ${llimage_HEADER_FILES})
add_library (llimage ${llimage_SOURCE_FILES})
target_include_directories( llimage INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
# Libraries on which this library depends, needed for Linux builds
# Sort by high-level to low-level
if (USE_KDU)
target_link_libraries(llimage llkdu)
else (USE_KDU)
target_link_libraries(llimage llimagej2coj)
endif (USE_KDU)
target_link_libraries(llimage
llfilesystem
llmath
llcommon
ll::libpng
ll::libjpeg
)
# Add tests
if (LL_TESTS)
SET(llimage_TEST_SOURCE_FILES
llimageworker.cpp
)
LL_ADD_PROJECT_UNIT_TESTS(llimage "${llimage_TEST_SOURCE_FILES}")
endif (LL_TESTS)
File diff suppressed because it is too large Load Diff
+407
View File
@@ -0,0 +1,407 @@
/**
* @file llimage.h
* @brief Object for managing images and their textures.
*
* $LicenseInfo:firstyear=2000&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#ifndef LL_LLIMAGE_H
#define LL_LLIMAGE_H
#include "lluuid.h"
#include "llstring.h"
#include "llpointer.h"
#include "lltrace.h"
constexpr S32 MIN_IMAGE_MIP = 2; // 4x4, only used for expand/contract power of 2
constexpr S32 MAX_IMAGE_MIP = 12; // 4096x4096
// *TODO : Use MAX_IMAGE_MIP as max discard level and modify j2c management so that the number
// of levels is read from the header's file, not inferred from its size.
constexpr S32 MAX_DISCARD_LEVEL = 5;
// JPEG2000 size constraints
// Those are declared here as they are germane to other image constraints used in the viewer
// and declared right here. Some come from the JPEG2000 spec, some conventions specific to SL.
constexpr S32 MAX_DECOMPOSITION_LEVELS = 32; // Number of decomposition levels cannot exceed 32 according to jpeg2000 spec
constexpr S32 MIN_DECOMPOSITION_LEVELS = 5; // the SL viewer will *crash* trying to decode images with fewer than 5 decomposition levels (unless image is small that is)
constexpr S32 MAX_PRECINCT_SIZE = 4096; // No reason to be bigger than MAX_IMAGE_SIZE
constexpr S32 MIN_PRECINCT_SIZE = 4; // Can't be smaller than MIN_BLOCK_SIZE
constexpr S32 MAX_BLOCK_SIZE = 64; // Max total block size is 4096, hence 64x64 when using square blocks
constexpr S32 MIN_BLOCK_SIZE = 4; // Min block dim is 4 according to jpeg2000 spec
constexpr S32 MIN_LAYER_SIZE = 2000; // Size of the first quality layer (after header). Must be > to FIRST_PACKET_SIZE!!
constexpr S32 MAX_NB_LAYERS = 64; // Max number of layers we'll entertain in SL (practical limit)
constexpr S32 MIN_IMAGE_SIZE = (1<<MIN_IMAGE_MIP); // 4, only used for expand/contract power of 2
constexpr S32 MAX_IMAGE_SIZE = (1<<MAX_IMAGE_MIP); // 4096
constexpr S32 MIN_IMAGE_AREA = MIN_IMAGE_SIZE * MIN_IMAGE_SIZE;
constexpr S32 MAX_IMAGE_AREA = MAX_IMAGE_SIZE * MAX_IMAGE_SIZE;
constexpr S32 MAX_IMAGE_COMPONENTS = 8;
constexpr S32 MAX_IMAGE_DATA_SIZE = MAX_IMAGE_AREA * MAX_IMAGE_COMPONENTS; //4096 * 4096 * 8 = 128 MB
// Note! These CANNOT be changed without modifying simulator code
// *TODO: change both to 1024 when SIM texture fetching is deprecated
constexpr S32 FIRST_PACKET_SIZE = 600;
constexpr S32 MAX_IMG_PACKET_SIZE = 1000;
constexpr S32 HTTP_PACKET_SIZE = 1496;
// 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;
class LLColor3;
typedef enum e_image_codec
{
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_PNG = 7,
IMG_CODEC_EOF = 8
} EImageCodec;
//============================================================================
// library initialization class
// LLImage is frequently used in threads so do not convert it to LLSingleton
class LLImage
{
public:
static void initClass(bool use_new_byte_range = false, S32 minimal_reverse_byte_range_percent = 75);
static void cleanupClass();
static const std::string& getLastThreadError();
static void setLastError(const std::string& message);
static bool useNewByteRange() { return sUseNewByteRange; }
static S32 getReverseByteRangePercent() { return sMinimalReverseByteRangePercent; }
protected:
static thread_local std::string sLastThreadErrorMessage;
static bool sUseNewByteRange;
static S32 sMinimalReverseByteRangePercent;
};
//============================================================================
// Image base class
class LLImageBase
: public LLThreadSafeRefCount
{
protected:
virtual ~LLImageBase();
virtual void deleteData();
virtual U8* allocateData(S32 size = -1);
virtual U8* reallocateData(S32 size = -1);
public:
LLImageBase();
enum
{
TYPE_NORMAL = 0,
TYPE_AVATAR_BAKE = 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 ;
U8 *getData() ;
bool isBufferInvalid() const;
void setSize(S32 width, S32 height, S32 ncomponents);
U8* allocateDataSize(S32 width, S32 height, S32 ncomponents, S32 size = -1); // setSize() + allocateData()
void enableOverSize() {mAllowOverSize = true ;}
void disableOverSize() {mAllowOverSize = false; }
protected:
// special accessor to allow direct setting of mData and mDataSize by LLImageFormatted
void setDataAndSize(U8 *data, S32 size);
public:
static void generateMip(const U8 *indata, U8* mipdata, int width, int height, S32 nchannels);
// Function for calculating the download priority for textures
// <= 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 EImageCodec getCodecFromExtension(const std::string& exten);
//static LLTrace::MemStatHandle sMemStat;
private:
U8 *mData;
S32 mDataSize;
U16 mWidth;
U16 mHeight;
S8 mComponents;
bool mBadBufferAllocation;
bool mAllowOverSize;
private:
mutable LLSharedMutex mDataMutex;
public:
template<bool SHARED>
class DataLock : public LLSharedMutexLockTemplate<SHARED>
{
public:
DataLock(const LLImageBase* image)
: LLSharedMutexLockTemplate<SHARED>(image ? &image->mDataMutex : nullptr)
{
}
};
public:
// <FS:ND> Report amount of failed buffer allocations
static void addAllocationError();
static U32 getAllocationErrors();
private:
static U32 sAllocationErrors;
// </FS:ND>
};
using LLImageDataLock = LLImageBase::DataLock<false>;
using LLImageDataSharedLock = LLImageBase::DataLock<true>;
// 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(const U8* data, U16 width, U16 height, S8 components);
LLImageRaw(U8 *data, U16 width, U16 height, S8 components, bool no_copy = false);
// Construct using createFromFile (used by tools)
//LLImageRaw(const std::string& filename, bool j2c_lowest_mip_only = false);
/*virtual*/ void deleteData();
/*virtual*/ U8* allocateData(S32 size = -1);
/*virtual*/ U8* reallocateData(S32 size);
// use in conjunction with "no_copy" constructor to release data pointer before deleting
// so that deletion of this LLImageRaw will not free the memory at the "data" parameter
// provided to "no_copy" constructor
void releaseData();
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();
// Returns true if the image is not fully opaque
bool checkHasTransparentPixels();
// if the alpha channel is all 100% opaque, delete it
// returns true if alpha channel was deleted
bool optimizeAwayAlpha();
// Create an alpha channel if this image doesn't have one
bool makeAlpha();
static S32 biasedDimToPowerOfTwo(S32 curr_dim, S32 max_dim = MAX_IMAGE_SIZE);
static S32 expandDimToPowerOfTwo(S32 curr_dim, S32 max_dim = MAX_IMAGE_SIZE);
static S32 contractDimToPowerOfTwo(S32 curr_dim, S32 min_dim = MIN_IMAGE_SIZE);
void expandToPowerOfTwo(S32 max_dim = MAX_IMAGE_SIZE, bool scale_image = true);
void contractToPowerOfTwo(S32 max_dim = MAX_IMAGE_SIZE, bool scale_image = true);
void biasedScaleToPowerOfTwo(S32 max_dim = MAX_IMAGE_SIZE);
bool scale(S32 new_width, S32 new_height, bool scale_image = true);
LLPointer<LLImageRaw> scaled(S32 new_width, S32 new_height);
// Fill the buffer with a constant color
void fill( const LLColor4U& color );
// Multiply this raw image by the given color
void tint( const LLColor3& color );
// Copy operations
//duplicate this raw image if refCount > 1.
LLPointer<LLImageRaw> duplicate();
// Src and dst can be any size. Src and dst can each have 3 or 4 components.
void copy( const LLImageRaw* src );
// Src and dst are same size. Src and dst have same number of components.
void copyUnscaled( const LLImageRaw* src );
// Src and dst are same size. Src has 4 components. Dst has 3 components.
void copyUnscaled4onto3( const LLImageRaw* src );
// Src and dst are same size. Src has 3 components. Dst has 4 components.
void copyUnscaled3onto4( const LLImageRaw* src );
// Src and dst are same size. Src has 1 component. Dst has 4 components.
// Alpha component is set to source alpha mask component.
// RGB components are set to fill color.
void copyUnscaledAlphaMask( const LLImageRaw* src, const LLColor4U& fill);
// Src and dst can be any size. Src and dst have same number of components.
void copyScaled( const LLImageRaw* src );
// Composite operations
// Src and dst can be any size. Src and dst can each have 3 or 4 components.
void composite( const LLImageRaw* src );
// Emissive operations used by minimap
// Roughly emulates GLTF emissive texture, but is not GLTF-compliant
// *TODO: Remove in favor of shader
void addEmissive(LLImageRaw* src);
void addEmissiveScaled(LLImageRaw* src);
void addEmissiveUnscaled(LLImageRaw* src);
protected:
// Src and dst can be any size. Src has 4 components. Dst has 3 components.
void compositeScaled4onto3( const LLImageRaw* src );
// Src and dst are same size. Src has 4 components. Dst has 3 components.
void compositeUnscaled4onto3( const LLImageRaw* src );
// Src and dst can be any size. Src has 3 components. Dst has 4 components.
void copyScaled3onto4( const LLImageRaw* src );
// Src and dst can be any size. Src has 4 components. Dst has 3 components.
void copyScaled4onto3( const LLImageRaw* src );
// Create an image from a local file (generally used in tools)
//bool createFromFile(const std::string& filename, bool j2c_lowest_mip_only = false);
void copyLineScaled( const U8* in, U8* out, S32 in_pixel_len, S32 out_pixel_len, S32 in_pixel_step, S32 out_pixel_step );
void compositeRowScaled4onto3( const U8* in, U8* out, S32 in_pixel_len, S32 out_pixel_len );
static U8 fastFractionalMult(U8 a, U8 b);
void setDataAndSize(U8 *data, S32 width, S32 height, S8 components) ;
public:
static S32 sRawImageCount;
// <FS:Techwolf Lupindo> texture comment metadata reader
std::string mComment;
// </FS:Techwolf Lupindo>
private:
static bool validateSrcAndDst(std::string func, const LLImageRaw* src, const LLImageRaw* dst);
};
// Compressed representation of image.
// Subclass from this class for the different representations (J2C, bmp)
class LLImageFormatted : public LLImageBase
{
public:
static LLImageFormatted* createFromType(S8 codec);
static LLImageFormatted* loadFromMemory(const U8* data, U32 size, std::string_view mimetype);
static LLImageFormatted* createFromExtension(const std::string& instring);
static LLImageFormatted* createFromMimeType(std::string_view mimetype);
static S8 getCodecFromMimeType(std::string_view mimetype);
protected:
/*virtual*/ ~LLImageFormatted();
public:
LLImageFormatted(S8 codec);
// LLImageBase
/*virtual*/ void deleteData();
/*virtual*/ U8* allocateData(S32 size = -1);
/*virtual*/ U8* reallocateData(S32 size);
/*virtual*/ void dump();
/*virtual*/ void sanityCheck();
// New methods
// subclasses must return a prefered file extension (lowercase without a leading dot)
virtual std::string getExtension() = 0;
// calcHeaderSize() returns the maximum size of header;
// 0 indicates we don't have a header and have to read the entire file
virtual S32 calcHeaderSize() { return 0; };
// 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 std::string& filename, int load_size = 0);
bool save(const std::string& filename);
virtual bool updateData() = 0; // pure virtual
void setData(U8 *data, S32 size);
void appendData(U8 *data, S32 size);
// Loads first 4 channels.
virtual bool decode(LLImageRaw* raw_image, F32 decode_time) = 0;
// Subclasses that can handle more than 4 channels should override this function.
virtual bool decodeChannels(LLImageRaw* raw_image, F32 decode_time, S32 first_channel, S32 max_channel);
virtual bool encode(const LLImageRaw* raw_image, F32 encode_time) = 0;
S8 getCodec() const;
bool isDecoding() const { return mDecoding; }
bool isDecoded() const { return mDecoded; }
void setDiscardLevel(S8 discard_level) { mDiscardLevel = discard_level; }
S8 getDiscardLevel() const { return mDiscardLevel; }
S8 getLevels() const { return mLevels; }
void setLevels(S8 nlevels) { mLevels = nlevels; }
// setLastError needs to be deferred for J2C images since it may be called from a DLL
virtual void resetLastError();
virtual void setLastError(const std::string& message, const std::string& filename = std::string());
protected:
bool copyData(U8 *data, S32 size); // calls updateData()
protected:
S8 mCodec;
S8 mDecoding;
S8 mDecoded; // unused, but changing LLImage layout requires recompiling static Mac/Linux libs. 2009-01-30 JC
S8 mDiscardLevel; // Current resolution level worked on. 0 = full res, 1 = half res, 2 = quarter res, etc...
S8 mLevels; // Number of resolution levels in that image. Min is 1. 0 means unknown.
public:
static S32 sGlobalFormattedMemory;
};
#endif
+680
View File
@@ -0,0 +1,680 @@
/**
* @file llimagebmp.cpp
*
* $LicenseInfo:firstyear=2001&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#include "linden_common.h"
#include "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();
LLImageDataLock lock(this);
// 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( /* Flawfinder: ignore */
(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 && header.mSize <= 64 )
{
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); /* Flawfinder: ignore */
}
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 ) ); /* Flawfinder: ignore */
if( 3 == header.mCompression )
{
memcpy( mBitfieldMask, mdata + FILE_HEADER_SIZE + BITMAP_HEADER_SIZE, 4 * 4); /* Flawfinder: ignore */
}
// 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(std::nothrow) U8[color_palette_size];
if (!mColorPalette)
{
LLError::LLUserWarningMsg::showOutOfMemory();
LL_WARNS() << "Out of memory in LLImageBMP::updateData(), size: " << color_palette_size << LL_ENDL;
return false;
}
memcpy( mColorPalette, mdata + FILE_HEADER_SIZE + BITMAP_HEADER_SIZE + extension_size, color_palette_size ); /* Flawfinder: ignore */
}
return true;
}
bool LLImageBMP::decode(LLImageRaw* raw_image, F32 decode_time)
{
llassert_always(raw_image);
resetLastError();
LLImageDataLock lockIn(this);
LLImageDataLock lockOut(raw_image);
// Check to make sure that this instance has been initialized with data
const U8* mdata = getData();
if (!mdata || (0 == getDataSize()))
{
setLastError("llimagebmp trying to decode an image with no data!");
return false;
}
if (!raw_image->resize(getWidth(), getHeight(), 3))
{
setLastError("llimagebmp failed to resize image!");
return false;
}
const 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, const 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, const 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;
}
if (getWidth() * getHeight() * 4 > getDataSize() - mBitmapOffset)
{ //here we have situation when data size in src less than actually needed
return false;
}
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, const 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
if ((getWidth() * getHeight()) + getHeight() * alignment_bytes > getDataSize() - mBitmapOffset)
{ //here we have situation when data size in src less than actually needed
return false;
}
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, const 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
if ((getWidth() * getHeight() * 3) + getHeight() * alignment_bytes > getDataSize() - mBitmapOffset)
{ //here we have situation when data size in src less than actually needed
return false;
}
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();
LLImageDataSharedLock lockIn(raw_image);
LLImageDataLock lockOut(this);
S32 src_components = raw_image->getComponents();
S32 dst_components = ( src_components < 3 ) ? 1 : 3;
if( (2 == src_components) || (4 == src_components) )
{
LL_INFOS() << "Dropping alpha information during BMP encoding" << LL_ENDL;
}
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.
if(!allocateData(file_bytes)) //memory allocation failed
{
return false ;
}
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); /* Flawfinder: ignore */
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;
}
+64
View File
@@ -0,0 +1,64 @@
/**
* @file llimagebmp.h
* @brief Image implementation for BMP.
*
* $LicenseInfo:firstyear=2001&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#ifndef LL_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*/ std::string getExtension() { return std::string("bmp"); }
/*virtual*/ bool updateData();
/*virtual*/ bool decode(LLImageRaw* raw_image, F32 decode_time);
/*virtual*/ bool encode(const LLImageRaw* raw_image, F32 encode_time);
protected:
bool decodeColorTable8( U8* dst, const U8* src );
bool decodeColorMask16( U8* dst, const U8* src );
bool decodeTruecolor24( U8* dst, const U8* src );
bool decodeColorMask32( U8* dst, const U8* src );
U32 countTrailingZeros( U32 m );
protected:
S32 mColorPaletteColors;
U8* mColorPalette;
S32 mBitmapOffset;
S32 mBitsPerPixel;
U32 mBitfieldMask[4]; // rgba
bool mOriginAtTop;
};
#endif
+228
View File
@@ -0,0 +1,228 @@
/**
* @file llimagedimensionsinfo.cpp
*
* $LicenseInfo:firstyear=2002&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#include "linden_common.h"
#include "stdtypes.h"
#include "llimagejpeg.h"
#include "llimagedimensionsinfo.h"
// Value is true if one of Libjpeg's functions has encountered an error while working.
static bool sJpegErrorEncountered = false;
bool LLImageDimensionsInfo::load(const std::string& src_filename,U32 codec)
{
clean();
mSrcFilename = src_filename;
S32 file_size = 0;
apr_status_t s = mInfile.open(src_filename, LL_APR_RB, NULL, &file_size);
if (s != APR_SUCCESS)
{
setLastError("Unable to open file for reading", src_filename);
return false;
}
if (file_size == 0)
{
mWarning = "texture_load_empty_file";
setLastError("File is empty",src_filename);
return false;
}
switch (codec)
{
case IMG_CODEC_BMP:
return getImageDimensionsBmp();
case IMG_CODEC_TGA:
return getImageDimensionsTga();
case IMG_CODEC_JPEG:
return getImageDimensionsJpeg();
case IMG_CODEC_PNG:
return getImageDimensionsPng();
default:
return false;
}
}
bool LLImageDimensionsInfo::getImageDimensionsBmp()
{
// Make sure the file is long enough.
const S32 DATA_LEN = 26; // BMP header (14) + DIB header size (4) + width (4) + height (4)
if (!checkFileLength(DATA_LEN))
{
LL_WARNS() << "Premature end of file" << LL_ENDL;
return false;
}
// Read BMP signature.
U8 signature[2];
mInfile.read((void*)signature, sizeof(signature)/sizeof(signature[0]));
// Make sure this is actually a BMP file.
// We only support Windows bitmaps (BM), according to LLImageBMP::updateData().
if (signature[0] != 'B' || signature[1] != 'M')
{
LL_WARNS() << "Not a BMP" << LL_ENDL;
mWarning = "texture_load_format_error";
return false;
}
// Read image dimensions.
mInfile.seek(APR_CUR, 16);
mWidth = read_reverse_s32();
mHeight = read_reverse_s32();
return true;
}
bool LLImageDimensionsInfo::getImageDimensionsTga()
{
const S32 TGA_FILE_HEADER_SIZE = 12;
// Make sure the file is long enough.
if (!checkFileLength(TGA_FILE_HEADER_SIZE + 1 /* width */ + 1 /* height */))
{
LL_WARNS() << "Premature end of file" << LL_ENDL;
return false;
}
// *TODO: Detect non-TGA files somehow.
mInfile.seek(APR_CUR,TGA_FILE_HEADER_SIZE);
mWidth = read_byte() | read_byte() << 8;
mHeight = read_byte() | read_byte() << 8;
return true;
}
bool LLImageDimensionsInfo::getImageDimensionsPng()
{
const S32 PNG_MAGIC_SIZE = 8;
// Make sure the file is long enough.
if (!checkFileLength(PNG_MAGIC_SIZE + 8 + sizeof(S32) * 2 /* width, height */))
{
LL_WARNS() << "Premature end of file" << LL_ENDL;
return false;
}
// Read PNG signature.
const U8 png_magic[PNG_MAGIC_SIZE] = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A};
U8 signature[PNG_MAGIC_SIZE];
mInfile.read((void*)signature, PNG_MAGIC_SIZE);
// Make sure it's a PNG file.
if (memcmp(signature, png_magic, PNG_MAGIC_SIZE) != 0)
{
LL_WARNS() << "Not a PNG" << LL_ENDL;
mWarning = "texture_load_format_error";
return false;
}
// Read image dimensions.
mInfile.seek(APR_CUR, 8 /* chunk length + chunk type */);
mWidth = read_s32();
mHeight = read_s32();
return true;
}
// Called instead of exit() if Libjpeg encounters an error.
void on_jpeg_error(j_common_ptr cinfo)
{
(void) cinfo;
sJpegErrorEncountered = true;
LL_WARNS() << "Libjpeg has encountered an error!" << LL_ENDL;
}
bool LLImageDimensionsInfo::getImageDimensionsJpeg()
{
sJpegErrorEncountered = false;
clean();
FILE *fp = LLFile::fopen(mSrcFilename, "rb");
if (fp == NULL)
{
setLastError("Unable to open file for reading", mSrcFilename);
return false;
}
/* Make sure this is a JPEG file. */
const size_t JPEG_MAGIC_SIZE = 2;
const U8 jpeg_magic[JPEG_MAGIC_SIZE] = {0xFF, 0xD8};
U8 signature[JPEG_MAGIC_SIZE];
if (fread(signature, sizeof(signature), 1, fp) != 1)
{
LL_WARNS() << "Premature end of file" << LL_ENDL;
fclose(fp); // <FS:Ansariel> Don't leak the file handle
return false;
}
if (memcmp(signature, jpeg_magic, JPEG_MAGIC_SIZE) != 0)
{
LL_WARNS() << "Not a JPEG" << LL_ENDL;
mWarning = "texture_load_format_error";
fclose(fp); // <FS:ND/> Don't leak the file handle
return false;
}
fseek(fp, 0, SEEK_SET); // go back to start of the file
/* Init jpeg */
jpeg_error_mgr jerr;
jpeg_decompress_struct cinfo;
cinfo.err = jpeg_std_error(&jerr);
// Call our function instead of exit() if Libjpeg encounters an error.
// This is done to avoid crash in this case (STORM-472).
cinfo.err->error_exit = on_jpeg_error;
jpeg_create_decompress (&cinfo);
jpeg_stdio_src (&cinfo, fp);
jpeg_read_header (&cinfo, true);
cinfo.out_color_space = JCS_RGB;
jpeg_start_decompress (&cinfo);
mWidth = cinfo.output_width;
mHeight = cinfo.output_height;
jpeg_destroy_decompress(&cinfo);
fclose(fp);
return !sJpegErrorEncountered;
}
bool LLImageDimensionsInfo::checkFileLength(S32 min_len)
{
// Make sure the file is not shorter than min_len bytes.
// so that we don't have to check value returned by each read() or seek().
char* buf = new char[min_len];
int nread = mInfile.read(buf, min_len);
delete[] buf;
mInfile.seek(APR_SET, 0);
return nread == min_len;
}
+145
View File
@@ -0,0 +1,145 @@
/**
* @file llimagedimentionsinfo.h
*
* $LicenseInfo:firstyear=2001&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#ifndef LL_LLIMAGEDIMENSIONSINFO_H
#define LL_LLIMAGEDIMENSIONSINFO_H
#include "llapr.h"
//-----------------------------------------------------------------------------
// LLImageDimensionsInfo
// helper class to get image dimensions WITHOUT loading image to memore
// usefull when image may be too large...
//-----------------------------------------------------------------------------
class LLImageDimensionsInfo
{
public:
LLImageDimensionsInfo():
mData(NULL)
,mHeight(0)
,mWidth(0)
{}
~LLImageDimensionsInfo()
{
clean();
}
bool load(const std::string& src_filename,U32 codec);
S32 getWidth() const { return mWidth;}
S32 getHeight() const { return mHeight;}
const std::string& getLastError()
{
return mLastError;
}
const std::string& getWarningName()
{
return mWarning;
}
protected:
void clean()
{
mInfile.close();
delete[] mData;
mData = NULL;
mWidth = 0;
mHeight = 0;
}
U8* getData()
{
return mData;
}
void setLastError(const std::string& message, const std::string& filename)
{
std::string error = message;
if (!filename.empty())
error += std::string(" FILE: ") + filename;
mLastError = error;
}
bool getImageDimensionsBmp();
bool getImageDimensionsTga();
bool getImageDimensionsPng();
bool getImageDimensionsJpeg();
S32 read_s32()
{
char p[4];
mInfile.read(&p[0],4);
S32 temp = (((S32)p[3]) & 0x000000FF) |
(((S32)p[2] << 8 ) & 0x0000FF00) |
(((S32)p[1] << 16) & 0x00FF0000) |
(((S32)p[0] << 24) & 0xFF000000);
return temp;
}
S32 read_reverse_s32()
{
char p[4];
mInfile.read(&p[0],4);
S32 temp = (((S32)p[0]) & 0x000000FF) |
(((S32)p[1] << 8 ) & 0x0000FF00) |
(((S32)p[2] << 16) & 0x00FF0000) |
(((S32)p[3] << 24) & 0xFF000000);
return temp;
}
U8 read_byte()
{
U8 bt;
mInfile.read(&bt,1);
return bt;
}
U16 read_short()
{
return read_byte() << 8 | read_byte();
}
/// Check if the file is not shorter than min_len bytes.
bool checkFileLength(S32 min_len);
protected:
LLAPRFile mInfile ;
std::string mSrcFilename;
std::string mLastError;
std::string mWarning;
U8* mData;
S32 mWidth;
S32 mHeight;
};
#endif
+524
View File
@@ -0,0 +1,524 @@
/**
* @file llimagedxt.cpp
*
* $LicenseInfo:firstyear=2001&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#include "linden_common.h"
#include "llimagedxt.h"
#include "llmemory.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:
LL_ERRS() << "LLImageDXT::Unknown format: " << format << LL_ENDL;
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:
LL_ERRS() << "LLImageDXT::Unknown format: " << format << LL_ENDL;
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();
LLImageDataLock lock(this);
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)
{
LL_ERRS() << "LLImageDXT: not enough data" << LL_ENDL;
}
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)
{
LL_ERRS() << "getMipOffset called with old (unsupported) format" << LL_ENDL;
}
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: LL_ERRS() << "LLImageDXT::setFormat called with ncomponents = " << ncomponents << LL_ENDL;
}
mHeaderSize = calcHeaderSize();
}
// virtual
bool LLImageDXT::decode(LLImageRaw* raw_image, F32 time)
{
// *TODO: Test! This has been tweaked since its intial inception,
// but we don't use it any more!
llassert_always(raw_image);
if (mFileFormat >= FORMAT_DXT1 && mFileFormat <= FORMAT_DXR5)
{
LL_WARNS() << "Attempt to decode compressed LLImageDXT to Raw (unsupported)" << LL_ENDL;
return false;
}
LLImageDataSharedLock lockIn(this);
LLImageDataLock lockOut(raw_image);
S32 width = getWidth(), height = getHeight();
S32 ncomponents = getComponents();
U8* data = NULL;
if (mDiscardLevel >= 0)
{
data = getData() + getMipOffset(mDiscardLevel);
calcDiscardWidthHeight(mDiscardLevel, mFileFormat, width, height);
}
else
{
data = getData() + getMipOffset(0);
}
S32 image_size = formatBytes(mFileFormat, width, height);
if ((!getData()) || (data + image_size > getData() + getDataSize()))
{
setLastError("LLImageDXT trying to decode an image with not enough data!");
return false;
}
if (!raw_image->resize(width, height, ncomponents))
{
setLastError("llImageDXT failed to resize image!");
return false;
}
memcpy(raw_image->getData(), data, image_size); /* Flawfinder: ignore */
return true;
}
bool LLImageDXT::getMipData(LLPointer<LLImageRaw>& raw, S32 discard)
{
if (discard < 0)
{
discard = mDiscardLevel;
}
else if (discard < mDiscardLevel)
{
LL_ERRS() << "Request for invalid discard level" << LL_ENDL;
}
LLImageDataSharedLock lock(this);
U8* data = getData() + getMipOffset(discard);
S32 width = 0;
S32 height = 0;
calcDiscardWidthHeight(discard, mFileFormat, width, height);
raw = new LLImageRaw(data, width, height, getComponents());
return true;
}
bool LLImageDXT::encodeDXT(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:
LL_ERRS() << "LLImageDXT::encode: Unhandled channel number: " << ncomponents << LL_ENDL;
return 0;
}
LLImageDataLock lock(this);
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); /* Flawfinder: ignore */
}
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 encodeDXT(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:
LL_WARNS() << "convertToDXR: can not convert format: " << llformat("0x%08x",getFourCC(mFileFormat)) << LL_ENDL;
return false;
}
mFileFormat = newformat;
LLImageDataLock lock(this);
S32 width = getWidth(), height = getHeight();
S32 nmips = calcNumMips(width,height);
S32 total_bytes = getDataSize();
U8* olddata = getData();
U8* newdata = (U8*)ll_aligned_malloc_16(total_bytes);
if (!newdata)
{
LLError::LLUserWarningMsg::showOutOfMemory();
LL_WARNS() << "Out of memory in LLImageDXT::convertToDXR()" << LL_ENDL;
return false;
}
llassert(total_bytes > 0);
memset(newdata, 0, total_bytes);
memcpy(newdata, olddata, mHeaderSize); /* Flawfinder: ignore */
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); /* Flawfinder: ignore */
width >>= 1;
height >>= 1;
}
dxtfile_header_t* header = (dxtfile_header_t*)newdata;
header->pixel_fmt.fourcc = getFourCC(newformat);
setData(newdata, total_bytes);
updateData();
return true;
}
// virtual
S32 LLImageDXT::calcHeaderSize()
{
return static_cast<S32>(llmax(sizeof(dxtfile_header_old_t), sizeof(dxtfile_header_t)));
}
// virtual
S32 LLImageDXT::calcDataSize(S32 discard_level)
{
if (mFileFormat == FORMAT_UNKNOWN)
{
LL_ERRS() << "calcDataSize called with unloaded LLImageDXT" << LL_ENDL;
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); /* Flawfinder: ignore */
}
}
//============================================================================
+141
View File
@@ -0,0 +1,141 @@
/**
* @file llimagedxt.h
*
* $LicenseInfo:firstyear=2001&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#ifndef LL_LLIMAGEDXT_H
#define LL_LLIMAGEDXT_H
#include "llimage.h"
#include "llpointer.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();
private:
bool encodeDXT(const LLImageRaw* raw_image, F32 decode_time, bool explicit_mips);
public:
LLImageDXT();
/*virtual*/ std::string getExtension() { return std::string("dxt"); }
/*virtual*/ bool updateData();
/*virtual*/ bool decode(LLImageRaw* raw_image, F32 decode_time);
/*virtual*/ bool encode(const LLImageRaw* raw_image, F32 encode_time);
/*virtual*/ S32 calcHeaderSize();
/*virtual*/ S32 calcDataSize(S32 discard_level = 0);
bool getMipData(LLPointer<LLImageRaw>& raw, S32 discard=-1);
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
+941
View File
@@ -0,0 +1,941 @@
/**
* @file llimagefilter.cpp
* @brief Simple Image Filtering. See https://wiki.lindenlab.com/wiki/SL_Viewer_Image_Filters for complete documentation.
*
* $LicenseInfo:firstyear=2001&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2014, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#include "linden_common.h"
#include "llimagefilter.h"
#include "llmath.h"
#include "v3color.h"
#include "v4coloru.h"
#include "m3math.h"
#include "v3math.h"
#include "llsdserialize.h"
#include "llstring.h"
//---------------------------------------------------------------------------
// LLImageFilter
//---------------------------------------------------------------------------
LLImageFilter::LLImageFilter(const std::string& file_path) :
mFilterData(LLSD::emptyArray()),
mImage(NULL),
mHistoRed(NULL),
mHistoGreen(NULL),
mHistoBlue(NULL),
mHistoBrightness(NULL),
mStencilBlendMode(STENCIL_BLEND_MODE_BLEND),
mStencilShape(STENCIL_SHAPE_UNIFORM),
mStencilGamma(1.0),
mStencilMin(0.0),
mStencilMax(1.0)
{
// Load filter description from file
llifstream filter_xml(file_path.c_str());
if (filter_xml.is_open())
{
// Load and parse the file
LLPointer<LLSDParser> parser = new LLSDXMLParser();
parser->parse(filter_xml, mFilterData, LLSDSerialize::SIZE_UNLIMITED);
filter_xml.close();
}
}
LLImageFilter::~LLImageFilter()
{
mImage = NULL;
ll_aligned_free_16(mHistoRed);
ll_aligned_free_16(mHistoGreen);
ll_aligned_free_16(mHistoBlue);
ll_aligned_free_16(mHistoBrightness);
}
/*
*TODO
* Rename stencil to mask
* Improve perf: use LUT for alpha blending in uniform case
* Add gradient coloring as a filter
*/
//============================================================================
// Apply the filter data to the image passed as parameter
//============================================================================
void LLImageFilter::executeFilter(LLPointer<LLImageRaw> raw_image)
{
mImage = raw_image;
LLImageDataSharedLock lock(mImage); // <FS:Beq> FIRE-34564 Bugsplat SHARED vs EXCLUSIVE lock conflict
//std::cout << "Filter : size = " << mFilterData.size() << std::endl;
for (S32 i = 0; i < mFilterData.size(); ++i)
{
std::string filter_name = mFilterData[i][0].asString();
// Dump out the filter values (for debug)
//std::cout << "Filter : name = " << mFilterData[i][0].asString() << ", params = ";
//for (S32 j = 1; j < mFilterData[i].size(); ++j)
//{
// std::cout << mFilterData[i][j].asString() << ", ";
//}
//std::cout << std::endl;
if (filter_name == "stencil")
{
// Get the shape of the stencil, that is how the procedural alpha is computed geometrically
std::string filter_shape = mFilterData[i][1].asString();
EStencilShape shape = STENCIL_SHAPE_UNIFORM;
if (filter_shape == "uniform")
{
shape = STENCIL_SHAPE_UNIFORM;
}
else if (filter_shape == "gradient")
{
shape = STENCIL_SHAPE_GRADIENT;
}
else if (filter_shape == "vignette")
{
shape = STENCIL_SHAPE_VIGNETTE;
}
else if (filter_shape == "scanlines")
{
shape = STENCIL_SHAPE_SCAN_LINES;
}
// Get the blend mode of the stencil, that is how the effect is blended in the background through the stencil
std::string filter_mode = mFilterData[i][2].asString();
EStencilBlendMode mode = STENCIL_BLEND_MODE_BLEND;
if (filter_mode == "blend")
{
mode = STENCIL_BLEND_MODE_BLEND;
}
else if (filter_mode == "add")
{
mode = STENCIL_BLEND_MODE_ADD;
}
else if (filter_mode == "add_back")
{
mode = STENCIL_BLEND_MODE_ABACK;
}
else if (filter_mode == "fade")
{
mode = STENCIL_BLEND_MODE_FADE;
}
// Get the float params: mandatory min, max then the optional parameters (4 max)
F32 min = (F32)(mFilterData[i][3].asReal());
F32 max = (F32)(mFilterData[i][4].asReal());
F32 params[4] = {0.0, 0.0, 0.0, 0.0};
for (S32 j = 5; (j < mFilterData[i].size()) && (j < 9); j++)
{
params[j-5] = (F32)(mFilterData[i][j].asReal());
}
// Set the stencil
setStencil(shape,mode,min,max,params);
}
else if (filter_name == "sepia")
{
filterSepia();
}
else if (filter_name == "grayscale")
{
filterGrayScale();
}
else if (filter_name == "saturate")
{
filterSaturate((float)(mFilterData[i][1].asReal()));
}
else if (filter_name == "rotate")
{
filterRotate((float)(mFilterData[i][1].asReal()));
}
else if (filter_name == "gamma")
{
LLColor3 color((float)(mFilterData[i][2].asReal()),(float)(mFilterData[i][3].asReal()),(float)(mFilterData[i][4].asReal()));
filterGamma((float)(mFilterData[i][1].asReal()),color);
}
else if (filter_name == "colorize")
{
LLColor3 color((float)(mFilterData[i][1].asReal()),(float)(mFilterData[i][2].asReal()),(float)(mFilterData[i][3].asReal()));
LLColor3 alpha((F32)(mFilterData[i][4].asReal()),(float)(mFilterData[i][5].asReal()),(float)(mFilterData[i][6].asReal()));
filterColorize(color,alpha);
}
else if (filter_name == "contrast")
{
LLColor3 color((float)(mFilterData[i][2].asReal()),(float)(mFilterData[i][3].asReal()),(float)(mFilterData[i][4].asReal()));
filterContrast((float)(mFilterData[i][1].asReal()),color);
}
else if (filter_name == "brighten")
{
LLColor3 color((float)(mFilterData[i][2].asReal()),(float)(mFilterData[i][3].asReal()),(float)(mFilterData[i][4].asReal()));
filterBrightness((float)(mFilterData[i][1].asReal()),color);
}
else if (filter_name == "darken")
{
LLColor3 color((float)(mFilterData[i][2].asReal()),(float)(mFilterData[i][3].asReal()),(float)(mFilterData[i][4].asReal()));
filterBrightness((float)(-mFilterData[i][1].asReal()),color);
}
else if (filter_name == "linearize")
{
LLColor3 color((float)(mFilterData[i][2].asReal()),(float)(mFilterData[i][3].asReal()),(float)(mFilterData[i][4].asReal()));
filterLinearize((float)(mFilterData[i][1].asReal()),color);
}
else if (filter_name == "posterize")
{
LLColor3 color((float)(mFilterData[i][2].asReal()),(float)(mFilterData[i][3].asReal()),(float)(mFilterData[i][4].asReal()));
filterEqualize((S32)(mFilterData[i][1].asReal()),color);
}
else if (filter_name == "screen")
{
std::string screen_name = mFilterData[i][1].asString();
EScreenMode mode = SCREEN_MODE_2DSINE;
if (screen_name == "2Dsine")
{
mode = SCREEN_MODE_2DSINE;
}
else if (screen_name == "line")
{
mode = SCREEN_MODE_LINE;
}
filterScreen(mode,(F32)(mFilterData[i][2].asReal()),(F32)(mFilterData[i][3].asReal()));
}
else if (filter_name == "blur")
{
LLMatrix3 kernel;
for (S32 i = 0; i < NUM_VALUES_IN_MAT3; i++)
for (S32 j = 0; j < NUM_VALUES_IN_MAT3; j++)
kernel.mMatrix[i][j] = 1.0;
convolve(kernel,true,false);
}
else if (filter_name == "sharpen")
{
LLMatrix3 kernel;
for (S32 k = 0; k < NUM_VALUES_IN_MAT3; k++)
for (S32 j = 0; j < NUM_VALUES_IN_MAT3; j++)
kernel.mMatrix[k][j] = -1.0;
kernel.mMatrix[1][1] = 9.0;
convolve(kernel,false,false);
}
else if (filter_name == "gradient")
{
LLMatrix3 kernel;
for (S32 k = 0; k < NUM_VALUES_IN_MAT3; k++)
for (S32 j = 0; j < NUM_VALUES_IN_MAT3; j++)
kernel.mMatrix[k][j] = -1.0;
kernel.mMatrix[1][1] = 8.0;
convolve(kernel,false,true);
}
else if (filter_name == "convolve")
{
LLMatrix3 kernel;
S32 index = 1;
bool normalize = (mFilterData[i][index++].asReal() > 0.0);
bool abs_value = (mFilterData[i][index++].asReal() > 0.0);
for (S32 k = 0; k < NUM_VALUES_IN_MAT3; k++)
for (S32 j = 0; j < NUM_VALUES_IN_MAT3; j++)
kernel.mMatrix[k][j] = (F32)mFilterData[i][index++].asReal();
convolve(kernel,normalize,abs_value);
}
else if (filter_name == "colortransform")
{
LLMatrix3 transform;
S32 index = 1;
for (S32 k = 0; k < NUM_VALUES_IN_MAT3; k++)
for (S32 j = 0; j < NUM_VALUES_IN_MAT3; j++)
transform.mMatrix[k][j] = (F32)mFilterData[i][index++].asReal();
transform.transpose();
colorTransform(transform);
}
else
{
LL_WARNS() << "Filter unknown, cannot execute filter command : " << filter_name << LL_ENDL;
}
}
}
//============================================================================
// Filter Primitives
//============================================================================
void LLImageFilter::blendStencil(F32 alpha, U8* pixel, U8 red, U8 green, U8 blue)
{
F32 inv_alpha = 1.0f - alpha;
switch (mStencilBlendMode)
{
case STENCIL_BLEND_MODE_BLEND:
// Classic blend of incoming color with the background image
pixel[VRED] = (U8)(inv_alpha * pixel[VRED] + alpha * red);
pixel[VGREEN] = (U8)(inv_alpha * pixel[VGREEN] + alpha * green);
pixel[VBLUE] = (U8)(inv_alpha * pixel[VBLUE] + alpha * blue);
break;
case STENCIL_BLEND_MODE_ADD:
// Add incoming color to the background image
pixel[VRED] = (U8)llclampb(pixel[VRED] + alpha * red);
pixel[VGREEN] = (U8)llclampb(pixel[VGREEN] + alpha * green);
pixel[VBLUE] = (U8)llclampb(pixel[VBLUE] + alpha * blue);
break;
case STENCIL_BLEND_MODE_ABACK:
// Add back background image to the incoming color
pixel[VRED] = (U8)llclampb(inv_alpha * pixel[VRED] + red);
pixel[VGREEN] = (U8)llclampb(inv_alpha * pixel[VGREEN] + green);
pixel[VBLUE] = (U8)llclampb(inv_alpha * pixel[VBLUE] + blue);
break;
case STENCIL_BLEND_MODE_FADE:
// Fade incoming color to black
pixel[VRED] = (U8)(alpha * red);
pixel[VGREEN] = (U8)(alpha * green);
pixel[VBLUE] = (U8)(alpha * blue);
break;
}
}
void LLImageFilter::colorCorrect(const U8* lut_red, const U8* lut_green, const U8* lut_blue)
{
const S32 components = mImage->getComponents();
llassert( components >= 1 && components <= 4 );
S32 width = mImage->getWidth();
S32 height = mImage->getHeight();
U8* dst_data = mImage->getData();
for (S32 j = 0; j < height; j++)
{
for (S32 i = 0; i < width; i++)
{
// Blend LUT value
blendStencil(getStencilAlpha(i,j), dst_data, lut_red[dst_data[VRED]], lut_green[dst_data[VGREEN]], lut_blue[dst_data[VBLUE]]);
dst_data += components;
}
}
}
void LLImageFilter::colorTransform(const LLMatrix3 &transform)
{
const S32 components = mImage->getComponents();
llassert( components >= 1 && components <= 4 );
S32 width = mImage->getWidth();
S32 height = mImage->getHeight();
U8* dst_data = mImage->getData();
for (S32 j = 0; j < height; j++)
{
for (S32 i = 0; i < width; i++)
{
// Compute transform
LLVector3 src((F32)(dst_data[VRED]),(F32)(dst_data[VGREEN]),(F32)(dst_data[VBLUE]));
LLVector3 dst = src * transform;
dst.clamp(0.0f,255.0f);
// Blend result
blendStencil(getStencilAlpha(i,j), dst_data, (U8)dst.mV[VRED], (U8)dst.mV[VGREEN], (U8)dst.mV[VBLUE]);
dst_data += components;
}
}
}
void LLImageFilter::convolve(const LLMatrix3 &kernel, bool normalize, bool abs_value)
{
const S32 components = mImage->getComponents();
llassert( components >= 1 && components <= 4 );
// Compute normalization factors
F32 kernel_min = 0.0;
F32 kernel_max = 0.0;
for (S32 i = 0; i < NUM_VALUES_IN_MAT3; i++)
{
for (S32 j = 0; j < NUM_VALUES_IN_MAT3; j++)
{
if (kernel.mMatrix[i][j] >= 0.0)
kernel_max += kernel.mMatrix[i][j];
else
kernel_min += kernel.mMatrix[i][j];
}
}
if (abs_value)
{
kernel_max = llabs(kernel_max);
kernel_min = llabs(kernel_min);
kernel_max = llmax(kernel_max,kernel_min);
kernel_min = 0.0;
}
F32 kernel_range = kernel_max - kernel_min;
// Allocate temporary buffers and initialize algorithm's data
S32 width = mImage->getWidth();
S32 height = mImage->getHeight();
U8* dst_data = mImage->getData();
S32 buffer_size = width * components;
llassert_always(buffer_size > 0);
std::vector<U8> even_buffer(buffer_size);
std::vector<U8> odd_buffer(buffer_size);
U8* south_data = dst_data + buffer_size;
U8* east_west_data;
U8* north_data;
// Line 0 : we set the line to 0 (debatable)
memcpy( &even_buffer[0], dst_data, buffer_size ); /* Flawfinder: ignore */
for (S32 i = 0; i < width; i++)
{
blendStencil(getStencilAlpha(i,0), dst_data, 0, 0, 0);
dst_data += components;
}
south_data += buffer_size;
// All other lines
for (S32 j = 1; j < (height-1); j++)
{
// We need to buffer 2 lines. We flip north and east-west (current) to avoid moving too much memory around
if (j % 2)
{
memcpy( &odd_buffer[0], dst_data, buffer_size ); /* Flawfinder: ignore */
east_west_data = &odd_buffer[0];
north_data = &even_buffer[0];
}
else
{
memcpy( &even_buffer[0], dst_data, buffer_size ); /* Flawfinder: ignore */
east_west_data = &even_buffer[0];
north_data = &odd_buffer[0];
}
// First pixel : set to 0
blendStencil(getStencilAlpha(0,j), dst_data, 0, 0, 0);
dst_data += components;
// Set pointers to kernel
U8* NW = north_data;
U8* N = NW+components;
U8* NE = N+components;
U8* W = east_west_data;
U8* C = W+components;
U8* E = C+components;
U8* SW = south_data;
U8* S = SW+components;
U8* SE = S+components;
// All other pixels
for (S32 i = 1; i < (width-1); i++)
{
// Compute convolution
LLVector3 dst;
dst.mV[VRED] = (kernel.mMatrix[0][0]*NW[VRED] + kernel.mMatrix[0][1]*N[VRED] + kernel.mMatrix[0][2]*NE[VRED] +
kernel.mMatrix[1][0]*W[VRED] + kernel.mMatrix[1][1]*C[VRED] + kernel.mMatrix[1][2]*E[VRED] +
kernel.mMatrix[2][0]*SW[VRED] + kernel.mMatrix[2][1]*S[VRED] + kernel.mMatrix[2][2]*SE[VRED]);
dst.mV[VGREEN] = (kernel.mMatrix[0][0]*NW[VGREEN] + kernel.mMatrix[0][1]*N[VGREEN] + kernel.mMatrix[0][2]*NE[VGREEN] +
kernel.mMatrix[1][0]*W[VGREEN] + kernel.mMatrix[1][1]*C[VGREEN] + kernel.mMatrix[1][2]*E[VGREEN] +
kernel.mMatrix[2][0]*SW[VGREEN] + kernel.mMatrix[2][1]*S[VGREEN] + kernel.mMatrix[2][2]*SE[VGREEN]);
dst.mV[VBLUE] = (kernel.mMatrix[0][0]*NW[VBLUE] + kernel.mMatrix[0][1]*N[VBLUE] + kernel.mMatrix[0][2]*NE[VBLUE] +
kernel.mMatrix[1][0]*W[VBLUE] + kernel.mMatrix[1][1]*C[VBLUE] + kernel.mMatrix[1][2]*E[VBLUE] +
kernel.mMatrix[2][0]*SW[VBLUE] + kernel.mMatrix[2][1]*S[VBLUE] + kernel.mMatrix[2][2]*SE[VBLUE]);
if (abs_value)
{
dst.mV[VRED] = llabs(dst.mV[VRED]);
dst.mV[VGREEN] = llabs(dst.mV[VGREEN]);
dst.mV[VBLUE] = llabs(dst.mV[VBLUE]);
}
if (normalize)
{
dst.mV[VRED] = (dst.mV[VRED] - kernel_min)/kernel_range;
dst.mV[VGREEN] = (dst.mV[VGREEN] - kernel_min)/kernel_range;
dst.mV[VBLUE] = (dst.mV[VBLUE] - kernel_min)/kernel_range;
}
dst.clamp(0.0f,255.0f);
// Blend result
blendStencil(getStencilAlpha(i,j), dst_data, (U8)dst.mV[VRED], (U8)dst.mV[VGREEN], (U8)dst.mV[VBLUE]);
// Next pixel
dst_data += components;
NW += components;
N += components;
NE += components;
W += components;
C += components;
E += components;
SW += components;
S += components;
SE += components;
}
// Last pixel : set to 0
blendStencil(getStencilAlpha(width-1,j), dst_data, 0, 0, 0);
dst_data += components;
south_data += buffer_size;
}
// Last line
for (S32 i = 0; i < width; i++)
{
blendStencil(getStencilAlpha(i,0), dst_data, 0, 0, 0);
dst_data += components;
}
}
void LLImageFilter::filterScreen(EScreenMode mode, const F32 wave_length, const F32 angle)
{
const S32 components = mImage->getComponents();
llassert( components >= 1 && components <= 4 );
S32 width = mImage->getWidth();
S32 height = mImage->getHeight();
F32 wave_length_pixels = wave_length * (F32)(height) / 2.0f;
F32 sin = sinf(angle*DEG_TO_RAD);
F32 cos = cosf(angle*DEG_TO_RAD);
// Precompute the gamma table : gives us the gray level to use when cutting outside the screen (prevents strong aliasing on the screen)
U8 gamma[256];
for (S32 i = 0; i < 256; i++)
{
F32 gamma_i = llclampf((float)(powf((float)(i)/255.0f,1.0f/4.0f)));
gamma[i] = (U8)(255.0 * gamma_i);
}
U8* dst_data = mImage->getData();
for (S32 j = 0; j < height; j++)
{
for (S32 i = 0; i < width; i++)
{
// Compute screen value
F32 value = 0.0;
F32 di = 0.0;
F32 dj = 0.0;
switch (mode)
{
case SCREEN_MODE_2DSINE:
di = cos*i + sin*j;
dj = -sin*i + cos*j;
value = (sinf(2*F_PI*di/wave_length_pixels)*sinf(2*F_PI*dj/wave_length_pixels)+1.0f)*255.0f/2.0f;
break;
case SCREEN_MODE_LINE:
dj = sin*i - cos*j;
value = (sinf(2*F_PI*dj/wave_length_pixels)+1.0f)*255.0f/2.0f;
break;
}
U8 dst_value = (dst_data[VRED] >= (U8)(value) ? gamma[dst_data[VRED] - (U8)(value)] : 0);
// Blend result
blendStencil(getStencilAlpha(i,j), dst_data, dst_value, dst_value, dst_value);
dst_data += components;
}
}
}
//============================================================================
// Procedural Stencils
//============================================================================
void LLImageFilter::setStencil(EStencilShape shape, EStencilBlendMode mode, F32 min, F32 max, F32* params)
{
mStencilShape = shape;
mStencilBlendMode = mode;
mStencilMin = llmin(llmax(min, -1.0f), 1.0f);
mStencilMax = llmin(llmax(max, -1.0f), 1.0f);
// Each shape will interpret the 4 params differenly.
// We compute each systematically, though, clearly, values are meaningless when the shape doesn't correspond to the parameters
mStencilCenterX = (S32)(mImage->getWidth() + params[0] * (F32)(mImage->getHeight()))/2;
mStencilCenterY = (S32)(mImage->getHeight() + params[1] * (F32)(mImage->getHeight()))/2;
mStencilWidth = (S32)(params[2] * (F32)(mImage->getHeight()))/2;
mStencilGamma = (params[3] <= 0.0f ? 1.0f : params[3]);
mStencilWavelength = (params[0] <= 0.0f ? 10.0f : params[0] * (F32)(mImage->getHeight()) / 2.0f);
mStencilSine = sinf(params[1]*DEG_TO_RAD);
mStencilCosine = cosf(params[1]*DEG_TO_RAD);
mStencilStartX = ((F32)(mImage->getWidth()) + params[0] * (F32)(mImage->getHeight()))/2.0f;
mStencilStartY = ((F32)(mImage->getHeight()) + params[1] * (F32)(mImage->getHeight()))/2.0f;
F32 end_x = ((F32)(mImage->getWidth()) + params[2] * (F32)(mImage->getHeight()))/2.0f;
F32 end_y = ((F32)(mImage->getHeight()) + params[3] * (F32)(mImage->getHeight()))/2.0f;
mStencilGradX = end_x - mStencilStartX;
mStencilGradY = end_y - mStencilStartY;
mStencilGradN = mStencilGradX*mStencilGradX + mStencilGradY*mStencilGradY;
}
F32 LLImageFilter::getStencilAlpha(S32 i, S32 j)
{
F32 alpha = 1.0; // That init actually takes care of the STENCIL_SHAPE_UNIFORM case...
if (mStencilShape == STENCIL_SHAPE_VIGNETTE)
{
// alpha is a modified gaussian value, with a center and fading in a circular pattern toward the edges
// The gamma parameter controls the intensity of the drop down from alpha 1.0 (center) to 0.0
F32 d_center_square = (F32)((i - mStencilCenterX)*(i - mStencilCenterX) + (j - mStencilCenterY)*(j - mStencilCenterY));
alpha = powf(F_E, -(powf((d_center_square/(mStencilWidth*mStencilWidth)),mStencilGamma)/2.0f));
}
else if (mStencilShape == STENCIL_SHAPE_SCAN_LINES)
{
// alpha varies according to a squared sine function.
F32 d = mStencilSine*i - mStencilCosine*j;
alpha = (sinf(2*F_PI*d/mStencilWavelength) > 0.0f ? 1.0f : 0.0f);
}
else if (mStencilShape == STENCIL_SHAPE_GRADIENT)
{
alpha = (((F32)(i) - mStencilStartX)*mStencilGradX + ((F32)(j) - mStencilStartY)*mStencilGradY) / mStencilGradN;
alpha = llclampf(alpha);
}
// We rescale alpha between min and max
return (mStencilMin + alpha * (mStencilMax - mStencilMin));
}
//============================================================================
// Histograms
//============================================================================
U32* LLImageFilter::getBrightnessHistogram()
{
if (!mHistoBrightness)
{
computeHistograms();
}
return mHistoBrightness;
}
void LLImageFilter::computeHistograms()
{
const S32 components = mImage->getComponents();
llassert( components >= 1 && components <= 4 );
// Allocate memory for the histograms
if (!mHistoRed)
{
mHistoRed = (U32*) ll_aligned_malloc_16(256*sizeof(U32));
}
if (!mHistoGreen)
{
mHistoGreen = (U32*) ll_aligned_malloc_16(256*sizeof(U32));
}
if (!mHistoBlue)
{
mHistoBlue = (U32*) ll_aligned_malloc_16(256*sizeof(U32));
}
if (!mHistoBrightness)
{
mHistoBrightness = (U32*) ll_aligned_malloc_16(256*sizeof(U32));
}
// Initialize them
for (S32 i = 0; i < 256; i++)
{
mHistoRed[i] = 0;
mHistoGreen[i] = 0;
mHistoBlue[i] = 0;
mHistoBrightness[i] = 0;
}
// Compute them
S32 pixels = mImage->getWidth() * mImage->getHeight();
U8* dst_data = mImage->getData();
for (S32 i = 0; i < pixels; i++)
{
mHistoRed[dst_data[VRED]]++;
mHistoGreen[dst_data[VGREEN]]++;
mHistoBlue[dst_data[VBLUE]]++;
// Note: this is a very simple shorthand for brightness but it's OK for our use
S32 brightness = ((S32)(dst_data[VRED]) + (S32)(dst_data[VGREEN]) + (S32)(dst_data[VBLUE])) / 3;
mHistoBrightness[brightness]++;
// next pixel...
dst_data += components;
}
}
//============================================================================
// Secondary Filters
//============================================================================
void LLImageFilter::filterGrayScale()
{
LLMatrix3 gray_scale;
LLVector3 luminosity(0.2125f, 0.7154f, 0.0721f);
gray_scale.setRows(luminosity, luminosity, luminosity);
gray_scale.transpose();
colorTransform(gray_scale);
}
void LLImageFilter::filterSepia()
{
LLMatrix3 sepia;
sepia.setRows(LLVector3(0.3588f, 0.7044f, 0.1368f),
LLVector3(0.2990f, 0.5870f, 0.1140f),
LLVector3(0.2392f, 0.4696f, 0.0912f));
sepia.transpose();
colorTransform(sepia);
}
void LLImageFilter::filterSaturate(F32 saturation)
{
// Matrix to Lij
LLMatrix3 r_a;
LLMatrix3 r_b;
// 45 degre rotation around z
r_a.setRows(LLVector3( OO_SQRT2, OO_SQRT2, 0.0),
LLVector3(-OO_SQRT2, OO_SQRT2, 0.0),
LLVector3( 0.0, 0.0, 1.0));
// 54.73 degre rotation around y
float oo_sqrt3 = 1.0f / F_SQRT3;
float sin_54 = F_SQRT2 * oo_sqrt3;
r_b.setRows(LLVector3(oo_sqrt3, 0.0, -sin_54),
LLVector3(0.0, 1.0, 0.0),
LLVector3(sin_54, 0.0, oo_sqrt3));
// Coordinate conversion
LLMatrix3 Lij = r_b * r_a;
LLMatrix3 Lij_inv = Lij;
Lij_inv.transpose();
// Local saturation transform
LLMatrix3 s;
s.setRows(LLVector3(saturation, 0.0, 0.0),
LLVector3(0.0, saturation, 0.0),
LLVector3(0.0, 0.0, 1.0));
// Global saturation transform
LLMatrix3 transfo = Lij_inv * s * Lij;
colorTransform(transfo);
}
void LLImageFilter::filterRotate(F32 angle)
{
// Matrix to Lij
LLMatrix3 r_a;
LLMatrix3 r_b;
// 45 degre rotation around z
r_a.setRows(LLVector3( OO_SQRT2, OO_SQRT2, 0.0),
LLVector3(-OO_SQRT2, OO_SQRT2, 0.0),
LLVector3( 0.0, 0.0, 1.0));
// 54.73 degre rotation around y
float oo_sqrt3 = 1.0f / F_SQRT3;
float sin_54 = F_SQRT2 * oo_sqrt3;
r_b.setRows(LLVector3(oo_sqrt3, 0.0, -sin_54),
LLVector3(0.0, 1.0, 0.0),
LLVector3(sin_54, 0.0, oo_sqrt3));
// Coordinate conversion
LLMatrix3 Lij = r_b * r_a;
LLMatrix3 Lij_inv = Lij;
Lij_inv.transpose();
// Local color rotation transform
LLMatrix3 r;
angle *= DEG_TO_RAD;
r.setRows(LLVector3( cosf(angle), sinf(angle), 0.0),
LLVector3(-sinf(angle), cosf(angle), 0.0),
LLVector3( 0.0, 0.0, 1.0));
// Global color rotation transform
LLMatrix3 transfo = Lij_inv * r * Lij;
colorTransform(transfo);
}
void LLImageFilter::filterGamma(F32 gamma, const LLColor3& alpha)
{
U8 gamma_red_lut[256];
U8 gamma_green_lut[256];
U8 gamma_blue_lut[256];
for (S32 i = 0; i < 256; i++)
{
F32 gamma_i = llclampf((float)(powf((float)(i)/255.0f,1.0f/gamma)));
// Blend in with alpha values
gamma_red_lut[i] = (U8)((1.0f - alpha.mV[0]) * (float)(i) + alpha.mV[0] * 255.0f * gamma_i);
gamma_green_lut[i] = (U8)((1.0f - alpha.mV[1]) * (float)(i) + alpha.mV[1] * 255.0f * gamma_i);
gamma_blue_lut[i] = (U8)((1.0f - alpha.mV[2]) * (float)(i) + alpha.mV[2] * 255.0f * gamma_i);
}
colorCorrect(gamma_red_lut,gamma_green_lut,gamma_blue_lut);
}
void LLImageFilter::filterLinearize(F32 tail, const LLColor3& alpha)
{
// Get the histogram
U32* histo = getBrightnessHistogram();
// Compute cumulated histogram
U32 cumulated_histo[256];
cumulated_histo[0] = histo[0];
for (S32 i = 1; i < 256; i++)
{
cumulated_histo[i] = cumulated_histo[i-1] + histo[i];
}
// Compute min and max counts minus tail
tail = llclampf(tail);
U32 total = cumulated_histo[255];
U32 min_c = (U32)((F32)(total) * tail);
U32 max_c = (U32)((F32)(total) * (1.0 - tail));
// Find min and max values
S32 min_v = 0;
while (cumulated_histo[min_v] < min_c)
{
min_v++;
}
S32 max_v = 255;
while (cumulated_histo[max_v] > max_c)
{
max_v--;
}
// Compute linear lookup table
U8 linear_red_lut[256]{};
U8 linear_green_lut[256]{};
U8 linear_blue_lut[256]{};
if (max_v == min_v)
{
// Degenerated binary split case
for (S32 i = 0; i < 256; i++)
{
U8 value_i = (i < min_v ? 0 : 255);
// Blend in with alpha values
linear_red_lut[i] = (U8)((1.0f - alpha.mV[0]) * (float)(i) + alpha.mV[0] * value_i);
linear_green_lut[i] = (U8)((1.0f - alpha.mV[1]) * (float)(i) + alpha.mV[1] * value_i);
linear_blue_lut[i] = (U8)((1.0f - alpha.mV[2]) * (float)(i) + alpha.mV[2] * value_i);
}
}
else
{
// Linearize between min and max
F32 slope = 255.0f / (F32)(max_v - min_v);
F32 translate = -min_v * slope;
for (S32 i = 0; i < 256; i++)
{
U8 value_i = (U8)(llclampb((S32)(slope*i + translate)));
// Blend in with alpha values
linear_red_lut[i] = (U8)((1.0f - alpha.mV[0]) * (float)(i) + alpha.mV[0] * value_i);
linear_green_lut[i] = (U8)((1.0f - alpha.mV[1]) * (float)(i) + alpha.mV[1] * value_i);
linear_blue_lut[i] = (U8)((1.0f - alpha.mV[2]) * (float)(i) + alpha.mV[2] * value_i);
}
}
// Apply lookup table
colorCorrect(linear_red_lut,linear_green_lut,linear_blue_lut);
}
void LLImageFilter::filterEqualize(S32 nb_classes, const LLColor3& alpha)
{
// Regularize the parameter: must be between 2 and 255
nb_classes = llmax(nb_classes,2);
nb_classes = llclampb(nb_classes);
// Get the histogram
U32* histo = getBrightnessHistogram();
// Compute cumulated histogram
U32 cumulated_histo[256];
cumulated_histo[0] = histo[0];
for (S32 i = 1; i < 256; i++)
{
cumulated_histo[i] = cumulated_histo[i-1] + histo[i];
}
// Compute deltas
U32 total = cumulated_histo[255];
U32 delta_count = total / nb_classes;
U32 current_count = delta_count;
U32 delta_value = 256 / (nb_classes - 1);
U32 current_value = 0;
// Compute equalized lookup table
U8 equalize_red_lut[256]{};
U8 equalize_green_lut[256]{};
U8 equalize_blue_lut[256]{};
for (S32 i = 0; i < 256; i++)
{
// Blend in current_value with alpha values
equalize_red_lut[i] = (U8)((1.0f - alpha.mV[0]) * (float)(i) + alpha.mV[0] * current_value);
equalize_green_lut[i] = (U8)((1.0f - alpha.mV[1]) * (float)(i) + alpha.mV[1] * current_value);
equalize_blue_lut[i] = (U8)((1.0f - alpha.mV[2]) * (float)(i) + alpha.mV[2] * current_value);
if (cumulated_histo[i] >= current_count)
{
current_count += delta_count;
current_value += delta_value;
current_value = llclampb(current_value);
}
}
// Apply lookup table
colorCorrect(equalize_red_lut,equalize_green_lut,equalize_blue_lut);
}
void LLImageFilter::filterColorize(const LLColor3& color, const LLColor3& alpha)
{
U8 red_lut[256];
U8 green_lut[256];
U8 blue_lut[256];
F32 red_composite = 255.0f * alpha.mV[0] * color.mV[0];
F32 green_composite = 255.0f * alpha.mV[1] * color.mV[1];
F32 blue_composite = 255.0f * alpha.mV[2] * color.mV[2];
for (S32 i = 0; i < 256; i++)
{
red_lut[i] = (U8)(llclampb((S32)((1.0f - alpha.mV[0]) * (F32)(i) + red_composite)));
green_lut[i] = (U8)(llclampb((S32)((1.0f - alpha.mV[1]) * (F32)(i) + green_composite)));
blue_lut[i] = (U8)(llclampb((S32)((1.0f - alpha.mV[2]) * (F32)(i) + blue_composite)));
}
colorCorrect(red_lut,green_lut,blue_lut);
}
void LLImageFilter::filterContrast(F32 slope, const LLColor3& alpha)
{
U8 contrast_red_lut[256];
U8 contrast_green_lut[256];
U8 contrast_blue_lut[256];
F32 translate = 128.0f * (1.0f - slope);
for (S32 i = 0; i < 256; i++)
{
U8 value_i = (U8)(llclampb((S32)(slope*i + translate)));
// Blend in with alpha values
contrast_red_lut[i] = (U8)((1.0f - alpha.mV[0]) * (float)(i) + alpha.mV[0] * value_i);
contrast_green_lut[i] = (U8)((1.0f - alpha.mV[1]) * (float)(i) + alpha.mV[1] * value_i);
contrast_blue_lut[i] = (U8)((1.0f - alpha.mV[2]) * (float)(i) + alpha.mV[2] * value_i);
}
colorCorrect(contrast_red_lut,contrast_green_lut,contrast_blue_lut);
}
void LLImageFilter::filterBrightness(F32 add, const LLColor3& alpha)
{
U8 brightness_red_lut[256];
U8 brightness_green_lut[256];
U8 brightness_blue_lut[256];
S32 add_value = (S32)(add * 255.0f);
for (S32 i = 0; i < 256; i++)
{
U8 value_i = (U8)(llclampb(i + add_value));
// Blend in with alpha values
brightness_red_lut[i] = (U8)((1.0f - alpha.mV[0]) * (float)(i) + alpha.mV[0] * value_i);
brightness_green_lut[i] = (U8)((1.0f - alpha.mV[1]) * (float)(i) + alpha.mV[1] * value_i);
brightness_blue_lut[i] = (U8)((1.0f - alpha.mV[2]) * (float)(i) + alpha.mV[2] * value_i);
}
colorCorrect(brightness_red_lut,brightness_green_lut,brightness_blue_lut);
}
//============================================================================
+137
View File
@@ -0,0 +1,137 @@
/**
* @file llimagefilter.h
* @brief Simple Image Filtering. See https://wiki.lindenlab.com/wiki/SL_Viewer_Image_Filters for complete documentation.
*
* $LicenseInfo:firstyear=2000&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2014, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#ifndef LL_LLIMAGEFILTER_H
#define LL_LLIMAGEFILTER_H
#include "llsd.h"
#include "llimage.h"
class LLImageRaw;
class LLColor4U;
class LLColor3;
class LLMatrix3;
typedef enum e_stencil_blend_mode
{
STENCIL_BLEND_MODE_BLEND = 0,
STENCIL_BLEND_MODE_ADD = 1,
STENCIL_BLEND_MODE_ABACK = 2,
STENCIL_BLEND_MODE_FADE = 3
} EStencilBlendMode;
typedef enum e_stencil_shape
{
STENCIL_SHAPE_UNIFORM = 0,
STENCIL_SHAPE_GRADIENT = 1,
STENCIL_SHAPE_VIGNETTE = 2,
STENCIL_SHAPE_SCAN_LINES = 3
} EStencilShape;
typedef enum e_screen_mode
{
SCREEN_MODE_2DSINE = 0,
SCREEN_MODE_LINE = 1
} EScreenMode;
//============================================================================
// LLImageFilter
//============================================================================
class LLImageFilter
{
public:
LLImageFilter(const std::string& file_path);
~LLImageFilter();
void executeFilter(LLPointer<LLImageRaw> raw_image);
private:
// Filter Operations : Transforms
void filterGrayScale(); // Convert to grayscale
void filterSepia(); // Convert to sepia
void filterSaturate(F32 saturation); // < 1.0 desaturates, > 1.0 saturates
void filterRotate(F32 angle); // Rotates hue according to angle, angle in degrees
// Filter Operations : Color Corrections
// When specified, the LLColor3 alpha parameter indicates the intensity of the effect for each color channel
// acting in effect as an alpha blending factor different for each channel. For instance (1.0,0.0,0.0) will apply
// the effect only to the Red channel. Intermediate values blends the effect with the source color.
void filterGamma(F32 gamma, const LLColor3& alpha); // Apply gamma to each channel
void filterLinearize(F32 tail, const LLColor3& alpha); // Use histogram to linearize constrast between min and max values minus tail
void filterEqualize(S32 nb_classes, const LLColor3& alpha); // Use histogram to equalize constrast between nb_classes throughout the image
void filterColorize(const LLColor3& color, const LLColor3& alpha); // Colorize with color and alpha per channel
void filterContrast(F32 slope, const LLColor3& alpha); // Change contrast according to slope: > 1.0 more contrast, < 1.0 less contrast
void filterBrightness(F32 add, const LLColor3& alpha); // Change brightness according to add: > 0 brighter, < 0 darker
// Filter Primitives
void colorTransform(const LLMatrix3 &transform);
void colorCorrect(const U8* lut_red, const U8* lut_green, const U8* lut_blue);
void filterScreen(EScreenMode mode, const F32 wave_length, const F32 angle);
void blendStencil(F32 alpha, U8* pixel, U8 red, U8 green, U8 blue);
void convolve(const LLMatrix3 &kernel, bool normalize, bool abs_value);
// Procedural Stencils
void setStencil(EStencilShape shape, EStencilBlendMode mode, F32 min, F32 max, F32* params);
F32 getStencilAlpha(S32 i, S32 j);
// Histograms
U32* getBrightnessHistogram();
void computeHistograms();
LLSD mFilterData;
LLPointer<LLImageRaw> mImage;
// Histograms (if we ever happen to need them)
U32 *mHistoRed;
U32 *mHistoGreen;
U32 *mHistoBlue;
U32 *mHistoBrightness;
// Current Stencil Settings
EStencilBlendMode mStencilBlendMode;
EStencilShape mStencilShape;
F32 mStencilMin;
F32 mStencilMax;
S32 mStencilCenterX;
S32 mStencilCenterY;
S32 mStencilWidth;
F32 mStencilGamma;
F32 mStencilWavelength;
F32 mStencilSine;
F32 mStencilCosine;
F32 mStencilStartX;
F32 mStencilStartY;
F32 mStencilGradX;
F32 mStencilGradY;
F32 mStencilGradN;
};
#endif
+595
View File
@@ -0,0 +1,595 @@
/**
* @file llimagej2c.cpp
*
* $LicenseInfo:firstyear=2001&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#include "linden_common.h"
#include "llapr.h"
#include "lldir.h"
#include "llimagej2c.h"
#include "lltimer.h"
#include "llmath.h"
#include "llmemory.h"
#include "llsd.h"
// Declare the prototype for this factory function here. It is implemented in
// other files which define a LLImageJ2CImpl subclass, but only ONE static
// library which has the implementation for this function should ever be
// linked.
LLImageJ2CImpl* fallbackCreateLLImageJ2CImpl();
// Test data gathering handle
LLImageCompressionTester* LLImageJ2C::sTesterp = NULL ;
const std::string sTesterName("ImageCompressionTester");
//static
std::string LLImageJ2C::getEngineInfo()
{
// All known LLImageJ2CImpl implementation subclasses are cheap to
// construct.
std::unique_ptr<LLImageJ2CImpl> impl(fallbackCreateLLImageJ2CImpl());
return impl->getEngineInfo();
}
LLImageJ2C::LLImageJ2C() : LLImageFormatted(IMG_CODEC_J2C),
mMaxBytes(0),
mRawDiscardLevel(-1),
mRate(DEFAULT_COMPRESSION_RATE),
mReversible(false),
mAreaUsedForDataSizeCalcs(0)
{
mImpl.reset(fallbackCreateLLImageJ2CImpl());
// Clear data size table
for( S32 i = 0; i <= MAX_DISCARD_LEVEL; i++)
{ // Array size is MAX_DISCARD_LEVEL+1
mDataSizes[i] = 0;
}
// If that test log has ben requested but not yet created, create it
if (LLMetricPerformanceTesterBasic::isMetricLogRequested(sTesterName) && !LLMetricPerformanceTesterBasic::getTester(sTesterName))
{
sTesterp = new LLImageCompressionTester() ;
if (!sTesterp->isValid())
{
delete sTesterp;
sTesterp = NULL;
}
}
}
// virtual
LLImageJ2C::~LLImageJ2C() {}
// virtual
void LLImageJ2C::resetLastError()
{
mLastError.clear();
}
//virtual
void LLImageJ2C::setLastError(const std::string& message, const std::string& filename)
{
mLastError = message;
if (!filename.empty())
mLastError += std::string(" FILE: ") + filename;
}
// virtual
S8 LLImageJ2C::getRawDiscardLevel()
{
return mRawDiscardLevel;
}
bool LLImageJ2C::updateData()
{
bool res = true;
resetLastError();
LLImageDataLock lock(this);
// Check to make sure that this instance has been initialized with data
if (!getData() || (getDataSize() < 16))
{
setLastError("LLImageJ2C uninitialized");
res = false;
}
else
{
res = mImpl->getMetadata(*this);
}
if (res)
{
// SJB: override discard based on mMaxBytes elsewhere
S32 max_bytes = getDataSize(); // mMaxBytes ? mMaxBytes : getDataSize();
S32 discard = calcDiscardLevelBytes(max_bytes);
setDiscardLevel(discard);
}
if (!mLastError.empty())
{
LLImage::setLastError(mLastError);
}
return res;
}
bool LLImageJ2C::initDecode(LLImageRaw &raw_image, int discard_level, int* region)
{
setDiscardLevel(discard_level != -1 ? discard_level : 0);
return mImpl->initDecode(*this,raw_image,discard_level,region);
}
bool LLImageJ2C::initEncode(LLImageRaw &raw_image, int blocks_size, int precincts_size, int levels)
{
return mImpl->initEncode(*this,raw_image,blocks_size,precincts_size,levels);
}
bool LLImageJ2C::decode(LLImageRaw *raw_imagep, F32 decode_time)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE;
return decodeChannels(raw_imagep, decode_time, 0, 4);
}
// Returns true to mean done, whether successful or not.
bool LLImageJ2C::decodeChannels(LLImageRaw *raw_imagep, F32 decode_time, S32 first_channel, S32 max_channel_count )
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE;
LLTimer elapsed;
resetLastError();
bool res;
{
LLImageDataLock lock(this);
mDecoding = true;
// Check to make sure that this instance has been initialized with data
if (!getData() || (getDataSize() < 16))
{
setLastError("LLImageJ2C uninitialized");
res = true; // done
}
else
{
// Update the raw discard level
updateRawDiscardLevel();
res = mImpl->decodeImpl(*this, *raw_imagep, decode_time, first_channel, max_channel_count);
}
}
if (res)
{
if (!mDecoding)
{
// Failed
raw_imagep->deleteData();
res = false;
}
else
{
mDecoding = false;
}
}
else
{
if (mDecoding)
{
LL_WARNS() << "decodeImpl failed but mDecoding is true" << LL_ENDL;
mDecoding = false;
}
}
if (!mLastError.empty())
{
LLImage::setLastError(mLastError);
}
LLImageCompressionTester* tester = (LLImageCompressionTester*)LLMetricPerformanceTesterBasic::getTester(sTesterName);
if (tester)
{
// Decompression stat gathering
// Note that we *do not* take into account the decompression failures data so we might overestimate the time spent processing
// Always add the decompression time to the stat
tester->updateDecompressionStats(elapsed.getElapsedTimeF32()) ;
if (res)
{
// The whole data stream is finally decompressed when res is returned as true
tester->updateDecompressionStats(this->getDataSize(), raw_imagep->getDataSize()) ;
}
}
return res;
}
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)
{
LLTimer elapsed;
resetLastError();
bool res = mImpl->encodeImpl(*this, *raw_imagep, comment_text, encode_time, mReversible);
if (!mLastError.empty())
{
LLImage::setLastError(mLastError);
}
LLImageCompressionTester* tester = (LLImageCompressionTester*)LLMetricPerformanceTesterBasic::getTester(sTesterName);
if (tester)
{
// Compression stat gathering
// Note that we *do not* take into account the compression failures cases so we night overestimate the time spent processing
// Always add the compression time to the stat
tester->updateCompressionStats(elapsed.getElapsedTimeF32()) ;
if (res)
{
// The whole data stream is finally compressed when res is returned as true
tester->updateCompressionStats(this->getDataSize(), raw_imagep->getDataSize()) ;
}
}
return res;
}
//static
S32 LLImageJ2C::calcHeaderSizeJ2C()
{
return FIRST_PACKET_SIZE; // Hack. just needs to be >= actual header size...
}
//static
S32 LLImageJ2C::calcDataSizeJ2C(S32 w, S32 h, S32 comp, S32 discard_level, F32 rate)
{
// Note: This provides an estimation for the first to last quality layer of a given discard level
// This is however an efficient approximation, as the true discard level boundary would be
// in general too big for fast fetching.
// For details about the equation used here, see https://wiki.lindenlab.com/wiki/THX1138_KDU_Improvements#Byte_Range_Study
// Estimate the number of layers. This is consistent with what's done for j2c encoding in LLImageJ2CKDU::encodeImpl().
constexpr S32 precision = 8; // assumed bitrate per component channel, might change in future for HDR support
constexpr S32 max_components = 4; // assumed the file has four components; three color and alpha
// Use MAX_IMAGE_SIZE_DEFAULT (currently 2048) if either dimension is unknown (zero)
S32 width = (w > 0) ? w : 2048;
S32 height = (h > 0) ? h : 2048;
S32 max_dimension = llmax(width, height); // Find largest dimension
S32 block_area = MAX_BLOCK_SIZE * MAX_BLOCK_SIZE; // Calculated initial block area from established max block size (currently 64)
block_area *= llmax((max_dimension / MAX_BLOCK_SIZE / max_components), 1); // Adjust initial block area by ratio of largest dimension to block size per component
S32 totalbytes = (S32) (block_area * max_components * precision); // First block layer computed before loop without compression rate
S32 block_layers = 1; // Start at layer 1 since first block layer is computed outside loop
while (block_layers < 6) // Walk five layers for the five discards in JPEG2000
{
if (block_layers <= (5 - discard_level)) // Walk backwards from discard 5 to required discard layer.
totalbytes += (S32) (block_area * max_components * precision * rate); // Add each block layer reduced by assumed compression rate
block_layers++; // Move to next layer
block_area *= 4; // Increase block area by power of four
}
totalbytes /= 8; // to bytes
totalbytes += calcHeaderSizeJ2C(); // header
return totalbytes;
}
S32 LLImageJ2C::calcHeaderSize()
{
return calcHeaderSizeJ2C();
}
// calcDataSize() returns how many bytes to read to load discard_level (including header)
S32 LLImageJ2C::calcDataSize(S32 discard_level)
{
discard_level = llclamp(discard_level, 0, MAX_DISCARD_LEVEL);
if ( mAreaUsedForDataSizeCalcs != (getHeight() * getWidth())
|| (mDataSizes[0] == 0))
{
mAreaUsedForDataSizeCalcs = getHeight() * getWidth();
S32 level = MAX_DISCARD_LEVEL; // Start at the highest discard
while ( level >= 0 )
{
mDataSizes[level] = calcDataSizeJ2C(getWidth(), getHeight(), getComponents(), level, mRate);
level--;
}
}
return mDataSizes[discard_level];
}
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);
// Use TextureReverseByteRange percent (see settings.xml) of the optimal size to qualify as correct rendering for the given discard level
if (bytes >= (bytes_needed*LLImage::getReverseByteRangePercent()/100))
{
break;
}
discard_level++;
if (discard_level >= MAX_DISCARD_LEVEL)
{
break;
}
}
return discard_level;
}
void LLImageJ2C::setMaxBytes(S32 max_bytes)
{
mMaxBytes = max_bytes;
}
void LLImageJ2C::setReversible(const bool reversible)
{
mReversible = reversible;
}
bool LLImageJ2C::loadAndValidate(const std::string &filename)
{
bool res = true;
resetLastError();
S32 file_size = 0;
LLAPRFile infile ;
infile.open(filename, LL_APR_RB, NULL, &file_size);
// <FS:ND> Remove LLVolatileAPRPool/apr_file_t and use FILE* instead
// apr_file_t* apr_file = infile.getFileHandle() ;
LLAPRFile::tFiletype* apr_file = infile.getFileHandle() ;
// </FS:ND>
if (!apr_file)
{
setLastError("Unable to open file for reading", filename);
res = false;
}
else if (file_size == 0)
{
setLastError("File is empty",filename);
res = false;
}
else
{
U8 *data = (U8*)ll_aligned_malloc_16(file_size);
if (!data)
{
infile.close();
setLastError("Out of memory", filename);
res = false;
}
else
{
apr_size_t bytes_read = file_size;
apr_status_t s = apr_file_read(apr_file, data, &bytes_read); // modifies bytes_read
infile.close();
if (s != APR_SUCCESS || (S32)bytes_read != file_size)
{
ll_aligned_free_16(data);
setLastError("Unable to read entire file");
res = false;
}
else
{
res = validate(data, file_size);
}
}
}
if (!mLastError.empty())
{
LLImage::setLastError(mLastError);
}
return res;
}
bool LLImageJ2C::validate(U8 *data, U32 file_size)
{
resetLastError();
LLImageDataLock lock(this);
setData(data, file_size);
bool res = updateData();
if ( res )
{
// Check to make sure that this instance has been initialized with data
if (!getData() || (0 == getDataSize()))
{
setLastError("LLImageJ2C uninitialized");
res = false;
}
else
{
res = mImpl->getMetadata(*this);
}
}
if (!mLastError.empty())
{
LLImage::setLastError(mLastError);
}
return res;
}
void LLImageJ2C::decodeFailed()
{
mDecoding = false;
}
void LLImageJ2C::updateRawDiscardLevel()
{
mRawDiscardLevel = mMaxBytes ? calcDiscardLevelBytes(mMaxBytes) : mDiscardLevel;
}
LLImageJ2CImpl::~LLImageJ2CImpl()
{
}
//----------------------------------------------------------------------------------------------
// Start of LLImageCompressionTester
//----------------------------------------------------------------------------------------------
LLImageCompressionTester::LLImageCompressionTester() : LLMetricPerformanceTesterBasic(sTesterName)
{
addMetric("Time Decompression (s)");
addMetric("Volume In Decompression (kB)");
addMetric("Volume Out Decompression (kB)");
addMetric("Decompression Ratio (x:1)");
addMetric("Perf Decompression (kB/s)");
addMetric("Time Compression (s)");
addMetric("Volume In Compression (kB)");
addMetric("Volume Out Compression (kB)");
addMetric("Compression Ratio (x:1)");
addMetric("Perf Compression (kB/s)");
mRunBytesInDecompression = 0;
mRunBytesOutDecompression = 0;
mRunBytesInCompression = 0;
mTotalBytesInDecompression = 0;
mTotalBytesOutDecompression = 0;
mTotalBytesInCompression = 0;
mTotalBytesOutCompression = 0;
mTotalTimeDecompression = 0.0f;
mTotalTimeCompression = 0.0f;
mRunTimeDecompression = 0.0f;
}
LLImageCompressionTester::~LLImageCompressionTester()
{
outputTestResults();
LLImageJ2C::sTesterp = NULL;
}
//virtual
void LLImageCompressionTester::outputTestRecord(LLSD *sd)
{
std::string currentLabel = getCurrentLabelName();
F32 decompressionPerf = 0.0f;
F32 compressionPerf = 0.0f;
F32 decompressionRate = 0.0f;
F32 compressionRate = 0.0f;
F32 totalkBInDecompression = (F32)(mTotalBytesInDecompression) / 1000.f;
F32 totalkBOutDecompression = (F32)(mTotalBytesOutDecompression) / 1000.f;
F32 totalkBInCompression = (F32)(mTotalBytesInCompression) / 1000.f;
F32 totalkBOutCompression = (F32)(mTotalBytesOutCompression) / 1000.f;
if (!is_approx_zero(mTotalTimeDecompression))
{
decompressionPerf = totalkBInDecompression / mTotalTimeDecompression;
}
if (!is_approx_zero(totalkBInDecompression))
{
decompressionRate = totalkBOutDecompression / totalkBInDecompression;
}
if (!is_approx_zero(mTotalTimeCompression))
{
compressionPerf = totalkBInCompression / mTotalTimeCompression;
}
if (!is_approx_zero(totalkBOutCompression))
{
compressionRate = totalkBInCompression / totalkBOutCompression;
}
(*sd)[currentLabel]["Time Decompression (s)"] = (LLSD::Real)mTotalTimeDecompression;
(*sd)[currentLabel]["Volume In Decompression (kB)"] = (LLSD::Real)totalkBInDecompression;
(*sd)[currentLabel]["Volume Out Decompression (kB)"]= (LLSD::Real)totalkBOutDecompression;
(*sd)[currentLabel]["Decompression Ratio (x:1)"] = (LLSD::Real)decompressionRate;
(*sd)[currentLabel]["Perf Decompression (kB/s)"] = (LLSD::Real)decompressionPerf;
(*sd)[currentLabel]["Time Compression (s)"] = (LLSD::Real)mTotalTimeCompression;
(*sd)[currentLabel]["Volume In Compression (kB)"] = (LLSD::Real)totalkBInCompression;
(*sd)[currentLabel]["Volume Out Compression (kB)"] = (LLSD::Real)totalkBOutCompression;
(*sd)[currentLabel]["Compression Ratio (x:1)"] = (LLSD::Real)compressionRate;
(*sd)[currentLabel]["Perf Compression (kB/s)"] = (LLSD::Real)compressionPerf;
}
void LLImageCompressionTester::updateCompressionStats(const F32 deltaTime)
{
mTotalTimeCompression += deltaTime;
}
void LLImageCompressionTester::updateCompressionStats(const S32 bytesCompress, const S32 bytesRaw)
{
mTotalBytesInCompression += bytesRaw;
mRunBytesInCompression += bytesRaw;
mTotalBytesOutCompression += bytesCompress;
if (mRunBytesInCompression > (1000000))
{
// Output everything
outputTestResults();
// Reset the compression data of the run
mRunBytesInCompression = 0;
}
}
void LLImageCompressionTester::updateDecompressionStats(const F32 deltaTime)
{
mTotalTimeDecompression += deltaTime;
}
void LLImageCompressionTester::updateDecompressionStats(const S32 bytesIn, const S32 bytesOut)
{
mTotalBytesInDecompression += bytesIn;
mRunBytesInDecompression += bytesIn;
mTotalBytesOutDecompression += bytesOut;
mRunBytesOutDecompression += bytesOut;
//if (mRunBytesInDecompression > (1000000))
if (mRunBytesOutDecompression > (10000000))
//if ((mTotalTimeDecompression - mRunTimeDecompression) >= (5.0f))
{
// Output everything
outputTestResults();
// Reset the decompression data of the run
mRunBytesInDecompression = 0;
mRunBytesOutDecompression = 0;
mRunTimeDecompression = mTotalTimeDecompression;
}
}
//----------------------------------------------------------------------------------------------
// End of LLTexturePipelineTester
//----------------------------------------------------------------------------------------------
+172
View File
@@ -0,0 +1,172 @@
/**
* @file llimagej2c.h
* @brief Image implmenation for jpeg2000.
*
* $LicenseInfo:firstyear=2001&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#ifndef LL_LLIMAGEJ2C_H
#define LL_LLIMAGEJ2C_H
#include "llimage.h"
#include "llassettype.h"
#include "llmetricperformancetester.h"
// JPEG2000 : compression rate used in j2c conversion.
const F32 DEFAULT_COMPRESSION_RATE = 1.f/8.f;
class LLImageJ2CImpl;
class LLImageCompressionTester ;
class LLImageJ2C : public LLImageFormatted
{
protected:
virtual ~LLImageJ2C();
public:
LLImageJ2C();
// Base class overrides
/*virtual*/ std::string getExtension() { return std::string("j2c"); }
/*virtual*/ bool updateData();
/*virtual*/ bool decode(LLImageRaw *raw_imagep, F32 decode_time);
/*virtual*/ bool decodeChannels(LLImageRaw *raw_imagep, F32 decode_time, S32 first_channel, S32 max_channel_count);
/*virtual*/ bool encode(const LLImageRaw *raw_imagep, F32 encode_time);
/*virtual*/ S32 calcHeaderSize();
/*virtual*/ S32 calcDataSize(S32 discard_level = 0);
/*virtual*/ S32 calcDiscardLevelBytes(S32 bytes);
/*virtual*/ S8 getRawDiscardLevel();
// Override these so that we don't try to set a global variable from a DLL
/*virtual*/ void resetLastError();
/*virtual*/ void setLastError(const std::string& message, const std::string& filename = std::string());
bool initDecode(LLImageRaw &raw_image, int discard_level, int* region);
bool initEncode(LLImageRaw &raw_image, int blocks_size, int precincts_size, int levels);
// 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 std::string &filename);
// Encode accessors
void setReversible(const bool reversible); // Use non-lossy?
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 = DEFAULT_COMPRESSION_RATE);
static std::string getEngineInfo();
protected:
friend class LLImageJ2CImpl;
friend class LLImageJ2COJ;
friend class LLImageJ2CKDU;
friend class LLImageCompressionTester;
void decodeFailed();
void updateRawDiscardLevel();
S32 mMaxBytes; // Maximum number of bytes of data to use...
S32 mDataSizes[MAX_DISCARD_LEVEL+1]; // Size of data required to reach a given level
U32 mAreaUsedForDataSizeCalcs; // Height * width used to calculate mDataSizes
S8 mRawDiscardLevel;
F32 mRate;
bool mReversible;
std::unique_ptr<LLImageJ2CImpl> mImpl;
std::string mLastError;
// Image compression/decompression tester
static LLImageCompressionTester* sTesterp;
};
// Derive from this class to implement JPEG2000 decoding
class LLImageJ2CImpl
{
public:
virtual ~LLImageJ2CImpl();
protected:
// Find out the image size and number of channels.
// Return value:
// true: image size and number of channels was determined
// false: error on decode
virtual bool getMetadata(LLImageJ2C &base) = 0;
// Decode the raw image optionally aborting (to continue later) after
// decode_time seconds. Decode at most max_channel_count and start
// decoding channel first_channel.
// Return value:
// true: decoding complete (even if it failed)
// false: time expired while decoding
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,
bool reversible=false) = 0;
virtual bool initDecode(LLImageJ2C &base, LLImageRaw &raw_image, int discard_level = -1, int* region = NULL) = 0;
virtual bool initEncode(LLImageJ2C &base, LLImageRaw &raw_image, int blocks_size = -1, int precincts_size = -1, int levels = 0) = 0;
virtual std::string getEngineInfo() const = 0;
friend class LLImageJ2C;
};
#define LINDEN_J2C_COMMENT_PREFIX "LL_" // Used by LLAppearanceUtility
//
// This class is used for performance data gathering only.
// Tracks the image compression / decompression data,
// records and outputs them to the log file.
//
class LLImageCompressionTester : public LLMetricPerformanceTesterBasic
{
public:
LLImageCompressionTester();
~LLImageCompressionTester();
void updateDecompressionStats(const F32 deltaTime) ;
void updateDecompressionStats(const S32 bytesIn, const S32 bytesOut) ;
void updateCompressionStats(const F32 deltaTime) ;
void updateCompressionStats(const S32 bytesIn, const S32 bytesOut) ;
protected:
/*virtual*/ void outputTestRecord(LLSD* sd);
private:
//
// Data size
//
U32 mTotalBytesInDecompression; // Total bytes fed to decompressor
U32 mTotalBytesOutDecompression; // Total bytes produced by decompressor
U32 mTotalBytesInCompression; // Total bytes fed to compressor
U32 mTotalBytesOutCompression; // Total bytes produced by compressor
U32 mRunBytesInDecompression; // Bytes fed to decompressor in this run
U32 mRunBytesOutDecompression; // Bytes produced by the decompressor in this run
U32 mRunBytesInCompression; // Bytes fed to compressor in this run
//
// Time
//
F32 mTotalTimeDecompression; // Total time spent in computing decompression
F32 mTotalTimeCompression; // Total time spent in computing compression
F32 mRunTimeDecompression; // Time in this run (we output every 5 sec in decompress)
};
#endif
+675
View File
@@ -0,0 +1,675 @@
/**
* @file llimagejpeg.cpp
*
* $LicenseInfo:firstyear=2002&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#include "linden_common.h"
#include "stdtypes.h"
#include "llimagejpeg.h"
#include "llerror.h"
#include "llexception.h"
jmp_buf LLImageJPEG::sSetjmpBuffer ;
LLImageJPEG::LLImageJPEG(S32 quality)
: LLImageFormatted(IMG_CODEC_JPEG),
mOutputBuffer( NULL ),
mOutputBufferSize( 0 ),
mEncodeQuality( quality ) // 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();
LLImageDataLock lock(this);
// 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/catch will crash on Mac and Linux if LLImageJPEG::errorExit throws an error
//so as instead, we use setjmp/longjmp to avoid this crash, which is the best we can get. --bao
//
if(setjmp(sSetjmpBuffer))
{
jpeg_destroy_decompress(&cinfo);
return false;
}
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
}
// Returns true when done, whether or not decode was successful.
bool LLImageJPEG::decode(LLImageRaw* raw_image, F32 decode_time)
{
llassert_always(raw_image);
resetLastError();
LLImageDataLock lockIn(this);
LLImageDataLock lockOut(raw_image);
// 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 true; // done
}
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/catch will crash on Mac and Linux if LLImageJPEG::errorExit throws an error
//so as instead, we use setjmp/longjmp to avoid this crash, which is the best we can get. --bao
//
if(setjmp(sSetjmpBuffer))
{
jpeg_destroy_decompress(&cinfo);
return true; // done
}
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)
if (!raw_image->resize(getWidth(), getHeight(), getComponents()))
{
throw std::bad_alloc();
}
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 (std::bad_alloc&)
{
setLastError( "Out of memory");
jpeg_destroy_decompress(&cinfo);
return true; // done
}
catch (int)
{
jpeg_destroy_decompress(&cinfo);
return true; // done
}
// 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 true; // done
}
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(std::nothrow) U8[ new_buffer_size ];
if (!new_buffer)
{
self->setLastError("Out of memory in LLImageJPEG::encodeEmptyOutputBuffer( j_compress_ptr cinfo )");
LLTHROW(LLContinueError("Out of memory in LLImageJPEG::encodeEmptyOutputBuffer( j_compress_ptr cinfo )"));
}
memcpy( new_buffer, self->mOutputBuffer, self->mOutputBufferSize ); /* Flawfinder: ignore */
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;
LLImageDataLock lock(self);
S32 file_bytes = (S32)(self->mOutputBufferSize - cinfo->dest->free_in_buffer);
if(self->allocateData(file_bytes))
memcpy( self->getData(), self->mOutputBuffer, file_bytes ); /* Flawfinder: ignore */
else
LL_WARNS() << "allocateData() failed." << LL_ENDL;
}
// 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
longjmp(sSetjmpBuffer, 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]; /* Flawfinder: ignore */
(*cinfo->err->format_message) (cinfo, buffer);
std::string error = buffer ;
LLImage::setLastError(error);
bool is_decode = (cinfo->is_decompressor != 0);
LL_WARNS() << "LLImageJPEG " << (is_decode ? "decode " : "encode ") << " failed: " << buffer << LL_ENDL;
}
bool LLImageJPEG::encode( const LLImageRaw* raw_image, F32 encode_time )
{
llassert_always(raw_image);
resetLastError();
LLImageDataSharedLock lockIn(raw_image);
LLImageDataLock lockOut(this);
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(std::nothrow) U8[ mOutputBufferSize ];
if (mOutputBuffer == NULL)
{
mOutputBufferSize = 0;
setLastError("Failed to allocate output buffer");
return false;
}
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
//
//try/catch will crash on Mac and Linux if LLImageJPEG::errorExit throws an error
//so as instead, we use setjmp/longjmp to avoid this crash, which is the best we can get. --bao
//
if( setjmp(sSetjmpBuffer) )
{
// 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;
}
+84
View File
@@ -0,0 +1,84 @@
/**
* @file llimagejpeg.h
* @brief This class compresses and decompresses JPEG files
*
* $LicenseInfo:firstyear=2002&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#ifndef LL_LLIMAGEJPEG_H
#define LL_LLIMAGEJPEG_H
#include <csetjmp>
#include "llimage.h"
extern "C" {
#ifdef LL_USESYSTEMLIBS
# include <jpeglib.h>
# include <jerror.h>
#else
# include "jpeglib/jpeglib.h"
# include "jpeglib/jerror.h"
#endif
}
class LLImageJPEG : public LLImageFormatted
{
protected:
virtual ~LLImageJPEG();
public:
LLImageJPEG(S32 quality = 75);
/*virtual*/ std::string getExtension() { return std::string("jpg"); }
/*virtual*/ bool updateData();
/*virtual*/ bool decode(LLImageRaw* raw_image, F32 decode_time);
/*virtual*/ bool encode(const LLImageRaw* raw_image, F32 encode_time);
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);
protected:
U8* mOutputBuffer; // temp buffer used during encoding
S32 mOutputBufferSize; // bytes in mOuputBuffer
S32 mEncodeQuality; // on a scale from 1 to 100
private:
static jmp_buf sSetjmpBuffer; // To allow the library to abort.
};
#endif // LL_LLIMAGEJPEG_H
+178
View File
@@ -0,0 +1,178 @@
/*
* @file llimagepng.cpp
* @brief LLImageFormatted glue to encode / decode PNG files.
*
* $LicenseInfo:firstyear=2007&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#include "linden_common.h"
#include "stdtypes.h"
#include "llerror.h"
#include "llexception.h"
#include "llimage.h"
#include "llpngwrapper.h"
#include "llimagepng.h"
// ---------------------------------------------------------------------------
// LLImagePNG
// ---------------------------------------------------------------------------
LLImagePNG::LLImagePNG()
: LLImageFormatted(IMG_CODEC_PNG)
{
}
LLImagePNG::~LLImagePNG()
{
}
// Virtual
// Parse PNG image information and set the appropriate
// width, height and component (channel) information.
bool LLImagePNG::updateData()
{
resetLastError();
try
{
LLImageDataLock lock(this);
// Check to make sure that this instance has been initialized with data
if (!getData() || (0 == getDataSize()))
{
setLastError("Uninitialized instance of LLImagePNG");
return false;
}
// Decode the PNG data and extract sizing information
LLPngWrapper pngWrapper;
if (!pngWrapper.isValidPng(getData()))
{
setLastError("LLImagePNG data does not have a valid PNG header!");
return false;
}
LLPngWrapper::ImageInfo infop;
if (!pngWrapper.readPng(getData(), getDataSize(), NULL, &infop))
{
setLastError(pngWrapper.getErrorMessage());
return false;
}
setSize(infop.mWidth, infop.mHeight, infop.mComponents);
}
catch (const LLContinueError& msg)
{
setLastError(msg.what());
LOG_UNHANDLED_EXCEPTION("");
return false;
}
catch (...)
{
setLastError("LLImagePNG");
LOG_UNHANDLED_EXCEPTION("");
return false;
}
return true;
}
// Virtual
// Decode an in-memory PNG image into the raw RGB or RGBA format
// used within SecondLife.
bool LLImagePNG::decode(LLImageRaw* raw_image, F32 decode_time)
{
llassert_always(raw_image);
resetLastError();
LLImageDataSharedLock lockIn(this);
LLImageDataLock lockOut(raw_image);
// Check to make sure that this instance has been initialized with data
if (!getData() || (0 == getDataSize()))
{
setLastError("LLImagePNG trying to decode an image with no data!");
return false;
}
// Decode the PNG data into the raw image
LLPngWrapper pngWrapper;
if (!pngWrapper.isValidPng(getData()))
{
setLastError("LLImagePNG data does not have a valid PNG header!");
return false;
}
if (! pngWrapper.readPng(getData(), getDataSize(), raw_image))
{
setLastError(pngWrapper.getErrorMessage());
return false;
}
return true;
}
// Virtual
// Encode the in memory RGB image into PNG format.
bool LLImagePNG::encode(const LLImageRaw* raw_image, F32 encode_time)
{
llassert_always(raw_image);
resetLastError();
LLImageDataSharedLock lockIn(raw_image);
LLImageDataLock lockOut(this);
// Image logical size
setSize(raw_image->getWidth(), raw_image->getHeight(), raw_image->getComponents());
// Temporary buffer to hold the encoded image. Note: the final image
// size should be much smaller due to compression.
U32 bufferSize = getWidth() * getHeight() * getComponents() + 8192;
U8* tmpWriteBuffer = new(std::nothrow) U8[ bufferSize ];
if (!tmpWriteBuffer)
{
setLastError("LLImagePNG::out of memory");
return false;
}
// Delegate actual encoding work to wrapper
LLPngWrapper pngWrapper;
if (!pngWrapper.writePng(raw_image, tmpWriteBuffer, bufferSize))
{
setLastError(pngWrapper.getErrorMessage());
delete[] tmpWriteBuffer;
return false;
}
// Resize internal buffer and copy from temp
U32 encodedSize = pngWrapper.getFinalSize();
if(allocateData(encodedSize))
memcpy(getData(), tmpWriteBuffer, encodedSize);
else
LL_WARNS() << "allocateData() failed." << LL_ENDL;
delete[] tmpWriteBuffer;
return (getData()!=NULL);
}
+46
View File
@@ -0,0 +1,46 @@
/*
* @file llimagepng.h
*
* $LicenseInfo:firstyear=2007&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#ifndef LL_LLIMAGEPNG_H
#define LL_LLIMAGEPNG_H
#include "stdtypes.h"
#include "llimage.h"
class LLImagePNG : public LLImageFormatted
{
protected:
~LLImagePNG();
public:
LLImagePNG();
/*virtual*/ std::string getExtension() { return std::string("png"); }
/*virtual*/ bool updateData();
/*virtual*/ bool decode(LLImageRaw* raw_image, F32 decode_time);
/*virtual*/ bool encode(const LLImageRaw* raw_image, F32 encode_time);
};
#endif
File diff suppressed because it is too large Load Diff
+108
View File
@@ -0,0 +1,108 @@
/**
* @file llimagetga.h
* @brief Image implementation to compresses and decompressed TGA files.
*
* $LicenseInfo:firstyear=2001&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#ifndef LL_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 std::string& file_name);
/*virtual*/ std::string getExtension() { return std::string("tga"); }
/*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 std::string& 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
+240
View File
@@ -0,0 +1,240 @@
/**
* @file llimageworker.cpp
* @brief Base class for images.
*
* $LicenseInfo:firstyear=2001&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#include "linden_common.h"
#include "llimageworker.h"
#include "llimagedxt.h"
#include "threadpool.h"
/*--------------------------------------------------------------------------*/
class ImageRequest
{
public:
ImageRequest(const LLPointer<LLImageFormatted>& image,
S32 discard,
bool needs_aux,
const LLPointer<LLImageDecodeThread::Responder>& responder,
U32 request_id);
virtual ~ImageRequest();
/*virtual*/ bool processRequest();
/*virtual*/ void finishRequest(bool completed);
private:
// LLPointers stored in ImageRequest MUST be LLPointer instances rather
// than references: we need to increment the refcount when storing these.
// input
LLPointer<LLImageFormatted> mFormattedImage;
S32 mDiscardLevel;
U32 mRequestId;
bool mNeedsAux;
// output
LLPointer<LLImageRaw> mDecodedImageRaw;
LLPointer<LLImageRaw> mDecodedImageAux;
bool mDecodedRaw;
bool mDecodedAux;
LLPointer<LLImageDecodeThread::Responder> mResponder;
std::string mErrorString;};
//----------------------------------------------------------------------------
// MAIN THREAD
LLImageDecodeThread::LLImageDecodeThread(bool /*threaded*/)
: mDecodeCount(0)
{
mThreadPool.reset(new LL::ThreadPool("ImageDecode", 8));
mThreadPool->start();
}
//virtual
LLImageDecodeThread::~LLImageDecodeThread()
{}
// MAIN THREAD
// virtual
size_t LLImageDecodeThread::update(F32 max_time_ms)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE;
return getPending();
}
size_t LLImageDecodeThread::getPending()
{
return mThreadPool->getQueue().size();
}
LLImageDecodeThread::handle_t LLImageDecodeThread::decodeImage(
const LLPointer<LLImageFormatted>& image,
S32 discard,
bool needs_aux,
const LLPointer<LLImageDecodeThread::Responder>& responder)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE;
U32 decode_id = ++mDecodeCount;
if (decode_id == 0)
decode_id = ++mDecodeCount;
// Instantiate the ImageRequest right in the lambda, why not?
bool posted = mThreadPool->getQueue().post(
[req = ImageRequest(image, discard, needs_aux, responder, decode_id)]
() mutable
{
auto done = req.processRequest();
req.finishRequest(done);
});
if (! posted)
{
LL_DEBUGS() << "Tried to start decoding on shutdown" << LL_ENDL;
return 0;
}
return decode_id;
}
void LLImageDecodeThread::shutdown()
{
mThreadPool->close();
}
LLImageDecodeThread::Responder::~Responder()
{
}
//----------------------------------------------------------------------------
ImageRequest::ImageRequest(const LLPointer<LLImageFormatted>& image,
S32 discard,
bool needs_aux,
const LLPointer<LLImageDecodeThread::Responder>& responder,
U32 request_id)
: mFormattedImage(image),
mDiscardLevel(discard),
mNeedsAux(needs_aux),
mDecodedRaw(false),
mDecodedAux(false),
mResponder(responder),
mRequestId(request_id)
{
}
ImageRequest::~ImageRequest()
{
mDecodedImageRaw = NULL;
mDecodedImageAux = NULL;
mFormattedImage = NULL;
}
//----------------------------------------------------------------------------
// Returns true when done, whether or not decode was successful.
bool ImageRequest::processRequest()
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE;
if (mFormattedImage.isNull())
return true;
const F32 decode_time_slice = 0.f; //disable time slicing
bool done = true;
LLImageDataLock lockFormatted(mFormattedImage);
LLImageDataLock lockDecodedRaw(mDecodedImageRaw);
LLImageDataLock lockDecodedAux(mDecodedImageAux);
if (!mDecodedRaw)
{
// Decode primary channels
if (mDecodedImageRaw.isNull())
{
// parse formatted header
if (!mFormattedImage->updateData())
{
return true; // done (failed)
}
if ((mFormattedImage->getWidth() * mFormattedImage->getHeight() * mFormattedImage->getComponents()) == 0)
{
return true; // done (failed)
}
if (mDiscardLevel >= 0)
{
mFormattedImage->setDiscardLevel(mDiscardLevel);
}
mDecodedImageRaw = new LLImageRaw(mFormattedImage->getWidth(),
mFormattedImage->getHeight(),
mFormattedImage->getComponents());
}
// <FS:ND> Probably out of memory crash
// done = mFormattedImage->decode(mDecodedImageRaw, decode_time_slice);
if( mDecodedImageRaw->getData() )
done = mFormattedImage->decode(mDecodedImageRaw, decode_time_slice);
else
{
LL_WARNS() << "No memory for LLImageRaw of size " << (U32)mFormattedImage->getWidth() << "x" << (U32)mFormattedImage->getHeight() << "x"
<< (U32)mFormattedImage->getComponents() << LL_ENDL;
done = false;
}
// </FS:ND>
// some decoders are removing data when task is complete and there were errors
mDecodedRaw = done && mDecodedImageRaw->getData();
// Pick up errors from decoding
mErrorString = LLImage::getLastThreadError();
}
if (done && mNeedsAux && !mDecodedAux && mFormattedImage.notNull())
{
// Decode aux channel
if (!mDecodedImageAux)
{
mDecodedImageAux = new LLImageRaw(mFormattedImage->getWidth(),
mFormattedImage->getHeight(),
1);
}
done = mFormattedImage->decodeChannels(mDecodedImageAux, decode_time_slice, 4, 4);
mDecodedAux = done && mDecodedImageAux->getData();
// Pick up errors from decoding
mErrorString = LLImage::getLastThreadError();
}
return done;
}
void ImageRequest::finishRequest(bool completed)
{
LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE;
if (mResponder.notNull())
{
bool success = completed && mDecodedRaw && (!mNeedsAux || mDecodedAux);
mResponder->completed(success, mErrorString, mDecodedImageRaw, mDecodedImageAux, mRequestId);
}
// Will automatically be deleted
}
+67
View File
@@ -0,0 +1,67 @@
/**
* @file llimageworker.h
* @brief Object for managing images and their textures.
*
* $LicenseInfo:firstyear=2000&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#ifndef LL_LLIMAGEWORKER_H
#define LL_LLIMAGEWORKER_H
#include "llimage.h"
#include "llpointer.h"
#include "threadpool_fwd.h"
class LLImageDecodeThread
{
public:
class Responder : public LLThreadSafeRefCount
{
protected:
virtual ~Responder();
public:
virtual void completed(bool success, const std::string& error_message, LLImageRaw* raw, LLImageRaw* aux, U32 request_id) = 0;
};
public:
LLImageDecodeThread(bool threaded = true);
virtual ~LLImageDecodeThread();
// meant to resemble LLQueuedThread::handle_t
typedef U32 handle_t;
handle_t decodeImage(const LLPointer<LLImageFormatted>& image,
S32 discard, bool needs_aux,
const LLPointer<Responder>& responder);
size_t getPending();
size_t update(F32 max_time_ms);
S32 getTotalDecodeCount() { return mDecodeCount; }
void shutdown();
private:
// As of SL-17483, LLImageDecodeThread is no longer itself an
// LLQueuedThread - instead this is the API by which we submit work to the
// "ImageDecode" ThreadPool.
std::unique_ptr<LL::ThreadPool> mThreadPool;
LLAtomicU32 mDecodeCount;
};
#endif
+40
View File
@@ -0,0 +1,40 @@
/**
* @file llmapimagetype.h
*
* $LicenseInfo:firstyear=2003&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#ifndef LL_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
+415
View File
@@ -0,0 +1,415 @@
/*
* @file llpngwrapper.cpp
* @brief Encapsulates libpng read/write functionality.
*
* $LicenseInfo:firstyear=2007&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#include "linden_common.h"
#include "stdtypes.h"
#include "llerror.h"
#include "llimage.h"
#include "llpngwrapper.h"
#include "llexception.h"
namespace {
// Failure to load an image shouldn't crash the whole viewer.
struct PngError: public LLContinueError
{
PngError(png_const_charp msg): LLContinueError(msg) {}
};
} // anonymous namespace
// ---------------------------------------------------------------------------
// LLPngWrapper
// ---------------------------------------------------------------------------
LLPngWrapper::LLPngWrapper()
: mReadPngPtr( NULL ),
mReadInfoPtr( NULL ),
mWritePngPtr( NULL ),
mWriteInfoPtr( NULL ),
mRowPointers( NULL ),
mWidth( 0 ),
mHeight( 0 ),
mBitDepth( 0 ),
mColorType( 0 ),
mChannels( 0 ),
mInterlaceType( 0 ),
mCompressionType( 0 ),
mFilterMethod( 0 ),
mFinalSize( 0 ),
mGamma(0.f)
{
}
LLPngWrapper::~LLPngWrapper()
{
releaseResources();
}
// Checks the src for a valid PNG header
bool LLPngWrapper::isValidPng(U8* src)
{
const int PNG_BYTES_TO_CHECK = 8;
int sig = png_sig_cmp((png_bytep)src, (png_size_t)0, PNG_BYTES_TO_CHECK);
if (sig != 0)
{
mErrorMessage = "Invalid or corrupt PNG file";
return false;
}
return true;
}
// Called by the libpng library when a fatal encoding or decoding error
// occurs. We throw PngError and let our try/catch block clean up.
void LLPngWrapper::errorHandler(png_structp png_ptr, png_const_charp msg)
{
LLTHROW(PngError(msg));
}
// Called by the libpng library when reading (decoding) the PNG file. We
// copy the PNG data from our internal buffer into the PNG's data buffer.
void LLPngWrapper::readDataCallback(png_structp png_ptr, png_bytep dest, png_size_t length)
{
PngDataInfo *dataInfo = (PngDataInfo *) png_get_io_ptr(png_ptr);
if(dataInfo->mOffset + length > dataInfo->mDataSize)
{
png_error(png_ptr, "Data read error. Requested data size exceeds available data size.");
return;
}
U8 *src = &dataInfo->mData[dataInfo->mOffset];
memcpy(dest, src, length);
dataInfo->mOffset += static_cast<U32>(length);
}
// Called by the libpng library when writing (encoding) the PNG file. We
// copy the encoded result into our data buffer.
void LLPngWrapper::writeDataCallback(png_structp png_ptr, png_bytep src, png_size_t length)
{
PngDataInfo *dataInfo = (PngDataInfo *) png_get_io_ptr(png_ptr);
if (dataInfo->mOffset + length > dataInfo->mDataSize)
{
png_error(png_ptr, "Data write error. Requested data size exceeds available data size.");
return;
}
U8 *dest = &dataInfo->mData[dataInfo->mOffset];
memcpy(dest, src, length);
dataInfo->mOffset += static_cast<U32>(length);
}
// Flush the write output pointer
void LLPngWrapper::writeFlush(png_structp png_ptr)
{
// no-op since we're just writing to memory
}
// Read the PNG file using the libpng. The low-level interface is used here
// because we want to do various transformations (including applying gama)
// which can't be done with the high-level interface.
// The scanline also begins at the bottom of
// the image (per SecondLife conventions) instead of at the top, so we
// must assign row-pointers in "reverse" order.
bool LLPngWrapper::readPng(U8* src, S32 dataSize, LLImageRaw* rawImage, ImageInfo *infop)
{
try
{
// Create and initialize the png structures
mReadPngPtr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
this, &errorHandler, NULL);
if (mReadPngPtr == NULL)
{
LLTHROW(PngError("Problem creating png read structure"));
}
// Allocate/initialize the memory for image information.
mReadInfoPtr = png_create_info_struct(mReadPngPtr);
// Set up the input control
PngDataInfo dataPtr;
dataPtr.mData = src;
dataPtr.mOffset = 0;
dataPtr.mDataSize = dataSize;
png_set_read_fn(mReadPngPtr, &dataPtr, &readDataCallback);
png_set_sig_bytes(mReadPngPtr, 0);
// setup low-level read and get header information
png_read_info(mReadPngPtr, mReadInfoPtr);
png_get_IHDR(mReadPngPtr, mReadInfoPtr, &mWidth, &mHeight,
&mBitDepth, &mColorType, &mInterlaceType,
&mCompressionType, &mFilterMethod);
// Normalize the image, then get updated image information
// after transformations have been applied
normalizeImage();
updateMetaData();
// If a raw object is supplied, read the PNG image into its
// data space
if (rawImage != NULL)
{
LLImageDataLock lock(rawImage);
if (!rawImage->resize(static_cast<U16>(mWidth),
static_cast<U16>(mHeight), mChannels))
{
LLTHROW(PngError("Failed to resize image"));
}
U8 *dest = rawImage->getData();
int offset = mWidth * mChannels;
// Set up the row pointers and read the image
mRowPointers = new U8* [mHeight];
for (U32 i=0; i < mHeight; i++)
{
mRowPointers[i] = &dest[(mHeight-i-1)*offset];
}
png_read_image(mReadPngPtr, mRowPointers);
// Finish up, ensures all metadata are updated
png_read_end(mReadPngPtr, NULL);
}
// If an info object is supplied, copy the relevant info
if (infop != NULL)
{
infop->mHeight = static_cast<U16>(mHeight);
infop->mWidth = static_cast<U16>(mWidth);
infop->mComponents = mChannels;
}
mFinalSize = dataPtr.mOffset;
}
catch (const PngError& msg)
{
mErrorMessage = msg.what();
releaseResources();
return (false);
}
catch (std::bad_alloc&)
{
mErrorMessage = "LLPngWrapper";
releaseResources();
return (false);
}
catch (...)
{
mErrorMessage = "LLPngWrapper";
releaseResources();
LOG_UNHANDLED_EXCEPTION("");
return (false);
}
// Clean up and return
releaseResources();
return (true);
}
// Do transformations to normalize the input to 8-bpp RGBA
void LLPngWrapper::normalizeImage()
{
// 1. Expand any palettes
// 2. Convert grayscales to RGB
// 3. Create alpha layer from transparency
// 4. Ensure 8-bpp for all images
// 5. Set (or guess) gamma
if (mColorType == PNG_COLOR_TYPE_PALETTE)
{
png_set_palette_to_rgb(mReadPngPtr);
}
if (mColorType == PNG_COLOR_TYPE_GRAY && mBitDepth < 8)
{
png_set_expand_gray_1_2_4_to_8(mReadPngPtr);
}
if (mColorType == PNG_COLOR_TYPE_GRAY
|| mColorType == PNG_COLOR_TYPE_GRAY_ALPHA)
{
png_set_gray_to_rgb(mReadPngPtr);
}
if (png_get_valid(mReadPngPtr, mReadInfoPtr, PNG_INFO_tRNS))
{
png_set_tRNS_to_alpha(mReadPngPtr);
}
if (mBitDepth < 8)
{
png_set_packing(mReadPngPtr);
}
else if (mBitDepth == 16)
{
png_set_strip_16(mReadPngPtr);
}
const F64 SCREEN_GAMMA = 2.2;
if (png_get_gAMA(mReadPngPtr, mReadInfoPtr, &mGamma))
{
png_set_gamma(mReadPngPtr, SCREEN_GAMMA, mGamma);
}
else
{
png_set_gamma(mReadPngPtr, SCREEN_GAMMA, 1/SCREEN_GAMMA);
}
}
// Read out the image meta-data
void LLPngWrapper::updateMetaData()
{
png_read_update_info(mReadPngPtr, mReadInfoPtr);
mWidth = png_get_image_width(mReadPngPtr, mReadInfoPtr);
mHeight = png_get_image_height(mReadPngPtr, mReadInfoPtr);
mBitDepth = png_get_bit_depth(mReadPngPtr, mReadInfoPtr);
mColorType = png_get_color_type(mReadPngPtr, mReadInfoPtr);
mChannels = png_get_channels(mReadPngPtr, mReadInfoPtr);
}
// Method to write raw image into PNG at dest. The raw scanline begins
// at the bottom of the image per SecondLife conventions.
bool LLPngWrapper::writePng(const LLImageRaw* rawImage, U8* dest, size_t destSize)
{
try
{
S8 numComponents = rawImage->getComponents();
switch (numComponents)
{
case 1:
mColorType = PNG_COLOR_TYPE_GRAY;
break;
case 2:
mColorType = PNG_COLOR_TYPE_GRAY_ALPHA;
break;
case 3:
mColorType = PNG_COLOR_TYPE_RGB;
break;
case 4:
mColorType = PNG_COLOR_TYPE_RGB_ALPHA;
break;
default:
mColorType = -1;
}
if (mColorType == -1)
{
LLTHROW(PngError("Unsupported image: unexpected number of channels"));
}
mWritePngPtr = png_create_write_struct(PNG_LIBPNG_VER_STRING,
NULL, &errorHandler, NULL);
if (!mWritePngPtr)
{
LLTHROW(PngError("Problem creating png write structure"));
}
mWriteInfoPtr = png_create_info_struct(mWritePngPtr);
// Setup write function
PngDataInfo dataPtr{};
dataPtr.mData = dest;
dataPtr.mOffset = 0;
dataPtr.mDataSize = static_cast<S32>(destSize);
png_set_write_fn(mWritePngPtr, &dataPtr, &writeDataCallback, &writeFlush);
// Setup image params
mWidth = rawImage->getWidth();
mHeight = rawImage->getHeight();
mBitDepth = 8; // Fixed to 8-bpp in SL
mChannels = numComponents;
mInterlaceType = PNG_INTERLACE_NONE;
mCompressionType = PNG_COMPRESSION_TYPE_DEFAULT;
mFilterMethod = PNG_FILTER_TYPE_DEFAULT;
// Write header
png_set_IHDR(mWritePngPtr, mWriteInfoPtr, mWidth, mHeight,
mBitDepth, mColorType, mInterlaceType,
mCompressionType, mFilterMethod);
// Get data and compute row size
const U8* data = rawImage->getData();
int offset = mWidth * mChannels;
// Ready to write, start with the header
png_write_info(mWritePngPtr, mWriteInfoPtr);
// Write image (sorry, must const-cast for libpng)
const U8 * rowPointer;
for (U32 i=0; i < mHeight; i++)
{
rowPointer = &data[(mHeight-1-i)*offset];
png_write_row(mWritePngPtr, const_cast<png_bytep>(rowPointer));
}
// Finish up
png_write_end(mWritePngPtr, mWriteInfoPtr);
mFinalSize = dataPtr.mOffset;
}
catch (const PngError& msg)
{
mErrorMessage = msg.what();
releaseResources();
return (false);
}
releaseResources();
return true;
}
// Cleanup various internal structures
void LLPngWrapper::releaseResources()
{
if (mReadPngPtr || mReadInfoPtr)
{
png_destroy_read_struct(&mReadPngPtr, &mReadInfoPtr, NULL);
mReadPngPtr = NULL;
mReadInfoPtr = NULL;
}
if (mWritePngPtr || mWriteInfoPtr)
{
png_destroy_write_struct(&mWritePngPtr, &mWriteInfoPtr);
mWritePngPtr = NULL;
mWriteInfoPtr = NULL;
}
if (mRowPointers)
{
delete[] mRowPointers;
mRowPointers = NULL;
}
}
// Get final image size after compression
U32 LLPngWrapper::getFinalSize()
{
return mFinalSize;
}
// Get last error message, if any
const std::string& LLPngWrapper::getErrorMessage()
{
return mErrorMessage;
}
+97
View File
@@ -0,0 +1,97 @@
/*
* @file llpngwrapper.h
*
* $LicenseInfo:firstyear=2007&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#ifndef LL_LLPNGWRAPPER_H
#define LL_LLPNGWRAPPER_H
#include "png.h"
#include "llimage.h"
class LLPngWrapper
{
public:
LLPngWrapper();
virtual ~LLPngWrapper();
public:
struct ImageInfo
{
U16 mWidth;
U16 mHeight;
S8 mComponents;
};
bool isValidPng(U8* src);
bool readPng(U8* src, S32 dataSize, LLImageRaw* rawImage, ImageInfo *infop = NULL);
bool writePng(const LLImageRaw* rawImage, U8* dst, size_t destSize);
U32 getFinalSize();
const std::string& getErrorMessage();
protected:
void normalizeImage();
void updateMetaData();
private:
// Structure for writing/reading PNG data to/from memory
// as opposed to using a file.
struct PngDataInfo
{
U8 *mData;
U32 mOffset;
S32 mDataSize;
};
static void writeFlush(png_structp png_ptr);
static void errorHandler(png_structp png_ptr, png_const_charp msg);
static void readDataCallback(png_structp png_ptr, png_bytep dest, png_size_t length);
static void writeDataCallback(png_structp png_ptr, png_bytep src, png_size_t length);
void releaseResources();
png_structp mReadPngPtr;
png_infop mReadInfoPtr;
png_structp mWritePngPtr;
png_infop mWriteInfoPtr;
U8 **mRowPointers;
png_uint_32 mWidth;
png_uint_32 mHeight;
S32 mBitDepth;
S32 mColorType;
S32 mChannels;
S32 mInterlaceType;
S32 mCompressionType;
S32 mFilterMethod;
U32 mFinalSize;
F64 mGamma;
std::string mErrorMessage;
};
#endif
+169
View File
@@ -0,0 +1,169 @@
/**
* @file llimageworker_test.cpp
* @author Merov Linden
* @date 2009-04-28
*
* $LicenseInfo:firstyear=2006&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2010, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
// Precompiled header: almost always required for newview cpp files
#include "linden_common.h"
// Class to test
#include "../llimageworker.h"
// For timer class
#include "../llcommon/lltimer.h"
// for lltrace class
#include "../llcommon/lltrace.h"
// Tut header
#include "../test/lltut.h"
// -------------------------------------------------------------------------------------------
// Stubbing: Declarations required to link and run the class being tested
// Notes:
// * Add here stubbed implementation of the few classes and methods used in the class to be tested
// * Add as little as possible (let the link errors guide you)
// * Do not make any assumption as to how those classes or methods work (i.e. don't copy/paste code)
// * A simulator for a class can be implemented here. Please comment and document thoroughly.
LLImageBase::LLImageBase()
: mData(NULL),
mDataSize(0),
mWidth(0),
mHeight(0),
mComponents(0),
mBadBufferAllocation(false),
mAllowOverSize(false)
{
}
LLImageBase::~LLImageBase() {}
void LLImageBase::dump() { }
void LLImageBase::sanityCheck() { }
void LLImageBase::deleteData() { }
U8* LLImageBase::allocateData(S32 size) { return NULL; }
U8* LLImageBase::reallocateData(S32 size) { return NULL; }
LLImageRaw::LLImageRaw(U16 width, U16 height, S8 components) { }
LLImageRaw::~LLImageRaw() { }
void LLImageRaw::deleteData() { }
U8* LLImageRaw::allocateData(S32 size) { return NULL; }
U8* LLImageRaw::reallocateData(S32 size) { return NULL; }
const U8* LLImageBase::getData() const { return NULL; }
U8* LLImageBase::getData() { return NULL; }
const std::string& LLImage::getLastThreadError() { static std::string msg; return msg; }
// End Stubbing
// -------------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------------
// TUT
// -------------------------------------------------------------------------------------------
namespace tut
{
// Test wrapper declarations
// Note: We derive the responder class for 2 reasons:
// 1. It's a pure virtual class and we can't compile without completed() being implemented
// 2. We actually need a responder to test that the thread work test completed
// We implement this making no assumption on what's done in the thread or worker
// though, just that the responder's completed() method is called in the end.
// Note on responders: responders are ref counted and *will* be deleted by the request they are
// attached to when the queued request is deleted. The recommended way of using them is to
// create them when creating a request, put a callback method in completed() and not rely on
// anything to survive in the responder object once completed() has been called. Let the request
// do the deletion and clean up itself.
class responder_test : public LLImageDecodeThread::Responder
{
public:
responder_test(bool* res)
{
done = res;
*done = false;
}
virtual void completed(bool success, const std::string& error_message, LLImageRaw* raw, LLImageRaw* aux, U32 request_id)
{
*done = true;
}
private:
// This is what can be thought of as the minimal implementation of a responder
// Done will be switched to true when completed() is called and can be tested
// outside the responder. A better way of doing this is to store a callback here.
bool* done;
};
// Test wrapper declaration : decode thread
struct imagedecodethread_test
{
// Instance to be tested
LLImageDecodeThread* mThread;
// Constructor and destructor of the test wrapper
imagedecodethread_test()
{
mThread = NULL;
}
~imagedecodethread_test()
{
delete mThread;
}
};
// Tut templating thingamagic: test group, object and test instance
typedef test_group<imagedecodethread_test> imagedecodethread_t;
typedef imagedecodethread_t::object imagedecodethread_object_t;
tut::imagedecodethread_t tut_imagedecodethread("LLImageDecodeThread");
// ---------------------------------------------------------------------------------------
// Test functions
// Notes:
// * Test as many as you possibly can without requiring a full blown simulation of everything
// * The tests are executed in sequence so the test instance state may change between calls
// * Remember that you cannot test private methods with tut
// ---------------------------------------------------------------------------------------
// ---------------------------------------------------------------------------------------
// Test the LLImageDecodeThread interface
// ---------------------------------------------------------------------------------------
template<> template<>
void imagedecodethread_object_t::test<1>()
{
// Test a *threaded* instance of the class
mThread = new LLImageDecodeThread(true);
ensure("LLImageDecodeThread: threaded constructor failed", mThread != NULL);
// Insert something in the queue
bool done = false;
LLImageDecodeThread::handle_t decodeHandle = mThread->decodeImage(NULL, 0, false, new responder_test(&done));
// Verifies we get back a valid handle
ensure("LLImageDecodeThread: threaded decodeImage(), returned handle is null", decodeHandle != 0);
// Wait till the thread has time to handle the work order (though it doesn't do much per work order...)
const U32 INCREMENT_TIME = 500; // 500 milliseconds
const U32 MAX_TIME = 20 * INCREMENT_TIME; // Do the loop 20 times max, i.e. wait 10 seconds but no more
U32 total_time = 0;
while ((done == false) && (total_time < MAX_TIME))
{
ms_sleep(INCREMENT_TIME);
total_time += INCREMENT_TIME;
}
// Verifies that the responder has now been called
ensure("LLImageDecodeThread: threaded work unit not processed", done == true);
}
}