diff options
Diffstat (limited to 'game/client/econ/tool_items/custom_texture_tool.cpp')
| -rw-r--r-- | game/client/econ/tool_items/custom_texture_tool.cpp | 2766 |
1 files changed, 2766 insertions, 0 deletions
diff --git a/game/client/econ/tool_items/custom_texture_tool.cpp b/game/client/econ/tool_items/custom_texture_tool.cpp new file mode 100644 index 0000000..7f9e14a --- /dev/null +++ b/game/client/econ/tool_items/custom_texture_tool.cpp @@ -0,0 +1,2766 @@ +//========= 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 ); +} |