///////////////////////////////////////////////////////////////////////////// // Name: src/common/imagbmp.cpp // Purpose: wxImage BMP,ICO and CUR handlers // Author: Robert Roebling, Chris Elliott // Copyright: (c) Robert Roebling, Chris Elliott // Licence: wxWindows licence ///////////////////////////////////////////////////////////////////////////// // For compilers that support precompilation, includes "wx.h". #include "wx/wxprec.h" #ifdef __BORLANDC__ #pragma hdrstop #endif #if wxUSE_IMAGE #include "wx/imagbmp.h" #ifndef WX_PRECOMP #ifdef __WXMSW__ #include "wx/msw/wrapwin.h" #endif #include "wx/log.h" #include "wx/app.h" #include "wx/bitmap.h" #include "wx/palette.h" #include "wx/intl.h" #include "wx/math.h" #endif #include "wx/filefn.h" #include "wx/wfstream.h" #include "wx/quantize.h" #include "wx/scopeguard.h" #include "wx/scopedarray.h" #include "wx/scopedptr.h" #include "wx/anidecod.h" #include "wx/private/icondir.h" // For memcpy #include // ---------------------------------------------------------------------------- // private functions // ---------------------------------------------------------------------------- #if wxUSE_ICO_CUR static bool CanReadICOOrCUR(wxInputStream *stream, wxUint16 resourceType); #endif // wxUSE_ICO_CUR //----------------------------------------------------------------------------- // wxBMPHandler //----------------------------------------------------------------------------- wxIMPLEMENT_DYNAMIC_CLASS(wxBMPHandler,wxImageHandler); #if wxUSE_STREAMS #ifndef BI_RGB #define BI_RGB 0 #endif #ifndef BI_RLE8 #define BI_RLE8 1 #endif #ifndef BI_RLE4 #define BI_RLE4 2 #endif #ifndef BI_BITFIELDS #define BI_BITFIELDS 3 #endif #define poffset (line * width * 3 + column * 3) bool wxBMPHandler::SaveFile(wxImage *image, wxOutputStream& stream, bool verbose) { return SaveDib(image, stream, verbose, true/*IsBmp*/, false/*IsMask*/); } bool wxBMPHandler::SaveDib(wxImage *image, wxOutputStream& stream, bool verbose, bool IsBmp, bool IsMask) { wxCHECK_MSG( image, false, wxT("invalid pointer in wxBMPHandler::SaveFile") ); if ( !image->IsOk() ) { if ( verbose ) { wxLogError(_("BMP: Couldn't save invalid image.")); } return false; } // For icons, save alpha channel if available. const bool saveAlpha = !IsBmp && image->HasAlpha(); // get the format of the BMP file to save, // else (and always if alpha channel is present) use 24bpp unsigned format = wxBMP_24BPP; if ( image->HasOption(wxIMAGE_OPTION_BMP_FORMAT) && !saveAlpha ) format = image->GetOptionInt(wxIMAGE_OPTION_BMP_FORMAT); wxUint16 bpp; // # of bits per pixel int palette_size; // # of color map entries, ie. 2^bpp colors // set the bpp and appropriate palette_size, and do additional checks if ( (format == wxBMP_1BPP) || (format == wxBMP_1BPP_BW) ) { bpp = 1; palette_size = 2; } else if ( format == wxBMP_4BPP ) { bpp = 4; palette_size = 16; } else if ( (format == wxBMP_8BPP) || (format == wxBMP_8BPP_GREY) || (format == wxBMP_8BPP_RED) || (format == wxBMP_8BPP_PALETTE) ) { // need to set a wxPalette to use this, HOW TO CHECK IF VALID, SIZE? if (format == wxBMP_8BPP_PALETTE #if wxUSE_PALETTE && !image->HasPalette() #endif // wxUSE_PALETTE ) { if ( verbose ) { wxLogError(_("BMP: wxImage doesn't have own wxPalette.")); } return false; } bpp = 8; palette_size = 256; } else // you get 24bpp or 32bpp with alpha { format = wxBMP_24BPP; bpp = saveAlpha ? 32 : 24; palette_size = 0; } unsigned width = image->GetWidth(); unsigned row_padding = (4 - ((width * bpp + 7) / 8) % 4) % 4; // # bytes to pad to dword unsigned row_width = (width * bpp + 7) / 8 + row_padding; // # of bytes per row struct { // BitmapHeader: wxUint16 magic; // format magic, always 'BM' wxUint32 filesize; // total file size, inc. headers wxUint32 reserved; // for future use wxUint32 data_offset; // image data offset in the file // BitmapInfoHeader: wxUint32 bih_size; // 2nd part's size wxUint32 width, height; // bitmap's dimensions wxUint16 planes; // num of planes wxUint16 bpp; // bits per pixel wxUint32 compression; // compression method wxUint32 size_of_bmp; // size of the bitmap wxUint32 h_res, v_res; // image resolution in pixels-per-meter wxUint32 num_clrs; // number of colors used wxUint32 num_signif_clrs;// number of significant colors } hdr; wxUint32 hdr_size = 14/*BitmapHeader*/ + 40/*BitmapInfoHeader*/; hdr.magic = wxUINT16_SWAP_ON_BE(0x4D42/*'BM'*/); hdr.filesize = wxUINT32_SWAP_ON_BE( hdr_size + palette_size*4 + row_width * image->GetHeight() ); hdr.reserved = 0; hdr.data_offset = wxUINT32_SWAP_ON_BE(hdr_size + palette_size*4); hdr.bih_size = wxUINT32_SWAP_ON_BE(hdr_size - 14); hdr.width = wxUINT32_SWAP_ON_BE(image->GetWidth()); if ( IsBmp ) { hdr.height = wxUINT32_SWAP_ON_BE(image->GetHeight()); } else { hdr.height = wxUINT32_SWAP_ON_BE(2 * image->GetHeight()); } hdr.planes = wxUINT16_SWAP_ON_BE(1); // always 1 plane hdr.bpp = wxUINT16_SWAP_ON_BE(bpp); hdr.compression = 0; // RGB uncompressed hdr.size_of_bmp = wxUINT32_SWAP_ON_BE(row_width * image->GetHeight()); // get the resolution from the image options or fall back to 72dpi standard // for the BMP format if not specified int hres, vres; switch ( GetResolutionFromOptions(*image, &hres, &vres) ) { default: wxFAIL_MSG( wxT("unexpected image resolution units") ); wxFALLTHROUGH; case wxIMAGE_RESOLUTION_NONE: hres = vres = 72; wxFALLTHROUGH;// fall through to convert it to correct units case wxIMAGE_RESOLUTION_INCHES: // convert resolution in inches to resolution in centimeters hres = (int)(10*mm2inches*hres); vres = (int)(10*mm2inches*vres); wxFALLTHROUGH;// fall through to convert it to resolution in meters case wxIMAGE_RESOLUTION_CM: // convert resolution in centimeters to resolution in meters hres *= 100; vres *= 100; break; } hdr.h_res = wxUINT32_SWAP_ON_BE(hres); hdr.v_res = wxUINT32_SWAP_ON_BE(vres); hdr.num_clrs = wxUINT32_SWAP_ON_BE(palette_size); // # colors in colormap hdr.num_signif_clrs = 0; // all colors are significant if ( IsBmp ) { if (// VS: looks ugly but compilers tend to do ugly things with structs, // like aligning hdr.filesize's ofset to dword :( // VZ: we should add padding then... !stream.WriteAll(&hdr.magic, 2) || !stream.WriteAll(&hdr.filesize, 4) || !stream.WriteAll(&hdr.reserved, 4) || !stream.WriteAll(&hdr.data_offset, 4) ) { if (verbose) { wxLogError(_("BMP: Couldn't write the file (Bitmap) header.")); } return false; } } if ( !IsMask ) { if ( !stream.WriteAll(&hdr.bih_size, 4) || !stream.WriteAll(&hdr.width, 4) || !stream.WriteAll(&hdr.height, 4) || !stream.WriteAll(&hdr.planes, 2) || !stream.WriteAll(&hdr.bpp, 2) || !stream.WriteAll(&hdr.compression, 4) || !stream.WriteAll(&hdr.size_of_bmp, 4) || !stream.WriteAll(&hdr.h_res, 4) || !stream.WriteAll(&hdr.v_res, 4) || !stream.WriteAll(&hdr.num_clrs, 4) || !stream.WriteAll(&hdr.num_signif_clrs, 4) ) { if (verbose) { wxLogError(_("BMP: Couldn't write the file (BitmapInfo) header.")); } return false; } } #if wxUSE_PALETTE wxScopedPtr palette; // entries for quantized images #endif // wxUSE_PALETTE wxScopedArray rgbquad; // for the RGBQUAD bytes for the colormap wxScopedPtr q_image; // destination for quantized image // if <24bpp use quantization to reduce colors for *some* of the formats if ( (format == wxBMP_1BPP) || (format == wxBMP_4BPP) || (format == wxBMP_8BPP) || (format == wxBMP_8BPP_PALETTE) ) { // make a new palette and quantize the image if (format != wxBMP_8BPP_PALETTE) { q_image.reset(new wxImage()); // I get a delete error using Quantize when desired colors > 236 int quantize = ((palette_size > 236) ? 236 : palette_size); // fill the destination too, it gives much nicer 4bpp images #if wxUSE_PALETTE wxPalette* paletteTmp; wxQuantize::Quantize( *image, *q_image, &paletteTmp, quantize, 0, wxQUANTIZE_FILL_DESTINATION_IMAGE ); palette.reset(paletteTmp); #else // !wxUSE_PALETTE wxQuantize::Quantize( *image, *q_image, NULL, quantize, 0, wxQUANTIZE_FILL_DESTINATION_IMAGE ); #endif // wxUSE_PALETTE/!wxUSE_PALETTE } else { #if wxUSE_PALETTE palette.reset(new wxPalette(image->GetPalette())); #endif // wxUSE_PALETTE } int i; unsigned char r, g, b; wxScopedArray rgbquadTmp(palette_size*4); rgbquad.swap(rgbquadTmp); for (i = 0; i < palette_size; i++) { #if wxUSE_PALETTE if ( !palette->GetRGB(i, &r, &g, &b) ) #endif // wxUSE_PALETTE r = g = b = 0; rgbquad[i*4] = b; rgbquad[i*4+1] = g; rgbquad[i*4+2] = r; rgbquad[i*4+3] = 0; } } // make a 256 entry greyscale colormap or 2 entry black & white else if ( (format == wxBMP_8BPP_GREY) || (format == wxBMP_8BPP_RED) || (format == wxBMP_1BPP_BW) ) { wxScopedArray rgbquadTmp(palette_size*4); rgbquad.swap(rgbquadTmp); for ( int i = 0; i < palette_size; i++ ) { // if 1BPP_BW then the value should be either 0 or 255 wxUint8 c = (wxUint8)((i > 0) && (format == wxBMP_1BPP_BW) ? 255 : i); rgbquad[i*4] = rgbquad[i*4+1] = rgbquad[i*4+2] = c; rgbquad[i*4+3] = 0; } } // if the colormap was made, then it needs to be written if (rgbquad) { if ( !IsMask ) { if ( !stream.WriteAll(rgbquad.get(), palette_size*4) ) { if (verbose) { wxLogError(_("BMP: Couldn't write RGB color map.")); } return false; } } } // pointer to the image data, use quantized if available const unsigned char* const data = q_image && q_image->IsOk() ? q_image->GetData() : image->GetData(); const unsigned char* const alpha = saveAlpha ? image->GetAlpha() : NULL; wxScopedArray buffer(row_width); memset(buffer.get(), 0, row_width); int y; unsigned x; long int pixel; const int dstPixLen = saveAlpha ? 4 : 3; for (y = image->GetHeight() -1; y >= 0; y--) { if ( format == wxBMP_24BPP ) // 3 bytes per pixel red,green,blue { for ( x = 0; x < width; x++ ) { pixel = 3*(y*width + x); buffer[dstPixLen*x ] = data[pixel+2]; buffer[dstPixLen*x + 1] = data[pixel+1]; buffer[dstPixLen*x + 2] = data[pixel]; if ( saveAlpha ) buffer[dstPixLen*x + 3] = alpha[y*width + x]; } } else if ((format == wxBMP_8BPP) || // 1 byte per pixel in color (format == wxBMP_8BPP_PALETTE)) { for (x = 0; x < width; x++) { pixel = 3*(y*width + x); #if wxUSE_PALETTE buffer[x] = (wxUint8)palette->GetPixel( data[pixel], data[pixel+1], data[pixel+2] ); #else // FIXME: what should this be? use some std palette maybe? buffer[x] = 0; #endif // wxUSE_PALETTE } } else if ( format == wxBMP_8BPP_GREY ) // 1 byte per pix, rgb ave to grey { for (x = 0; x < width; x++) { pixel = 3*(y*width + x); buffer[x] = (wxUint8)(.299*data[pixel] + .587*data[pixel+1] + .114*data[pixel+2]); } } else if ( format == wxBMP_8BPP_RED ) // 1 byte per pixel, red as greys { for (x = 0; x < width; x++) { buffer[x] = (wxUint8)data[3*(y*width + x)]; } } else if ( format == wxBMP_4BPP ) // 4 bpp in color { for (x = 0; x < width; x+=2) { pixel = 3*(y*width + x); // fill buffer, ignore if > width #if wxUSE_PALETTE buffer[x/2] = (wxUint8)( ((wxUint8)palette->GetPixel(data[pixel], data[pixel+1], data[pixel+2]) << 4) | (((x+1) >= width) ? 0 : ((wxUint8)palette->GetPixel(data[pixel+3], data[pixel+4], data[pixel+5]) )) ); #else // FIXME: what should this be? use some std palette maybe? buffer[x/2] = 0; #endif // wxUSE_PALETTE } } else if ( format == wxBMP_1BPP ) // 1 bpp in "color" { for (x = 0; x < width; x+=8) { pixel = 3*(y*width + x); #if wxUSE_PALETTE buffer[x/8] = (wxUint8)( ((wxUint8)palette->GetPixel(data[pixel], data[pixel+1], data[pixel+2]) << 7) | (((x+1) >= width) ? 0 : ((wxUint8)palette->GetPixel(data[pixel+3], data[pixel+4], data[pixel+5]) << 6)) | (((x+2) >= width) ? 0 : ((wxUint8)palette->GetPixel(data[pixel+6], data[pixel+7], data[pixel+8]) << 5)) | (((x+3) >= width) ? 0 : ((wxUint8)palette->GetPixel(data[pixel+9], data[pixel+10], data[pixel+11]) << 4)) | (((x+4) >= width) ? 0 : ((wxUint8)palette->GetPixel(data[pixel+12], data[pixel+13], data[pixel+14]) << 3)) | (((x+5) >= width) ? 0 : ((wxUint8)palette->GetPixel(data[pixel+15], data[pixel+16], data[pixel+17]) << 2)) | (((x+6) >= width) ? 0 : ((wxUint8)palette->GetPixel(data[pixel+18], data[pixel+19], data[pixel+20]) << 1)) | (((x+7) >= width) ? 0 : ((wxUint8)palette->GetPixel(data[pixel+21], data[pixel+22], data[pixel+23]) )) ); #else // FIXME: what should this be? use some std palette maybe? buffer[x/8] = 0; #endif // wxUSE_PALETTE } } else if ( format == wxBMP_1BPP_BW ) // 1 bpp B&W colormap from red color ONLY { for (x = 0; x < width; x+=8) { pixel = 3*(y*width + x); buffer[x/8] = (wxUint8)( (((wxUint8)(data[pixel] /128.)) << 7) | (((x+1) >= width) ? 0 : (((wxUint8)(data[pixel+3] /128.)) << 6)) | (((x+2) >= width) ? 0 : (((wxUint8)(data[pixel+6] /128.)) << 5)) | (((x+3) >= width) ? 0 : (((wxUint8)(data[pixel+9] /128.)) << 4)) | (((x+4) >= width) ? 0 : (((wxUint8)(data[pixel+12]/128.)) << 3)) | (((x+5) >= width) ? 0 : (((wxUint8)(data[pixel+15]/128.)) << 2)) | (((x+6) >= width) ? 0 : (((wxUint8)(data[pixel+18]/128.)) << 1)) | (((x+7) >= width) ? 0 : (((wxUint8)(data[pixel+21]/128.)) )) ); } } if ( !stream.WriteAll(buffer.get(), row_width) ) { if (verbose) { wxLogError(_("BMP: Couldn't write data.")); } return false; } } return true; } struct BMPPalette { static void Free(BMPPalette* pal) { delete [] pal; } unsigned char r, g, b; }; bool wxBMPHandler::DoLoadDib(wxImage * image, int width, int height, int bpp, int ncolors, int comp, wxFileOffset bmpOffset, wxInputStream& stream, bool verbose, bool IsBmp, bool hasPalette, int colEntrySize) { wxInt32 aDword, rmask = 0, gmask = 0, bmask = 0, amask = 0; int rshift = 0, gshift = 0, bshift = 0, ashift = 0; int rbits = 0, gbits = 0, bbits = 0; wxInt32 dbuf[4]; wxInt8 bbuf[4]; wxUint8 aByte; wxUint16 aWord; // allocate space for palette if needed: BMPPalette *cmap; if ( bpp < 16 ) { cmap = new BMPPalette[ncolors]; if ( !cmap ) { if (verbose) { wxLogError(_("BMP: Couldn't allocate memory.")); } return false; } } else // no palette { cmap = NULL; } wxON_BLOCK_EXIT1(&BMPPalette::Free, cmap); bool isUpsideDown = true; if (height < 0) { isUpsideDown = false; height = -height; } // 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.") ); } return false; } unsigned char *alpha; if ( bpp == 32 ) { // tell the image to allocate an alpha buffer image->SetAlpha(); alpha = image->GetAlpha(); if ( !alpha ) { if ( verbose ) { wxLogError(_("BMP: Couldn't allocate memory.")); } return false; } } else // no alpha { alpha = NULL; } // Reading the palette, if it exists: if ( bpp < 16 && ncolors != 0 ) { wxScopedArray r(ncolors), g(ncolors), b(ncolors); for (int j = 0; j < ncolors; j++) { if (hasPalette) { if ( !stream.ReadAll(bbuf, colEntrySize) ) return false; cmap[j].b = bbuf[0]; cmap[j].g = bbuf[1]; cmap[j].r = bbuf[2]; r[j] = cmap[j].r; g[j] = cmap[j].g; b[j] = cmap[j].b; } else { //used in reading .ico file mask r[j] = cmap[j].r = g[j] = cmap[j].g = b[j] = cmap[j].b = ( j ? 255 : 0 ); } } #if wxUSE_PALETTE // Set the palette for the wxImage image->SetPalette(wxPalette(ncolors, r.get(), g.get(), b.get())); #endif // wxUSE_PALETTE } else if ( bpp == 16 || bpp == 32 ) { if ( comp == BI_BITFIELDS ) { int bit; if ( !stream.ReadAll(dbuf, 4 * 3) ) return false; rmask = wxINT32_SWAP_ON_BE(dbuf[0]); gmask = wxINT32_SWAP_ON_BE(dbuf[1]); bmask = wxINT32_SWAP_ON_BE(dbuf[2]); // find shift amount (Least significant bit of mask) for (bit = bpp-1; bit>=0; bit--) { if (bmask & (1 << bit)) bshift = bit; if (gmask & (1 << bit)) gshift = bit; if (rmask & (1 << bit)) rshift = bit; } // Find number of bits in mask (MSB-LSB+1) for (bit = 0; bit < bpp; bit++) { if (bmask & (1 << bit)) bbits = bit-bshift+1; if (gmask & (1 << bit)) gbits = bit-gshift+1; if (rmask & (1 << bit)) rbits = bit-rshift+1; } } else if ( bpp == 16 ) { rmask = 0x7C00; gmask = 0x03E0; bmask = 0x001F; rshift = 10; gshift = 5; bshift = 0; rbits = 5; gbits = 5; bbits = 5; } else if ( bpp == 32 ) { rmask = 0x00FF0000; gmask = 0x0000FF00; bmask = 0x000000FF; amask = 0xFF000000; ashift = 24; rshift = 16; gshift = 8; bshift = 0; rbits = 8; gbits = 8; bbits = 8; } } /* * Reading the image data */ if ( IsBmp ) { // NOTE: seeking a positive amount in wxFromCurrent mode allows us to // load even non-seekable streams (see wxInputStream::SeekI docs)! const wxFileOffset pos = stream.TellI(); if ( pos == wxInvalidOffset || (bmpOffset > pos && stream.SeekI(bmpOffset - pos, wxFromCurrent) == wxInvalidOffset) ) return false; //else: icon, just carry on } unsigned char *data = ptr; /* set the whole image to the background color */ if ( bpp < 16 && (comp == BI_RLE4 || comp == BI_RLE8) ) { for (int i = 0; i < width * height; i++) { *ptr++ = cmap[0].r; *ptr++ = cmap[0].g; *ptr++ = cmap[0].b; } ptr = data; } int linesize = ((width * bpp + 31) / 32) * 4; // flag indicating if we have any not fully transparent alpha values: this // is used to account for the bitmaps which use 32bpp format (normally // meaning that they have alpha channel) but have only zeroes in it so that // without this hack they appear fully transparent -- and as this is // unlikely intentional, we consider that they don't have alpha at all in // this case (see #10915) bool hasValidAlpha = false; for ( int row = 0; row < height; row++ ) { int line = isUpsideDown ? height - 1 - row : row; int linepos = 0; for ( int column = 0; column < width ; ) { if ( bpp < 16 ) { linepos++; aByte = stream.GetC(); if ( !stream.IsOk() ) return false; if ( bpp == 1 ) { for (int bit = 0; bit < 8 && column < width; bit++) { int index = ((aByte & (0x80 >> bit)) ? 1 : 0); ptr[poffset] = cmap[index].r; ptr[poffset + 1] = cmap[index].g; ptr[poffset + 2] = cmap[index].b; column++; } } else if ( bpp == 4 ) { if ( comp == BI_RLE4 ) { wxUint8 first; first = aByte; aByte = stream.GetC(); if ( !stream.IsOk() ) return false; if ( first == 0 ) { if ( aByte == 0 ) { // end of scanline marker column = width; row--; } else if ( aByte == 1 ) { // end of RLE data marker, stop decoding column = width; row = height; } else if ( aByte == 2 ) { // delta marker, move in image aByte = stream.GetC(); if ( !stream.IsOk() ) return false; column += aByte; linepos = column * bpp / 4; aByte = stream.GetC(); if ( !stream.IsOk() ) return false; row += aByte; // upside down } else { int absolute = aByte; wxUint8 nibble[2] ; int readBytes = 0 ; for (int k = 0; k < absolute; k++) { if ( !(k % 2 ) ) { ++readBytes ; aByte = stream.GetC(); if ( !stream.IsOk() ) return false; nibble[0] = (wxUint8)( (aByte & 0xF0) >> 4 ) ; nibble[1] = (wxUint8)( aByte & 0x0F ) ; } ptr[poffset ] = cmap[nibble[k%2]].r; ptr[poffset + 1] = cmap[nibble[k%2]].g; ptr[poffset + 2] = cmap[nibble[k%2]].b; column++; if ( k % 2 ) linepos++; } if ( readBytes & 0x01 ) { aByte = stream.GetC(); if ( !stream.IsOk() ) return false; } } } else { wxUint8 nibble[2] ; nibble[0] = (wxUint8)( (aByte & 0xF0) >> 4 ) ; nibble[1] = (wxUint8)( aByte & 0x0F ) ; for ( int l = 0; l < first && column < width; l++ ) { ptr[poffset ] = cmap[nibble[l%2]].r; ptr[poffset + 1] = cmap[nibble[l%2]].g; ptr[poffset + 2] = cmap[nibble[l%2]].b; column++; if ( l % 2 ) linepos++; } } } else { for (int nibble = 0; nibble < 2 && column < width; nibble++) { int index = ((aByte & (0xF0 >> (nibble * 4))) >> (!nibble * 4)); if ( index >= 16 ) index = 15; ptr[poffset] = cmap[index].r; ptr[poffset + 1] = cmap[index].g; ptr[poffset + 2] = cmap[index].b; column++; } } } else if ( bpp == 8 ) { if ( comp == BI_RLE8 ) { unsigned char first; first = aByte; aByte = stream.GetC(); if ( !stream.IsOk() ) return false; if ( first == 0 ) { if ( aByte == 0 ) { // end of scanline marker column = width; row--; } else if ( aByte == 1 ) { // end of RLE data marker, stop decoding column = width; row = height; } else if ( aByte == 2 ) { // delta marker, move in image aByte = stream.GetC(); if ( !stream.IsOk() ) return false; column += aByte; linepos = column * bpp / 8; aByte = stream.GetC(); if ( !stream.IsOk() ) return false; row -= aByte; } else { int absolute = aByte; for (int k = 0; k < absolute; k++) { linepos++; aByte = stream.GetC(); if ( !stream.IsOk() ) return false; ptr[poffset ] = cmap[aByte].r; ptr[poffset + 1] = cmap[aByte].g; ptr[poffset + 2] = cmap[aByte].b; column++; } if ( absolute & 0x01 ) { aByte = stream.GetC(); if ( !stream.IsOk() ) return false; } } } else { for ( int l = 0; l < first && column < width; l++ ) { ptr[poffset ] = cmap[aByte].r; ptr[poffset + 1] = cmap[aByte].g; ptr[poffset + 2] = cmap[aByte].b; column++; linepos++; } } } else { ptr[poffset ] = cmap[aByte].r; ptr[poffset + 1] = cmap[aByte].g; ptr[poffset + 2] = cmap[aByte].b; column++; // linepos += size; seems to be wrong, RR } } } else if ( bpp == 24 ) { if ( !stream.ReadAll(bbuf, 3) ) return false; linepos += 3; ptr[poffset ] = (unsigned char)bbuf[2]; ptr[poffset + 1] = (unsigned char)bbuf[1]; ptr[poffset + 2] = (unsigned char)bbuf[0]; column++; } else if ( bpp == 16 ) { unsigned char temp; if ( !stream.ReadAll(&aWord, 2) ) return false; wxUINT16_SWAP_ON_BE_IN_PLACE(aWord); linepos += 2; /* Use the masks and calculated amount of shift to retrieve the color data out of the word. Then shift it left by (8 - number of bits) such that the image has the proper dynamic range */ temp = (unsigned char)(((aWord & rmask) >> rshift) << (8-rbits)); ptr[poffset] = temp; temp = (unsigned char)(((aWord & gmask) >> gshift) << (8-gbits)); ptr[poffset + 1] = temp; temp = (unsigned char)(((aWord & bmask) >> bshift) << (8-bbits)); ptr[poffset + 2] = temp; column++; } else { unsigned char temp; if ( !stream.ReadAll(&aDword, 4) ) return false; wxINT32_SWAP_ON_BE_IN_PLACE(aDword); linepos += 4; temp = (unsigned char)((aDword & rmask) >> rshift); ptr[poffset] = temp; temp = (unsigned char)((aDword & gmask) >> gshift); ptr[poffset + 1] = temp; temp = (unsigned char)((aDword & bmask) >> bshift); ptr[poffset + 2] = temp; if ( alpha ) { temp = (unsigned char)((aDword & amask) >> ashift); alpha[line * width + column] = temp; if ( temp != wxALPHA_TRANSPARENT ) hasValidAlpha = true; } column++; } } while ( (linepos < linesize) && (comp != 1) && (comp != 2) ) { ++linepos; if ( !stream.ReadAll(&aByte, 1) ) break; } } image->SetMask(false); // check if we had any valid alpha values in this bitmap if ( alpha && !hasValidAlpha ) { // we didn't, so finally discard the alpha channel completely image->ClearAlpha(); } const wxStreamError err = stream.GetLastError(); return err == wxSTREAM_NO_ERROR || err == wxSTREAM_EOF; } bool wxBMPHandler::LoadDib(wxImage *image, wxInputStream& stream, bool verbose, bool IsBmp) { wxUint16 aWord; wxInt32 dbuf[4]; // offset to bitmap data wxFileOffset offset; // DIB header size (used to distinguish different versions of DIB header) wxInt32 hdrSize; if ( IsBmp ) { wxInt8 bbuf[4]; // read the header off the .BMP format file if ( !stream.ReadAll(bbuf, 2) || !stream.ReadAll(dbuf, 16) ) return false; #if 0 // unused wxInt32 size = wxINT32_SWAP_ON_BE(dbuf[0]); #endif offset = wxINT32_SWAP_ON_BE(dbuf[2]); hdrSize = wxINT32_SWAP_ON_BE(dbuf[3]); } else { if ( !stream.ReadAll(dbuf, 4) ) return false; offset = wxInvalidOffset; // not used in loading ICO/CUR DIBs hdrSize = wxINT32_SWAP_ON_BE(dbuf[0]); } // Bitmap files come in old v1 format using BITMAPCOREHEADER or a newer // format (typically BITMAPV5HEADER, but we don't // really support any features specific to later formats such as gamma // correction or ICC profiles, so it doesn't matter much to us). const bool usesV1 = hdrSize == 12; int width; int height; if ( usesV1 ) { wxInt16 buf[2]; if ( !stream.ReadAll(buf, sizeof(buf)) ) return false; width = wxINT16_SWAP_ON_BE((short)buf[0]); height = wxINT16_SWAP_ON_BE((short)buf[1]); } else // We have at least BITMAPINFOHEADER { if ( !stream.ReadAll(dbuf, 4 * 2) ) return false; width = wxINT32_SWAP_ON_BE((int)dbuf[0]); height = wxINT32_SWAP_ON_BE((int)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; } if ( !stream.ReadAll(&aWord, 2) ) return false; /* TODO int planes = (int)wxUINT16_SWAP_ON_BE( aWord ); */ if ( !stream.ReadAll(&aWord, 2) ) return false; int bpp = wxUINT16_SWAP_ON_BE((int)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; } class Resolution { public: Resolution() { m_valid = false; // Still initialize them as some compilers are smart enough to // give "use of possibly uninitialized variable" for them (but not // smart enough to see that this is not really the case). m_x = m_y = 0; } void Init(int x, int y) { m_x = x; m_y = y; m_valid = true; } bool IsValid() const { return m_valid; } int GetX() const { return m_x; } int GetY() const { return m_y; } private: int m_x, m_y; bool m_valid; } res; int comp; int ncolors; if ( usesV1 ) { // The only possible format is BI_RGB and colours count is not used. comp = BI_RGB; ncolors = 0; } else // We have at least BITMAPINFOHEADER { if ( !stream.ReadAll(dbuf, 4 * 4) ) return false; comp = wxINT32_SWAP_ON_BE((int)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; } if ( !stream.ReadAll(dbuf, 4 * 2) ) return false; ncolors = wxINT32_SWAP_ON_BE( (int)dbuf[0] ); res.Init(dbuf[2]/100, dbuf[3]/100); } 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, usesV1 ? 3 : 4) ) { 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->SetMaskFromImage(mask, 255, 255, 255); } // the resolution in the bitmap header is in meters, convert to centimeters if ( res.IsValid() ) { image->SetOption(wxIMAGE_OPTION_RESOLUTIONUNIT, wxIMAGE_RESOLUTION_CM); image->SetOption(wxIMAGE_OPTION_RESOLUTIONX, res.GetX()); image->SetOption(wxIMAGE_OPTION_RESOLUTIONY, res.GetY()); } return true; } bool wxBMPHandler::LoadFile(wxImage *image, wxInputStream& stream, bool verbose, int WXUNUSED(index)) { // Read a single DIB fom the file: return LoadDib(image, stream, verbose, true/*isBmp*/); } bool wxBMPHandler::DoCanRead(wxInputStream& stream) { unsigned char hdr[2]; if ( !stream.ReadAll(hdr, WXSIZEOF(hdr)) ) // it's ok to modify the stream position here return false; // do we have the BMP file signature? return hdr[0] == 'B' && hdr[1] == 'M'; } #endif // wxUSE_STREAMS #if wxUSE_ICO_CUR //----------------------------------------------------------------------------- // wxICOHandler //----------------------------------------------------------------------------- wxIMPLEMENT_DYNAMIC_CLASS(wxICOHandler, wxBMPHandler); #if wxUSE_STREAMS bool wxICOHandler::SaveFile(wxImage *image, wxOutputStream& stream, bool verbose) { // sanity check; icon must be no larger than 256x256 if ( image->GetHeight () > 256 ) { if ( verbose ) { wxLogError(_("ICO: Image too tall for an icon.")); } return false; } if ( image->GetWidth () > 256 ) { if ( verbose ) { wxLogError(_("ICO: Image too wide for an icon.")); } return false; } const int images = 1; // only generate one image // VS: This is a hack of sort - since ICO and CUR files are almost // identical, we have all the meat in wxICOHandler and check for // the actual (handler) type when the code has to distinguish between // the two formats int type = (this->GetType() == wxBITMAP_TYPE_CUR) ? 2 : 1; // write a header, (ICONDIR) // Calculate the header size wxUint32 offset = 3 * sizeof(wxUint16); ICONDIR IconDir; IconDir.idReserved = 0; IconDir.idType = wxUINT16_SWAP_ON_BE((wxUint16)type); IconDir.idCount = wxUINT16_SWAP_ON_BE((wxUint16)images); if ( !stream.WriteAll(&IconDir.idReserved, sizeof(IconDir.idReserved)) || !stream.WriteAll(&IconDir.idType, sizeof(IconDir.idType)) || !stream.WriteAll(&IconDir.idCount, sizeof(IconDir.idCount)) ) { if ( verbose ) { wxLogError(_("ICO: Error writing the image file!")); } return false; } // for each iamage write a description ICONDIRENTRY: ICONDIRENTRY icondirentry; for (int img = 0; img < images; img++) { wxImage mask; if ( image->HasMask() ) { // make another image with black/white: mask = image->ConvertToMono (image->GetMaskRed(), image->GetMaskGreen(), image->GetMaskBlue() ); // now we need to change the masked regions to black: unsigned char r = image->GetMaskRed(); unsigned char g = image->GetMaskGreen(); unsigned char b = image->GetMaskBlue(); if ( (r != 0) || (g != 0) || (b != 0) ) { // Go round and apply black to the masked bits: int i, j; for (i = 0; i < mask.GetWidth(); i++) { for (j = 0; j < mask.GetHeight(); j++) { if ((r == mask.GetRed(i, j)) && (g == mask.GetGreen(i, j))&& (b == mask.GetBlue(i, j)) ) image->SetRGB(i, j, 0, 0, 0 ); } } } } else { // just make a black mask all over: mask = image->Copy(); int i, j; for (i = 0; i < mask.GetWidth(); i++) for (j = 0; j < mask.GetHeight(); j++) mask.SetRGB(i, j, 0, 0, 0 ); } // Set the formats for image and mask // The format depends on the number of the colours used, so count them, // but stop at 257 because we have to use 24 bpp anyhow if we have that // many of them. const int colours = image->CountColours(257); int bppFormat; int bpp; if ( image->HasAlpha() ) { // Icons with alpha channel are always stored in ARGB format. bppFormat = wxBMP_24BPP; bpp = 32; } else if ( colours > 256 ) { bppFormat = wxBMP_24BPP; bpp = 24; } else if ( colours > 16 ) { bppFormat = wxBMP_8BPP; bpp = 8; } else if ( colours > 2 ) { bppFormat = wxBMP_4BPP; bpp = 4; } else { bppFormat = wxBMP_1BPP; bpp = 1; } image->SetOption(wxIMAGE_OPTION_BMP_FORMAT, bppFormat); // monochome bitmap: mask.SetOption(wxIMAGE_OPTION_BMP_FORMAT, wxBMP_1BPP_BW); bool IsBmp = false; bool IsMask = false; //calculate size and offset of image and mask wxCountingOutputStream cStream; bool bResult; #if wxUSE_LIBPNG // Typically, icons larger then 128x128 are saved as PNG images. bool saveAsPNG = false; if ( image->GetHeight() > 128 || image->GetWidth() > 128 ) { wxPNGHandler handlerPNG; bResult = handlerPNG.SaveFile(image, cStream, verbose); if ( !bResult ) { if ( verbose ) { wxLogError(_("ICO: Error writing the image file!")); } return false; } saveAsPNG = true; } if ( !saveAsPNG ) #endif // wxUSE_LIBPNG { bResult = SaveDib(image, cStream, verbose, IsBmp, IsMask); if ( !bResult ) { if ( verbose ) { wxLogError(_("ICO: Error writing the image file!")); } return false; } IsMask = true; bResult = SaveDib(&mask, cStream, verbose, IsBmp, IsMask); if ( !bResult ) { if ( verbose ) { wxLogError(_("ICO: Error writing the image file!")); } return false; } } wxUint32 Size = cStream.GetSize(); // wxCountingOutputStream::IsOk() always returns true for now and this // "if" provokes VC++ warnings in optimized build #if 0 if ( !cStream.IsOk() ) { if ( verbose ) { wxLogError(_("ICO: Error writing the image file!")); } return false; } #endif // 0 offset = offset + sizeof(ICONDIRENTRY); // Notice that the casts work correctly for width/height of 256 as it's // represented by 0 in ICO file format -- and larger values are not // allowed at all. icondirentry.bWidth = (wxUint8)image->GetWidth(); icondirentry.bHeight = (wxUint8)image->GetHeight(); icondirentry.bColorCount = 0; icondirentry.bReserved = 0; icondirentry.wPlanes = wxUINT16_SWAP_ON_BE(1); icondirentry.wBitCount = wxUINT16_SWAP_ON_BE(bpp); if ( type == 2 /*CUR*/) { int hx = image->HasOption(wxIMAGE_OPTION_CUR_HOTSPOT_X) ? image->GetOptionInt(wxIMAGE_OPTION_CUR_HOTSPOT_X) : image->GetWidth() / 2; int hy = image->HasOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y) ? image->GetOptionInt(wxIMAGE_OPTION_CUR_HOTSPOT_Y) : image->GetHeight() / 2; // actually write the values of the hot spot here: icondirentry.wPlanes = wxUINT16_SWAP_ON_BE((wxUint16)hx); icondirentry.wBitCount = wxUINT16_SWAP_ON_BE((wxUint16)hy); } icondirentry.dwBytesInRes = wxUINT32_SWAP_ON_BE(Size); icondirentry.dwImageOffset = wxUINT32_SWAP_ON_BE(offset); // increase size to allow for the data written: offset += Size; // write to stream: if ( !stream.WriteAll(&icondirentry.bWidth, sizeof(icondirentry.bWidth)) || !stream.WriteAll(&icondirentry.bHeight, sizeof(icondirentry.bHeight)) || !stream.WriteAll(&icondirentry.bColorCount, sizeof(icondirentry.bColorCount)) || !stream.WriteAll(&icondirentry.bReserved, sizeof(icondirentry.bReserved)) || !stream.WriteAll(&icondirentry.wPlanes, sizeof(icondirentry.wPlanes)) || !stream.WriteAll(&icondirentry.wBitCount, sizeof(icondirentry.wBitCount)) || !stream.WriteAll(&icondirentry.dwBytesInRes, sizeof(icondirentry.dwBytesInRes)) || !stream.WriteAll(&icondirentry.dwImageOffset, sizeof(icondirentry.dwImageOffset)) ) { if ( verbose ) { wxLogError(_("ICO: Error writing the image file!")); } return false; } // actually save it: #if wxUSE_LIBPNG if ( saveAsPNG ) { wxPNGHandler handlerPNG; bResult = handlerPNG.SaveFile(image, stream, verbose); if ( !bResult ) { if ( verbose ) { wxLogError(_("ICO: Error writing the image file!")); } return false; } } else #endif // wxUSE_LIBPNG { IsMask = false; bResult = SaveDib(image, stream, verbose, IsBmp, IsMask); if ( !bResult ) { if ( verbose ) { wxLogError(_("ICO: Error writing the image file!")); } return false; } IsMask = true; bResult = SaveDib(&mask, stream, verbose, IsBmp, IsMask); if ( !bResult ) { if ( verbose ) { wxLogError(_("ICO: Error writing the image file!")); } return false; } } } // end of for loop return true; } bool wxICOHandler::LoadFile(wxImage *image, wxInputStream& stream, bool verbose, int index) { if ( stream.IsSeekable() && stream.SeekI(0) == wxInvalidOffset ) { return false; } return DoLoadFile(image, stream, verbose, index); } bool wxICOHandler::DoLoadFile(wxImage *image, wxInputStream& stream, bool verbose, int index) { bool bResult wxDUMMY_INITIALIZE(false); ICONDIR IconDir; if ( !stream.ReadAll(&IconDir, sizeof(IconDir)) ) return false; wxUint16 nIcons = wxUINT16_SWAP_ON_BE(IconDir.idCount); // nType is 1 for Icons, 2 for Cursors: wxUint16 nType = wxUINT16_SWAP_ON_BE(IconDir.idType); // loop round the icons and choose the best one: wxScopedArray pIconDirEntry(nIcons); ICONDIRENTRY *pCurrentEntry = pIconDirEntry.get(); int wMax = 0; int colmax = 0; int iSel = wxNOT_FOUND; // remember how many bytes we read from the stream: wxFileOffset alreadySeeked = sizeof(IconDir); for (unsigned int i = 0; i < nIcons; i++ ) { if ( !stream.ReadAll(pCurrentEntry, sizeof(ICONDIRENTRY)) ) return false; alreadySeeked += stream.LastRead(); // ICO file format uses only a single byte for width and if it is 0, it // means that the width is actually 256 pixels. const wxUint16 widthReal = pCurrentEntry->bWidth ? pCurrentEntry->bWidth : 256; // bHeight and bColorCount are wxUint8 if ( widthReal >= wMax ) { // see if we have more colors, ==0 indicates > 8bpp: if ( pCurrentEntry->bColorCount == 0 ) pCurrentEntry->bColorCount = 255; if ( pCurrentEntry->bColorCount >= colmax ) { iSel = i; wMax = widthReal; colmax = pCurrentEntry->bColorCount; } } pCurrentEntry++; } if ( index != -1 ) { // VS: Note that we *have* to run the loop above even if index != -1, because // it reads ICONDIRENTRies. iSel = index; } if ( iSel == wxNOT_FOUND || iSel < 0 || iSel >= nIcons ) { wxLogError(_("ICO: Invalid icon index.")); bResult = false; } else { // seek to selected icon: pCurrentEntry = pIconDirEntry.get() + iSel; // NOTE: seeking a positive amount in wxFromCurrent mode allows us to // load even non-seekable streams (see wxInputStream::SeekI docs)! wxFileOffset offset = wxUINT32_SWAP_ON_BE(pCurrentEntry->dwImageOffset) - alreadySeeked; if (offset != 0 && stream.SeekI(offset, wxFromCurrent) == wxInvalidOffset) return false; #if wxUSE_LIBPNG // We can't fall back to loading an icon in the usual BMP format after // trying to load it as PNG if we have an unseekable stream, so to // avoid breaking the existing code which does successfully load icons // from such streams, we only try to load them as PNGs if we can unwind // back later. // // Ideal would be to modify LoadDib() to accept the first 8 bytes not // coming from the stream but from the signature buffer below, as then // we'd be able to load PNG icons from any kind of streams. bool isPNG; if ( stream.IsSeekable() ) { // Check for the PNG signature first to avoid wasting time on // trying to load typical ICO files which are not PNGs at all. static const unsigned char signaturePNG[] = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A }; static const int signatureLen = WXSIZEOF(signaturePNG); unsigned char signature[signatureLen]; if ( !stream.ReadAll(signature, signatureLen) ) return false; isPNG = memcmp(signature, signaturePNG, signatureLen) == 0; // Rewind to the beginning of the image in any case. if ( stream.SeekI(-signatureLen, wxFromCurrent) == wxInvalidOffset ) return false; } else // Not seekable stream { isPNG = false; } if ( isPNG ) { wxPNGHandler handlerPNG; bResult = handlerPNG.LoadFile(image, stream, verbose); } else #endif // wxUSE_LIBPNG { bResult = LoadDib(image, stream, verbose, false /* not BMP */); } bool bIsCursorType = (this->GetType() == wxBITMAP_TYPE_CUR) || (this->GetType() == wxBITMAP_TYPE_ANI); if ( bResult && bIsCursorType && nType == 2 ) { // it is a cursor, so let's set the hotspot: image->SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_X, wxUINT16_SWAP_ON_BE(pCurrentEntry->wPlanes)); image->SetOption(wxIMAGE_OPTION_CUR_HOTSPOT_Y, wxUINT16_SWAP_ON_BE(pCurrentEntry->wBitCount)); } } return bResult; } int wxICOHandler::DoGetImageCount(wxInputStream& stream) { // It's ok to modify the stream position in this function. if ( stream.IsSeekable() && stream.SeekI(0) == wxInvalidOffset ) { return 0; } ICONDIR IconDir; if ( !stream.ReadAll(&IconDir, sizeof(IconDir)) ) return 0; return (int)wxUINT16_SWAP_ON_BE(IconDir.idCount); } bool wxICOHandler::DoCanRead(wxInputStream& stream) { return CanReadICOOrCUR(&stream, 1 /*for identifying an icon*/); } #endif // wxUSE_STREAMS //----------------------------------------------------------------------------- // wxCURHandler //----------------------------------------------------------------------------- wxIMPLEMENT_DYNAMIC_CLASS(wxCURHandler, wxICOHandler); #if wxUSE_STREAMS bool wxCURHandler::DoCanRead(wxInputStream& stream) { return CanReadICOOrCUR(&stream, 2 /*for identifying a cursor*/); } #endif // wxUSE_STREAMS //----------------------------------------------------------------------------- // wxANIHandler //----------------------------------------------------------------------------- wxIMPLEMENT_DYNAMIC_CLASS(wxANIHandler, wxCURHandler); #if wxUSE_STREAMS bool wxANIHandler::LoadFile(wxImage *image, wxInputStream& stream, bool WXUNUSED(verbose), int index) { wxANIDecoder decoder; if (!decoder.Load(stream)) return false; return decoder.ConvertToImage(index != -1 ? (size_t)index : 0, image); } bool wxANIHandler::DoCanRead(wxInputStream& stream) { wxANIDecoder decod; return decod.CanRead(stream); // it's ok to modify the stream position here } int wxANIHandler::DoGetImageCount(wxInputStream& stream) { wxANIDecoder decoder; if (!decoder.Load(stream)) // it's ok to modify the stream position here return wxNOT_FOUND; return decoder.GetFrameCount(); } static bool CanReadICOOrCUR(wxInputStream *stream, wxUint16 resourceType) { // It's ok to modify the stream position in this function. if ( stream->IsSeekable() && stream->SeekI(0) == wxInvalidOffset ) { return false; } ICONDIR iconDir; if ( !stream->ReadAll(&iconDir, sizeof(iconDir)) ) { return false; } return !iconDir.idReserved // reserved, must be 0 && wxUINT16_SWAP_ON_BE(iconDir.idType) == resourceType // either 1 or 2 && iconDir.idCount; // must contain at least one image } #endif // wxUSE_STREAMS #endif // wxUSE_ICO_CUR #endif // wxUSE_IMAGE