Modified source engine (2017) developed by valve and leaked in 2020. Not for commercial purporses
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.

2767 lines
97 KiB

5 years ago
//========= 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 );
}