Applied Chris' patch for support for ICO loading.
git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@12473 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
@@ -50,12 +50,48 @@ public:
|
||||
};
|
||||
|
||||
#if wxUSE_STREAMS
|
||||
virtual bool SaveFile( wxImage *image, wxOutputStream& stream, bool verbose=TRUE );
|
||||
|
||||
virtual bool SaveFile( wxImage *image, wxOutputStream& stream, bool verbose=TRUE );
|
||||
virtual bool LoadFile( wxImage *image, wxInputStream& stream, bool verbose=TRUE, int index=0 );
|
||||
virtual bool DoCanRead( wxInputStream& stream );
|
||||
|
||||
protected:
|
||||
bool DoLoadDib (wxImage * image, int width, int height, int bpp, int ncolors, int comp,
|
||||
off_t bmpOffset, wxInputStream& stream,
|
||||
bool verbose, bool IsBmp, bool hasPalette ) ;
|
||||
bool LoadDib( wxImage *image, wxInputStream& stream, bool verbose, bool IsBmp ) ;
|
||||
|
||||
#endif // wxUSE_STREAMS
|
||||
|
||||
private:
|
||||
DECLARE_DYNAMIC_CLASS(wxBMPHandler)
|
||||
};
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// wxICOHandler
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
class WXDLLEXPORT wxICOHandler : public wxBMPHandler
|
||||
{
|
||||
public:
|
||||
wxICOHandler()
|
||||
{
|
||||
m_name = _T("ICO file");
|
||||
m_extension = _T("ico");
|
||||
m_type = wxBITMAP_TYPE_ICO;
|
||||
m_mime = _T("image/icon");
|
||||
};
|
||||
|
||||
#if wxUSE_STREAMS
|
||||
|
||||
virtual bool SaveFile( wxImage *image, wxOutputStream& stream, bool verbose=TRUE );
|
||||
virtual bool LoadFile( wxImage *image, wxInputStream& stream, bool verbose=TRUE, int index=0 );
|
||||
virtual bool DoCanRead( wxInputStream& stream );
|
||||
|
||||
#endif // wxUSE_STREAMS
|
||||
|
||||
private:
|
||||
DECLARE_DYNAMIC_CLASS(wxBMPHandler)
|
||||
};
|
||||
|
||||
|
@@ -151,6 +151,10 @@ public:
|
||||
unsigned char GetGreen( int x, int y ) const;
|
||||
unsigned char GetBlue( int x, int y ) const;
|
||||
|
||||
// used to manipulate the icons while extracting from .ico files
|
||||
bool GetUnusedColour( unsigned char *r, unsigned char *g, unsigned char *b );
|
||||
bool ApplyMask( const wxImage & mask );
|
||||
|
||||
static bool CanRead( const wxString& name );
|
||||
virtual bool LoadFile( const wxString& name, long type = wxBITMAP_TYPE_ANY );
|
||||
virtual bool LoadFile( const wxString& name, const wxString& mimetype );
|
||||
|
@@ -55,6 +55,7 @@ void wxInitAllImageHandlers()
|
||||
#if wxUSE_XPM
|
||||
wxImage::AddHandler( new wxXPMHandler );
|
||||
#endif
|
||||
wxImage::AddHandler( new wxICOHandler );
|
||||
}
|
||||
|
||||
#endif // wxUSE_IMAGE
|
||||
|
@@ -409,111 +409,74 @@ bool wxBMPHandler::SaveFile(wxImage *image,
|
||||
|
||||
#define poffset (line * width * 3 + column * 3)
|
||||
|
||||
bool wxBMPHandler::LoadFile( wxImage *image, wxInputStream& stream, bool verbose, int WXUNUSED(index) )
|
||||
|
||||
|
||||
struct ICONDIRENTRY
|
||||
{
|
||||
wxUint8 bWidth; // Width of the image
|
||||
wxUint8 bHeight; // Height of the image (times 2)
|
||||
wxUint8 bColorCount; // Number of colors in image (0 if >=8bpp)
|
||||
wxUint8 bReserved; // Reserved
|
||||
wxUint16 wPlanes; // Color Planes
|
||||
wxUint16 wBitCount; // Bits per pixel
|
||||
wxUint32 dwBytesInRes; // how many bytes in this resource?
|
||||
wxUint32 dwImageOffset; // where in the file is this image
|
||||
} ;
|
||||
|
||||
|
||||
struct ICONDIR
|
||||
{
|
||||
wxUint16 idReserved; // Reserved
|
||||
wxUint16 idType; // resource type (1 for icons)
|
||||
wxUint16 idCount; // how many images?
|
||||
} ;
|
||||
|
||||
|
||||
bool wxBMPHandler::DoLoadDib (wxImage * image, int width, int height, int bpp, int ncolors, int comp,
|
||||
off_t bmpOffset, wxInputStream& stream,
|
||||
bool verbose, bool IsBmp, bool hasPalette )
|
||||
{
|
||||
|
||||
wxInt32 aDword, rmask = 0, gmask = 0, bmask = 0;
|
||||
int rshift = 0, gshift = 0, bshift = 0;
|
||||
wxInt32 dbuf[4];
|
||||
wxInt8 bbuf[4];
|
||||
wxUint8 aByte;
|
||||
wxUint16 aWord;
|
||||
wxInt32 dbuf[4];
|
||||
wxInt32 aDword, rmask = 0, gmask = 0, bmask = 0;
|
||||
wxInt8 bbuf[4];
|
||||
struct _cmap {
|
||||
|
||||
|
||||
// allocate space for palette if needed
|
||||
struct _cmap
|
||||
{
|
||||
unsigned char r, g, b;
|
||||
} *cmap = NULL;
|
||||
|
||||
off_t start_offset = stream.TellI();
|
||||
if (start_offset == wxInvalidOffset) start_offset = 0;
|
||||
|
||||
image->Destroy();
|
||||
|
||||
/*
|
||||
* Read the BMP header
|
||||
*/
|
||||
|
||||
stream.Read( bbuf, 2 );
|
||||
stream.Read( dbuf, 4 * 4 );
|
||||
|
||||
#if 0 // unused
|
||||
wxInt32 size = wxINT32_SWAP_ON_BE( dbuf[0] );
|
||||
#endif
|
||||
wxInt32 offset = wxINT32_SWAP_ON_BE( dbuf[2] );
|
||||
|
||||
stream.Read(dbuf, 4 * 2);
|
||||
int width = (int)wxINT32_SWAP_ON_BE( dbuf[0] );
|
||||
int height = (int)wxINT32_SWAP_ON_BE( dbuf[1] );
|
||||
if (width > 32767)
|
||||
{
|
||||
if (verbose)
|
||||
wxLogError( _("BMP: Image width > 32767 pixels for file.") );
|
||||
return FALSE;
|
||||
}
|
||||
if (height > 32767)
|
||||
{
|
||||
if (verbose)
|
||||
wxLogError( _("BMP: Image height > 32767 pixels for file.") );
|
||||
return FALSE;
|
||||
}
|
||||
*cmap = NULL;
|
||||
|
||||
stream.Read( &aWord, 2 );
|
||||
/*
|
||||
TODO
|
||||
int planes = (int)wxUINT16_SWAP_ON_BE( aWord );
|
||||
*/
|
||||
stream.Read( &aWord, 2 );
|
||||
int bpp = (int)wxUINT16_SWAP_ON_BE( aWord );
|
||||
if (bpp != 1 && bpp != 4 && bpp != 8 && bpp != 16 && bpp != 24 && bpp != 32)
|
||||
{
|
||||
if (verbose)
|
||||
wxLogError( _("BMP: Unknown bitdepth in file.") );
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
stream.Read( dbuf, 4 * 4 );
|
||||
int comp = (int)wxINT32_SWAP_ON_BE( dbuf[0] );
|
||||
if (comp != BI_RGB && comp != BI_RLE4 && comp != BI_RLE8 && comp != BI_BITFIELDS)
|
||||
{
|
||||
if (verbose)
|
||||
wxLogError( _("BMP: Unknown encoding in file.") );
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
stream.Read( dbuf, 4 * 2 );
|
||||
int ncolors = (int)wxINT32_SWAP_ON_BE( dbuf[0] );
|
||||
if (ncolors == 0)
|
||||
ncolors = 1 << bpp;
|
||||
/* some more sanity checks */
|
||||
if (((comp == BI_RLE4) && (bpp != 4)) ||
|
||||
((comp == BI_RLE8) && (bpp != 8)) ||
|
||||
((comp == BI_BITFIELDS) && (bpp != 16 && bpp != 32)))
|
||||
{
|
||||
if (verbose)
|
||||
wxLogError( _("BMP: Encoding doesn't match bitdepth.") );
|
||||
return FALSE;
|
||||
}
|
||||
if (bpp < 16)
|
||||
{
|
||||
cmap = (struct _cmap *)malloc(sizeof(struct _cmap) * ncolors);
|
||||
if (!cmap)
|
||||
{
|
||||
if (verbose)
|
||||
wxLogError( _("BMP: Couldn't allocate memory.") );
|
||||
wxLogError( _("Loading DIB : Couldn't allocate memory.") );
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
else
|
||||
cmap = NULL;
|
||||
|
||||
// destroy existing here instead of
|
||||
image->Destroy();
|
||||
image->Create( width, height );
|
||||
unsigned char *ptr = image->GetData();
|
||||
if (!ptr)
|
||||
{
|
||||
if (verbose)
|
||||
wxLogError( _("BMP: Couldn't allocate memory.") );
|
||||
wxLogError( _("Loading DIB : Couldn't allocate memory.") );
|
||||
if (cmap)
|
||||
free(cmap);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Reading the palette, if it exists.
|
||||
*/
|
||||
@@ -523,6 +486,8 @@ bool wxBMPHandler::LoadFile( wxImage *image, wxInputStream& stream, bool verbose
|
||||
unsigned char* g = new unsigned char[ncolors];
|
||||
unsigned char* b = new unsigned char[ncolors];
|
||||
for (int j = 0; j < ncolors; j++)
|
||||
{
|
||||
if (hasPalette)
|
||||
{
|
||||
stream.Read( bbuf, 4 );
|
||||
cmap[j].b = bbuf[0];
|
||||
@@ -533,6 +498,14 @@ bool wxBMPHandler::LoadFile( wxImage *image, wxInputStream& stream, bool verbose
|
||||
g[j] = cmap[j].g;
|
||||
b[j] = cmap[j].b;
|
||||
}
|
||||
else
|
||||
{
|
||||
//used in reading .ico file mask
|
||||
r[j] = cmap[j].r = j * 255;
|
||||
g[j] = cmap[j].g = j * 255;
|
||||
b[j] = cmap[j].b = j * 255;
|
||||
}
|
||||
}
|
||||
|
||||
#if wxUSE_PALETTE
|
||||
// Set the palette for the wxImage
|
||||
@@ -586,7 +559,8 @@ bool wxBMPHandler::LoadFile( wxImage *image, wxInputStream& stream, bool verbose
|
||||
/*
|
||||
* Reading the image data
|
||||
*/
|
||||
stream.SeekI( start_offset + offset );
|
||||
if ( IsBmp ) stream.SeekI( bmpOffset ); // else icon, just carry on
|
||||
|
||||
unsigned char *data = ptr;
|
||||
|
||||
/* set the whole image to the background color */
|
||||
@@ -633,7 +607,7 @@ bool wxBMPHandler::LoadFile( wxImage *image, wxInputStream& stream, bool verbose
|
||||
if (comp == BI_RLE4)
|
||||
{
|
||||
if (verbose)
|
||||
wxLogError( _("BMP: Cannot deal with 4bit encoded yet.") );
|
||||
wxLogError( _("DIB Header: Cannot deal with 4bit encoded yet.") );
|
||||
image->Destroy();
|
||||
free(cmap);
|
||||
return FALSE;
|
||||
@@ -768,9 +742,188 @@ bool wxBMPHandler::LoadFile( wxImage *image, wxInputStream& stream, bool verbose
|
||||
|
||||
image->SetMask( FALSE );
|
||||
|
||||
return stream.IsOk();
|
||||
}
|
||||
|
||||
|
||||
bool wxBMPHandler::LoadDib( wxImage *image, wxInputStream& stream, bool verbose, bool IsBmp )
|
||||
{
|
||||
|
||||
wxUint8 aByte;
|
||||
wxUint16 aWord;
|
||||
wxInt32 dbuf[4];
|
||||
wxInt8 bbuf[4];
|
||||
off_t offset;
|
||||
|
||||
offset = 0; // keep gcc quiet
|
||||
if ( IsBmp )
|
||||
{
|
||||
// read the header off the .BMP format file
|
||||
|
||||
offset = stream.TellI();
|
||||
if (offset == wxInvalidOffset) offset = 0;
|
||||
|
||||
stream.Read( bbuf, 2 );
|
||||
|
||||
stream.Read( dbuf, 16 );
|
||||
}
|
||||
else
|
||||
{
|
||||
stream.Read( dbuf, 4 );
|
||||
}
|
||||
#if 0 // unused
|
||||
wxInt32 size = wxINT32_SWAP_ON_BE( dbuf[0] );
|
||||
#endif
|
||||
offset = offset + wxINT32_SWAP_ON_BE( dbuf[2] );
|
||||
|
||||
stream.Read(dbuf, 4 * 2);
|
||||
int width = (int)wxINT32_SWAP_ON_BE( dbuf[0] );
|
||||
int height = (int)wxINT32_SWAP_ON_BE( dbuf[1] );
|
||||
if ( !IsBmp ) height = height / 2; // for icons divide by 2
|
||||
|
||||
if (width > 32767)
|
||||
{
|
||||
if (verbose)
|
||||
wxLogError( _("DIB Header: Image width > 32767 pixels for file.") );
|
||||
return FALSE;
|
||||
}
|
||||
if (height > 32767)
|
||||
{
|
||||
if (verbose)
|
||||
wxLogError( _("DIB Header: Image height > 32767 pixels for file.") );
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
stream.Read( &aWord, 2 );
|
||||
/*
|
||||
TODO
|
||||
int planes = (int)wxUINT16_SWAP_ON_BE( aWord );
|
||||
*/
|
||||
stream.Read( &aWord, 2 );
|
||||
int bpp = (int)wxUINT16_SWAP_ON_BE( aWord );
|
||||
if (bpp != 1 && bpp != 4 && bpp != 8 && bpp != 16 && bpp != 24 && bpp != 32)
|
||||
{
|
||||
if (verbose)
|
||||
wxLogError( _("DIB Header: Unknown bitdepth in file.") );
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
stream.Read( dbuf, 4 * 4 );
|
||||
int comp = (int)wxINT32_SWAP_ON_BE( dbuf[0] );
|
||||
if (comp != BI_RGB && comp != BI_RLE4 && comp != BI_RLE8 && comp != BI_BITFIELDS)
|
||||
{
|
||||
if (verbose)
|
||||
wxLogError( _("DIB Header: Unknown encoding in file.") );
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
stream.Read( dbuf, 4 * 2 );
|
||||
int ncolors = (int)wxINT32_SWAP_ON_BE( dbuf[0] );
|
||||
if (ncolors == 0)
|
||||
ncolors = 1 << bpp;
|
||||
/* some more sanity checks */
|
||||
if (((comp == BI_RLE4) && (bpp != 4)) ||
|
||||
((comp == BI_RLE8) && (bpp != 8)) ||
|
||||
((comp == BI_BITFIELDS) && (bpp != 16 && bpp != 32)))
|
||||
{
|
||||
if (verbose)
|
||||
wxLogError( _("DIB Header: Encoding doesn't match bitdepth.") );
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
//read DIB; this is the BMP image or the XOR part of an icon image
|
||||
if (!DoLoadDib (image, width, height, bpp, ncolors, comp, offset, stream,
|
||||
verbose, IsBmp, TRUE ) )
|
||||
{
|
||||
if (verbose)
|
||||
wxLogError( _("Error in reading image DIB .") );
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if ( !IsBmp )
|
||||
{
|
||||
//read Icon mask which is monochrome
|
||||
//there is no palette, so we will create one
|
||||
wxImage mask ;
|
||||
if (!DoLoadDib (&mask, width, height, 1, 2, BI_RGB, offset, stream,
|
||||
verbose, IsBmp, FALSE ) )
|
||||
{
|
||||
if (verbose)
|
||||
wxLogError( _("ICO: Error in reading mask DIB.") );
|
||||
return FALSE;
|
||||
}
|
||||
image -> ApplyMask ( &mask );
|
||||
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
bool wxBMPHandler::LoadFile ( wxImage *image, wxInputStream& stream, bool verbose, int WXUNUSED(index) )
|
||||
{
|
||||
bool IsBmp = TRUE;
|
||||
//Read a single DIB fom the file
|
||||
return LoadDib ( image, stream, verbose, IsBmp ) ;
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool wxICOHandler::LoadFile ( wxImage *image, wxInputStream& stream, bool verbose, int WXUNUSED(index) )
|
||||
{
|
||||
bool bResult = FALSE ;
|
||||
bool IsBmp = FALSE;
|
||||
|
||||
ICONDIR m_IconDir ;
|
||||
stream.Read (&m_IconDir, sizeof(m_IconDir));
|
||||
wxUint16 nIcons = wxUINT16_SWAP_ON_BE ( m_IconDir.idCount ) ;
|
||||
|
||||
//loop round the icons and choose the best one
|
||||
ICONDIRENTRY * pIconDirEntry = new ICONDIRENTRY [nIcons];
|
||||
ICONDIRENTRY * pCurrentEntry = pIconDirEntry ;
|
||||
int i ;
|
||||
int wMax = 0 ;
|
||||
int colmax = 0 ;
|
||||
int iSel = wxNOT_FOUND ;
|
||||
for (i=0; i < nIcons ; i++ )
|
||||
{
|
||||
stream.Read(pCurrentEntry, sizeof(ICONDIRENTRY));
|
||||
//bHeight and bColorCount are wxUint8
|
||||
if (pCurrentEntry->bWidth >= wMax )
|
||||
{
|
||||
// see if we have more colors, ==0 indicates > 8bpp
|
||||
if (pCurrentEntry->bColorCount == 0 ) pCurrentEntry->bColorCount = 255 ;
|
||||
if (pCurrentEntry->bColorCount >= colmax)
|
||||
{
|
||||
iSel = i ;
|
||||
wMax = pCurrentEntry->bWidth ;
|
||||
colmax = pCurrentEntry->bColorCount ;
|
||||
}
|
||||
}
|
||||
pCurrentEntry ++ ;
|
||||
}
|
||||
if (iSel == wxNOT_FOUND)
|
||||
{
|
||||
bResult = FALSE;
|
||||
}
|
||||
else
|
||||
{
|
||||
//seek to selected icon
|
||||
pCurrentEntry = pIconDirEntry + iSel ;
|
||||
stream.SeekI (wxUINT32_SWAP_ON_BE ( pCurrentEntry -> dwImageOffset ), wxFromStart ) ;
|
||||
bResult = LoadDib ( image, stream, TRUE, IsBmp );
|
||||
}
|
||||
delete [] pIconDirEntry ;
|
||||
return bResult
|
||||
;
|
||||
}
|
||||
|
||||
bool wxICOHandler::SaveFile(wxImage *image,
|
||||
wxOutputStream& stream,
|
||||
bool verbose)
|
||||
{
|
||||
return FALSE ;
|
||||
}
|
||||
|
||||
bool wxBMPHandler::DoCanRead( wxInputStream& stream )
|
||||
{
|
||||
unsigned char hdr[2];
|
||||
@@ -780,6 +933,15 @@ bool wxBMPHandler::DoCanRead( wxInputStream& stream )
|
||||
return (hdr[0] == 'B' && hdr[1] == 'M');
|
||||
}
|
||||
|
||||
bool wxICOHandler::DoCanRead( wxInputStream& stream )
|
||||
{
|
||||
unsigned char hdr[4];
|
||||
|
||||
stream.Read(hdr, 4);
|
||||
stream.SeekI(-4, wxFromCurrent);
|
||||
return (hdr[0] == '\0' && hdr[1] == '\0' && hdr[2] == '\1' && hdr[3] == '\0');
|
||||
}
|
||||
|
||||
#endif // wxUSE_STREAMS
|
||||
|
||||
#endif // wxUSE_IMAGE
|
||||
|
@@ -720,6 +720,100 @@ int wxImage::GetHeight() const
|
||||
return M_IMGDATA->m_height;
|
||||
}
|
||||
|
||||
|
||||
bool wxImage::GetUnusedColour ( unsigned char * r, unsigned char * g, unsigned char * b )
|
||||
{
|
||||
wxHashTable hTable;
|
||||
unsigned long key;
|
||||
|
||||
ComputeHistogram( hTable );
|
||||
|
||||
// start with blackest color and work to lightest
|
||||
// 0,0,0 is quite likely to be a used color
|
||||
unsigned char r2 = 1;
|
||||
unsigned char g2 = 0;
|
||||
unsigned char b2 = 0;
|
||||
|
||||
key = (r2 << 16) | (g2 << 8) | b2;
|
||||
|
||||
while ( (wxHNode *) hTable.Get(key) )
|
||||
{
|
||||
// color already used
|
||||
r2 ++ ;
|
||||
if ( r2 >= 255 )
|
||||
{
|
||||
r2 = 0;
|
||||
g2 ++ ;
|
||||
if ( g2 >= 255 )
|
||||
{
|
||||
g2 = 0 ;
|
||||
b2 ++ ;
|
||||
if ( b2 >= 255 )
|
||||
{
|
||||
wxLogError( _("GetUnusedColour:: No Unused Color in image ") );
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
key = (r2 << 16) | (g2 << 8) | b2;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
bool wxImage::ApplyMask ( const wxImage & mask )
|
||||
{
|
||||
// what to do if we already have a mask ??
|
||||
if (M_IMGDATA->m_hasMask || mask.HasMask() )
|
||||
{
|
||||
wxLogError( _("Image already masked") );
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// check that the images are the same size
|
||||
if ( (M_IMGDATA->m_height != mask.GetHeight() ) || (M_IMGDATA->m_width != mask.GetWidth () ) )
|
||||
{
|
||||
wxLogError( _("Image and Mask have different sizes") );
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// find unused colour
|
||||
unsigned char r,g,b ;
|
||||
if (!GetUnusedColour (&r, &g, &b))
|
||||
{
|
||||
wxLogError( _("No Unused Color in image being masked") );
|
||||
return FALSE ;
|
||||
}
|
||||
|
||||
char unsigned *imgdata = GetData();
|
||||
char unsigned *maskdata = mask.GetData();
|
||||
|
||||
const int w = GetWidth();
|
||||
const int h = GetHeight();
|
||||
|
||||
for (int j = 0; j < h; j++)
|
||||
for (int i = 0; i < w; i++)
|
||||
{
|
||||
if ((maskdata[0] > 128) && (maskdata[1] > 128) && (maskdata[2] > 128 ))
|
||||
{
|
||||
imgdata[0] = r;
|
||||
imgdata[1] = g;
|
||||
imgdata[2] = b;
|
||||
}
|
||||
imgdata += 3;
|
||||
maskdata += 3;
|
||||
}
|
||||
|
||||
M_IMGDATA->m_maskRed = r;
|
||||
M_IMGDATA->m_maskGreen = g;
|
||||
M_IMGDATA->m_maskBlue = b;
|
||||
M_IMGDATA->m_hasMask = TRUE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
#if wxUSE_PALETTE
|
||||
|
||||
// Palette functions
|
||||
|
Reference in New Issue
Block a user