You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2766 lines
97 KiB
2766 lines
97 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
//=============================================================================// |
|
|
|
|
|
#include "cbase.h" |
|
|
|
// for the tool |
|
#include "econ_gcmessages.h" |
|
#include "econ_item_system.h" |
|
#include "econ_item_constants.h" |
|
#include "tool_items.h" |
|
#include "imageutils.h" |
|
#include "econ_ui.h" |
|
#include "econ_item_inventory.h" |
|
#include "econ_item_tools.h" |
|
#include "checksum_md5.h" |
|
#include "gc_clientsystem.h" |
|
#include "materialsystem/itexture.h" |
|
#include "pixelwriter.h" |
|
|
|
#include "filesystem.h" |
|
|
|
// for UI |
|
#include "confirm_dialog.h" |
|
#include "vgui_controls/EditablePanel.h" |
|
#include "vgui_controls/FileOpenDialog.h" |
|
#include "vgui_controls/ImagePanel.h" |
|
#include "vgui_controls/TextEntry.h" |
|
#include "vgui_controls/RadioButton.h" |
|
#include "vgui_controls/ComboBox.h" |
|
#include "vgui_controls/Slider.h" |
|
#include "vgui/Cursor.h" |
|
#include "vgui/IInput.h" |
|
#include "vgui/ISurface.h" |
|
#include "vgui/IImage.h" |
|
#include "vgui/IBorder.h" |
|
#include "VGuiMatSurface/IMatSystemSurface.h" |
|
#include "bitmap/tgawriter.h" |
|
#include "bitmap/bitmap.h" |
|
#include "vgui_bitmappanel.h" |
|
#include "tool_items/custom_texture_cache.h" |
|
#include "util_shared.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include <tier0/memdbgon.h> |
|
|
|
using namespace CustomTextureSystem; |
|
|
|
|
|
// Turn this on to run the filters on a bunch of test images when the dialog is opened |
|
//#define TEST_FILTERS |
|
|
|
#define DEFINE_BLEND(code) \ |
|
for (int y = 0 ; y < imgSource.Height() ; ++y ) \ |
|
{ \ |
|
for (int x = 0 ; x < imgSource.Width() ; ++x ) \ |
|
{ \ |
|
Color sc = imgSource.GetColor( x,y ); \ |
|
Color dc = imgDest.GetColor( x,y ); \ |
|
float sr = (float)sc.r()/255.0f, sg = (float)sc.g()/255.0f, sb = (float)sc.b()/255.0f, sa = (float)sc.a()/255.0f; \ |
|
float dr = (float)dc.r()/255.0f, dg = (float)dc.g()/255.0f, db = (float)dc.b()/255.0f, da = (float)dc.a()/255.0f; \ |
|
float blendPct = sa * flOpacity; \ |
|
code \ |
|
imgDest.SetColor( x,y, FloatRGBAToColor( dr*255.0f, dg*255.0f, db*255.0f, da*255.0f ) ); \ |
|
} \ |
|
} |
|
|
|
static void DoNormalBlend( const Bitmap_t &imgSource, Bitmap_t &imgDest, float flOpacity ) |
|
{ |
|
DEFINE_BLEND( |
|
dr += (sr - dr) * blendPct; |
|
dg += (sg - dg) * blendPct; |
|
db += (sb - db) * blendPct; |
|
) |
|
} |
|
|
|
static void DoMultiplyBlend( const Bitmap_t &imgSource, Bitmap_t &imgDest, float flOpacity ) |
|
{ |
|
DEFINE_BLEND( |
|
dr += (dr*sr - dr) * blendPct; |
|
dg += (dg*sg - dg) * blendPct; |
|
db += (db*sb - db) * blendPct; |
|
) |
|
} |
|
|
|
static inline float screen( float a, float b ) |
|
{ |
|
return 1.0f - (1.0f-a)*(1.0f-b); |
|
} |
|
|
|
static void DoScreenBlend( const Bitmap_t &imgSource, Bitmap_t &imgDest, float flOpacity ) |
|
{ |
|
DEFINE_BLEND( |
|
dr += (screen(dr,sr) - dr) * blendPct; |
|
dg += (screen(dg,sg) - dg) * blendPct; |
|
db += (screen(db,sb) - db) * blendPct; |
|
) |
|
} |
|
|
|
static inline float overlay( float a, float b ) |
|
{ |
|
if ( a < .5f ) |
|
{ |
|
return a * b * 2.0f; |
|
} |
|
float t = a * 2.0f - 1.0f; |
|
return screen( t, b ); |
|
} |
|
|
|
static void DoOverlayBlend( const Bitmap_t &imgSource, Bitmap_t &imgDest, float flOpacity ) |
|
{ |
|
DEFINE_BLEND( |
|
dr += (overlay(dr,sr) - dr) * blendPct; |
|
dg += (overlay(dg,sg) - dg) * blendPct; |
|
db += (overlay(db,sb) - db) * blendPct; |
|
) |
|
} |
|
|
|
static void DoReplaceAlphaBlend( const Bitmap_t &imgSource, Bitmap_t &imgDest, float flOpacity ) |
|
{ |
|
DEFINE_BLEND( |
|
float k = (sr + sb + sg) / 3.0f; |
|
da += (k - da) * blendPct; |
|
) |
|
} |
|
|
|
// Custom compositing blend operations. Mostly these are direct translations of standard Photoshop operations. |
|
// For most operations, the source alpha used as per-pixel blend factor (multiplied by the layer opacity), and |
|
// the dest alpha is just copied |
|
enum ELayerBlendOp |
|
{ |
|
eLayerBlendOp_Invalid, // Debugging placeholder value |
|
eLayerBlendOp_Normal, // Regular blend (lerp) |
|
eLayerBlendOp_Multiply, // Multiply color channels |
|
eLayerBlendOp_Screen, // 1 - (1-A) * (1-B) |
|
eLayerBlendOp_Overlay, // Multiply or screen, depending on source |
|
eLayerBlendOp_ReplaceAlpha, // Blend the source alpha channel with the greyscale value from the layer. Color channel is not modified |
|
}; |
|
|
|
/// A custom compositing step |
|
struct SDecalBlendLayer |
|
{ |
|
|
|
/// Which operation to perform? |
|
ELayerBlendOp eLayerOp; |
|
|
|
/// The image data |
|
Bitmap_t m_image; |
|
|
|
/// Opacity multiplier. The full blend color is calculated by performing the blend |
|
/// operation ignoring opacity. Then this result is lerped with the dest fragment by |
|
/// the effective blend factor. The effective per-pixel blend factor is taken as the |
|
/// source alpha times this value. |
|
float m_fLayerOpacity; |
|
|
|
/// Parse from keyvalues. |
|
bool FromKV( KeyValues *pkvLayerBlock, CUtlString &errMsg ) |
|
{ |
|
const char *op = pkvLayerBlock->GetString( "op", "(none)" ); |
|
if ( !Q_stricmp( op, "normal" ) ) eLayerOp = eLayerBlendOp_Normal; |
|
else if ( !Q_stricmp( op, "multiply" ) ) eLayerOp = eLayerBlendOp_Multiply; |
|
else if ( !Q_stricmp( op, "screen" ) ) eLayerOp = eLayerBlendOp_Screen; |
|
else if ( !Q_stricmp( op, "overlay" ) ) eLayerOp = eLayerBlendOp_Overlay; |
|
else if ( !Q_stricmp( op, "ReplaceAlpha" ) ) eLayerOp = eLayerBlendOp_ReplaceAlpha; |
|
else |
|
{ |
|
errMsg.Format( "Invalid blend operation '%s'", op ); |
|
return false; |
|
} |
|
|
|
const char *pszImageFilename = pkvLayerBlock->GetString( "image", NULL ); |
|
if ( pszImageFilename == NULL ) |
|
{ |
|
errMsg = "Must specify 'image'"; |
|
return false; |
|
} |
|
if ( ImgUtl_LoadBitmap( pszImageFilename, m_image ) != CE_SUCCESS ) |
|
{ |
|
errMsg.Format( "Can't load image '%s'", pszImageFilename ); |
|
return false; |
|
} |
|
|
|
m_fLayerOpacity = pkvLayerBlock->GetFloat( "opacity", 1.0f ); |
|
|
|
return true; |
|
} |
|
|
|
/// Apply the operation |
|
void Apply( Bitmap_t &imgDest ) const |
|
{ |
|
if ( !m_image.IsValid() || !imgDest.IsValid() || imgDest.Width() != m_image.Width() || imgDest.Height() != m_image.Height() ) |
|
{ |
|
Assert( m_image.IsValid() ); |
|
Assert( imgDest.IsValid() ); |
|
Assert( imgDest.Width() == m_image.Width() ); |
|
Assert( imgDest.Height() == m_image.Height() ); |
|
return; |
|
} |
|
|
|
switch ( eLayerOp ) |
|
{ |
|
default: |
|
case eLayerBlendOp_Invalid: |
|
Assert( !"Bogus blend op!" ); |
|
case eLayerBlendOp_Normal: |
|
DoNormalBlend( m_image, imgDest, m_fLayerOpacity ); |
|
break; |
|
case eLayerBlendOp_Multiply: |
|
DoMultiplyBlend( m_image, imgDest, m_fLayerOpacity ); |
|
break; |
|
case eLayerBlendOp_Screen: |
|
DoScreenBlend( m_image, imgDest, m_fLayerOpacity ); |
|
break; |
|
case eLayerBlendOp_Overlay: |
|
DoOverlayBlend( m_image, imgDest, m_fLayerOpacity ); |
|
break; |
|
case eLayerBlendOp_ReplaceAlpha: |
|
DoReplaceAlphaBlend( m_image, imgDest, m_fLayerOpacity ); |
|
break; |
|
} |
|
} |
|
}; |
|
|
|
// Note: uses a non-linear non-perceptual color space. But it will be good enough, |
|
// probably |
|
inline int ApproxColorDistSq( const Color &a, const Color &b ) |
|
{ |
|
int dr = (int)a.r() - (int)b.r(); |
|
int dg = (int)a.g() - (int)b.g(); |
|
int db = (int)a.b() - (int)b.b(); |
|
return dr*dr + dg*dg + db*db; |
|
} |
|
|
|
// Return cheesy color distance calculation, approximately normalized from 0...1 |
|
inline float ApproxColorDist( const Color &a, const Color &b ) |
|
{ |
|
return sqrt( (float)ApproxColorDistSq( a, b ) ) * ( 1.0f / 441.67f ); |
|
} |
|
|
|
// Convert linear RGB -> XYZ color space. |
|
Vector LinearRGBToXYZ( const Vector &rgb ) |
|
{ |
|
|
|
// http://en.wikipedia.org/wiki/SRGB |
|
Vector xyz; |
|
xyz.x = rgb.x * 0.4124 + rgb.y*0.3576 + rgb.z*0.1805; |
|
xyz.y = rgb.x * 0.2126 + rgb.y*0.7152 + rgb.z*0.0722; |
|
xyz.z = rgb.x * 0.0193 + rgb.y*0.1192 + rgb.z*0.9505; |
|
return xyz; |
|
} |
|
|
|
inline float lab_f( float t ) |
|
{ |
|
if ( t > (6.0/29.0)*(6.0/29.0)*(6.0/29.0) ) |
|
{ |
|
return pow( t, .333333f ); |
|
} |
|
return ( (1.0f/3.0f) * (29.0f/6.0f) * (29.0f/6.0f) ) * t + (4.0f/29.0f); |
|
} |
|
|
|
// Convert CIE XYZ -> L*a*b* |
|
Vector XYZToLab( const Vector &xyz ) |
|
{ |
|
|
|
// http://en.wikipedia.org/wiki/Lab_color_space |
|
const float X_n = 0.9505; |
|
const float Y_n = 1.0000; |
|
const float Z_n = 1.0890; |
|
|
|
float f_X = lab_f( xyz.x / X_n ); |
|
float f_Y = lab_f( xyz.y / Y_n ); |
|
float f_Z = lab_f( xyz.z / Z_n ); |
|
|
|
Vector lab; |
|
lab.x = 116.0f*f_Y - 16.0f; // L* |
|
lab.y = 500.0f * ( f_X - f_Y ); // a* |
|
lab.z = 200.0f * ( f_Y - f_Z ); // b* |
|
return lab; |
|
} |
|
|
|
// Convert texture-space RGB values to linear RGB space |
|
Vector TextureToLinearRGB( Color c ) |
|
{ |
|
Vector rgb; |
|
rgb.x = SrgbGammaToLinear( (float)c.r() / 255.0f ); |
|
rgb.y = SrgbGammaToLinear( (float)c.g() / 255.0f ); |
|
rgb.z = SrgbGammaToLinear( (float)c.b() / 255.0f ); |
|
return rgb; |
|
} |
|
|
|
// Convert texture-space RGB values to perceptually linear L*a*b* space |
|
Vector TextureToLab( Color c ) |
|
{ |
|
Vector linearRGB = TextureToLinearRGB( c ); |
|
Vector xyz = LinearRGBToXYZ( linearRGB ); |
|
return XYZToLab( xyz ); |
|
} |
|
|
|
static void SymmetricNearestNeighborFilter( const Bitmap_t &imgSrc, Bitmap_t &imgDest, int radius, float amount = 1.0f ) |
|
{ |
|
|
|
// Make sure image is allocated properly |
|
int nWidth = imgSrc.Width(); |
|
int nHeight = imgSrc.Height(); |
|
imgDest.Init( nWidth, nHeight, IMAGE_FORMAT_RGBA8888 ); |
|
|
|
float flWeightBias = (2 + radius + radius ); |
|
int filteredBlendWeight = int(amount * 256.0f); |
|
int originalBlendWeight = 256 - filteredBlendWeight; |
|
|
|
// For each dest pixel |
|
for ( int y = 0 ; y < nHeight ; ++y ) |
|
{ |
|
for ( int x = 0 ; x < nWidth ; ++x ) |
|
{ |
|
Color c = imgSrc.GetColor( x, y ); |
|
|
|
// Iterate over half of the kernel. (Doesn't matter which half.) |
|
// Kernel pixels are examined in opposing pairs |
|
Vector4D sum(0,0,0,0); |
|
float flTotalWeight = 0.0f; |
|
for (int ry = 0 ; ry <= radius ; ++ry ) |
|
{ |
|
int sy1 = clamp(y + ry, 0, nHeight-1); |
|
int sy2 = clamp(y - ry, 0, nHeight-1); |
|
for (int rx = (ry == 0) ? 0 : -radius ; rx <= radius ; ++rx ) |
|
{ |
|
int sx1 = clamp(x + rx, 0, nWidth-1); |
|
int sx2 = clamp(x - rx, 0, nWidth-1); |
|
|
|
Color s1 = imgSrc.GetColor( sx1, sy1 ); |
|
Color s2 = imgSrc.GetColor( sx2, sy2 ); |
|
|
|
// Calculate difference. Here, maybe we should be using |
|
// a perceptual difference in linear color space. Who cares. |
|
int d1 = ApproxColorDistSq( c, s1 ); |
|
int d2 = ApproxColorDistSq( c, s2 ); |
|
|
|
float weight = flWeightBias - fabs((float)ry) - fabs((float)rx); |
|
if ( d1 < d2 ) |
|
{ |
|
sum.x += (float)s1.r() * weight; |
|
sum.y += (float)s1.g() * weight; |
|
sum.z += (float)s1.b() * weight; |
|
sum.w += (float)s1.a() * weight; |
|
} |
|
else |
|
{ |
|
sum.x += (float)s2.r() * weight; |
|
sum.y += (float)s2.g() * weight; |
|
sum.z += (float)s2.b() * weight; |
|
sum.w += (float)s2.a() * weight; |
|
} |
|
flTotalWeight += weight; |
|
} |
|
} |
|
|
|
sum /= flTotalWeight; |
|
int filterR = (int)clamp(sum.x, 0.0f, 255.0f); |
|
int filterG = (int)clamp(sum.y, 0.0f, 255.0f); |
|
int filterB = (int)clamp(sum.z, 0.0f, 255.0f); |
|
int filterA = (int)clamp(sum.w, 0.0f, 255.0f); |
|
Color result( |
|
(filterR*filteredBlendWeight + c.r()*originalBlendWeight) >> 8, |
|
(filterG*filteredBlendWeight + c.g()*originalBlendWeight) >> 8, |
|
(filterB*filteredBlendWeight + c.b()*originalBlendWeight) >> 8, |
|
(filterA*filteredBlendWeight + c.a()*originalBlendWeight) >> 8 |
|
); |
|
imgDest.SetColor( x, y, result ); |
|
} |
|
} |
|
} |
|
|
|
static void BilateralFilter( const Bitmap_t &imgSrc, Bitmap_t &imgDest, int radius, float colorDiffThreshold, float amount = 1.0f ) |
|
{ |
|
|
|
// Make sure image is allocated properly |
|
int nWidth = imgSrc.Width(); |
|
int nHeight = imgSrc.Height(); |
|
imgDest.Init( nWidth, nHeight, IMAGE_FORMAT_RGBA8888 ); |
|
|
|
float flWeightBias = (2 + radius + radius ); |
|
int filteredBlendWeight = int(amount * 256.0f); |
|
int originalBlendWeight = 256 - filteredBlendWeight; |
|
|
|
// For each dest pixel |
|
for ( int y = 0 ; y < nHeight ; ++y ) |
|
{ |
|
for ( int x = 0 ; x < nWidth ; ++x ) |
|
{ |
|
Color c = imgSrc.GetColor( x, y ); |
|
|
|
// Iterate over the kernel |
|
Vector4D sum(0,0,0,0); |
|
float flTotalWeight = 0.0f; |
|
for (int ry = -radius ; ry <= radius ; ++ry ) |
|
{ |
|
int sy = clamp(y + ry, 0, nHeight-1); |
|
for (int rx = -radius ; rx <= radius ; ++rx ) |
|
{ |
|
int sx = clamp(x + rx, 0, nWidth-1); |
|
|
|
Color s = imgSrc.GetColor( sx, sy ); |
|
|
|
// Calculate difference. Here, maybe we should be using |
|
// a perceptual difference in linear color space. Who cares. |
|
float colorDist = ApproxColorDist( c, s ); |
|
|
|
// Geometry-based weight |
|
float geomWeight = flWeightBias - fabs((float)ry) - fabs((float)rx); |
|
|
|
// Distance-based weight |
|
float diffWeight = 1.0f - colorDist - colorDiffThreshold; |
|
|
|
// Total weight |
|
float weight = geomWeight * diffWeight; |
|
if ( weight > 0.0f ) |
|
{ |
|
sum.x += (float)s.r() * weight; |
|
sum.y += (float)s.g() * weight; |
|
sum.z += (float)s.b() * weight; |
|
sum.w += (float)s.a() * weight; |
|
flTotalWeight += weight; |
|
} |
|
} |
|
} |
|
|
|
sum /= flTotalWeight; |
|
int filterR = (int)clamp(sum.x, 0.0f, 255.0f); |
|
int filterG = (int)clamp(sum.y, 0.0f, 255.0f); |
|
int filterB = (int)clamp(sum.z, 0.0f, 255.0f); |
|
int filterA = (int)clamp(sum.w, 0.0f, 255.0f); |
|
Color result( |
|
(filterR*filteredBlendWeight + c.r()*originalBlendWeight) >> 8, |
|
(filterG*filteredBlendWeight + c.g()*originalBlendWeight) >> 8, |
|
(filterB*filteredBlendWeight + c.b()*originalBlendWeight) >> 8, |
|
(filterA*filteredBlendWeight + c.a()*originalBlendWeight) >> 8 |
|
); |
|
imgDest.SetColor( x, y, result ); |
|
} |
|
} |
|
} |
|
|
|
// Scan image and replace each pixel with the closest matching swatch |
|
static void ColorReplace( const Bitmap_t &imgSrc, Bitmap_t &imgDest, int nSwatchCount, const Color *pSwatchList, float amount = 1.0f, const float *pSwatchWeightList = NULL ) |
|
{ |
|
Assert( nSwatchCount >= 1 ); |
|
|
|
CUtlVector<Vector> swatchLab; |
|
for ( int i = 0 ; i < nSwatchCount ; ++i ) |
|
{ |
|
swatchLab.AddToTail( TextureToLab( pSwatchList[i] ) ); |
|
} |
|
|
|
// Make sure image is allocated properly |
|
int nWidth = imgSrc.Width(); |
|
int nHeight = imgSrc.Height(); |
|
imgDest.Init( nWidth, nHeight, IMAGE_FORMAT_RGBA8888 ); |
|
|
|
CUtlVector<float> vecDistScale; |
|
if ( pSwatchWeightList ) |
|
{ |
|
float total = 0.0f; |
|
for (int i = 0 ; i < nSwatchCount ; ++i) |
|
{ |
|
total += pSwatchWeightList[i]; |
|
} |
|
total *= 1.05f; |
|
for (int i = 0 ; i < nSwatchCount ; ++i) |
|
{ |
|
vecDistScale.AddToTail( total - pSwatchWeightList[i] ); |
|
} |
|
} |
|
else |
|
{ |
|
for (int i = 0 ; i < nSwatchCount ; ++i) |
|
{ |
|
vecDistScale.AddToTail( 1.0f ); |
|
} |
|
} |
|
|
|
// For each dest pixel |
|
for ( int y = 0 ; y < nHeight ; ++y ) |
|
{ |
|
for ( int x = 0 ; x < nWidth ; ++x ) |
|
{ |
|
// Fetch source color |
|
Color c = imgSrc.GetColor( x, y ); |
|
Vector lab = TextureToLab( c ); |
|
|
|
// Search for the closest matching swatch in the palette |
|
Color closestSwatchColor = pSwatchList[0]; |
|
//int bestDist = ApproxColorDistSq( c, closestSwatchColor ); |
|
float bestDist = lab.DistTo( swatchLab[0] ) * vecDistScale[0]; |
|
for ( int i = 1 ; i < nSwatchCount ; ++i ) |
|
{ |
|
//int dist = ApproxColorDistSq( c, pSwatchList[i] ); |
|
float dist = lab.DistTo( swatchLab[i] ) * vecDistScale[i]; |
|
if ( dist < bestDist ) |
|
{ |
|
bestDist = dist; |
|
closestSwatchColor = pSwatchList[i]; |
|
} |
|
} |
|
|
|
imgDest.SetColor( x, y, LerpColor( c, closestSwatchColor, amount ) ); |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Custom control for the gradient editing |
|
//----------------------------------------------------------------------------- |
|
class CustomTextureStencilGradientMapWidget : public vgui::Panel |
|
{ |
|
DECLARE_CLASS_SIMPLE( CustomTextureStencilGradientMapWidget, vgui::Panel ); |
|
|
|
public: |
|
CustomTextureStencilGradientMapWidget(vgui::Panel *parent, const char *panelName); |
|
|
|
// Slam range count, forcing nobs to be spaced evenly |
|
void InitRangeCount( int nRangeCount ); |
|
|
|
// Set new number of ranges, attempting to adjust nob positions in a "reasonable" way |
|
void AdjustRangeCount( int nRangeCount ); |
|
int GetRangeCount() const { return m_nRangeCount; } |
|
int GetNobCount() const { return m_nRangeCount-1; } |
|
int GetNobValue( int nNobIndex ) const; // allows virtual "nobs" at indices -1 and m_nRangeCount |
|
void SetNobValue( int nNobIndex, int value ); // clamp to adjacent nobs |
|
void SlamNobValue( int nNobIndex, int value ); // force nob to particular value, and don't check it |
|
void SetRangeColors( const Color *rColors ) |
|
{ |
|
memcpy( m_colRangeColor, rColors, m_nRangeCount*sizeof(m_colRangeColor[0]) ); |
|
ComputeGradient(); |
|
} |
|
|
|
/// Convert local x coordinate to value |
|
int LocalXToVal( int x, bool bClamp = true ); |
|
|
|
/// Convert value to local x coordinate |
|
int ValToLocalX( int value, bool bClamp = true ); |
|
|
|
enum { k_nMaxRangeCount = 4 }; |
|
|
|
virtual void OnCursorMoved(int x, int y); |
|
virtual void OnMousePressed(vgui::MouseCode code); |
|
virtual void OnMouseDoublePressed(vgui::MouseCode code); |
|
virtual void OnMouseReleased(vgui::MouseCode code); |
|
|
|
Color m_colorGradient[ 256 ]; |
|
|
|
protected: |
|
virtual void Paint(); |
|
virtual void PaintBackground(); |
|
virtual void ApplySchemeSettings(vgui::IScheme *pScheme); |
|
|
|
|
|
int m_iDraggedNob; // -1 if none |
|
int m_nRangeCount; |
|
int m_nNobVal[k_nMaxRangeCount-1]; |
|
Color m_colRangeColor[k_nMaxRangeCount]; |
|
int m_iNobSizeX; |
|
int m_iNobSizeY; |
|
int m_iNobRelPosY; |
|
int m_iRibbonSizeY; |
|
int m_iRibbonRelPosY; |
|
int m_iMinVal; |
|
int m_iMaxVal; |
|
int m_iNobValCushion; // closest that we allow two nobs to be together |
|
int m_iClickOffsetX; |
|
|
|
// size (in intensity values on 255 scale) of transition band centered on nob |
|
int m_iTransitionBandSize; |
|
|
|
// The regions between the nobs are not *quite* a sold color. They have a slight |
|
// gradient in them. This value control the max difference in the ends of this |
|
// gradient |
|
int m_iRegionGradientRange; |
|
|
|
Color m_TickColor; |
|
Color m_TrackColor; |
|
|
|
Color m_DisabledTextColor1; |
|
Color m_DisabledTextColor2; |
|
|
|
vgui::IBorder *_sliderBorder; |
|
vgui::IBorder *_insetBorder; |
|
|
|
void SendSliderMovedMessage(); |
|
|
|
/// Mouse hit testing. Returns index of the nob under the cursor, or |
|
/// -1 if none. Coords are local |
|
int HitTest( int x, int y, int &outOffsetX ); |
|
|
|
/// Fetch local rectangle for given nob |
|
void GetNobRect( int iNobIndex, int &x1, int &y1, int &xs, int &ys ); |
|
|
|
void GetColorRibbonRect( int &x1, int &y1, int &xs, int &ys ); |
|
|
|
void ComputeSizes(); |
|
void ComputeGradient(); |
|
|
|
bool m_bClickOnRanges; |
|
}; |
|
|
|
DECLARE_BUILD_FACTORY( CustomTextureStencilGradientMapWidget ); |
|
|
|
//----------------------------------------------------------------------------- |
|
CustomTextureStencilGradientMapWidget::CustomTextureStencilGradientMapWidget(Panel *parent, const char *panelName ) |
|
: Panel(parent, panelName) |
|
{ |
|
m_iDraggedNob = -1; |
|
m_nRangeCount = 4; |
|
m_nNobVal[0] = 64; |
|
m_nNobVal[1] = 128; |
|
m_nNobVal[2] = 192; |
|
m_colRangeColor[0] = Color(183,224,252,255); |
|
m_colRangeColor[1] = Color(83,109,205,255); |
|
m_colRangeColor[2] = Color(98,48,43,255); |
|
m_colRangeColor[3] = Color(234,198,113,255); |
|
m_iNobSizeX = 4; |
|
m_iNobSizeY = 4; |
|
m_iNobRelPosY = 0; |
|
m_iRibbonSizeY = 0; |
|
m_iRibbonRelPosY = 4; |
|
m_iMinVal = 0; |
|
m_iMaxVal = 255; |
|
m_iNobValCushion = 8; |
|
m_iClickOffsetX = 0; |
|
m_bClickOnRanges = false; |
|
|
|
m_iTransitionBandSize = 8; |
|
m_iRegionGradientRange = 8; |
|
|
|
_sliderBorder = NULL; |
|
_insetBorder = NULL; |
|
|
|
//AddActionSignalTarget( parent ); |
|
SetBlockDragChaining( true ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Send a message to interested parties when the slider moves |
|
//----------------------------------------------------------------------------- |
|
void CustomTextureStencilGradientMapWidget::SendSliderMovedMessage() |
|
{ |
|
// send a changed message |
|
KeyValues *pParams = new KeyValues("SliderMoved"); |
|
pParams->SetPtr( "panel", this ); |
|
PostActionSignal( pParams ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CustomTextureStencilGradientMapWidget::ApplySchemeSettings(vgui::IScheme *pScheme) |
|
{ |
|
BaseClass::ApplySchemeSettings(pScheme); |
|
|
|
SetFgColor(GetSchemeColor("Slider.NobColor", pScheme)); |
|
// this line is useful for debugging |
|
//SetBgColor(GetSchemeColor("0 0 0 255")); |
|
|
|
m_TickColor = pScheme->GetColor( "Slider.TextColor", GetFgColor() ); |
|
m_TrackColor = pScheme->GetColor( "Slider.TrackColor", GetFgColor() ); |
|
|
|
m_DisabledTextColor1 = pScheme->GetColor( "Slider.DisabledTextColor1", GetFgColor() ); |
|
m_DisabledTextColor2 = pScheme->GetColor( "Slider.DisabledTextColor2", GetFgColor() ); |
|
|
|
_sliderBorder = pScheme->GetBorder("ButtonBorder"); |
|
_insetBorder = pScheme->GetBorder("ButtonDepressedBorder"); |
|
|
|
ComputeSizes(); |
|
ComputeGradient(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Draw everything on screen |
|
//----------------------------------------------------------------------------- |
|
void CustomTextureStencilGradientMapWidget::Paint() |
|
{ |
|
// DrawTicks(); |
|
// |
|
// DrawTickLabels(); |
|
// |
|
// // Draw nob last so it draws over ticks. |
|
// DrawNob(); |
|
|
|
// Draw nobs last |
|
for ( int i = 0 ; i < GetNobCount() ; ++i ) |
|
{ |
|
int x1, y1, xs, ys; |
|
GetNobRect( i, x1, y1, xs, ys ); |
|
|
|
Color col = GetFgColor(); |
|
g_pMatSystemSurface->DrawSetColor(col); |
|
g_pMatSystemSurface->DrawFilledRect( |
|
x1, |
|
y1, |
|
x1+xs, |
|
y1+ys |
|
); |
|
|
|
} |
|
|
|
// // border |
|
// if (_sliderBorder) |
|
// { |
|
// _sliderBorder->Paint( |
|
// _nobPos[0], |
|
// y + tall / 2 - nobheight / 2, |
|
// _nobPos[1], |
|
// y + tall / 2 + nobheight / 2); |
|
// } |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Draw the slider track |
|
//----------------------------------------------------------------------------- |
|
void CustomTextureStencilGradientMapWidget::PaintBackground() |
|
{ |
|
BaseClass::PaintBackground(); |
|
|
|
int x1, y1, xs, ys; |
|
GetColorRibbonRect( x1, y1, xs, ys ); |
|
|
|
// This is utterly terrible. It could be drawn a LOT more efficiently! |
|
for ( int x = 0 ; x < xs ; ++x ) |
|
{ |
|
int v = x * 256 / xs; |
|
vgui::surface()->DrawSetColor( m_colorGradient[v] ); |
|
vgui::surface()->DrawFilledRect( x1 + x, y1, x1 + x + 1, y1 + ys ); |
|
} |
|
|
|
// int x, y; |
|
// int wide,tall; |
|
// |
|
// GetTrackRect( x, y, wide, tall ); |
|
// |
|
// surface()->DrawSetColor( m_TrackColor ); |
|
// surface()->DrawFilledRect( x, y, x + wide, y + tall ); |
|
// if (_insetBorder) |
|
// { |
|
// _insetBorder->Paint( x, y, x + wide, y + tall ); |
|
// } |
|
} |
|
|
|
void CustomTextureStencilGradientMapWidget::ComputeGradient() |
|
{ |
|
|
|
struct GradientInterpolationPoint |
|
{ |
|
int m_iVal; |
|
Color m_color; |
|
}; |
|
|
|
GradientInterpolationPoint rGradPoints[ k_nMaxRangeCount * 2 ]; |
|
int nGradPoints = 0; |
|
|
|
// Put two interpolation points per region. |
|
for ( int iRange = 0 ; iRange < m_nRangeCount ; ++iRange ) |
|
{ |
|
// Get nob values on either side |
|
// of the region |
|
int lVal = GetNobValue( iRange-1 ); |
|
int rVal = GetNobValue( iRange ); |
|
|
|
// Push them together slightly, to create a small gradient band |
|
// around the nobs |
|
int d = rVal - lVal; |
|
if ( d > 2 ) |
|
{ |
|
int iPush = MIN ( d, m_iTransitionBandSize ) / 2; |
|
if ( iRange > 0 ) |
|
{ |
|
lVal += iPush; |
|
} |
|
if ( iRange < m_nRangeCount-1 ) |
|
{ |
|
rVal -= iPush; |
|
} |
|
} |
|
|
|
Color lColor = m_colRangeColor[iRange]; |
|
Color rColor = m_colRangeColor[iRange]; |
|
// !FIXME! Nudge color towards neighbors |
|
|
|
// Insert interpolation points |
|
Assert( nGradPoints+2 <= ARRAYSIZE( rGradPoints ) ); |
|
rGradPoints[ nGradPoints ].m_iVal = lVal; |
|
rGradPoints[ nGradPoints ].m_color = lColor; |
|
++nGradPoints; |
|
rGradPoints[ nGradPoints ].m_iVal = rVal; |
|
rGradPoints[ nGradPoints ].m_color = rColor; |
|
++nGradPoints; |
|
} |
|
|
|
// Now fill in gradient |
|
Assert( m_iMinVal == 0 ); |
|
Assert( m_iMaxVal == 255 ); |
|
COMPILE_TIME_ASSERT( ARRAYSIZE( m_colorGradient ) == 256 ); |
|
|
|
int iRightIndex = 1; // current interpolation point on right hand side |
|
for ( int i = 0 ; i < 256 ; ++i ) |
|
{ |
|
while ( i >= rGradPoints[ iRightIndex ].m_iVal && iRightIndex < nGradPoints-1) |
|
{ |
|
++iRightIndex; |
|
} |
|
int iLeftIndex = iRightIndex-1; |
|
int iLeftVal = rGradPoints[ iLeftIndex ].m_iVal; |
|
int iRightVal = rGradPoints[ iRightIndex ].m_iVal; |
|
Assert( i >= iLeftVal ); |
|
Assert( i <= iRightVal ); |
|
|
|
Color lColor = rGradPoints[ iLeftIndex ].m_color; |
|
Color rColor = rGradPoints[ iRightIndex ].m_color; |
|
|
|
if ( i <= iLeftVal ) |
|
{ |
|
m_colorGradient[i] = lColor; |
|
} |
|
else if ( i >= iRightVal ) |
|
{ |
|
m_colorGradient[i] = rColor; |
|
} |
|
else |
|
{ |
|
float pct = float( i - iLeftVal ) / float( iRightVal - iLeftVal ); |
|
m_colorGradient[i] = LerpColor( lColor, rColor, pct ); |
|
} |
|
} |
|
|
|
} |
|
|
|
void CustomTextureStencilGradientMapWidget::ComputeSizes() |
|
{ |
|
m_iNobSizeX = 5; |
|
int sizeY = GetTall(); |
|
|
|
m_iNobSizeY = sizeY * 2 / 5; |
|
m_iNobRelPosY = sizeY - m_iNobSizeY; |
|
|
|
m_iRibbonRelPosY = 0; |
|
m_iRibbonSizeY = m_iNobRelPosY - 1; |
|
} |
|
|
|
void CustomTextureStencilGradientMapWidget::GetColorRibbonRect( int &x1, int &y1, int &xs, int &ys ) |
|
{ |
|
int controlSizeX, controlSizeY; |
|
GetSize( controlSizeX, controlSizeY ); |
|
|
|
x1 = 0; |
|
xs = controlSizeX; |
|
y1 = m_iRibbonRelPosY; |
|
ys = m_iRibbonSizeY; |
|
} |
|
|
|
void CustomTextureStencilGradientMapWidget::GetNobRect( int iNobIndex, int &x1, int &y1, int &xs, int &ys ) |
|
{ |
|
|
|
Assert( iNobIndex >= 0 ); |
|
Assert( iNobIndex < GetNobCount() ); |
|
|
|
// Fetch x center position |
|
int iNobVal = GetNobValue( iNobIndex ); |
|
int cx = ValToLocalX( iNobVal ); |
|
|
|
int controlSizeX, controlSizeY; |
|
GetSize( controlSizeX, controlSizeY ); |
|
|
|
x1 = cx - m_iNobSizeX/2; |
|
xs = m_iNobSizeX; |
|
y1 = m_iNobRelPosY; |
|
ys = m_iNobSizeY; |
|
} |
|
|
|
int CustomTextureStencilGradientMapWidget::HitTest( int x, int y, int &outOffsetX ) |
|
{ |
|
int result = -1; |
|
const int k_Tol = 3; |
|
int bestDist = k_Tol; |
|
outOffsetX = 0; |
|
for ( int i = 0 ; i < GetNobCount() ; ++i ) |
|
{ |
|
int x1, y1, xs, ys; |
|
GetNobRect( i, x1, y1, xs, ys ); |
|
|
|
// Reject if too far away on Y |
|
if ( !m_bClickOnRanges ) |
|
{ |
|
y1 = 0; |
|
ys = GetTall(); |
|
} |
|
if ( y < y1-k_Tol ) continue; |
|
if ( y > y1+ys+k_Tol) continue; |
|
|
|
// Get horizontal error |
|
int d = 0; |
|
if ( x < x1 ) d = x1 - x; |
|
else if ( x > x1+xs) d = x - (x1+xs); |
|
|
|
// Closest match found so far? |
|
if ( d < bestDist ) |
|
{ |
|
bestDist = d; |
|
result = i; |
|
outOffsetX = (x1 + xs/2) - x; |
|
} |
|
} |
|
|
|
return result; |
|
} |
|
|
|
int CustomTextureStencilGradientMapWidget::ValToLocalX( int value, bool bClamp ) |
|
{ |
|
int w = GetWide(); |
|
if ( bClamp ) |
|
{ |
|
if ( value < m_iMinVal ) return 0; |
|
if ( value >= m_iMaxVal ) return w; |
|
} |
|
|
|
int r = m_iMaxVal - m_iMinVal; |
|
|
|
// Don't divide by zero |
|
if (r < 1 ) |
|
{ |
|
return 0; |
|
} |
|
|
|
return ( ( value - m_iMinVal ) * w + (w>>1) ) / r; |
|
} |
|
|
|
int CustomTextureStencilGradientMapWidget::LocalXToVal( int x, bool bClamp ) |
|
{ |
|
int w = GetWide(); |
|
|
|
// Don't divide by zero |
|
if (w < 1 ) |
|
{ |
|
return m_iMinVal; |
|
} |
|
|
|
if ( bClamp ) |
|
{ |
|
if ( x < 0 ) return m_iMinVal; |
|
if ( x >= w ) return m_iMaxVal; |
|
} |
|
|
|
int r = m_iMaxVal - m_iMinVal; |
|
return m_iMinVal + ( x * r + (r>>1) ) / w; |
|
} |
|
|
|
int CustomTextureStencilGradientMapWidget::GetNobValue( int nNobIndex ) const |
|
{ |
|
|
|
// Sentinel nob to the left? |
|
if ( nNobIndex < 0 ) |
|
{ |
|
Assert( nNobIndex == -1 ); |
|
return m_iMinVal; |
|
} |
|
|
|
// Sentinel nob to the right? |
|
if ( nNobIndex >= GetNobCount() ) |
|
{ |
|
Assert( nNobIndex == GetNobCount() ); |
|
return m_iMaxVal; |
|
} |
|
|
|
return m_nNobVal[ nNobIndex ]; |
|
} |
|
|
|
void CustomTextureStencilGradientMapWidget::SetNobValue( int nNobIndex, int value ) |
|
{ |
|
if ( nNobIndex < 0 || nNobIndex >= GetNobCount() ) |
|
{ |
|
Assert( nNobIndex >= 0 ); |
|
Assert( nNobIndex < GetNobCount() ); |
|
return; |
|
} |
|
|
|
// Get neighboring nob values |
|
int iValLeft = GetNobValue( nNobIndex-1 ); |
|
int iValRight = GetNobValue( nNobIndex+1 ); |
|
Assert( iValLeft < iValRight ); |
|
|
|
// Subtract off the cushion |
|
iValLeft += m_iNobValCushion; |
|
iValRight -= m_iNobValCushion; |
|
|
|
// No wiggle room?!?! |
|
if ( iValLeft > iValRight ) |
|
{ |
|
Assert( iValLeft <= iValRight ); |
|
|
|
// Do the best we can |
|
value = (iValLeft + iValRight) / 2; |
|
} |
|
else |
|
{ |
|
if ( value < iValLeft ) |
|
{ |
|
value = iValLeft; |
|
} |
|
else if ( value > iValRight ) |
|
{ |
|
value = iValRight; |
|
} |
|
} |
|
|
|
// We've clamped the value --- now slam it in place |
|
SlamNobValue( nNobIndex, value ); |
|
} |
|
|
|
void CustomTextureStencilGradientMapWidget::InitRangeCount( int nRangeCount ) |
|
{ |
|
m_nRangeCount = clamp( nRangeCount, 2, k_nMaxRangeCount ); |
|
for ( int i = 0 ; i < GetNobCount() ; ++i ) |
|
{ |
|
SlamNobValue( i, m_iMinVal + ( i + 1 ) * ( m_iMaxVal - m_iMinVal ) / m_nRangeCount ); |
|
} |
|
} |
|
|
|
void CustomTextureStencilGradientMapWidget::AdjustRangeCount( int nRangeCount ) |
|
{ |
|
nRangeCount = clamp( nRangeCount, 2, k_nMaxRangeCount ); |
|
Assert( m_nRangeCount >= 2 ); |
|
|
|
int oldNobCount = GetNobCount(); |
|
int oldRangeCount = m_nRangeCount; |
|
m_nRangeCount = nRangeCount; |
|
if ( m_nRangeCount < oldRangeCount ) |
|
{ |
|
// Removing ranges / nobs. Just need to space existing nobs further apart |
|
// |
|
// Work from back to front, so we won't |
|
// conflict with the safety checks in SetNobValue |
|
for ( int i = GetNobCount()-1 ; i >= 0 ; --i ) |
|
{ |
|
SetNobValue( i, m_iMinVal + ( GetNobValue( i ) - m_iMinVal ) * oldRangeCount / m_nRangeCount ); |
|
} |
|
} |
|
else if ( m_nRangeCount > oldRangeCount ) |
|
{ |
|
// Adding ranges / nobs. Compress existing nobs, and add the |
|
// new once evenly at the top |
|
|
|
// Slam new nob values to be space evenly in the space at the top |
|
for ( int i = oldNobCount ; i < GetNobCount() ; ++i ) |
|
{ |
|
SlamNobValue( i, m_iMinVal + ( i + 1 ) * ( m_iMaxVal - m_iMinVal ) / m_nRangeCount ); |
|
} |
|
|
|
// Work from front to back, so we won't |
|
// conflict with the safety checks in SetNobValue |
|
for ( int i = 0 ; i < oldNobCount ; ++i ) |
|
{ |
|
SetNobValue( i, m_iMinVal + ( GetNobValue( i ) - m_iMinVal ) * oldRangeCount / m_nRangeCount ); |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void CustomTextureStencilGradientMapWidget::SlamNobValue( int nNobIndex, int value ) |
|
{ |
|
if ( nNobIndex < 0 || nNobIndex >= GetNobCount() ) |
|
{ |
|
Assert( nNobIndex >= 0 ); |
|
Assert( nNobIndex < GetNobCount() ); |
|
return; |
|
} |
|
|
|
Assert( value >= m_iMinVal ); |
|
Assert( value <= m_iMaxVal ); |
|
m_nNobVal[ nNobIndex ] = value; |
|
ComputeGradient(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void CustomTextureStencilGradientMapWidget::OnCursorMoved(int x,int y) |
|
{ |
|
if( m_iDraggedNob < 0 ) |
|
{ |
|
return; |
|
} |
|
|
|
g_pVGuiInput->GetCursorPosition( x, y ); |
|
ScreenToLocal(x,y); |
|
|
|
x += m_iClickOffsetX; |
|
int v = LocalXToVal( x, true ); |
|
SetNobValue( m_iDraggedNob, v ); |
|
|
|
Repaint(); |
|
SendSliderMovedMessage(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void CustomTextureStencilGradientMapWidget::OnMousePressed(vgui::MouseCode code) |
|
{ |
|
int x,y; |
|
|
|
if (!IsEnabled()) |
|
return; |
|
|
|
g_pVGuiInput->GetCursorPosition( x, y ); |
|
|
|
ScreenToLocal(x,y); |
|
RequestFocus(); |
|
|
|
m_iDraggedNob = HitTest( x, y, m_iClickOffsetX ); |
|
|
|
if ( m_iDraggedNob >= 0 ) |
|
{ |
|
// drag the nob |
|
g_pVGuiInput->SetMouseCapture(GetVPanel()); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
void CustomTextureStencilGradientMapWidget::OnMouseDoublePressed(vgui::MouseCode code) |
|
{ |
|
// Just handle double presses like mouse presses |
|
OnMousePressed(code); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
void CustomTextureStencilGradientMapWidget::OnMouseReleased(vgui::MouseCode code) |
|
{ |
|
|
|
if ( m_iDraggedNob >= 0 ) |
|
{ |
|
m_iDraggedNob = -1; |
|
g_pVGuiInput->SetMouseCapture(null); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: UI to select the custom image and confirm tool application |
|
//----------------------------------------------------------------------------- |
|
class CConfirmCustomizeTextureDialog : public CBaseToolUsageDialog, private ITextureRegenerator |
|
{ |
|
DECLARE_CLASS_SIMPLE( CConfirmCustomizeTextureDialog, CBaseToolUsageDialog ); |
|
|
|
public: |
|
CConfirmCustomizeTextureDialog( vgui::Panel *pParent, CEconItemView *pTool, CEconItemView *pToolSubject ); |
|
virtual ~CConfirmCustomizeTextureDialog( void ); |
|
|
|
virtual void ApplySchemeSettings( vgui::IScheme *scheme ); |
|
virtual void Apply( void ); |
|
virtual void OnCommand( const char *command ); |
|
virtual void OnTick( void ); |
|
|
|
void ConversionError( ConversionErrorType nError ); |
|
|
|
MESSAGE_FUNC_CHARPTR( OnFileSelected, "FileSelected", fullpath ); |
|
//MESSAGE_FUNC_PTR( OnRadioButtonChecked, "RadioButtonChecked", panel ); |
|
MESSAGE_FUNC_PTR( OnTextChanged, "TextChanged", panel ); // send by the filter combo box when it changes |
|
|
|
MESSAGE_FUNC_PTR( OnRadioButtonChecked, "RadioButtonChecked", panel ) |
|
{ |
|
if ( eCurrentPage != ePage_SelectImage ) |
|
{ |
|
Assert( eCurrentPage == ePage_SelectImage ); |
|
return; |
|
} |
|
if ( panel == m_pUseAvatarRadioButton ) |
|
{ |
|
if ( !m_bUseAvatar ) |
|
{ |
|
UseAvatarImage(); |
|
} |
|
} |
|
else if ( panel == m_pUseAnyImageRadioButton ) |
|
{ |
|
if ( m_bUseAvatar ) |
|
{ |
|
m_imgSource.Clear(); |
|
m_bUseAvatar = false; |
|
} |
|
MarkSquareImageDirty(); |
|
WriteSelectImagePageControls(); |
|
} |
|
else |
|
{ |
|
Assert( false ); // who else is talking to us? |
|
} |
|
} |
|
|
|
MESSAGE_FUNC_PTR( OnSliderMoved, "SliderMoved", panel ) |
|
{ |
|
if ( panel == m_pStencilGradientWidget ) |
|
{ |
|
MarkFilteredImageDirty(); |
|
} |
|
else |
|
{ |
|
// What other is talking to us? |
|
Assert( false ); |
|
} |
|
} |
|
|
|
void OnImageUploadedToCloud( RemoteStorageFileShareResult_t *pResult, bool bIOFailure ); |
|
|
|
void CleanSquareImage() |
|
{ |
|
if ( m_bSquareImageDirty ) |
|
{ |
|
PerformSquarize(); |
|
Assert( !m_bSquareImageDirty ); |
|
Assert( m_bFilteredImageDirty ); |
|
} |
|
} |
|
|
|
void CleanFilteredImage() |
|
{ |
|
CleanSquareImage(); |
|
if ( m_bFilteredImageDirty ) |
|
{ |
|
PerformFilter(); |
|
Assert( !m_bFilteredImageDirty ); |
|
} |
|
} |
|
|
|
void CloseWithGenericError(); |
|
|
|
private: |
|
|
|
struct CroppedImagePanel : public CBitmapPanel { |
|
|
|
CroppedImagePanel( CConfirmCustomizeTextureDialog *pDlg, vgui::Panel *parent ) |
|
: CBitmapPanel( parent, "PreviewCroppedImage" ) |
|
, m_pDlg(pDlg) |
|
{ |
|
} |
|
|
|
CConfirmCustomizeTextureDialog *m_pDlg; |
|
|
|
void Paint() |
|
{ |
|
m_pDlg->CleanSquareImage(); |
|
CBitmapPanel::Paint(); |
|
} |
|
}; |
|
|
|
struct FilteredImagePanel : public CBitmapPanel { |
|
|
|
FilteredImagePanel( CConfirmCustomizeTextureDialog *pDlg, vgui::Panel *parent ) |
|
: CBitmapPanel( parent, "PreviewFilteredImage" ) |
|
, m_pDlg(pDlg) |
|
{ |
|
} |
|
|
|
CConfirmCustomizeTextureDialog *m_pDlg; |
|
|
|
void Paint() |
|
{ |
|
m_pDlg->CleanFilteredImage(); |
|
CBitmapPanel::Paint(); |
|
} |
|
}; |
|
|
|
vgui::FileOpenDialog *m_hImportImageDialog; |
|
CBitmapPanel *m_pFilteredTextureImagePanel; |
|
CBitmapPanel *m_pCroppedTextureImagePanel; |
|
bool m_bFilteredImageDirty; |
|
bool m_bSquareImageDirty; |
|
bool m_bStencilShapeReducedImageDirty; |
|
bool m_bUseAvatar; |
|
bool m_bCropToSquare; // if false, we'll stretch |
|
int m_nSelectedStencilPalette; |
|
CUtlVector< CUtlVector< Color > > m_vecStencilPalettes; |
|
CustomTextureStencilGradientMapWidget *m_pStencilGradientWidget; |
|
|
|
enum EPage |
|
{ |
|
ePage_SelectImage, |
|
ePage_AdjustFilter, |
|
ePage_FinalConfirm, |
|
ePage_PerformingAction, |
|
|
|
k_NumPages |
|
}; |
|
EPage eCurrentPage; |
|
void SetPage( EPage page ); |
|
|
|
// Page container widgets |
|
vgui::EditablePanel *m_rpPagePanel[k_NumPages]; |
|
CItemModelPanel *m_pItemModelPanel; |
|
|
|
Bitmap_t m_imgSource; // original resolution and aspect |
|
Bitmap_t m_imgSquare; // cropped/stretched to square at submitted res |
|
Bitmap_t m_imgSquareDisplay; // cropped/stretched to square at final res |
|
Bitmap_t m_imgFinal; // final output res |
|
Bitmap_t m_imgStencilShapeReduced; |
|
|
|
/// Custom compositing steps defined for this item |
|
CUtlVector<SDecalBlendLayer> m_vecBlendLayers; |
|
|
|
inline bool IsSourceImageSquare() const |
|
{ |
|
// We must know the size |
|
Assert( m_imgSource.IsValid() ); |
|
return |
|
m_imgSource.Width()*99 < m_imgSource.Height()*100 |
|
&& m_imgSource.Height()*99 < m_imgSource.Width()*100; |
|
} |
|
|
|
ITexture *m_pCurrentPreviewedTexture; |
|
|
|
void ActivateFileOpenDialog(); |
|
void PerformSquarize(); |
|
void PerformFilter(); |
|
|
|
vgui::ComboBox *m_pFilterCombo; |
|
vgui::ComboBox *m_pSquarizeCombo; |
|
vgui::ComboBox *m_pStencilModeCombo; |
|
vgui::RadioButton *m_pUseAvatarRadioButton; |
|
vgui::RadioButton *m_pUseAnyImageRadioButton; |
|
|
|
enum EFilter |
|
{ |
|
eFilter_Stencil, |
|
eFilter_Identity, |
|
eFilter_Painterly, |
|
}; |
|
|
|
void PerformIdentityFilter(); |
|
void PerformStencilFilter(); |
|
void PerformPainterlyFilter(); |
|
|
|
// From ITextureRegenerator |
|
virtual void RegenerateTextureBits( ITexture *pTexture, IVTFTexture *pVTFTexture, Rect_t *pRect ); |
|
virtual void Release(); |
|
|
|
void MarkSquareImageDirty() |
|
{ |
|
m_bSquareImageDirty = true; |
|
MarkStencilShapeReducedImageDirty(); |
|
MarkFilteredImageDirty(); |
|
} |
|
|
|
void MarkStencilShapeReducedImageDirty() |
|
{ |
|
m_bStencilShapeReducedImageDirty = true; |
|
MarkFilteredImageDirty(); |
|
} |
|
|
|
void MarkFilteredImageDirty() |
|
{ |
|
m_bFilteredImageDirty = true; |
|
g_pPreviewCustomTextureDirty = true; |
|
} |
|
|
|
void ShowFilterControls(); |
|
void WriteSelectImagePageControls(); |
|
void UseAvatarImage(); |
|
void SelectStencilPalette( int nPalette ); |
|
|
|
// Test harness, for tweaking various values |
|
#ifdef TEST_FILTERS |
|
void TestFilters(); |
|
#endif |
|
}; |
|
|
|
CConfirmCustomizeTextureDialog::CConfirmCustomizeTextureDialog( vgui::Panel *parent, CEconItemView *pTool, CEconItemView *pToolSubject ) |
|
: CBaseToolUsageDialog( parent, "ConfirmCustomizeTextureDialog", pTool, pToolSubject ) |
|
, m_hImportImageDialog( NULL ) |
|
, m_bFilteredImageDirty(true) |
|
, m_bStencilShapeReducedImageDirty(true) |
|
, m_bSquareImageDirty(true) |
|
, m_bCropToSquare(false) |
|
, m_bUseAvatar(true) |
|
, m_pCurrentPreviewedTexture(NULL) |
|
, m_pFilterCombo(NULL) |
|
, m_pSquarizeCombo(NULL) |
|
, m_pStencilModeCombo(NULL) |
|
, m_pUseAvatarRadioButton(NULL) |
|
, m_pUseAnyImageRadioButton(NULL) |
|
, m_pStencilGradientWidget(NULL) |
|
, m_nSelectedStencilPalette(-1) |
|
{ |
|
// clear so that the preview is accurate |
|
Assert( g_pPreviewCustomTexture == NULL ); |
|
Assert( g_pPreviewEconItem == NULL ); |
|
eCurrentPage = ePage_SelectImage; |
|
|
|
m_pItemModelPanel = new CItemModelPanel( this, "paint_model" ); |
|
m_pItemModelPanel->SetItem( pToolSubject ); |
|
m_pItemModelPanel->SetActAsButton( true, false ); |
|
|
|
COMPILE_TIME_ASSERT( k_NumPages == 4 ); |
|
m_rpPagePanel[ePage_SelectImage] = new vgui::EditablePanel( this, "SelectImagePage" ); |
|
m_rpPagePanel[ePage_AdjustFilter] = new vgui::EditablePanel( this, "AdjustFilterPage" ); |
|
m_rpPagePanel[ePage_FinalConfirm] = new vgui::EditablePanel( this, "FinalConfirmPage" ); |
|
m_rpPagePanel[ePage_PerformingAction] = new vgui::EditablePanel( this, "PerformingActionPage" ); |
|
|
|
vgui::EditablePanel *pSelectImagePreviewGroupBox = new vgui::EditablePanel( m_rpPagePanel[ePage_SelectImage], "PreviewImageGroupBox" ); |
|
m_pCroppedTextureImagePanel = new CroppedImagePanel( this, pSelectImagePreviewGroupBox ); |
|
|
|
vgui::EditablePanel *pAdjustFilterPreviewGroupBox = new vgui::EditablePanel( m_rpPagePanel[ePage_AdjustFilter], "PreviewImageGroupBox" ); |
|
m_pFilteredTextureImagePanel = new FilteredImagePanel( this, pAdjustFilterPreviewGroupBox ); |
|
|
|
// |
|
// Locate / create the procedoral material & texture to show the |
|
// results of the filtered texture |
|
// |
|
|
|
ITexture *pPreviewTexture = NULL; |
|
if ( g_pMaterialSystem->IsTextureLoaded( k_rchCustomTextureFilterPreviewTextureName ) ) |
|
{ |
|
pPreviewTexture = g_pMaterialSystem->FindTexture( k_rchCustomTextureFilterPreviewTextureName, TEXTURE_GROUP_VGUI ); |
|
pPreviewTexture->AddRef(); |
|
Assert( pPreviewTexture ); |
|
} |
|
else |
|
{ |
|
pPreviewTexture = g_pMaterialSystem->CreateProceduralTexture( |
|
k_rchCustomTextureFilterPreviewTextureName, |
|
TEXTURE_GROUP_VGUI, |
|
k_nCustomImageSize, k_nCustomImageSize, |
|
IMAGE_FORMAT_RGBA8888, |
|
TEXTUREFLAGS_CLAMPS | TEXTUREFLAGS_CLAMPT | TEXTUREFLAGS_NOMIP | TEXTUREFLAGS_NOLOD |
|
); |
|
Assert( pPreviewTexture ); |
|
} |
|
pPreviewTexture->SetTextureRegenerator( this ); // note carefully order of operations here. See Release() |
|
g_pPreviewCustomTexture = pPreviewTexture; |
|
g_pPreviewCustomTextureDirty = true; |
|
g_pPreviewEconItem = m_pItemModelPanel->GetItem(); |
|
|
|
vgui::ivgui()->AddTickSignal( GetVPanel(), 0 ); |
|
|
|
// Parse blend operations from the tool definition KV |
|
KeyValues *pkvBlendLayers = pToolSubject->GetDefinitionKey( "custom_texture_blend_steps" ); |
|
if ( pkvBlendLayers ) |
|
{ |
|
for ( KeyValues *kvLayer = pkvBlendLayers->GetFirstTrueSubKey() ; kvLayer ; kvLayer = kvLayer->GetNextTrueSubKey() ) |
|
{ |
|
int idx = m_vecBlendLayers.AddToTail(); |
|
CUtlString sErrMsg; |
|
if ( !m_vecBlendLayers[idx].FromKV( kvLayer, sErrMsg ) ) |
|
{ |
|
Warning( "Bogus custom texture blend layer definition '%s'. %s\n", kvLayer->GetName(), (const char *)sErrMsg ); |
|
Assert( !"Bogus custom texture blend layer!" ); |
|
m_vecBlendLayers.Remove( idx ); |
|
} |
|
} |
|
} |
|
|
|
// Setup stencil palettes |
|
|
|
CUtlVector<Color> *pPalette; |
|
pPalette = &m_vecStencilPalettes[ m_vecStencilPalettes.AddToTail() ]; |
|
pPalette->AddToTail( Color( 54, 38, 0, 255 ) ); |
|
pPalette->AddToTail( Color( 236, 236, 217, 255 ) ); |
|
|
|
pPalette = &m_vecStencilPalettes[ m_vecStencilPalettes.AddToTail() ]; |
|
pPalette->AddToTail( Color( 54, 38, 0, 255 ) ); |
|
pPalette->AddToTail( Color( 137, 131, 116, 255 ) ); |
|
pPalette->AddToTail( Color( 236, 236, 217, 255 ) ); |
|
pPalette->AddToTail( Color( 254, 255, 228, 255 ) ); |
|
|
|
pPalette = &m_vecStencilPalettes[ m_vecStencilPalettes.AddToTail() ]; |
|
pPalette->AddToTail( Color( 186, 80, 34, 255 ) ); |
|
pPalette->AddToTail( Color( 243, 231, 194, 255 ) ); |
|
|
|
pPalette = &m_vecStencilPalettes[ m_vecStencilPalettes.AddToTail() ]; |
|
pPalette->AddToTail( Color( 186, 80, 34, 255 ) ); |
|
pPalette->AddToTail( Color( 217, 162, 121, 255 ) ); |
|
pPalette->AddToTail( Color( 243, 231, 194, 255 ) ); |
|
pPalette->AddToTail( Color( 255, 247, 220, 255 ) ); |
|
|
|
pPalette = &m_vecStencilPalettes[ m_vecStencilPalettes.AddToTail() ]; |
|
pPalette->AddToTail( Color( 101, 72, 54, 255 ) ); |
|
pPalette->AddToTail( Color( 229, 150, 73, 255 ) ); |
|
|
|
pPalette = &m_vecStencilPalettes[ m_vecStencilPalettes.AddToTail() ]; |
|
pPalette->AddToTail( Color( 101, 72, 54, 255 ) ); |
|
pPalette->AddToTail( Color( 161, 100, 47, 255 ) ); |
|
pPalette->AddToTail( Color( 229, 150, 73, 255 ) ); |
|
pPalette->AddToTail( Color( 255, 207, 154, 255 ) ); |
|
|
|
pPalette = &m_vecStencilPalettes[ m_vecStencilPalettes.AddToTail() ]; |
|
pPalette->AddToTail( Color( 88, 84, 80, 255 ) ); |
|
pPalette->AddToTail( Color( 160, 84, 72, 255 ) ); |
|
pPalette->AddToTail( Color( 216, 212, 192, 255 ) ); |
|
|
|
pPalette = &m_vecStencilPalettes[ m_vecStencilPalettes.AddToTail() ]; |
|
pPalette->AddToTail( Color( 54, 38, 0, 255 ) ); |
|
pPalette->AddToTail( Color( 163, 110, 0, 255 ) ); |
|
pPalette->AddToTail( Color( 215, 171, 2, 255 ) ); |
|
pPalette->AddToTail( Color( 197, 192, 171, 255 ) ); |
|
|
|
// !TEST! Import the palettes from an image |
|
#if 0 |
|
{ |
|
m_vecStencilPalettes.RemoveAll(); |
|
Bitmap_t imgPal; |
|
Assert( ImgUtl_LoadBitmap( "d:/decal_tool_palettes_bay.png", imgPal ) == CE_SUCCESS ); |
|
const int kSwatchSz = 10; |
|
for (int y = kSwatchSz/2 ; y < imgPal.Height() ; y += kSwatchSz ) |
|
{ |
|
CUtlVector<Color> palette; |
|
for (int x = kSwatchSz/2 ; x < imgPal.Width() ; x += kSwatchSz ) |
|
{ |
|
palette.AddToTail( imgPal.GetColor( x, y ) ); |
|
} |
|
|
|
// Strip off solid white entries from the end. (If these are in the palette, |
|
// they have to come first!) |
|
while ( palette.Count() > 0 && ApproxColorDistSq( palette[palette.Count()-1], Color(255,255,255,255) ) < 12 ) |
|
{ |
|
palette.Remove( palette.Count()-1 ); |
|
} |
|
Assert( palette.Count() != 1 ); // only a single entry in the palette? Should be 0, or at least 2 |
|
if ( palette.Count() > 1 ) |
|
{ |
|
// Reverse the palette, so it is ordered dark -> light. |
|
for ( int l = 0, r = palette.Count()-1 ; l < r ; ++l, --r ) |
|
{ |
|
Color t = palette[l]; |
|
palette[l] = palette[r]; |
|
palette[r] = t; |
|
} |
|
|
|
CUtlVector<Color> *pPalette = &m_vecStencilPalettes[ m_vecStencilPalettes.AddToTail() ]; |
|
Msg( "pPalette = &m_vecStencilPalettes[ m_vecStencilPalettes.AddToTail() ];\n" ); |
|
for (int j = 0 ; j < palette.Count() ; ++j ) |
|
{ |
|
pPalette->AddToTail( palette[j] ); |
|
Msg( "pPalette->AddToTail( Color( %d, %d, %d, 255 ) );\n", palette[j].r(), palette[j].g(), palette[j].b() ); |
|
} |
|
Msg( "\n" ); |
|
} |
|
} |
|
} |
|
#endif |
|
|
|
} |
|
|
|
CConfirmCustomizeTextureDialog::~CConfirmCustomizeTextureDialog( void ) |
|
{ |
|
|
|
// Clean up filtered texture |
|
Release(); |
|
|
|
delete m_hImportImageDialog; |
|
m_hImportImageDialog = NULL; |
|
} |
|
|
|
void CConfirmCustomizeTextureDialog::SetPage( EPage page ) |
|
{ |
|
eCurrentPage = page; |
|
switch ( eCurrentPage ) |
|
{ |
|
default: |
|
Assert(false); |
|
eCurrentPage = ePage_SelectImage; |
|
case ePage_SelectImage: |
|
WriteSelectImagePageControls(); |
|
break; |
|
|
|
case ePage_AdjustFilter: |
|
// Make sure proper controls are shown |
|
ShowFilterControls(); |
|
break; |
|
|
|
case ePage_FinalConfirm: |
|
break; |
|
|
|
case ePage_PerformingAction: |
|
break; |
|
} |
|
|
|
// !KLUDGE! We need to hide ourselves while the file open dialog is up |
|
//SetVisible( eCurrentPage != ePage_SelectImage ); |
|
|
|
for ( int i = 0 ; i < k_NumPages ; ++i ) |
|
{ |
|
if ( m_rpPagePanel[i] ) |
|
{ |
|
m_rpPagePanel[i]->SetVisible( i == eCurrentPage ); |
|
} |
|
} |
|
|
|
vgui::EditablePanel *pPreviewProupPanel = NULL; |
|
if ( m_rpPagePanel[eCurrentPage] ) |
|
{ |
|
pPreviewProupPanel = dynamic_cast<vgui::EditablePanel *>( m_rpPagePanel[eCurrentPage]->FindChildByName( "PreviewModelGroupBox" ) ); |
|
} |
|
if ( pPreviewProupPanel ) |
|
{ |
|
m_pItemModelPanel->SetVisible( true ); |
|
m_pItemModelPanel->SetParent( pPreviewProupPanel ); |
|
m_pItemModelPanel->SetPos( 10, 10 ); |
|
m_pItemModelPanel->SetSize( pPreviewProupPanel->GetWide() - 20, pPreviewProupPanel->GetTall() - 20 ); |
|
m_pItemModelPanel->UpdatePanels(); |
|
} |
|
else |
|
{ |
|
m_pItemModelPanel->SetVisible( false ); |
|
} |
|
} |
|
|
|
void CConfirmCustomizeTextureDialog::ActivateFileOpenDialog() |
|
{ |
|
// Create the dialog the first time it's used |
|
if (m_hImportImageDialog == NULL) |
|
{ |
|
m_hImportImageDialog = new vgui::FileOpenDialog( NULL, "#ToolCustomizeTextureBrowseDialogTitle", true ); |
|
#ifdef WIN32 |
|
m_hImportImageDialog->AddFilter( "*.tga,*.jpg,*.png,*.bmp", "#GameUI_All_Images", true ); |
|
#else |
|
m_hImportImageDialog->AddFilter( "*.tga,*.jpg,*.png", "#GameUI_All_Images", true ); |
|
#endif |
|
m_hImportImageDialog->AddFilter( "*.tga", "#GameUI_TGA_Images", false ); |
|
m_hImportImageDialog->AddFilter( "*.jpg", "#GameUI_JPEG_Images", false ); |
|
m_hImportImageDialog->AddFilter( "*.png", "#GameUI_PNG_Images", false ); |
|
#ifdef WIN32 |
|
m_hImportImageDialog->AddFilter( "*.bmp", "#GameUI_BMP_Images", false ); |
|
#endif |
|
m_hImportImageDialog->AddActionSignalTarget( this ); |
|
} |
|
|
|
// Activate it |
|
m_hImportImageDialog->DoModal( false ); |
|
m_hImportImageDialog->Activate(); |
|
} |
|
|
|
void CConfirmCustomizeTextureDialog::OnCommand( const char *command ) |
|
{ |
|
if (!stricmp( command, "pick_image" ) ) |
|
{ |
|
ActivateFileOpenDialog(); |
|
return; |
|
} |
|
|
|
// !KLUDGE! Base class closes window. I don't want to do this. |
|
if ( !Q_stricmp( command, "apply" ) ) |
|
{ |
|
Apply(); |
|
return; |
|
} |
|
|
|
if ( !Q_stricmp( command, "next_page" ) ) |
|
{ |
|
if ( eCurrentPage < ePage_FinalConfirm ) |
|
{ |
|
SetPage( (EPage)(eCurrentPage + 1) ); |
|
} |
|
return; |
|
} |
|
|
|
if ( !Q_stricmp( command, "prev_page" ) ) |
|
{ |
|
if ( eCurrentPage > ePage_SelectImage ) |
|
{ |
|
SetPage( (EPage)(eCurrentPage - 1) ); |
|
} |
|
return; |
|
} |
|
|
|
if ( !Q_stricmp( command, "next_stencil_palette" ) ) |
|
{ |
|
SelectStencilPalette( m_nSelectedStencilPalette + 1 ); |
|
return; |
|
} |
|
|
|
if ( !Q_stricmp( command, "prev_stencil_palette" ) ) |
|
{ |
|
SelectStencilPalette( m_nSelectedStencilPalette + m_vecStencilPalettes.Count() - 1 ); |
|
return; |
|
} |
|
|
|
BaseClass::OnCommand( command ); |
|
} |
|
|
|
void CConfirmCustomizeTextureDialog::SelectStencilPalette( int nPalette ) |
|
{ |
|
while ( nPalette < 0 ) |
|
{ |
|
nPalette += m_vecStencilPalettes.Count(); |
|
} |
|
m_nSelectedStencilPalette = nPalette % m_vecStencilPalettes.Count(); |
|
MarkFilteredImageDirty(); |
|
|
|
if ( m_pStencilGradientWidget ) |
|
{ |
|
const CUtlVector<Color> &pal = m_vecStencilPalettes[m_nSelectedStencilPalette]; |
|
m_pStencilGradientWidget->InitRangeCount( pal.Count() ); |
|
m_pStencilGradientWidget->SetRangeColors( pal.Base() ); |
|
} |
|
} |
|
|
|
|
|
static void ListenToControlsRecursive( vgui::Panel *pPanel, vgui::Panel *pListener ) |
|
{ |
|
if ( pPanel == NULL ) |
|
{ |
|
return; |
|
} |
|
if ( |
|
dynamic_cast<vgui::Button *>( pPanel ) |
|
|| dynamic_cast<vgui::Slider *>( pPanel ) |
|
|| dynamic_cast<vgui::ComboBox *>( pPanel ) |
|
|| dynamic_cast<CustomTextureStencilGradientMapWidget *>( pPanel ) |
|
) |
|
{ |
|
pPanel->AddActionSignalTarget( pListener ); |
|
} |
|
else |
|
{ |
|
for ( int i = 0 ; i < pPanel->GetChildCount() ; ++i ) |
|
{ |
|
ListenToControlsRecursive( pPanel->GetChild(i), pListener ); |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CConfirmCustomizeTextureDialog::ApplySchemeSettings( vgui::IScheme *pScheme ) |
|
{ |
|
LoadControlSettings( "Resource/UI/econ/ConfirmCustomizeTextureDialog.res" ); |
|
|
|
BaseClass::ApplySchemeSettings( pScheme ); |
|
|
|
m_pFilterCombo = dynamic_cast<vgui::ComboBox *>( FindChildByName("FilterComboBox", true ) ); |
|
Assert( m_pFilterCombo ); |
|
if ( m_pFilterCombo ) |
|
{ |
|
m_pFilterCombo->RemoveAll(); |
|
|
|
COMPILE_TIME_ASSERT( eFilter_Stencil == 0 ); |
|
m_pFilterCombo->AddItem( "#ToolCustomizeTextureFilterStencil", NULL ); |
|
|
|
//COMPILE_TIME_ASSERT( eFilter_Painterly == 0 ); |
|
//m_pFilterCombo->AddItem( "#ToolCustomizeTextureFilterPainterly", NULL ); |
|
|
|
COMPILE_TIME_ASSERT( eFilter_Identity == 1 ); |
|
#ifdef _DEBUG |
|
m_pFilterCombo->AddItem( "None", NULL ); |
|
#endif |
|
|
|
//m_pFilterCombo->SilentActivateItemByRow( eFilter_Painterly ); |
|
m_pFilterCombo->SilentActivateItemByRow( eFilter_Stencil ); |
|
} |
|
|
|
m_pSquarizeCombo = dynamic_cast<vgui::ComboBox *>( FindChildByName("SquarizeComboBox", true ) ); |
|
Assert( m_pSquarizeCombo ); |
|
|
|
m_pStencilModeCombo = dynamic_cast<vgui::ComboBox *>( FindChildByName("StencilModeComboBox", true ) ); |
|
Assert( m_pStencilModeCombo ); |
|
if ( m_pStencilModeCombo ) |
|
{ |
|
m_pStencilModeCombo->AddItem( "#ToolCustomizeTextureStencilMatchByIntensity", NULL ); |
|
m_pStencilModeCombo->AddItem( "#ToolCustomizeTextureStencilMatchByColor", NULL ); |
|
m_pStencilModeCombo->SilentActivateItemByRow( 0 ); |
|
} |
|
|
|
m_pUseAvatarRadioButton = dynamic_cast<vgui::RadioButton *>( FindChildByName("UseAvatarRadio", true ) ); |
|
m_pUseAnyImageRadioButton = dynamic_cast<vgui::RadioButton *>( FindChildByName("UseAnyimageRadio", true ) ); |
|
|
|
m_pStencilGradientWidget = dynamic_cast<CustomTextureStencilGradientMapWidget *>( FindChildByName("StencilGradientMap", true ) ); |
|
Assert( m_pStencilGradientWidget ); |
|
|
|
for ( int i = 0 ; i < k_NumPages ; ++i ) |
|
{ |
|
ListenToControlsRecursive( m_rpPagePanel[i], this ); |
|
} |
|
|
|
UseAvatarImage(); |
|
|
|
SetPage( ePage_SelectImage ); |
|
|
|
// Flip this flag to activate the test harness |
|
#ifdef TEST_FILTERS |
|
TestFilters(); |
|
#endif |
|
|
|
SelectStencilPalette( 0 ); |
|
} |
|
|
|
void CConfirmCustomizeTextureDialog::UseAvatarImage() |
|
{ |
|
// assume failure |
|
m_imgSource.Clear(); |
|
m_bUseAvatar = false; |
|
|
|
if ( steamapicontext && steamapicontext->SteamUser() ) |
|
{ |
|
|
|
const int k_nAvatarImageSize = 184; |
|
m_imgSource.Init( k_nAvatarImageSize, k_nAvatarImageSize, IMAGE_FORMAT_RGBA8888 ); |
|
int iAvatar = steamapicontext->SteamFriends()->GetLargeFriendAvatar( steamapicontext->SteamUser()->GetSteamID() ); |
|
if ( !steamapicontext->SteamUtils()->GetImageRGBA( iAvatar, m_imgSource.GetBits(), k_nAvatarImageSize*k_nAvatarImageSize*4 ) ) |
|
{ |
|
m_imgSource.Clear(); |
|
} |
|
else |
|
{ |
|
m_bUseAvatar = true; |
|
} |
|
} |
|
|
|
WriteSelectImagePageControls(); |
|
MarkSquareImageDirty(); |
|
} |
|
|
|
void CConfirmCustomizeTextureDialog::WriteSelectImagePageControls() |
|
{ |
|
if ( !m_pSquarizeCombo ) |
|
{ |
|
return; |
|
} |
|
|
|
m_pSquarizeCombo->RemoveAll(); |
|
|
|
CExButton *pNextButton = dynamic_cast<CExButton *>( m_rpPagePanel[ePage_SelectImage]->FindChildByName( "NextButton", true ) ); |
|
if ( !pNextButton ) |
|
{ |
|
return; |
|
} |
|
|
|
if ( m_pUseAvatarRadioButton ) |
|
{ |
|
if ( !m_pUseAvatarRadioButton->IsSelected() ) |
|
{ |
|
m_pUseAvatarRadioButton->SetSelected( m_bUseAvatar ); |
|
} |
|
} |
|
if ( m_pUseAnyImageRadioButton ) |
|
{ |
|
if ( !m_pUseAnyImageRadioButton->IsSelected() ) |
|
{ |
|
m_pUseAnyImageRadioButton->SetSelected( !m_bUseAvatar ); |
|
} |
|
} |
|
|
|
if ( !m_imgSource.IsValid() ) |
|
{ |
|
// No image yet selected |
|
m_pSquarizeCombo->SetVisible( false ); |
|
pNextButton->SetEnabled( false ); |
|
m_pCroppedTextureImagePanel->SetVisible( false ); |
|
return; |
|
} |
|
m_pCroppedTextureImagePanel->SetVisible( true ); |
|
pNextButton->SetEnabled( true ); |
|
|
|
// Nearly square already? |
|
if ( IsSourceImageSquare() ) |
|
{ |
|
// Nearly square. No need to offer any options |
|
m_pSquarizeCombo->SetVisible( false ); |
|
} |
|
else |
|
{ |
|
m_pSquarizeCombo->AddItem( "#ToolCustomizeTextureStretch", NULL ); |
|
m_pSquarizeCombo->AddItem( "#ToolCustomizeTextureCrop", NULL ); |
|
m_pSquarizeCombo->SetVisible( true ); |
|
m_pSquarizeCombo->ActivateItemByRow( m_bCropToSquare ? 1 : 0 ); |
|
} |
|
|
|
|
|
} |
|
|
|
void CConfirmCustomizeTextureDialog::OnTick( void ) |
|
{ |
|
BaseClass::OnTick(); |
|
|
|
// Process, depending on currently selected page |
|
switch ( eCurrentPage ) |
|
{ |
|
default: |
|
Assert(false); |
|
eCurrentPage = ePage_SelectImage; |
|
case ePage_SelectImage: |
|
break; |
|
|
|
case ePage_AdjustFilter: |
|
break; |
|
|
|
case ePage_FinalConfirm: |
|
break; |
|
|
|
case ePage_PerformingAction: |
|
break; |
|
} |
|
} |
|
|
|
void CConfirmCustomizeTextureDialog::ShowFilterControls() |
|
{ |
|
EFilter f = (EFilter)m_pFilterCombo->GetActiveItem(); |
|
|
|
vgui::Panel *p; |
|
|
|
p = m_rpPagePanel[ePage_AdjustFilter]->FindChildByName( "PainterlyOptions", true ); |
|
if ( p ) |
|
{ |
|
p->SetVisible( f == eFilter_Painterly ); |
|
} |
|
|
|
p = m_rpPagePanel[ePage_AdjustFilter]->FindChildByName( "StencilOptions", true ); |
|
if ( p ) |
|
{ |
|
p->SetVisible( f == eFilter_Stencil ); |
|
} |
|
} |
|
|
|
void CConfirmCustomizeTextureDialog::PerformSquarize() |
|
{ |
|
if ( m_bCropToSquare && !IsSourceImageSquare() ) |
|
{ |
|
// Select the smaller dimension as the size |
|
int nSize = MIN( m_imgSource.Width(), m_imgSource.Height() ); |
|
|
|
// Crop it. |
|
// Yeah, the crop and resize could be done all in one step. |
|
// And...I don't care. |
|
int x0 = ( m_imgSource.Width() - nSize ) / 2; |
|
int y0 = ( m_imgSource.Height() - nSize ) / 2; |
|
m_imgSquare.Crop( x0, y0, nSize, nSize, &m_imgSource ); |
|
} |
|
else |
|
{ |
|
m_imgSquare.MakeLogicalCopyOf( m_imgSource ); |
|
} |
|
|
|
// Reduce it for display purposes |
|
ImgUtl_ResizeBitmap( m_imgSquareDisplay, k_nCustomImageSize, k_nCustomImageSize, &m_imgSquare ); |
|
|
|
// Square image is now up-to-date with options |
|
m_bSquareImageDirty = false; |
|
|
|
if ( m_pCroppedTextureImagePanel != NULL ) |
|
{ |
|
m_pCroppedTextureImagePanel->SetBitmap( m_imgSquareDisplay ); |
|
} |
|
|
|
// We need to re-run our filter anytime this changes |
|
MarkFilteredImageDirty(); |
|
} |
|
|
|
void CConfirmCustomizeTextureDialog::PerformFilter() |
|
{ |
|
|
|
// this can take a while, put up a waiting cursor |
|
vgui::surface()->SetCursor( vgui::dc_hourglass ); |
|
|
|
switch ( (EFilter)m_pFilterCombo->GetActiveItem() ) |
|
{ |
|
case eFilter_Identity: |
|
// !FIXME! Only allow while in dev universe? |
|
PerformIdentityFilter(); |
|
break; |
|
default: |
|
Assert( false ); |
|
case eFilter_Stencil: |
|
PerformStencilFilter(); |
|
break; |
|
case eFilter_Painterly: |
|
PerformPainterlyFilter(); |
|
break; |
|
} |
|
|
|
// Now apply the blend layers |
|
static bool bDoBlendLayers = true; |
|
if ( bDoBlendLayers ) |
|
{ |
|
for ( int i = 0; i < m_vecBlendLayers.Size() ; ++i ) |
|
{ |
|
m_vecBlendLayers[i].Apply( m_imgFinal ); |
|
} |
|
} |
|
|
|
// And the texture on the 3D model |
|
g_pPreviewCustomTextureDirty = true; |
|
|
|
m_bFilteredImageDirty = false; |
|
|
|
if ( m_pFilteredTextureImagePanel != NULL ) |
|
{ |
|
m_pFilteredTextureImagePanel->SetBitmap( m_imgFinal ); |
|
} |
|
|
|
// change the cursor back to normal |
|
vgui::surface()->SetCursor( vgui::dc_user ); |
|
} |
|
|
|
void CConfirmCustomizeTextureDialog::PerformIdentityFilter() |
|
{ |
|
ImgUtl_ResizeBitmap( m_imgFinal, k_nCustomImageSize, k_nCustomImageSize, &m_imgSquare ); |
|
} |
|
|
|
void CConfirmCustomizeTextureDialog::PerformStencilFilter() |
|
{ |
|
|
|
// Check if the shape reduced image is dirty |
|
if ( m_bStencilShapeReducedImageDirty ) |
|
{ |
|
Bitmap_t imgTemp1, imgTemp2; |
|
|
|
// Need a slider to control this. Works OK for color match, poorly for intensity. |
|
// Best for all cases is to just do nothing |
|
// // Downsample FIRST to 2X res |
|
// ImgUtl_ResizeBitmap( imgTemp1, k_nCustomImageSize*2, k_nCustomImageSize*2, &m_imgSquare ); |
|
// |
|
// // Run the bilateral filter several times |
|
// static float thresh1 = .7f; static int rad1 = 1; static float amount1 = 1.0f; |
|
// static float thresh2 = .8f; static int rad2 = 1; static float amount2 = 1.0f; |
|
// static float thresh3 = .9f; static int rad3 = 2; static float amount3 = 1.0f; |
|
// Bitmap_t t; |
|
// BilateralFilter( imgTemp1, imgTemp2, rad1, thresh1, amount1 ); |
|
// static int rounds = 4; |
|
// for ( int r = 0 ; r < rounds ; ++r ) |
|
// { |
|
// BilateralFilter( imgTemp2, imgTemp1, rad2, thresh2, amount2 ); |
|
// BilateralFilter( imgTemp1, imgTemp2, rad2, thresh2, amount2 ); |
|
// } |
|
// //BilateralFilter( imgTemp2, m_imgFinal, rad3, thresh3, amount3 ); |
|
// BilateralFilter( imgTemp2, m_imgStencilShapeReduced, rad3, thresh3, amount3 ); |
|
|
|
// Downsample FIRST to 2X res |
|
ImgUtl_ResizeBitmap( m_imgStencilShapeReduced, k_nCustomImageSize*2, k_nCustomImageSize*2, &m_imgSquare ); |
|
|
|
m_bStencilShapeReducedImageDirty = false; |
|
} |
|
|
|
// Color matching |
|
{ |
|
// Color swatches[] = |
|
// { |
|
// Color( 255, 255, 255 ), |
|
// Color( 183, 224, 252 ), // sky light |
|
// Color( 83, 109, 205 ), // sky med |
|
// Color( 64, 68, 195 ), // sky dark |
|
// Color( 100, 68, 57 ), // skin demo |
|
// Color( 139, 101, 84 ), // skin demo light |
|
// Color( 133, 105, 68 ), // saxton hair |
|
// Color( 252, 169, 131 ), // skin light |
|
// Color( 194, 132, 106 ), // skin |
|
// |
|
// //Color( 255, 255, 255 ), |
|
// //Color( 246, 231, 222 ), |
|
// //Color( 218, 189, 171 ), |
|
// //Color( 193, 161, 138 ), |
|
// // |
|
// //Color( 248, 185, 138 ), |
|
// //Color( 245, 173, 135 ), |
|
// //Color( 239, 152, 73 ), |
|
// //Color( 241, 129, 73 ), |
|
// // |
|
// //Color( 106, 69, 52 ), |
|
// //Color( 145, 58, 31 ), |
|
// //Color( 189, 58, 58 ), |
|
// //Color( 157, 48, 47 ), |
|
// //Color( 69, 44, 37 ), |
|
// // |
|
// //Color( 107, 106, 101 ), |
|
// //Color( 118, 138, 136 ), |
|
// //Color( 91, 122, 140 ), |
|
// //Color( 56, 92, 120 ), |
|
// //Color( 52, 47, 44 ), |
|
// }; |
|
|
|
Bitmap_t imgTemp1; |
|
|
|
static float colorReplacePct = 1.0f; |
|
|
|
// match by color, or intensity? |
|
if ( m_pStencilModeCombo && m_pStencilModeCombo->GetActiveItem() == 0 && m_pStencilGradientWidget ) |
|
{ |
|
imgTemp1.Init( m_imgStencilShapeReduced.Width(), m_imgStencilShapeReduced.Height(), IMAGE_FORMAT_RGBA8888 ); |
|
for ( int y = 0 ; y < imgTemp1.Height() ; ++y ) |
|
{ |
|
for ( int x = 0 ; x < imgTemp1.Width() ; ++x ) |
|
{ |
|
Color c = m_imgStencilShapeReduced.GetColor( x, y ); |
|
Vector lab = TextureToLab( c ); |
|
int index = clamp(lab.x * (255.0f/100.0f) + .5f, 0.0, 255.0f); |
|
imgTemp1.SetColor( x, y, m_pStencilGradientWidget->m_colorGradient[ index ] ); |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
|
|
Assert( m_nSelectedStencilPalette >= 0 ); |
|
Assert( m_nSelectedStencilPalette < m_vecStencilPalettes.Count() ); |
|
const CUtlVector<Color> &pal = m_vecStencilPalettes[ m_nSelectedStencilPalette ]; |
|
|
|
// Determine "weight" of each swatch, from the relative sizes of the |
|
// gradient widget ranges |
|
CUtlVector<float> vecSwatchWeight; |
|
for ( int i = 0 ; i < pal.Size() ; ++i ) |
|
{ |
|
float weight = 1.0f; |
|
if ( m_pStencilGradientWidget ) |
|
{ |
|
weight = float( m_pStencilGradientWidget->GetNobValue(i) - m_pStencilGradientWidget->GetNobValue(i - 1) ); |
|
} |
|
vecSwatchWeight.AddToTail( weight ); |
|
|
|
} |
|
|
|
ColorReplace( m_imgStencilShapeReduced, imgTemp1, pal.Count(), pal.Base(), colorReplacePct, vecSwatchWeight.Base() ); |
|
} |
|
|
|
// Now downsample to the final size |
|
ImgUtl_ResizeBitmap( m_imgFinal, k_nCustomImageSize, k_nCustomImageSize, &imgTemp1 );\ |
|
} |
|
|
|
// // !KLUDGE! |
|
// if ( m_pStencilGradientWidget == NULL ) |
|
// { |
|
// PerformIdentityFilter(); |
|
// return; |
|
// } |
|
// |
|
// // Make sure temp image is properly allocated |
|
// imgTemp1.Init( m_imgSquare.Width(), m_imgSquare.Height(), IMAGE_FORMAT_RGBA8888 ); |
|
// |
|
// // Perform stencil operation |
|
// for ( int y = 0 ; y < m_imgSquare.Height() ; ++y ) |
|
// { |
|
// for ( int x = 0 ; x < m_imgSquare.Height() ; ++x ) |
|
// { |
|
// Color c = m_imgSquare.GetColor(x,y); |
|
// |
|
// // Compute "value" using simple average. (No visual |
|
// // weighting for this.) |
|
// int v = ( (int)c.r() + (int)c.g() + (int)c.g() ) / 3; |
|
// |
|
// // Apply gradient map |
|
// Color result = m_pStencilGradientWidget->m_colorGradient[ v ]; |
|
// imgTemp1.SetColor( x, y, result ); |
|
// } |
|
// } |
|
// |
|
// // Now downsample to the final size |
|
// ImgUtl_ResizeBitmap( m_imgFinal, k_nCustomImageSize, k_nCustomImageSize, &m_imgTemp ); |
|
} |
|
|
|
const int k_BrushStrokeSize = 64; |
|
static byte s_bBrushStrokeData[k_BrushStrokeSize][k_BrushStrokeSize] = |
|
{ |
|
{ 0x8C, 0x86, 0x87, 0x87, 0x86, 0x88, 0x88, 0x87, 0x86, 0x88, 0x8F, 0x8E, 0x8C, 0x8E, 0x8D, 0x8E, 0x8B, 0x8A, 0x8A, 0x94, 0xAE, 0xB6, 0xB5, 0xB5, 0xB4, 0xB4, 0xB4, 0xB4, 0xB3, 0xB0, 0xB0, 0xB2, 0x9E, 0x9A, 0x9C, 0x9C, 0x9A, 0x99, 0x97, 0x98, 0x96, 0x9A, 0x9D, 0x9F, 0x9E, 0x9D, 0x9E, 0x9F, 0x9B, 0x9A, 0x99, 0x98, 0x95, 0x91, 0x8F, 0x8F, 0x8E, 0x89, 0x88, 0x89, 0x88, 0x85, 0x87, 0x8B }, |
|
{ 0x85, 0x7C, 0x7D, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7D, 0x7F, 0x87, 0x88, 0x88, 0x89, 0x87, 0x86, 0x86, 0x83, 0x81, 0x8B, 0xA9, 0xB2, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xAF, 0xAD, 0xAD, 0xAE, 0x97, 0x92, 0x94, 0x94, 0x93, 0x92, 0x91, 0x92, 0x91, 0x94, 0x98, 0x9B, 0x9A, 0x99, 0x99, 0x99, 0x96, 0x95, 0x96, 0x98, 0x98, 0x98, 0x99, 0x9A, 0x93, 0x86, 0x84, 0x84, 0x80, 0x7D, 0x7D, 0x83 }, |
|
{ 0x86, 0x7D, 0x7E, 0x81, 0x80, 0x7F, 0x7F, 0x82, 0x83, 0x84, 0x8A, 0x89, 0x86, 0x87, 0x88, 0x89, 0x86, 0x87, 0x85, 0x8E, 0xAA, 0xB2, 0xB0, 0xB1, 0xB0, 0xB1, 0xB1, 0xB1, 0xB0, 0xAE, 0xAD, 0xAD, 0x9B, 0x96, 0x96, 0x96, 0x94, 0x95, 0x94, 0x96, 0x97, 0x99, 0x9C, 0x9E, 0x9D, 0x9D, 0x9C, 0x9C, 0x9A, 0x99, 0x99, 0x99, 0x9A, 0x9B, 0x9D, 0x9E, 0x9B, 0x86, 0x85, 0x86, 0x83, 0x80, 0x7F, 0x88 }, |
|
{ 0x84, 0x7D, 0x7F, 0x81, 0x7F, 0x80, 0x83, 0x88, 0x88, 0x88, 0x8B, 0x8A, 0x88, 0x89, 0x89, 0x89, 0x84, 0x85, 0x85, 0x8E, 0xAB, 0xB4, 0xB2, 0xB3, 0xB2, 0xB2, 0xB3, 0xB3, 0xB2, 0xB0, 0xAF, 0xAE, 0x99, 0x94, 0x94, 0x94, 0x93, 0x96, 0x97, 0x99, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9C, 0x99, 0x99, 0x99, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9D, 0x85, 0x85, 0x87, 0x84, 0x82, 0x7E, 0x87 }, |
|
{ 0x85, 0x7D, 0x7E, 0x7E, 0x7F, 0x84, 0x88, 0x8A, 0x8A, 0x88, 0x88, 0x89, 0x87, 0x87, 0x89, 0x88, 0x89, 0x88, 0x86, 0x90, 0xAA, 0xB4, 0xB3, 0xB1, 0xB1, 0xB2, 0xB3, 0xB3, 0xB2, 0xB1, 0xAF, 0xAE, 0x97, 0x93, 0x94, 0x94, 0x95, 0x99, 0x9C, 0x9E, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x97, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9C, 0x9C, 0x86, 0x86, 0x86, 0x85, 0x85, 0x7F, 0x86 }, |
|
{ 0x85, 0x7A, 0x76, 0x75, 0x77, 0x7D, 0x7D, 0x7A, 0x7B, 0x7D, 0x7D, 0x7D, 0x7A, 0x7F, 0x8E, 0x97, 0x90, 0x94, 0x9A, 0xA0, 0xAF, 0xB2, 0xB2, 0xB2, 0xB1, 0xB2, 0xB3, 0xB3, 0xB3, 0xB2, 0xB0, 0xAF, 0x96, 0x92, 0x94, 0x93, 0x93, 0x97, 0x99, 0x9A, 0x9A, 0x9B, 0x9C, 0x9E, 0x9E, 0x9D, 0x9C, 0x9C, 0x9A, 0x9A, 0x9B, 0x9C, 0x9E, 0x9F, 0x9F, 0x9E, 0x9B, 0x8A, 0x87, 0x87, 0x86, 0x85, 0x80, 0x86 }, |
|
{ 0x89, 0x7B, 0x75, 0x74, 0x75, 0x77, 0x75, 0x73, 0x75, 0x77, 0x79, 0x7B, 0x7C, 0x82, 0x92, 0x9C, 0x97, 0x9F, 0xAB, 0xAE, 0xB1, 0xB1, 0xB4, 0xB4, 0xB3, 0xB4, 0xB5, 0xB5, 0xB4, 0xB5, 0xB3, 0xB2, 0x9A, 0x97, 0x99, 0x98, 0x97, 0x99, 0x98, 0x98, 0x9A, 0x9B, 0x9E, 0x9F, 0x9F, 0x9F, 0x9E, 0x9D, 0x9A, 0x9B, 0x9C, 0x9D, 0xA1, 0xA5, 0xA7, 0xA6, 0xA7, 0x9A, 0x94, 0x94, 0x93, 0x8A, 0x83, 0x89 }, |
|
{ 0x8B, 0x7E, 0x7A, 0x7B, 0x79, 0x79, 0x78, 0x79, 0x75, 0x77, 0x7E, 0x90, 0xA0, 0xA8, 0xAC, 0xA7, 0xA9, 0xAA, 0xB0, 0xB0, 0xB1, 0xB1, 0xB4, 0xB2, 0xB3, 0xB5, 0xB6, 0xB5, 0xB5, 0xB6, 0xB5, 0xB3, 0x9B, 0x99, 0x9D, 0x9D, 0x9C, 0x9E, 0x9C, 0x9B, 0x9C, 0x9D, 0x9F, 0xA0, 0x9F, 0x9F, 0x9E, 0x9F, 0x9A, 0x9B, 0x9B, 0x9C, 0xA0, 0xA5, 0xA7, 0xA8, 0xA9, 0xA0, 0x9A, 0x9C, 0x9B, 0x8D, 0x85, 0x8B }, |
|
{ 0x8A, 0x7E, 0x7B, 0x77, 0x79, 0x78, 0x77, 0x74, 0x75, 0x70, 0x87, 0xAC, 0xAF, 0xAF, 0xB1, 0xB1, 0xAE, 0xAD, 0xB1, 0xB0, 0xB1, 0xB0, 0xB5, 0xB5, 0xB5, 0xB4, 0xB5, 0xB5, 0xB4, 0xB5, 0xB4, 0xB1, 0x9A, 0x9B, 0x9C, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9E, 0x9F, 0x9F, 0x9F, 0x9F, 0x9E, 0x9A, 0x9A, 0x9C, 0x9D, 0xA0, 0xA5, 0xA7, 0xA9, 0xA8, 0xA0, 0x9B, 0x9A, 0x99, 0x8D, 0x83, 0x8B }, |
|
{ 0x8B, 0x7D, 0x7A, 0x77, 0x78, 0x77, 0x78, 0x76, 0x77, 0x73, 0x88, 0xA8, 0xAC, 0xAE, 0xB2, 0xB3, 0xB2, 0xB1, 0xB1, 0xB0, 0xB5, 0xB4, 0xB5, 0xB4, 0xB6, 0xB5, 0xB6, 0xB6, 0xB5, 0xB5, 0xB5, 0xB2, 0x9A, 0x9B, 0x9C, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9E, 0x9D, 0x9E, 0x9E, 0xA2, 0xA7, 0xA7, 0xA7, 0xAB, 0xA0, 0x9A, 0x9B, 0x9B, 0x8E, 0x83, 0x8B }, |
|
{ 0x88, 0x78, 0x77, 0x77, 0x77, 0x76, 0x76, 0x73, 0x72, 0x74, 0x8B, 0xA8, 0xAC, 0xAE, 0xB1, 0xB0, 0xAF, 0xB3, 0xB4, 0xB1, 0xB6, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB6, 0xB6, 0xB5, 0xB6, 0xB6, 0xB3, 0x99, 0x9A, 0x9B, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9B, 0x9B, 0x9B, 0x9C, 0x9C, 0x9D, 0x9D, 0x9E, 0x9E, 0x9D, 0x9E, 0x9F, 0xA3, 0xA8, 0xA8, 0xA9, 0xAB, 0x9F, 0x98, 0x9A, 0x9A, 0x8D, 0x81, 0x88 }, |
|
{ 0x8C, 0x7A, 0x7A, 0x7C, 0x7C, 0x7C, 0x7E, 0x7A, 0x7A, 0x7A, 0x88, 0x97, 0x97, 0x96, 0x98, 0x97, 0x99, 0xA1, 0xA5, 0xA6, 0xB1, 0xB4, 0xB4, 0xB5, 0xB5, 0xB4, 0xB5, 0xB5, 0xB4, 0xB5, 0xB5, 0xB3, 0x9A, 0x9A, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9C, 0x9D, 0x9D, 0xA0, 0xA1, 0xA5, 0xAA, 0xAB, 0xAD, 0xAD, 0xA3, 0x9D, 0x9E, 0x9E, 0x92, 0x87, 0x8C }, |
|
{ 0x92, 0x7F, 0x7C, 0x7B, 0x7A, 0x7A, 0x7E, 0x7C, 0x7B, 0x7B, 0x82, 0x87, 0x85, 0x83, 0x85, 0x84, 0x84, 0x8A, 0x8E, 0x96, 0xAD, 0xB5, 0xB4, 0xB2, 0xB4, 0xB4, 0xB4, 0xB4, 0xB3, 0xB5, 0xB5, 0xB2, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9A, 0x9A, 0x9A, 0x9B, 0x9B, 0x9C, 0x9C, 0x9C, 0x9D, 0x9D, 0x9D, 0x9E, 0x9E, 0xA1, 0xA3, 0xA8, 0xAE, 0xAE, 0xAF, 0xB0, 0xAD, 0xA9, 0xA8, 0xA9, 0xA1, 0x97, 0x9B }, |
|
{ 0xA4, 0x92, 0x8D, 0x89, 0x85, 0x82, 0x82, 0x7F, 0x80, 0x81, 0x87, 0x89, 0x8A, 0x88, 0x8A, 0x89, 0x8A, 0x8F, 0x93, 0x9B, 0xAF, 0xB6, 0xB5, 0xB4, 0xB5, 0xB4, 0xB5, 0xB5, 0xB4, 0xB5, 0xB5, 0xB3, 0x9B, 0x9A, 0x99, 0x99, 0x9A, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9C, 0x9C, 0x9D, 0x9D, 0x9D, 0x9D, 0x9E, 0x9C, 0x9F, 0xA3, 0xAA, 0xB1, 0xB1, 0xB0, 0xB0, 0xB0, 0xAE, 0xAD, 0xAF, 0xAA, 0xA3, 0xA6 }, |
|
{ 0xA8, 0x9E, 0x9F, 0xA0, 0xA2, 0xA1, 0xA1, 0x9F, 0xA0, 0x9E, 0x9E, 0x99, 0x9B, 0x99, 0x9C, 0x9D, 0x9D, 0xA0, 0xA2, 0xA5, 0xB1, 0xB4, 0xB5, 0xB6, 0xB5, 0xB4, 0xB5, 0xB4, 0xB3, 0xB4, 0xB4, 0xB2, 0x99, 0x98, 0x98, 0x97, 0x98, 0x99, 0x99, 0x99, 0x9A, 0x9A, 0x9A, 0x9B, 0x9C, 0x9D, 0x9D, 0x9E, 0x9F, 0xA0, 0xA3, 0xA6, 0xAA, 0xB0, 0xB0, 0xB0, 0xAE, 0xAD, 0xAC, 0xAC, 0xAF, 0xAB, 0xA4, 0xA9 }, |
|
{ 0xA5, 0x9D, 0x9F, 0xA0, 0xA4, 0xA4, 0xA4, 0xA3, 0xA3, 0xA3, 0xA3, 0xA0, 0xA4, 0xA0, 0xA1, 0xA0, 0xA2, 0xA1, 0xA1, 0xA3, 0xAF, 0xB1, 0xB3, 0xB4, 0xB4, 0xB3, 0xB4, 0xB3, 0xB2, 0xB3, 0xB3, 0xB1, 0x8F, 0x8E, 0x8D, 0x8C, 0x8D, 0x8E, 0x8E, 0x8E, 0x91, 0x92, 0x94, 0x98, 0x9C, 0xA1, 0xA5, 0xA7, 0xA6, 0xA9, 0xAE, 0xAC, 0xAB, 0xAD, 0xAD, 0xB0, 0xB1, 0xAD, 0xAB, 0xAC, 0xB0, 0xAB, 0xA4, 0xA9 }, |
|
{ 0xA9, 0x9A, 0x9C, 0xA4, 0xA5, 0xA6, 0xA2, 0x9D, 0xA0, 0xA0, 0x9F, 0x9F, 0x9F, 0x9F, 0xA0, 0xA0, 0xA0, 0xA1, 0xA0, 0xA2, 0xAA, 0xB0, 0xB2, 0xB4, 0xB3, 0xB2, 0xB0, 0xAF, 0xAF, 0xB1, 0xB2, 0xB1, 0x8B, 0x8A, 0x8A, 0x8C, 0x8D, 0x8F, 0x8F, 0x8A, 0x8B, 0x8E, 0x91, 0x95, 0x9B, 0xA2, 0xA5, 0xA7, 0xA6, 0xAA, 0xAE, 0xB0, 0xB0, 0xAF, 0xB0, 0xB1, 0xB1, 0xAE, 0xAC, 0xAC, 0xAC, 0xA7, 0xA2, 0xA8 }, |
|
{ 0xAA, 0x9B, 0x9D, 0xA5, 0xA6, 0xA7, 0xA4, 0xA0, 0xA2, 0xA2, 0xA2, 0xA3, 0xA3, 0xA3, 0xA3, 0xA3, 0xA2, 0xA4, 0xA4, 0xA7, 0xAE, 0xB3, 0xB5, 0xB5, 0xB4, 0xB3, 0xB2, 0xB2, 0xB2, 0xB4, 0xB3, 0xB2, 0x96, 0x94, 0x95, 0x96, 0x96, 0x98, 0x97, 0x93, 0x95, 0x97, 0x99, 0x9C, 0xA0, 0xA5, 0xA7, 0xA8, 0xA9, 0xAC, 0xB0, 0xB1, 0xB0, 0xAF, 0xAF, 0xB0, 0xB1, 0xAE, 0xAD, 0xAD, 0xAE, 0xAA, 0xA5, 0xAB }, |
|
{ 0xAA, 0x9C, 0x9D, 0xA5, 0xA6, 0xA7, 0xA6, 0xA3, 0xA4, 0xA4, 0xA5, 0xA6, 0xA7, 0xA6, 0xA6, 0xA5, 0xA5, 0xA6, 0xA9, 0xAC, 0xAF, 0xB2, 0xB4, 0xB3, 0xB5, 0xB4, 0xB1, 0xAF, 0xB0, 0xB1, 0xB0, 0xAF, 0xA2, 0xA1, 0xA2, 0xA2, 0xA2, 0xA4, 0xA3, 0xA1, 0xA3, 0xA4, 0xA5, 0xA7, 0xA9, 0xAD, 0xAE, 0xAE, 0xAD, 0xB0, 0xB3, 0xB3, 0xB3, 0xB1, 0xB1, 0xB1, 0xB4, 0xB2, 0xB1, 0xB0, 0xB1, 0xAC, 0xA7, 0xAB }, |
|
{ 0xAA, 0x9B, 0x9D, 0xA4, 0xA4, 0xA5, 0xA5, 0xA4, 0xA4, 0xA5, 0xA6, 0xA7, 0xA7, 0xA7, 0xA7, 0xA6, 0xA7, 0xA7, 0xAA, 0xAD, 0xAC, 0xAE, 0xAF, 0xAD, 0xAF, 0xAC, 0xA6, 0xA2, 0xA3, 0xA4, 0xA4, 0xA4, 0xA1, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA5, 0xA4, 0xA6, 0xA7, 0xA8, 0xA9, 0xAC, 0xAF, 0xB0, 0xB0, 0xB0, 0xB2, 0xB4, 0xB5, 0xB5, 0xB4, 0xB4, 0xB4, 0xB6, 0xB4, 0xB4, 0xB3, 0xB4, 0xAF, 0xA8, 0xAB }, |
|
{ 0xAB, 0x9C, 0x9D, 0xA4, 0xA3, 0xA4, 0xA4, 0xA5, 0xA4, 0xA4, 0xA5, 0xA6, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xAB, 0xAD, 0xAB, 0xAB, 0xAC, 0xAB, 0xAC, 0xA9, 0xA2, 0x9C, 0x9D, 0x9F, 0x9F, 0xA0, 0xA0, 0xA1, 0xA2, 0xA3, 0xA3, 0xA4, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAB, 0xAE, 0xB0, 0xB0, 0xB0, 0xB1, 0xB3, 0xB4, 0xB4, 0xB4, 0xB3, 0xB4, 0xB4, 0xB4, 0xB4, 0xB3, 0xB5, 0xB2, 0xAB, 0xAD }, |
|
{ 0xAC, 0x9C, 0x9E, 0xA4, 0xA4, 0xA3, 0xA4, 0xA5, 0xA5, 0xA5, 0xA5, 0xA6, 0xA6, 0xA7, 0xA7, 0xA8, 0xA8, 0xA8, 0xAB, 0xAD, 0xAC, 0xAC, 0xAD, 0xAE, 0xAE, 0xAD, 0xA6, 0xA2, 0xA4, 0xA5, 0xA4, 0xA5, 0xA6, 0xA7, 0xA7, 0xA8, 0xA9, 0xA8, 0xA8, 0xAA, 0xA9, 0xAA, 0xAB, 0xAC, 0xAE, 0xB1, 0xB2, 0xB2, 0xB2, 0xB3, 0xB4, 0xB4, 0xB4, 0xB3, 0xB3, 0xB3, 0xB4, 0xB5, 0xB5, 0xB4, 0xB6, 0xB3, 0xAC, 0xAD }, |
|
{ 0xAC, 0x9C, 0x9D, 0xA5, 0xA4, 0xA3, 0xA4, 0xA5, 0xA5, 0xA5, 0xA6, 0xA6, 0xA7, 0xA7, 0xA7, 0xA8, 0xA7, 0xA9, 0xAB, 0xAC, 0xAD, 0xAC, 0xAD, 0xAF, 0xAE, 0xAE, 0xA8, 0xA3, 0xA5, 0xA6, 0xA5, 0xA6, 0xA8, 0xA8, 0xA8, 0xA9, 0xAA, 0xA9, 0xA9, 0xAB, 0xAB, 0xAB, 0xAC, 0xAC, 0xAE, 0xB1, 0xB2, 0xB2, 0xB3, 0xB4, 0xB5, 0xB5, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB6, 0xB6, 0xB4, 0xB6, 0xB4, 0xAC, 0xAC }, |
|
{ 0xAC, 0x9B, 0x9C, 0xA4, 0xA4, 0xA3, 0xA4, 0xA5, 0xA5, 0xA5, 0xA6, 0xA6, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA9, 0xAA, 0xAB, 0xAD, 0xAB, 0xAA, 0xAE, 0xAE, 0xAE, 0xA8, 0xA2, 0xA4, 0xA5, 0xA4, 0xA6, 0xA5, 0xA6, 0xA6, 0xA7, 0xA9, 0xA9, 0xA8, 0xAC, 0xAB, 0xAC, 0xAC, 0xAD, 0xAF, 0xB1, 0xB2, 0xB2, 0xB1, 0xB3, 0xB4, 0xB4, 0xB3, 0xB3, 0xB3, 0xB4, 0xB3, 0xB4, 0xB5, 0xB3, 0xB6, 0xB5, 0xAE, 0xAE }, |
|
{ 0xAC, 0x9B, 0x9F, 0xA3, 0xA5, 0xA4, 0xA4, 0xA3, 0xA5, 0xA5, 0xA6, 0xA6, 0xA7, 0xA7, 0xA7, 0xA7, 0xA6, 0xA7, 0xA9, 0xAB, 0xAB, 0xAA, 0xAC, 0xAE, 0xAE, 0xAD, 0xA7, 0xA4, 0xA4, 0xA4, 0xA5, 0xA5, 0xA6, 0xA6, 0xA7, 0xA8, 0xA9, 0xA9, 0xAA, 0xAB, 0xAA, 0xAB, 0xAC, 0xAD, 0xAF, 0xB1, 0xB3, 0xB3, 0xB2, 0xB3, 0xB5, 0xB4, 0xB2, 0xB1, 0xB2, 0xB3, 0xB5, 0xB3, 0xB5, 0xB4, 0xB4, 0xB4, 0xAF, 0xAD }, |
|
{ 0xAC, 0x9A, 0x9F, 0xA2, 0xA4, 0xA4, 0xA5, 0xA5, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA7, 0xA7, 0xA7, 0xA8, 0xAA, 0xAB, 0xAB, 0xAA, 0xAB, 0xAD, 0xAE, 0xAD, 0xA6, 0xA3, 0xA4, 0xA4, 0xA6, 0xA5, 0xA7, 0xA7, 0xA8, 0xA9, 0xA9, 0xA9, 0xAA, 0xAB, 0xAB, 0xAC, 0xAC, 0xAD, 0xAF, 0xB2, 0xB4, 0xB4, 0xB3, 0xB4, 0xB5, 0xB5, 0xB3, 0xB2, 0xB3, 0xB3, 0xB3, 0xB2, 0xB5, 0xB5, 0xB5, 0xB5, 0xB1, 0xAF }, |
|
{ 0xB5, 0xA3, 0xA5, 0xA7, 0xA7, 0xA6, 0xA6, 0xA6, 0xA7, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA7, 0xA7, 0xA6, 0xA7, 0xA9, 0xAA, 0xAA, 0xA9, 0xAA, 0xAC, 0xAD, 0xAC, 0xA5, 0xA2, 0xA4, 0xA4, 0xA5, 0xA4, 0xA5, 0xA5, 0xA6, 0xA8, 0xA9, 0xA9, 0xAA, 0xAB, 0xAB, 0xAC, 0xAD, 0xAE, 0xB0, 0xB3, 0xB5, 0xB4, 0xB3, 0xB4, 0xB5, 0xB5, 0xB4, 0xB3, 0xB3, 0xB3, 0xB3, 0xB2, 0xB4, 0xB5, 0xB5, 0xB6, 0xB3, 0xB3 }, |
|
{ 0xB5, 0xA2, 0xA4, 0xA6, 0xA7, 0xA7, 0xA7, 0xA7, 0xA6, 0xA7, 0xA7, 0xA8, 0xA8, 0xA7, 0xA6, 0xA6, 0xA3, 0xA5, 0xA7, 0xA9, 0xA9, 0xA9, 0xAA, 0xAB, 0xAC, 0xAA, 0xA3, 0xA1, 0xA3, 0xA3, 0xA4, 0xA2, 0xA0, 0xA1, 0xA3, 0xA6, 0xA8, 0xA9, 0xAA, 0xAC, 0xAA, 0xAC, 0xAD, 0xAF, 0xB2, 0xB4, 0xB4, 0xB3, 0xB1, 0xB2, 0xB3, 0xB4, 0xB3, 0xB2, 0xB2, 0xB2, 0xB4, 0xB3, 0xB4, 0xB4, 0xB5, 0xB6, 0xB4, 0xB6 }, |
|
{ 0xB2, 0x9F, 0xA1, 0xA3, 0xA5, 0xA5, 0xA6, 0xA6, 0xA6, 0xA7, 0xA7, 0xA8, 0xA8, 0xA7, 0xA7, 0xA6, 0xA3, 0xA5, 0xA7, 0xA8, 0xA9, 0xAA, 0xAA, 0xAA, 0xAB, 0xAA, 0xA2, 0xA1, 0xA3, 0xA3, 0xA3, 0xA0, 0x9E, 0x9F, 0xA2, 0xA5, 0xA7, 0xA8, 0xA9, 0xAB, 0xAA, 0xAB, 0xAD, 0xAF, 0xB1, 0xB3, 0xB3, 0xB2, 0xAF, 0xB0, 0xB2, 0xB2, 0xB3, 0xB2, 0xB2, 0xB1, 0xB3, 0xB3, 0xB5, 0xB5, 0xB6, 0xB5, 0xB2, 0xB6 }, |
|
{ 0xB4, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA7, 0xA7, 0xA7, 0xA7, 0xA4, 0xA6, 0xA8, 0xA9, 0xAA, 0xAA, 0xAA, 0xAA, 0xAB, 0xAA, 0xA2, 0xA0, 0xA2, 0xA2, 0xA2, 0xA0, 0x9F, 0xA0, 0xA2, 0xA4, 0xA6, 0xA6, 0xA7, 0xA9, 0xA9, 0xAB, 0xAC, 0xAD, 0xAF, 0xB2, 0xB3, 0xB4, 0xB0, 0xB1, 0xB2, 0xB2, 0xB3, 0xB2, 0xB2, 0xB2, 0xB1, 0xB3, 0xB5, 0xB5, 0xB6, 0xB4, 0xB1, 0xB6 }, |
|
{ 0xB5, 0xA0, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA4, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA7, 0xA7, 0xA6, 0xA3, 0xA6, 0xA8, 0xA8, 0xA9, 0xAA, 0xAA, 0xA9, 0xAB, 0xA9, 0xA1, 0x9F, 0xA0, 0xA0, 0xA1, 0x9F, 0xA0, 0xA0, 0xA1, 0xA3, 0xA4, 0xA4, 0xA5, 0xA6, 0xA7, 0xAA, 0xAC, 0xAD, 0xAF, 0xB2, 0xB4, 0xB4, 0xB0, 0xB1, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB0, 0xB2, 0xB4, 0xB3, 0xB4, 0xB3, 0xB1, 0xB8 }, |
|
{ 0xBB, 0xA6, 0xA3, 0xA2, 0xA1, 0xA0, 0xA0, 0xA0, 0xA0, 0xA2, 0xA5, 0xA8, 0xA8, 0xA7, 0xA5, 0xA4, 0xA1, 0xA4, 0xA6, 0xA7, 0xA8, 0xAA, 0xAA, 0xA9, 0xAA, 0xA8, 0xA0, 0x9C, 0x9E, 0x9E, 0x9F, 0x9E, 0x9F, 0x9F, 0xA0, 0xA1, 0xA2, 0xA2, 0xA3, 0xA4, 0xA4, 0xA8, 0xAD, 0xAF, 0xB1, 0xB3, 0xB3, 0xB3, 0xB1, 0xB1, 0xB2, 0xB2, 0xB1, 0xB0, 0xB1, 0xB1, 0xB1, 0xB2, 0xB1, 0xB0, 0xB1, 0xB1, 0xB2, 0xBB }, |
|
{ 0xB7, 0xAC, 0xA8, 0xA8, 0xA7, 0xA6, 0xA4, 0xA5, 0xA4, 0xA1, 0xA8, 0xAC, 0xAD, 0xA3, 0x86, 0x88, 0x83, 0x8B, 0x8F, 0x91, 0x96, 0x98, 0x99, 0x9D, 0x9D, 0x9E, 0x9F, 0x9F, 0x9D, 0x9C, 0x9D, 0x9F, 0x9F, 0xA0, 0xA1, 0xA1, 0xA2, 0xA3, 0xA3, 0xA3, 0xA4, 0xA8, 0xAD, 0xB0, 0xB1, 0xB3, 0xB5, 0xB6, 0xB3, 0xB2, 0xB2, 0xB1, 0xB1, 0xB2, 0xB2, 0xB3, 0xB1, 0xB1, 0xB3, 0xB1, 0xB0, 0xB0, 0xB2, 0xBA }, |
|
{ 0xB5, 0xAE, 0xAD, 0xAE, 0xAD, 0xAD, 0xAC, 0xAD, 0xAC, 0xAC, 0xB1, 0xB0, 0xAF, 0xA6, 0x88, 0x83, 0x81, 0x8B, 0x91, 0x93, 0x98, 0x9B, 0x9D, 0xA0, 0x9F, 0xA0, 0xA1, 0xA1, 0xA0, 0x9F, 0xA0, 0xA1, 0xA2, 0xA2, 0xA3, 0xA4, 0xA4, 0xA5, 0xA5, 0xA5, 0xA6, 0xAA, 0xAE, 0xB0, 0xB1, 0xB3, 0xB5, 0xB6, 0xB3, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB3, 0xB3, 0xB3, 0xB4, 0xB2, 0xAF, 0xAE, 0xAE, 0xB5 }, |
|
{ 0xB2, 0xAE, 0xAF, 0xAF, 0xAE, 0xAF, 0xAF, 0xB0, 0xB1, 0xB2, 0xB7, 0xB5, 0xB5, 0xB1, 0x91, 0x84, 0x85, 0x90, 0x96, 0x98, 0x9B, 0x9E, 0x9E, 0xA0, 0xA1, 0xA2, 0xA4, 0xA4, 0xA4, 0xA3, 0xA3, 0xA3, 0xA3, 0xA2, 0xA2, 0xA2, 0xA3, 0xA4, 0xA5, 0xA5, 0xA7, 0xAA, 0xAE, 0xB0, 0xB2, 0xB4, 0xB6, 0xB6, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB3, 0xB3, 0xB3, 0xB3, 0xB4, 0xB2, 0xB1, 0xAF, 0xAE, 0xB4 }, |
|
{ 0xB1, 0xAE, 0xAE, 0xAC, 0xAB, 0xB0, 0xB2, 0xB3, 0xB5, 0xB5, 0xB6, 0xB5, 0xB6, 0xB2, 0x93, 0x81, 0x82, 0x8D, 0x95, 0x97, 0x9A, 0x9E, 0xA0, 0xA1, 0xA1, 0xA2, 0xA3, 0xA3, 0xA3, 0xA3, 0xA2, 0xA1, 0xA2, 0xA2, 0xA1, 0xA2, 0xA4, 0xA6, 0xA9, 0xAB, 0xAD, 0xAF, 0xB1, 0xB2, 0xB3, 0xB5, 0xB6, 0xB5, 0xB2, 0xB2, 0xB2, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB2, 0xB2, 0xB4, 0xB4, 0xB3, 0xB2, 0xB0, 0xB5 }, |
|
{ 0xB2, 0xAE, 0xAD, 0xAB, 0xAD, 0xB3, 0xB6, 0xB7, 0xB7, 0xB7, 0xB6, 0xB6, 0xB5, 0xB2, 0x9C, 0x8E, 0x8D, 0x96, 0x9C, 0x9D, 0x9F, 0xA2, 0xA4, 0xA5, 0xA7, 0xA7, 0xA8, 0xA9, 0xA9, 0xA8, 0xA6, 0xA5, 0xA6, 0xA6, 0xA6, 0xA7, 0xA9, 0xAE, 0xB2, 0xB4, 0xB5, 0xB6, 0xB6, 0xB6, 0xB5, 0xB6, 0xB6, 0xB5, 0xB2, 0xB2, 0xB2, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB4, 0xB3, 0xB5, 0xB4, 0xB4, 0xB3, 0xB0, 0xB4 }, |
|
{ 0xB2, 0xAD, 0xAD, 0xAC, 0xAE, 0xB3, 0xB3, 0xB3, 0xB2, 0xB3, 0xB5, 0xB8, 0xB6, 0xB7, 0xB1, 0xAD, 0xAE, 0xB2, 0xB4, 0xB2, 0xB0, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB2, 0xB3, 0xB2, 0xB1, 0xB0, 0xAE, 0xAD, 0xAC, 0xAB, 0xAB, 0xAC, 0xAF, 0xB1, 0xB3, 0xB3, 0xB5, 0xB6, 0xB5, 0xB5, 0xB6, 0xB6, 0xB5, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB4, 0xB4, 0xB4, 0xB3, 0xB0, 0xB4 }, |
|
{ 0xB3, 0xAF, 0xB0, 0xAF, 0xAF, 0xB0, 0xAF, 0xAF, 0xB0, 0xB1, 0xB3, 0xB7, 0xB4, 0xB7, 0xB7, 0xB7, 0xB4, 0xB4, 0xB4, 0xB4, 0xB3, 0xB3, 0xB3, 0xB4, 0xB4, 0xB4, 0xB5, 0xB5, 0xB5, 0xB4, 0xB3, 0xB2, 0xB1, 0xB0, 0xAF, 0xAE, 0xAE, 0xAE, 0xAF, 0xAF, 0xAF, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB6, 0xB6, 0xB2, 0xB2, 0xB2, 0xB1, 0xB2, 0xB2, 0xB3, 0xB3, 0xB3, 0xB2, 0xB4, 0xB4, 0xB5, 0xB4, 0xB3, 0xB7 }, |
|
{ 0xB7, 0xB3, 0xB5, 0xB4, 0xB3, 0xB4, 0xB3, 0xB4, 0xB3, 0xB4, 0xB4, 0xB8, 0xB4, 0xB6, 0xB6, 0xB3, 0xB2, 0xB0, 0xB0, 0xB1, 0xB2, 0xB3, 0xB5, 0xB6, 0xB4, 0xB5, 0xB6, 0xB6, 0xB6, 0xB5, 0xB4, 0xB4, 0xB2, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB1, 0xB4, 0xB5, 0xB5, 0xB5, 0xB6, 0xB6, 0xB6, 0xB2, 0xB2, 0xB1, 0xB1, 0xB1, 0xB2, 0xB3, 0xB3, 0xB4, 0xB4, 0xB5, 0xB5, 0xB6, 0xB5, 0xB4, 0xB9 }, |
|
{ 0xB6, 0xB3, 0xB3, 0xB4, 0xB3, 0xB3, 0xB4, 0xB3, 0xB3, 0xB4, 0xB5, 0xB6, 0xB5, 0xB5, 0xB6, 0xB6, 0xB3, 0xB3, 0xB4, 0xB4, 0xB4, 0xB4, 0xB5, 0xB5, 0xB6, 0xB7, 0xB7, 0xB8, 0xB7, 0xB6, 0xB4, 0xB4, 0xB3, 0xB3, 0xB4, 0xB4, 0xB4, 0xB3, 0xB3, 0xB3, 0xB4, 0xB3, 0xB4, 0xB5, 0xB5, 0xB4, 0xB4, 0xB7, 0xB1, 0xB1, 0xB1, 0xB1, 0xB2, 0xB2, 0xB3, 0xB3, 0xB4, 0xB5, 0xB5, 0xB5, 0xB5, 0xB4, 0xB4, 0xB7 }, |
|
{ 0xB6, 0xB3, 0xB3, 0xB4, 0xB3, 0xB3, 0xB4, 0xB3, 0xB3, 0xB4, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB6, 0xB4, 0xB4, 0xB5, 0xB5, 0xB5, 0xB5, 0xB6, 0xB6, 0xB6, 0xB7, 0xB7, 0xB7, 0xB7, 0xB6, 0xB5, 0xB4, 0xB2, 0xB3, 0xB3, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB3, 0xB4, 0xB5, 0xB4, 0xB3, 0xB2, 0xB4, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB4, 0xB4, 0xB5, 0xB5, 0xB6, 0xB5, 0xB5, 0xB6, 0xB4, 0xB4, 0xB7 }, |
|
{ 0xB6, 0xB3, 0xB3, 0xB4, 0xB3, 0xB3, 0xB4, 0xB4, 0xB3, 0xB4, 0xB5, 0xB5, 0xB4, 0xB4, 0xB4, 0xB5, 0xB5, 0xB5, 0xB6, 0xB6, 0xB6, 0xB6, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB6, 0xB6, 0xB2, 0xB3, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB5, 0xB5, 0xB5, 0xB4, 0xB4, 0xB3, 0xB2, 0xB2, 0xB3, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB5, 0xB5, 0xB5, 0xB5, 0xB6, 0xB6, 0xB5, 0xB6, 0xB5, 0xB5, 0xB8 }, |
|
{ 0xB6, 0xB3, 0xB3, 0xB4, 0xB3, 0xB4, 0xB5, 0xB4, 0xB4, 0xB5, 0xB5, 0xB5, 0xB4, 0xB4, 0xB4, 0xB4, 0xB5, 0xB6, 0xB6, 0xB6, 0xB6, 0xB7, 0xB7, 0xB8, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB4, 0xB5, 0xB5, 0xB5, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB2, 0xB3, 0xB2, 0xB2, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB5, 0xB5, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB5, 0xB6, 0xB9 }, |
|
{ 0xB7, 0xB4, 0xB3, 0xB4, 0xB3, 0xB4, 0xB5, 0xB4, 0xB4, 0xB5, 0xB6, 0xB5, 0xB4, 0xB4, 0xB4, 0xB4, 0xB5, 0xB6, 0xB6, 0xB6, 0xB6, 0xB7, 0xB7, 0xB8, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB8, 0xB5, 0xB5, 0xB6, 0xB6, 0xB5, 0xB4, 0xB4, 0xB4, 0xB3, 0xB4, 0xB1, 0xB2, 0xB2, 0xB1, 0xB4, 0xB3, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB5, 0xB5, 0xB6, 0xB7, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB9 }, |
|
{ 0xB7, 0xB4, 0xB3, 0xB4, 0xB4, 0xB4, 0xB4, 0xB3, 0xB4, 0xB5, 0xB6, 0xB5, 0xB4, 0xB4, 0xB4, 0xB4, 0xB5, 0xB5, 0xB6, 0xB6, 0xB6, 0xB6, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB4, 0xB4, 0xB6, 0xB2, 0xB3, 0xB3, 0xB2, 0xB5, 0xB3, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB5, 0xB5, 0xB6, 0xB6, 0xB7, 0xB6, 0xB6, 0xB6, 0xB6, 0xB7, 0xBA }, |
|
{ 0xB8, 0xB4, 0xB3, 0xB4, 0xB4, 0xB4, 0xB4, 0xB3, 0xB4, 0xB4, 0xB5, 0xB5, 0xB4, 0xB4, 0xB4, 0xB4, 0xB5, 0xB5, 0xB6, 0xB6, 0xB6, 0xB6, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB5, 0xB5, 0xB4, 0xB4, 0xB5, 0xB4, 0xB3, 0xB2, 0xB4, 0xB4, 0xAD, 0xAC, 0xAD, 0xAE, 0xB5, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB5, 0xB5, 0xB6, 0xB6, 0xB6, 0xB6, 0xB5, 0xB6, 0xB6, 0xB7, 0xBA }, |
|
{ 0xB8, 0xB5, 0xB4, 0xB5, 0xB4, 0xB4, 0xB4, 0xB2, 0xB3, 0xB4, 0xB5, 0xB5, 0xB4, 0xB3, 0xB4, 0xB4, 0xB5, 0xB5, 0xB6, 0xB6, 0xB6, 0xB6, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB6, 0xB6, 0xB6, 0xB5, 0xB4, 0xB4, 0xB4, 0xB3, 0xB2, 0xB0, 0xB0, 0xAF, 0xA4, 0xA2, 0xA3, 0xA7, 0xB2, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB4, 0xB4, 0xB5, 0xB5, 0xB6, 0xB6, 0xB5, 0xB5, 0xB6, 0xB6, 0xB7, 0xBA }, |
|
{ 0xBA, 0xB5, 0xB5, 0xB6, 0xB5, 0xB6, 0xB4, 0xAE, 0xB0, 0xB1, 0xB2, 0xB3, 0xB3, 0xB3, 0xB5, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB6, 0xB6, 0xB6, 0xB6, 0xB5, 0xB5, 0xB5, 0xB4, 0xB4, 0xB3, 0xAF, 0xAA, 0xAB, 0xAC, 0x9C, 0x99, 0x9B, 0xA2, 0xB3, 0xB2, 0xB4, 0xB4, 0xB5, 0xB5, 0xB6, 0xB6, 0xB6, 0xB6, 0xB7, 0xB5, 0xB7, 0xB7, 0xB7, 0xB6, 0xB4, 0xBB }, |
|
{ 0xB8, 0xB5, 0xB5, 0xB6, 0xB5, 0xB5, 0xB4, 0xAE, 0xB0, 0xB1, 0xB2, 0xB3, 0xB3, 0xB4, 0xB4, 0xB5, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB8, 0xB7, 0xB7, 0xB7, 0xB7, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB5, 0xB5, 0xB5, 0xB3, 0xB0, 0xAC, 0xAC, 0xAD, 0x9C, 0x98, 0x9A, 0xA1, 0xB3, 0xB4, 0xB4, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB7, 0xB5, 0xB6, 0xB5, 0xB6, 0xB7, 0xB6, 0xBB }, |
|
{ 0xB9, 0xB5, 0xB5, 0xB6, 0xB5, 0xB6, 0xB6, 0xB2, 0xB4, 0xB4, 0xB5, 0xB6, 0xB6, 0xB5, 0xB5, 0xB4, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB6, 0xB6, 0xB6, 0xB6, 0xB5, 0xB4, 0xB4, 0xB4, 0xB3, 0xB3, 0xB2, 0xB0, 0xAD, 0xAF, 0xAF, 0xA0, 0x9D, 0x9E, 0xA2, 0xB2, 0xB3, 0xB4, 0xB5, 0xB5, 0xB6, 0xB5, 0xB5, 0xB5, 0xB6, 0xB6, 0xB6, 0xB6, 0xB4, 0xB5, 0xB7, 0xB6, 0xB8 }, |
|
{ 0xB1, 0xAD, 0xAD, 0xAF, 0xAF, 0xB2, 0xB6, 0xB5, 0xB6, 0xB6, 0xB6, 0xB7, 0xB7, 0xB7, 0xB6, 0xB6, 0xB6, 0xB6, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB6, 0xB7, 0xB7, 0xB6, 0xB6, 0xB6, 0xB6, 0xB5, 0xB5, 0xB6, 0xB6, 0xB5, 0xB5, 0xB5, 0xB4, 0xB3, 0xB2, 0xB1, 0xB0, 0xA3, 0x9F, 0x9E, 0xA1, 0xAF, 0xB0, 0xB1, 0xB2, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB2, 0xB3 }, |
|
{ 0xB1, 0xAC, 0xAD, 0xAE, 0xAD, 0xB0, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB5, 0xB6, 0xB6, 0xB7, 0xB7, 0xB6, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB6, 0xB6, 0xB7, 0xB7, 0xB6, 0xB6, 0xB6, 0xB6, 0xB5, 0xB5, 0xB6, 0xB6, 0xB5, 0xB4, 0xB4, 0xB3, 0xB2, 0xB2, 0xB4, 0xB1, 0xA3, 0x9F, 0x9E, 0xA1, 0xAE, 0xB1, 0xB1, 0xB2, 0xB2, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB4, 0xB4, 0xB4, 0xB5, 0xB5, 0xB3, 0xB4, 0xB4 }, |
|
{ 0xB0, 0xAC, 0xAD, 0xAF, 0xAF, 0xB0, 0xB4, 0xB4, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB6, 0xB6, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB6, 0xB6, 0xB6, 0xB6, 0xB5, 0xB4, 0xB2, 0xB1, 0xB0, 0xAF, 0xAE, 0xAD, 0xAD, 0xAC, 0xAA, 0xA1, 0x9F, 0x9F, 0xA0, 0xA9, 0xAB, 0xAB, 0xAB, 0xAC, 0xAF, 0xB1, 0xB3, 0xB4, 0xB4, 0xB4, 0xB4, 0xB3, 0xB4, 0xB4, 0xB3, 0xB4, 0xB1 }, |
|
{ 0x9C, 0x98, 0x9B, 0xA1, 0xA4, 0xA9, 0xB0, 0xB3, 0xB4, 0xB5, 0xB6, 0xB6, 0xB5, 0xB5, 0xB5, 0xB6, 0xB8, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB6, 0xB6, 0xB6, 0xB6, 0xA4, 0xA3, 0xA1, 0xA2, 0xA1, 0xA0, 0xA0, 0xA0, 0xA0, 0xA0, 0x9C, 0x9E, 0x9E, 0x9C, 0xA0, 0x9F, 0xA0, 0xA0, 0xA1, 0xA5, 0xA9, 0xAD, 0xAD, 0xAD, 0xAC, 0xAE, 0xAD, 0xAD, 0xAD, 0xAE, 0xAB, 0xA0 }, |
|
{ 0x8D, 0x88, 0x8A, 0x91, 0x95, 0x9D, 0xA7, 0xAB, 0xAF, 0xB1, 0xB3, 0xB4, 0xB4, 0xB5, 0xB6, 0xB7, 0xB7, 0xB7, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB7, 0xB7, 0xB7, 0xB7, 0xB6, 0xB6, 0xB6, 0xB6, 0xA0, 0x9E, 0x9D, 0x9F, 0x9F, 0x9F, 0x9F, 0x9F, 0xA0, 0x9F, 0x9C, 0x9F, 0x9F, 0x9C, 0x9E, 0x9D, 0x9E, 0x9D, 0x9E, 0xA1, 0xA6, 0xA8, 0xA8, 0xA7, 0xA8, 0xAD, 0xAC, 0xAB, 0xAB, 0xAD, 0xA5, 0x92 }, |
|
{ 0x8E, 0x87, 0x8A, 0x91, 0x93, 0x9B, 0xA7, 0xAC, 0xAE, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB6, 0xB6, 0xB8, 0xB6, 0xB5, 0xB6, 0xB3, 0xB3, 0xB8, 0xB7, 0xB7, 0xB8, 0xB7, 0xB6, 0xB6, 0xB7, 0xB5, 0xA2, 0x9F, 0x9F, 0xA0, 0x9F, 0x9F, 0x9F, 0x9F, 0xA0, 0x9F, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9D, 0x9E, 0x9C, 0x9E, 0xA3, 0xA7, 0xA8, 0xA8, 0xA7, 0xA9, 0xAD, 0xAC, 0xAB, 0xAA, 0xAB, 0xA8, 0x94 }, |
|
{ 0x8D, 0x87, 0x89, 0x8E, 0x90, 0x97, 0xA2, 0xA6, 0xAC, 0xAD, 0xAF, 0xAF, 0xB0, 0xB3, 0xB5, 0xB6, 0xB4, 0xB7, 0xB7, 0xB6, 0xB5, 0xB3, 0xB3, 0xB6, 0xB7, 0xB7, 0xB7, 0xB7, 0xB6, 0xB7, 0xB7, 0xB5, 0xA0, 0x9E, 0x9E, 0x9F, 0x9E, 0x9E, 0x9F, 0x9F, 0x9F, 0x9E, 0x9D, 0x9C, 0x9D, 0x9D, 0x9D, 0x9C, 0x9D, 0x9C, 0x9D, 0xA0, 0xA4, 0xA6, 0xA8, 0xA9, 0xA5, 0xAA, 0xAC, 0xAC, 0xAA, 0xA8, 0x9F, 0x8C }, |
|
{ 0x8C, 0x86, 0x88, 0x8D, 0x8F, 0x95, 0x9E, 0xA2, 0xA5, 0xA7, 0xA8, 0xA8, 0xA8, 0xA9, 0xAB, 0xAC, 0xAF, 0xB2, 0xB4, 0xB5, 0xB5, 0xB5, 0xB4, 0xB3, 0xB5, 0xB4, 0xB5, 0xB5, 0xB4, 0xB5, 0xB6, 0xB3, 0x9E, 0x9C, 0x9E, 0xA0, 0x9F, 0xA0, 0xA0, 0xA0, 0xA0, 0x9F, 0x9E, 0x9E, 0x9E, 0x9E, 0x9E, 0x9D, 0x9C, 0x9C, 0x9D, 0x9F, 0xA1, 0xA3, 0xA5, 0xA6, 0xAA, 0xAA, 0xAB, 0xA8, 0xA7, 0xA9, 0x9D, 0x8C }, |
|
{ 0x87, 0x82, 0x85, 0x8B, 0x8E, 0x95, 0x9D, 0xA1, 0xA5, 0xA7, 0xA9, 0xA9, 0xA9, 0xA9, 0xAA, 0xAA, 0xA6, 0xA8, 0xAA, 0xAC, 0xAF, 0xB2, 0xB3, 0xB1, 0xB3, 0xB3, 0xB4, 0xB4, 0xB4, 0xB5, 0xB5, 0xB2, 0x99, 0x98, 0x9B, 0x9E, 0x9E, 0x9E, 0x9F, 0x9E, 0xA0, 0x9E, 0x9D, 0x9D, 0x9D, 0x9D, 0x9C, 0x9C, 0x99, 0x9A, 0x9C, 0x9E, 0xA1, 0xA4, 0xA6, 0xA5, 0xA5, 0xA6, 0xAB, 0xA6, 0xA8, 0xAC, 0x98, 0x86 }, |
|
{ 0x84, 0x80, 0x83, 0x8A, 0x8E, 0x94, 0x9C, 0x9F, 0xA3, 0xA6, 0xA8, 0xA8, 0xA9, 0xAA, 0xAC, 0xAC, 0xA8, 0xAC, 0xAD, 0xAD, 0xB0, 0xB2, 0xB3, 0xB3, 0xB3, 0xB3, 0xB4, 0xB5, 0xB5, 0xB5, 0xB4, 0xB1, 0x97, 0x97, 0x9A, 0x9D, 0x9E, 0x9E, 0x9E, 0x9D, 0x9F, 0x9E, 0x9C, 0x9C, 0x9C, 0x9D, 0x9C, 0x9B, 0x9A, 0x9A, 0x9B, 0x9D, 0xA1, 0xA5, 0xA6, 0xA6, 0xA3, 0xA6, 0xAC, 0xA3, 0xA4, 0xA9, 0x97, 0x89 }, |
|
{ 0x85, 0x80, 0x81, 0x86, 0x88, 0x8A, 0x8E, 0x91, 0x91, 0x93, 0x94, 0x94, 0x93, 0x95, 0x97, 0x99, 0x9C, 0xA4, 0xA9, 0xAD, 0xB3, 0xB3, 0xB1, 0xB2, 0xB1, 0xB1, 0xB3, 0xB4, 0xB3, 0xB4, 0xB3, 0xAF, 0x97, 0x96, 0x99, 0x9C, 0x9C, 0x9C, 0x9C, 0x9B, 0x9C, 0x9B, 0x9B, 0x9B, 0x9B, 0x9B, 0x9A, 0x9A, 0x97, 0x96, 0x96, 0x99, 0x9E, 0xA1, 0xA4, 0xA6, 0xA6, 0xA7, 0xA4, 0x93, 0x8E, 0x91, 0x89, 0x87 }, |
|
{ 0x85, 0x80, 0x7F, 0x82, 0x81, 0x81, 0x82, 0x83, 0x83, 0x85, 0x87, 0x86, 0x85, 0x85, 0x86, 0x87, 0x84, 0x8B, 0x91, 0x9D, 0xAF, 0xB3, 0xB0, 0xB1, 0xB2, 0xB2, 0xB3, 0xB3, 0xB2, 0xB3, 0xB2, 0xB0, 0x98, 0x96, 0x97, 0x99, 0x99, 0x98, 0x98, 0x97, 0x98, 0x99, 0x9A, 0x9A, 0x9A, 0x99, 0x98, 0x98, 0x95, 0x93, 0x91, 0x91, 0x8E, 0x8A, 0x89, 0x8B, 0x8D, 0x8E, 0x8B, 0x83, 0x81, 0x82, 0x80, 0x85 }, |
|
{ 0x8A, 0x85, 0x85, 0x87, 0x87, 0x86, 0x87, 0x88, 0x87, 0x8B, 0x8E, 0x8F, 0x8E, 0x8E, 0x8D, 0x8D, 0x89, 0x89, 0x86, 0x94, 0xAF, 0xB9, 0xB5, 0xB7, 0xB5, 0xB5, 0xB6, 0xB5, 0xB4, 0xB4, 0xB5, 0xB3, 0x9E, 0x9B, 0x9B, 0x9C, 0x9B, 0x9A, 0x9A, 0x99, 0x9B, 0x9D, 0x9F, 0x9F, 0x9F, 0x9E, 0x9D, 0x9D, 0x99, 0x98, 0x98, 0x98, 0x92, 0x8A, 0x87, 0x8A, 0x88, 0x87, 0x85, 0x85, 0x88, 0x86, 0x85, 0x8D }, |
|
}; |
|
|
|
struct CheesyRand |
|
{ |
|
CheesyRand( int seed = 12345 ) |
|
{ |
|
z = seed | 0x00010000; |
|
w = ~seed | 0x00000100; |
|
} |
|
|
|
uint32 RandInt() |
|
{ |
|
// http://en.wikipedia.org/wiki/Random_number_generation#Computational_methods |
|
z = 36969 * (z & 65535) + (z >> 16); |
|
w = 18000 * (w & 65535) + (w >> 16); |
|
return (z << 16) + w; |
|
} |
|
|
|
inline float RandFloat01() |
|
{ |
|
return RandInt() / 4294970000.0f; |
|
} |
|
|
|
inline float RandFloatNeg1To1() |
|
{ |
|
return RandFloat01() * 2.0f - 1.0f; |
|
} |
|
|
|
uint32 z, w; |
|
}; |
|
|
|
void CConfirmCustomizeTextureDialog::PerformPainterlyFilter() |
|
{ |
|
|
|
// Resample it to 2x the final resolution. Having a fixed resolution |
|
// for the "source" image makes it easier, since we can use fixed size |
|
// kernels, etc |
|
Bitmap_t imageTemp1, imageTemp2; |
|
ImgUtl_ResizeBitmap( imageTemp1, k_nCustomImageSize*2, k_nCustomImageSize*2, &m_imgSquare ); |
|
|
|
// |
|
// Shape correction V1: |
|
// |
|
#if 1 |
|
// Perform symmetric nearest neighbor |
|
float filterStrength = .95f; |
|
SymmetricNearestNeighborFilter( imageTemp1, imageTemp2, 5, filterStrength ); |
|
BilateralFilter( imageTemp2, imageTemp1, 4, .5, .15 ); |
|
BilateralFilter( imageTemp1, imageTemp2, 2, .9, .7 ); |
|
imageTemp1.SetPixelData( imageTemp2 ); |
|
#endif |
|
|
|
// |
|
// Shape correction V2: |
|
// |
|
#if 0 |
|
// Perform symmetric nearest neighbor |
|
float snnFilterStrength = .7f; |
|
SymmetricNearestNeighborFilter( imageTemp1, imageTemp2, 4, snnFilterStrength ); |
|
|
|
// And some bilateral filtering to smooth it |
|
BilateralFilter( imageTemp2, imageTemp1, 2, .75, .5 ); |
|
BilateralFilter( imageTemp1, imageTemp2, 3, .7, .3 ); |
|
BilateralFilter( imageTemp2, imageTemp1, 4, .6, .2 ); |
|
#endif |
|
|
|
// // Load up brush strokes |
|
// if ( !m_imgBrushStrokes.IsValid() ) |
|
// { |
|
// m_imgBrushStrokes.Load( "d:\\texture.jpg" ); |
|
// |
|
// for (int y = 0 ; y < m_imgBrushStrokes.Height() ; ++y ) |
|
// { |
|
// for (int x = 0 ; x < m_imgBrushStrokes.Width() ; ++x ) |
|
// { |
|
// Warning("0x%02X, ", m_imgBrushStrokes.GetColor(x,y).r() ); |
|
// } |
|
// Warning("\n"); |
|
// } |
|
// |
|
// m_imgBrushStrokes.Resize( m_imgTemp.Width(), m_imgTemp.Height() ); |
|
// } |
|
|
|
// |
|
// Color correction |
|
// |
|
for ( int y = 0 ; y < imageTemp1.Height() ; ++y ) |
|
{ |
|
for ( int x = 0 ; x < imageTemp1.Width() ; ++x ) |
|
{ |
|
|
|
// Fetch original pixel in RGB space |
|
Color c = imageTemp1.GetColor( x,y ); |
|
Vector rgb((float)c.r(), (float)c.g(), (float)c.b()); |
|
|
|
// Convert to HSV |
|
Vector hsv; |
|
RGBtoHSV( rgb, hsv ); |
|
|
|
// |
|
// Color correction V1 |
|
// |
|
#if 0 |
|
// Shift towards red, away from blue |
|
//rgb.x += rgb.z * .2f; |
|
//rgb.z *= 0.7f; |
|
// Desaturate |
|
float satMult = .65; // desaturate |
|
hsv.y *= satMult; |
|
#endif |
|
|
|
// |
|
// Color correction V2 |
|
// |
|
|
|
#if 1 |
|
static const Color swatches[] = |
|
{ |
|
Color( 183, 224, 252, 255 ), // sky light |
|
Color( 83, 109, 205, 255 ), // sky med |
|
Color( 64, 68, 195, 255 ), // sky dark |
|
Color( 100, 68, 57, 255 ), // skin demo |
|
Color( 139, 101, 84, 255 ), // skin demo light |
|
Color( 133, 105, 68, 255 ), // saxton hair |
|
Color( 252, 169, 131, 255 ), // skin light |
|
Color( 194, 132, 106, 255 ), // skin |
|
|
|
Color( 255, 255, 255, 255 ), |
|
Color( 246, 231, 222, 255 ), |
|
Color( 218, 189, 171, 255 ), |
|
Color( 193, 161, 138, 255 ), |
|
|
|
Color( 248, 185, 138, 255 ), |
|
Color( 245, 173, 135, 255 ), |
|
Color( 239, 152, 73, 255 ), |
|
Color( 241, 129, 73, 255 ), |
|
|
|
Color( 106, 69, 52, 255 ), |
|
Color( 145, 58, 31, 255 ), |
|
Color( 189, 58, 58, 255 ), |
|
Color( 157, 48, 47, 255 ), |
|
Color( 69, 44, 37, 255 ), |
|
|
|
Color( 107, 106, 101, 255 ), |
|
Color( 118, 138, 136, 255 ), |
|
Color( 91, 122, 140, 255 ), |
|
Color( 56, 92, 120, 255 ), |
|
Color( 52, 47, 44, 255 ), |
|
}; |
|
|
|
static float selfWeight = .15f; |
|
static float thresh = .60f; |
|
Vector rgb2((float)c.r(), (float)c.g(), (float)c.b()); |
|
float totalWeight = selfWeight; |
|
rgb2 *= selfWeight; |
|
for ( int i = 0 ; i < ARRAYSIZE(swatches) ; ++i ) |
|
{ |
|
float similarity = 1.0f - ApproxColorDist( c, swatches[i] ) - thresh; |
|
if ( similarity > 0.0f ) |
|
{ |
|
similarity /= (1.0f - thresh); // get in 0...1 scale |
|
similarity *= similarity*similarity; |
|
rgb2.x += similarity*(float)swatches[i].r(); |
|
rgb2.y += similarity*(float)swatches[i].g(); |
|
rgb2.z += similarity*(float)swatches[i].b(); |
|
totalWeight += similarity; |
|
} |
|
} |
|
rgb2 /= totalWeight; |
|
|
|
// Calc hue for the shifted one |
|
Vector hsv2; |
|
RGBtoHSV( rgb2, hsv2 ); |
|
|
|
// Replace hue and saturation |
|
hsv.x = hsv2.x; |
|
hsv.y = hsv2.y; |
|
#endif |
|
// Convert back to RGB space |
|
HSVtoRGB( hsv, rgb ); |
|
|
|
// Overlay brush stroke noise |
|
Vector overlayValue; |
|
int brushX = x * k_BrushStrokeSize / imageTemp1.Width(); |
|
int brushY = y * k_BrushStrokeSize / imageTemp1.Height(); |
|
//float k = (float)m_imgBrushStrokes.GetColor( x, y ).r() / 255.0f; |
|
float k = (float)s_bBrushStrokeData[brushY][brushX] / 255.0f; |
|
if ( k < .5f ) |
|
{ |
|
overlayValue = rgb * k * 2.0f; |
|
} |
|
else |
|
{ |
|
Vector kWhite( 255.0f, 255.0f, 255.0f ); |
|
float q = 2.0f * ( 1.0f - k ); // 0.5 -> 1.0 , 1.0 -> 0 |
|
overlayValue = kWhite - ( kWhite - rgb ) * q; |
|
} |
|
|
|
float overlayStrength = .10f; |
|
rgb += (overlayValue - rgb) * overlayStrength; |
|
|
|
// Put back into the image |
|
Color result( |
|
(unsigned char)clamp(rgb.x, 0.0f, 255.0f), |
|
(unsigned char)clamp(rgb.y, 0.0f, 255.0f), |
|
(unsigned char)clamp(rgb.z, 0.0f, 255.0f), |
|
c.a() |
|
); |
|
imageTemp2.SetColor( x, y, result ); |
|
} |
|
} |
|
|
|
// Now downsample to the final size |
|
ImgUtl_ResizeBitmap( m_imgFinal, k_nCustomImageSize, k_nCustomImageSize, &imageTemp2 ); |
|
|
|
// Add noise to the final image |
|
// Use deterministic random number generator (i.e. let's not call rand()), |
|
// so uploading the same image twice will produce the same hash |
|
CheesyRand noiseRand; |
|
for ( int y = 0 ; y < m_imgFinal.Height() ; ++y ) |
|
{ |
|
for ( int x = 0 ; x < m_imgFinal.Width() ; ++x ) |
|
{ |
|
float noiseStrength = 2.0f; |
|
int noise = (int)floor( noiseRand.RandFloatNeg1To1() * noiseStrength + .5f ); |
|
Color c = m_imgFinal.GetColor( x, y ); |
|
Color result( |
|
clamp( c.r() + noise, 0, 255 ), |
|
clamp( c.g() + noise, 0, 255 ), |
|
clamp( c.b() + noise, 0, 255 ), |
|
c.a() |
|
); |
|
m_imgFinal.SetColor( x, y, result ); |
|
} |
|
} |
|
} |
|
|
|
#ifdef TEST_FILTERS |
|
void CConfirmCustomizeTextureDialog::TestFilters() |
|
{ |
|
const char *szTestImageFilenames[] = |
|
{ |
|
"d:/custom_images/borat.jpg", |
|
"d:/custom_images/cloud_strife-profile.jpg", |
|
"d:/custom_images/ladies_man.png", |
|
"d:/custom_images/dota_hero.jpg", |
|
"d:/custom_images/elmo balls.jpg", |
|
"d:/custom_images/halolz-dot-com-teamfortress2-sexyheavy-prematureubers.jpg", |
|
"d:/custom_images/doug_loves_movies.jpg", |
|
"d:/custom_images/lolcat.jpg", |
|
"d:/custom_images/mario_3d.jpg", |
|
//"d:/custom_images/pulp_fiction_sam.gif", |
|
"d:/custom_images/RainbowBright.jpg", |
|
"d:/custom_images/elliot_and_travis.tga", |
|
"d:/custom_images/give_peace_a_chance.jpg", |
|
}; |
|
const int k_nTestImages = ARRAYSIZE(szTestImageFilenames); |
|
|
|
Bitmap_t imageOutput; |
|
imageOutput.Init( k_nCustomImageSize*3, k_nCustomImageSize*k_nTestImages, IMAGE_FORMAT_RGBA8888 ); |
|
|
|
for ( int i = 0 ; i < k_nTestImages ; ++i ) |
|
{ |
|
ConversionErrorType nErrorCode = ImgUtl_LoadBitmap( szTestImageFilenames[i], m_imgSource ); |
|
if ( nErrorCode != CE_SUCCESS ) |
|
{ |
|
Assert( nErrorCode == CE_SUCCESS ); |
|
continue; |
|
} |
|
|
|
PerformSquarize(); |
|
PerformPainterlyFilter(); |
|
int y = i*k_nCustomImageSize; |
|
imageOutput.SetPixelData( m_imgSquareDisplay, 0, y ); |
|
imageOutput.SetPixelData( m_imgFinal, k_nCustomImageSize, y ); |
|
} |
|
|
|
CUtlBuffer pngFileData; |
|
ImgUtl_SavePNGBitmapToBuffer( pngFileData, imageOutput ); |
|
|
|
g_pFullFileSystem->WriteFile( "d:/painterly.png", NULL, pngFileData ); |
|
} |
|
#endif |
|
|
|
void CConfirmCustomizeTextureDialog::RegenerateTextureBits( ITexture *pTexture, IVTFTexture *pVTFTexture, Rect_t *pRect ) |
|
{ |
|
|
|
// Check if we need to redo the filter |
|
CleanFilteredImage(); |
|
|
|
Assert( pVTFTexture->FrameCount() == 1 ); |
|
Assert( pVTFTexture->FaceCount() == 1 ); |
|
Assert( pTexture == g_pPreviewCustomTexture ); |
|
Assert( !pTexture->IsMipmapped() ); |
|
|
|
int nWidth, nHeight, nDepth; |
|
pVTFTexture->ComputeMipLevelDimensions( 0, &nWidth, &nHeight, &nDepth ); |
|
Assert( nDepth == 1 ); |
|
Assert( nWidth == m_imgFinal.Width() && nHeight == m_imgFinal.Height() ); |
|
|
|
CPixelWriter pixelWriter; |
|
pixelWriter.SetPixelMemory( pVTFTexture->Format(), |
|
pVTFTexture->ImageData( 0, 0, 0 ), pVTFTexture->RowSizeInBytes( 0 ) ); |
|
|
|
// !SPEED! 'Tis probably DEATHLY slow... |
|
for ( int y = 0; y < nHeight; ++y ) |
|
{ |
|
pixelWriter.Seek( 0, y ); |
|
for ( int x = 0; x < nWidth; ++x ) |
|
{ |
|
Color c = m_imgFinal.GetColor( x, y ); |
|
pixelWriter.WritePixel( c.r(), c.g(), c.b(), c.a() ); |
|
} |
|
} |
|
|
|
// We're no longer dirty |
|
g_pPreviewCustomTextureDirty = false; |
|
} |
|
|
|
void CConfirmCustomizeTextureDialog::Release() |
|
{ |
|
if ( g_pPreviewCustomTexture ) |
|
{ |
|
ITexture *tex = g_pPreviewCustomTexture; |
|
g_pPreviewCustomTexture = NULL; // clear pointer first, to prevent infinite recursion |
|
tex->SetTextureRegenerator( NULL ); |
|
tex->Release(); |
|
} |
|
g_pPreviewEconItem = NULL; |
|
} |
|
|
|
class CCustomizeTextureJobDialog : public CApplyCustomTextureJob |
|
{ |
|
public: |
|
CCustomizeTextureJobDialog( const void *pPNGData, int nPNGDataBytes, CConfirmCustomizeTextureDialog *pDlg ) |
|
: CApplyCustomTextureJob( pDlg->GetToolItem()->GetItemID(), pDlg->GetSubjectItem()->GetItemID(), pPNGData, nPNGDataBytes ) |
|
, m_pDlg( pDlg ) |
|
{ |
|
} |
|
|
|
protected: |
|
|
|
virtual EResult YieldingRunJob() |
|
{ |
|
// Base class do the work |
|
EResult result = CApplyCustomTextureJob::YieldingRunJob(); |
|
|
|
CloseWaitingDialog(); |
|
|
|
// Show result |
|
if ( result == k_EResultOK ) |
|
{ |
|
m_pDlg->OnCommand("close"); |
|
} |
|
else |
|
{ |
|
m_pDlg->CloseWithGenericError(); |
|
} |
|
|
|
// Return status code |
|
return result; |
|
} |
|
|
|
CConfirmCustomizeTextureDialog *m_pDlg; |
|
}; |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CConfirmCustomizeTextureDialog::Apply( void ) |
|
{ |
|
Assert( m_imgFinal.IsValid() ); |
|
|
|
// Throw up a busy dialog |
|
SetPage( ePage_PerformingAction ); |
|
|
|
// Write PNG data |
|
CUtlBuffer bufPNGData; |
|
if ( ImgUtl_SavePNGBitmapToBuffer( bufPNGData, m_imgFinal ) != CE_SUCCESS ) |
|
{ |
|
Warning( "Failed to write PNG\n" ); |
|
CloseWithGenericError(); |
|
return; |
|
} |
|
|
|
// Stats |
|
EconUI()->Gamestats_ItemTransaction( IE_ITEM_USED_TOOL, m_pToolModelPanel->GetItem(), "customized_texture" ); |
|
|
|
// Start a job to do the async work |
|
CCustomizeTextureJobDialog *pJob = new CCustomizeTextureJobDialog( bufPNGData.Base(), bufPNGData.TellPut(), this ); |
|
pJob->StartJob( NULL ); |
|
} |
|
|
|
void CConfirmCustomizeTextureDialog::CloseWithGenericError() |
|
{ |
|
CloseWaitingDialog(); |
|
|
|
// Show error message dialog |
|
ShowMessageBox( "#ToolCustomizeTextureError", "#ToolCustomizeTextureErrorMsg", "#GameUI_OK" ); |
|
|
|
// Close this window |
|
OnCommand("close"); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CConfirmCustomizeTextureDialog::ConversionError( ConversionErrorType nError ) |
|
{ |
|
const char *pErrorText = NULL; |
|
|
|
switch ( nError ) |
|
{ |
|
case CE_MEMORY_ERROR: |
|
pErrorText = "#GameUI_Spray_Import_Error_Memory"; |
|
break; |
|
|
|
case CE_CANT_OPEN_SOURCE_FILE: |
|
pErrorText = "#GameUI_Spray_Import_Error_Reading_Image"; |
|
break; |
|
|
|
case CE_ERROR_PARSING_SOURCE: |
|
pErrorText = "#GameUI_Spray_Import_Error_Image_File_Corrupt"; |
|
break; |
|
|
|
case CE_SOURCE_FILE_SIZE_NOT_SUPPORTED: |
|
pErrorText = "#GameUI_Spray_Import_Image_Wrong_Size"; |
|
break; |
|
|
|
case CE_SOURCE_FILE_FORMAT_NOT_SUPPORTED: |
|
pErrorText = "#GameUI_Spray_Import_Image_Wrong_Size"; |
|
break; |
|
|
|
case CE_SOURCE_FILE_TGA_FORMAT_NOT_SUPPORTED: |
|
pErrorText = "#GameUI_Spray_Import_Error_TGA_Format_Not_Supported"; |
|
break; |
|
|
|
case CE_SOURCE_FILE_BMP_FORMAT_NOT_SUPPORTED: |
|
pErrorText = "#GameUI_Spray_Import_Error_BMP_Format_Not_Supported"; |
|
break; |
|
|
|
case CE_ERROR_WRITING_OUTPUT_FILE: |
|
pErrorText = "#GameUI_Spray_Import_Error_Writing_Temp_Output"; |
|
break; |
|
|
|
case CE_ERROR_LOADING_DLL: |
|
pErrorText = "#GameUI_Spray_Import_Error_Cant_Load_VTEX_DLL"; |
|
break; |
|
} |
|
|
|
if ( pErrorText ) |
|
{ |
|
ShowMessageBox( "#ToolCustomizeTextureError", pErrorText, "#GameUI_OK" ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CConfirmCustomizeTextureDialog::OnFileSelected(const char *fullpath) |
|
{ |
|
// this can take a while, put up a waiting cursor |
|
vgui::surface()->SetCursor( vgui::dc_hourglass ); |
|
|
|
// they apparently don't want to use their avatar |
|
m_bUseAvatar = false; |
|
|
|
// Will need to be restretched/cropped/filtered, no matter what happens next |
|
MarkSquareImageDirty(); |
|
|
|
// Load up the data as raw RGBA |
|
ConversionErrorType nErrorCode = ImgUtl_LoadBitmap( fullpath, m_imgSource ); |
|
if ( nErrorCode != CE_SUCCESS ) |
|
{ |
|
// Report error, if any |
|
ConversionError( nErrorCode ); |
|
} |
|
|
|
// Slam alpha to 255. We do not support images with alpha |
|
for ( int y = 0 ; y < m_imgSource.Height() ; ++y ) |
|
{ |
|
for ( int x = 0 ; x < m_imgSource.Width() ; ++x ) |
|
{ |
|
Color c = m_imgSource.GetColor( x, y ); |
|
c[3] = 255; |
|
m_imgSource.SetColor( x, y, c ); |
|
} |
|
} |
|
|
|
// Show/hide controls as appropriate |
|
WriteSelectImagePageControls(); |
|
|
|
// Tick the palette entries right now, no matter what else happened |
|
//OnTick(); |
|
|
|
// change the cursor back to normal |
|
vgui::surface()->SetCursor( vgui::dc_user ); |
|
} |
|
|
|
void CConfirmCustomizeTextureDialog::OnTextChanged( vgui::Panel *panel ) |
|
{ |
|
// Check for known controls |
|
if ( panel == m_pFilterCombo ) |
|
{ |
|
|
|
// Mark us as dirty |
|
MarkFilteredImageDirty(); |
|
|
|
// Update controls |
|
ShowFilterControls(); |
|
} |
|
else if ( panel == m_pSquarizeCombo ) |
|
{ |
|
|
|
// If image is nearly square, ignore this, there shouldn't |
|
// be any options |
|
if ( !IsSourceImageSquare() ) |
|
{ |
|
|
|
// Set new option, if it is changing |
|
bool bNewOption = ( m_pSquarizeCombo->GetActiveItem() == 1 ); |
|
if ( !bNewOption != !m_bCropToSquare ) |
|
{ |
|
m_bCropToSquare = bNewOption; |
|
MarkSquareImageDirty(); |
|
} |
|
} |
|
} |
|
else if ( panel == m_pStencilModeCombo ) |
|
{ |
|
MarkFilteredImageDirty(); |
|
} |
|
else |
|
{ |
|
// Who else is talking to us? |
|
Assert( false ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CEconTool_CustomizeTexture::OnClientApplyTool( CEconItemView *pTool, CEconItemView *pSubject, vgui::Panel *pParent ) const |
|
{ |
|
CConfirmCustomizeTextureDialog *dialog = vgui::SETUP_PANEL( new CConfirmCustomizeTextureDialog( pParent, pTool, pSubject ) ); |
|
MakeModalAndBringToFront( dialog ); |
|
}
|
|
|