X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/38755449339718307f594d0c92f7d37ce56c908c..396825dced4558d8d724d0e38f7e1017f76d0a96:/src/common/xpmdecod.cpp diff --git a/src/common/xpmdecod.cpp b/src/common/xpmdecod.cpp index 08ad872940..8d38a3132e 100644 --- a/src/common/xpmdecod.cpp +++ b/src/common/xpmdecod.cpp @@ -1,5 +1,5 @@ ///////////////////////////////////////////////////////////////////////////// -// Name: xpmdecod.cpp +// Name: src/common/xpmdecod.cpp // Purpose: wxXPMDecoder // Author: John Cristy, Vaclav Slavik // RCS-ID: $Id$ @@ -90,58 +90,62 @@ license is as follows: * in this Software without prior written authorization from GROUPE BULL. */ -#ifdef __GNUG__ -#pragma implementation "xpmdecod.h" -#endif - // For compilers that support precompilation, includes "wx.h". #include "wx/wxprec.h" #ifdef __BORLANDC__ -# pragma hdrstop + #pragma hdrstop #endif +#if wxUSE_IMAGE && wxUSE_XPM + +#include "wx/xpmdecod.h" + #ifndef WX_PRECOMP -# include "wx/defs.h" + #include "wx/intl.h" + #include "wx/log.h" + #include "wx/utils.h" + #include "wx/hashmap.h" + #include "wx/stream.h" + #include "wx/image.h" + #include "wx/palette.h" #endif -#if wxUSE_IMAGE && wxUSE_XPM - -#include "wx/stream.h" -#include "wx/image.h" -#include "wx/utils.h" -#include "wx/log.h" -#include "wx/intl.h" #include - -#include "wx/xpmdecod.h" +#include #if wxUSE_STREAMS bool wxXPMDecoder::CanRead(wxInputStream& stream) { unsigned char buf[9]; - stream.Read(buf, 9); - stream.SeekI(-9, wxFromCurrent); + if ( !stream.Read(buf, WXSIZEOF(buf)) ) + return false; - return (memcmp(buf, "/* XPM */", 9) == 0); + stream.SeekI(-(wxFileOffset)WXSIZEOF(buf), wxFromCurrent); + + return memcmp(buf, "/* XPM */", WXSIZEOF(buf)) == 0; } wxImage wxXPMDecoder::ReadFile(wxInputStream& stream) { size_t length = stream.GetSize(); - wxCHECK_MSG(length != 0, wxNullImage, wxT("Cannot read XPM from stream of unknown size")); + wxCHECK_MSG( length != 0, wxNullImage, + wxT("Cannot read XPM from stream of unknown size") ); - char *xpm_buffer = new char[length]; - char *p, *q; - size_t i; + // use a smart buffer to be sure to free memory even when we return on + // error + wxCharBuffer buffer(length); - if ( stream.Read(xpm_buffer, length).LastError() != wxSTREAM_NO_ERROR ) - return FALSE; + char *xpm_buffer = (char *)buffer.data(); + if ( stream.Read(xpm_buffer, length).GetLastError() == wxSTREAM_READ_ERROR ) + return wxNullImage; + xpm_buffer[length] = '\0'; /* * Remove comments from the file: */ + char *p, *q; for (p = xpm_buffer; *p != '\0'; p++) { if ( (*p == '"') || (*p == '\'') ) @@ -169,13 +173,16 @@ wxImage wxXPMDecoder::ReadFile(wxInputStream& stream) if ( (*q == '*') && (*(q + 1) == '/') ) break; } - strcpy(p, q + 2); + + // memmove allows overlaps (unlike strcpy): + size_t cpylen = strlen(q + 2) + 1; + memmove(p, q + 2, cpylen); } /* * Remove unquoted characters: */ - i = 0; + size_t i = 0; for (p = xpm_buffer; *p != '\0'; p++) { if ( *p != '"' ) @@ -203,7 +210,13 @@ wxImage wxXPMDecoder::ReadFile(wxInputStream& stream) lines_cnt++; } - xpm_lines = new const char*[lines_cnt]; + if ( !lines_cnt ) + { + // this doesn't really look an XPM image + return wxNullImage; + } + + xpm_lines = new const char*[lines_cnt + 1]; xpm_lines[0] = xpm_buffer; line = 1; for (p = xpm_buffer; (*p != '\0') && (line < lines_cnt); p++) @@ -216,13 +229,15 @@ wxImage wxXPMDecoder::ReadFile(wxInputStream& stream) } } + xpm_lines[lines_cnt] = NULL; + /* * Read the image: */ wxImage img = ReadData(xpm_lines); - delete[] xpm_buffer; delete[] xpm_lines; + return img; } #endif // wxUSE_STREAMS @@ -245,13 +260,13 @@ wxImage wxXPMDecoder::ReadFile(wxInputStream& stream) typedef struct { - char *name; + const char *name; wxUint32 rgb; } rgbRecord; #define myRGB(r,g,b) ((wxUint32)r<<16|(wxUint32)g<<8|(wxUint32)b) -static rgbRecord theRGBRecords[] = +static const rgbRecord theRGBRecords[] = { {"aliceblue", myRGB(240, 248, 255)}, {"antiquewhite", myRGB(250, 235, 215)}, @@ -469,6 +484,7 @@ static rgbRecord theRGBRecords[] = {"seagreen", myRGB(82, 149, 132)}, {"seashell", myRGB(255, 245, 238)}, {"sienna", myRGB(150, 82, 45)}, + {"silver", myRGB(192, 192, 192)}, {"skyblue", myRGB(114, 159, 255)}, {"slateblue", myRGB(126, 136, 171)}, {"slategray", myRGB(112, 128, 144)}, @@ -489,7 +505,26 @@ static rgbRecord theRGBRecords[] = {"yellowgreen", myRGB(50, 216, 56)}, {NULL, myRGB(0, 0, 0)} }; -static int numTheRGBRecords = 234; +static const int numTheRGBRecords = 235; + +static unsigned char ParseHexadecimal(char digit1, char digit2) +{ + unsigned char i1, i2; + + if (digit1 >= 'a') + i1 = (unsigned char)(digit1 - 'a' + 0x0A); + else if (digit1 >= 'A') + i1 = (unsigned char)(digit1 - 'A' + 0x0A); + else + i1 = (unsigned char)(digit1 - '0'); + if (digit2 >= 'a') + i2 = (unsigned char)(digit2 - 'a' + 0x0A); + else if (digit2 >= 'A') + i2 = (unsigned char)(digit2 - 'A' + 0x0A); + else + i2 = (unsigned char)(digit2 - '0'); + return (unsigned char)(0x10 * i1 + i2); +} static bool GetRGBFromName(const char *inname, bool *isNone, unsigned char *r, unsigned char*g, unsigned char *b) @@ -500,23 +535,19 @@ static bool GetRGBFromName(const char *inname, bool *isNone, char *name; char *grey, *p; - // #rrggbb are not in database, we parse them directly - if ( *inname == '#' && strlen(inname) == 7 ) + // Neither #rrggbb nor #rrrrggggbbbb are in database, we parse them directly + size_t inname_len = strlen(inname); + if ( *inname == '#' && (inname_len == 7 || inname_len == 13)) { - char buf[3]; - buf[2] = 0; - buf[0] = inname[1]; buf[1] = inname[2]; - *r = (unsigned char) wxHexToDec(buf); - buf[0] = inname[3]; buf[1] = inname[4]; - *g = (unsigned char) wxHexToDec(buf); - buf[0] = inname[5]; buf[1] = inname[6]; - *b = (unsigned char) wxHexToDec(buf); - *isNone = FALSE; - - return TRUE; + size_t ofs = (inname_len == 7) ? 2 : 4; + *r = ParseHexadecimal(inname[1], inname[2]); + *g = ParseHexadecimal(inname[1*ofs+1], inname[1*ofs+2]); + *b = ParseHexadecimal(inname[2*ofs+1], inname[2*ofs+2]); + *isNone = false; + return true; } - name = strdup(inname); + name = wxStrdupA(inname); // theRGBRecords[] has no names with spaces, and no grey, but a // lot of gray... @@ -534,7 +565,7 @@ static bool GetRGBFromName(const char *inname, bool *isNone, p = name; while (*p) { - *p = tolower(*p); + *p = (char)tolower(*p); p++; } @@ -544,41 +575,47 @@ static bool GetRGBFromName(const char *inname, bool *isNone, grey[2] = 'a'; // check for special 'none' colour: + bool found; if ( strcmp(name, "none") == 0 ) { - *isNone = TRUE; - return TRUE; + *isNone = true; + found = true; } - - // binary search: - left = 0; - right = numTheRGBRecords - 1; - do + else // not "None" { - middle = (left + right) / 2; - cmp = strcmp(name, theRGBRecords[middle].name); - if ( cmp == 0 ) - { - rgbVal = theRGBRecords[middle].rgb; - *r = (rgbVal >> 16) & 0xFF; - *g = (rgbVal >> 8) & 0xFF; - *b = (rgbVal) & 0xFF; - *isNone = FALSE; - free(name); - return TRUE; - } - else if ( cmp < 0 ) + found = false; + + // binary search: + left = 0; + right = numTheRGBRecords - 1; + do { - right = middle - 1; - } - else - { // > 0 - left = middle + 1; - } - } while (left <= right); + middle = (left + right) / 2; + cmp = strcmp(name, theRGBRecords[middle].name); + if ( cmp == 0 ) + { + rgbVal = theRGBRecords[middle].rgb; + *r = (unsigned char)((rgbVal >> 16) & 0xFF); + *g = (unsigned char)((rgbVal >> 8) & 0xFF); + *b = (unsigned char)((rgbVal) & 0xFF); + *isNone = false; + found = true; + break; + } + else if ( cmp < 0 ) + { + right = middle - 1; + } + else // cmp > 0 + { + left = middle + 1; + } + } while (left <= right); + } free(name); - return FALSE; + + return found; } static const char *ParseColor(const char *data) @@ -613,32 +650,37 @@ static const char *ParseColor(const char *data) return NULL; } -class wxXPMColourMapData : public wxObject +struct wxXPMColourMapData { - public: - unsigned char R,G,B; + wxXPMColourMapData() { R = G = B = 0; } + unsigned char R,G,B; }; +WX_DECLARE_STRING_HASH_MAP(wxXPMColourMapData, wxXPMColourMap); -wxImage wxXPMDecoder::ReadData(const char **xpm_data) +wxImage wxXPMDecoder::ReadData(const char* const* xpm_data) { + wxCHECK_MSG(xpm_data, wxNullImage, wxT("NULL XPM data") ); + wxImage img; int count; unsigned width, height, colors_cnt, chars_per_pixel; - unsigned i, j; + size_t i, j, i_key; char key[64]; const char *clr_def; - bool hasMask; - wxXPMColourMapData *clr_data; - wxHashTable clr_tbl(wxKEY_STRING); + wxXPMColourMap clr_tbl; + wxXPMColourMap::iterator it; + wxString maskKey; + wxString keyString; /* * Read hints and initialize structures: */ + count = sscanf(xpm_data[0], "%u %u %u %u", &width, &height, &colors_cnt, &chars_per_pixel); if ( count != 4 || width * height * colors_cnt == 0 ) { - wxLogError(_T("XPM: Not XPM data!")); + wxLogError(_("XPM: incorrect header format!")); return wxNullImage; } @@ -647,55 +689,82 @@ wxImage wxXPMDecoder::ReadData(const char **xpm_data) // 8bit RGB... wxCHECK_MSG(chars_per_pixel < 64, wxNullImage, wxT("XPM colormaps this large not supported.")); - img.Create(width, height); - if ( !img.Ok() ) return img; + if (!img.Create(width, height, false)) + return wxNullImage; - img.SetMask(FALSE); key[chars_per_pixel] = '\0'; - hasMask = FALSE; - clr_tbl.DeleteContents(TRUE); /* * Create colour map: */ + wxXPMColourMapData clr_data; for (i = 0; i < colors_cnt; i++) { - memcpy(key, xpm_data[1 + i], chars_per_pixel); - clr_def = ParseColor(xpm_data[1 + i]); - clr_data = new wxXPMColourMapData; + const char *xmpColLine = xpm_data[1 + i]; + + // we must have at least " x y" after the colour index, hence +5 + if ( !xmpColLine || strlen(xmpColLine) < chars_per_pixel + 5 ) + { + wxLogError(_("XPM: incorrect colour description in line %d"), + (int)(1 + i)); + return wxNullImage; + } + + for (i_key = 0; i_key < chars_per_pixel; i_key++) + key[i_key] = xmpColLine[i_key]; + clr_def = ParseColor(xmpColLine + chars_per_pixel); if ( clr_def == NULL ) { - wxLogError(_("XPM: malformed colour definition '%s'!"), xpm_data[1+i]); - clr_data->R = 255, clr_data->G = 0, clr_data->B = 255; + wxLogError(_("XPM: malformed colour definition '%s' at line %d!"), + xmpColLine, (int)(1 + i)); + return wxNullImage; } - else + + bool isNone = false; + if ( !GetRGBFromName(clr_def, &isNone, + &clr_data.R, &clr_data.G, &clr_data.B) ) { - bool isNone; - if ( !GetRGBFromName(clr_def, &isNone, - &clr_data->R, &clr_data->G, &clr_data->B) ) - { - wxLogError(_("XPM: malformed colour definition '%s'!"), xpm_data[1+i]); - clr_data->R = 255, clr_data->G = 0, clr_data->B = 255; - } - else - { - if ( isNone ) - { - img.SetMask(TRUE); - img.SetMaskColour(255, 0, 255); - hasMask = TRUE; - clr_data->R = 255, clr_data->G = 0, clr_data->B = 255; - } - else - { - if ( hasMask && clr_data->R == 255 && - clr_data->G == 0 && clr_data->B == 255 ) - clr_data->B = 254; - } - } + wxLogError(_("XPM: malformed colour definition '%s' at line %d!"), + xmpColLine, (int)(1 + i)); + return wxNullImage; } - clr_tbl.Put(key, clr_data); + + keyString = key; + if ( isNone ) + maskKey = keyString; + + clr_tbl[keyString] = clr_data; + } + + // deal with the mask: we must replace pseudo-colour "None" with the mask + // colour (which can be any colour not otherwise used in the image) + if (!maskKey.empty()) + { + wxLongToLongHashMap rgb_table; + long rgb; + const size_t n = clr_tbl.size(); + wxXPMColourMap::const_iterator iter = clr_tbl.begin(); + for (i = 0; i < n; ++i, ++iter) + { + const wxXPMColourMapData& data = iter->second; + rgb = (data.R << 16) + (data.G << 8) + data.B; + rgb_table[rgb]; + } + for (rgb = 0; rgb <= 0xffffff && rgb_table.count(rgb); ++rgb) + ; + if (rgb > 0xffffff) + { + wxLogError(_("XPM: no colors left to use for mask!")); + return wxNullImage; + } + + wxXPMColourMapData& maskData = clr_tbl[maskKey]; + maskData.R = wxByte(rgb >> 16); + maskData.G = wxByte(rgb >> 8); + maskData.B = wxByte(rgb); + + img.SetMaskColour(maskData.R, maskData.G, maskData.B); } /* @@ -703,29 +772,62 @@ wxImage wxXPMDecoder::ReadData(const char **xpm_data) */ unsigned char *img_data = img.GetData(); + wxXPMColourMap::iterator entry; + wxXPMColourMap::iterator end = clr_tbl.end(); + for (j = 0; j < height; j++) { for (i = 0; i < width; i++, img_data += 3) { - memcpy(key, - xpm_data[1 + colors_cnt + j] + chars_per_pixel * i, - chars_per_pixel); - clr_data = (wxXPMColourMapData*) clr_tbl.Get(key); - if ( clr_data == NULL ) + const char *xpmImgLine = xpm_data[1 + colors_cnt + j]; + if ( !xpmImgLine || strlen(xpmImgLine) < width*chars_per_pixel ) { - wxLogError(_("XPM: Malformed pixel data!")); + wxLogError(_("XPM: truncated image data at line %d!"), + (int)(1 + colors_cnt + j)); + return wxNullImage; + } + + for (i_key = 0; i_key < chars_per_pixel; i_key++) + { + key[i_key] = xpmImgLine[chars_per_pixel * i + i_key]; } - else + + keyString = key; + entry = clr_tbl.find(keyString); + if ( entry == end ) { - img_data[0] = clr_data->R; - img_data[1] = clr_data->G; - img_data[2] = clr_data->B; + wxLogError(_("XPM: Malformed pixel data!")); + + // better return right now as otherwise we risk to flood the + // user with error messages as something seems to be seriously + // wrong with the file and so we could give this message for + // each remaining pixel if we don't bail out + return wxNullImage; } + + img_data[0] = entry->second.R; + img_data[1] = entry->second.G; + img_data[2] = entry->second.B; } } +#if wxUSE_PALETTE + unsigned char* r = new unsigned char[colors_cnt]; + unsigned char* g = new unsigned char[colors_cnt]; + unsigned char* b = new unsigned char[colors_cnt]; + for (it = clr_tbl.begin(), i = 0; it != clr_tbl.end(); it++, i++) + { + r[i] = it->second.R; + g[i] = it->second.G; + b[i] = it->second.B; + } + wxASSERT(i == colors_cnt); + img.SetPalette(wxPalette(colors_cnt, r, g, b)); + delete[] r; + delete[] g; + delete[] b; +#endif // wxUSE_PALETTE return img; } - #endif // wxUSE_IMAGE && wxUSE_XPM