Add real support for monochrome bitmaps to wxMSW

In order to be able to use monochrome bitmaps as wxMask, improve support
for them in various ways:

1. Implement loading and saving of monochrome BMP files.
2. Add wxMonoPixelData for direct access to monochrome bitmap pixels.
3. Implement conversion from wxImage to monochrome wxBitmap.

Closes https://github.com/wxWidgets/wxWidgets/pull/2032
This commit is contained in:
Bill Su
2020-10-19 20:50:31 -04:00
committed by Vadim Zeitlin
parent c0e3bdaab9
commit 7e9afad53a
5 changed files with 602 additions and 69 deletions

View File

@@ -41,8 +41,8 @@ public:
#ifdef __WXMSW__ #ifdef __WXMSW__
// create a DIB from the DDB // create a DIB from the DDB
wxDIB(const wxBitmap& bmp) wxDIB(const wxBitmap& bmp, int depth = -1)
{ Init(); (void)Create(bmp); } { Init(); (void)Create(bmp, depth); }
#endif // __WXMSW__ #endif // __WXMSW__
// create a DIB from the Windows DDB // create a DIB from the Windows DDB
@@ -58,9 +58,9 @@ public:
// same as the corresponding ctors but with return value // same as the corresponding ctors but with return value
bool Create(int width, int height, int depth); bool Create(int width, int height, int depth);
#ifdef __WXMSW__ #ifdef __WXMSW__
bool Create(const wxBitmap& bmp) { return Create(GetHbitmapOf(bmp)); } bool Create(const wxBitmap& bmp, int depth = -1) { return Create(GetHbitmapOf(bmp), depth); }
#endif #endif
bool Create(HBITMAP hbmp); bool Create(HBITMAP hbmp, int depth = -1);
bool Load(const wxString& filename); bool Load(const wxString& filename);
// dtor is not virtual, this class is not meant to be used polymorphically // dtor is not virtual, this class is not meant to be used polymorphically
@@ -154,14 +154,14 @@ public:
// can be used with ::AlphaBlend() but it is also possible to disable // can be used with ::AlphaBlend() but it is also possible to disable
// pre-multiplication for the DIB to be usable with ImageList_Draw() which // pre-multiplication for the DIB to be usable with ImageList_Draw() which
// does pre-multiplication internally. // does pre-multiplication internally.
wxDIB(const wxImage& image, PixelFormat pf = PixelFormat_PreMultiplied) wxDIB(const wxImage& image, PixelFormat pf = PixelFormat_PreMultiplied, int depth = -1)
{ {
Init(); Init();
(void)Create(image, pf); (void)Create(image, pf, depth);
} }
// same as the above ctor but with the return code // same as the above ctor but with the return code
bool Create(const wxImage& image, PixelFormat pf = PixelFormat_PreMultiplied); bool Create(const wxImage& image, PixelFormat pf = PixelFormat_PreMultiplied, int depth = -1);
// create wxImage having the same data as this DIB // create wxImage having the same data as this DIB

View File

@@ -153,6 +153,24 @@ typedef wxPixelFormat<unsigned char, 24, 0, 1, 2> wxImagePixelFormat;
typedef wxPixelFormat<unsigned char, 24, 2, 1, 0> wxNativePixelFormat; typedef wxPixelFormat<unsigned char, 24, 2, 1, 0> wxNativePixelFormat;
#define wxPIXEL_FORMAT_ALPHA 3 #define wxPIXEL_FORMAT_ALPHA 3
template<>
struct wxPixelFormat<void, 1, -1, -1, -1, -1, bool>
{
// the type which may hold the entire pixel value
typedef bool PixelType;
// size of one pixel in bits
static const int BitsPerPixel = 1;
// size of one pixel in ChannelType units (usually bytes)
static const int SizePixel = 1;
// true if we have an alpha channel (together with the other channels, this
// doesn't cover the case of wxImage which stores alpha separately)
enum { HasAlpha = false };
};
typedef wxPixelFormat<void, 1, -1, -1, -1, -1, bool> wxMonoPixelFormat;
#elif defined(__WXMAC__) #elif defined(__WXMAC__)
// under Mac, first component is unused but still present, hence we use // under Mac, first component is unused but still present, hence we use
// 32bpp, not 24 // 32bpp, not 24
@@ -682,6 +700,220 @@ struct wxPixelDataOut<wxBitmap>
}; };
}; };
#if defined(__WXMSW__)
template <>
struct wxPixelDataOut<wxBitmap>::wxPixelDataIn<wxMonoPixelFormat> : public wxPixelDataBase
{
public:
// the type of the class we're working with
typedef wxBitmap ImageType;
// Reference emulates ChannelType& for monochrome bitmap
class Iterator;
class Reference
{
public:
Reference& operator=(bool b)
{
wxByte mask = static_cast<wxByte>(1 << m_bit);
wxByte value = static_cast<wxByte>(b << m_bit);
(*m_ptr &= ~mask) |= value;
return *this;
}
operator bool() const
{
wxByte mask = static_cast<wxByte>(1 << m_bit);
return (*m_ptr & mask) != 0;
}
private:
Reference(const Iterator& i) :
m_ptr(i.m_ptr),
m_bit(i.m_bit)
{
}
wxByte* m_ptr;
wxInt8 m_bit;
friend class Iterator;
};
class Iterator
{
public:
// emulate unspecialized template
typedef wxMonoPixelFormat Format;
// the pixel format we use
typedef Format PixelFormat;
// the pixel data we're working with
typedef wxPixelDataOut<wxBitmap>::wxPixelDataIn<Format> PixelData;
// go back to (0, 0)
void Reset(const PixelData& data)
{
*this = data.GetPixels();
}
// initializes the iterator to point to the origin of the given
// pixel data
Iterator(PixelData& data)
{
Reset(data);
}
// initializes the iterator to point to the origin of the given
// bitmap
Iterator(wxBitmap& bmp, PixelData& data)
{
// using cast here is ugly but it should be safe as
// GetRawData() real return type should be consistent with
// BitsPerPixel (which is in turn defined by ChannelType) and
// this is the only thing we can do without making GetRawData()
// a template function which is undesirable
m_ptr = (wxByte*)
bmp.GetRawData(data, PixelFormat::BitsPerPixel);
m_bit = 7;
}
// default constructor
Iterator()
{
m_ptr = NULL;
// m_bit doesn't need to be set until m_ptr != NULL
}
// return true if this iterator is valid
bool IsOk() const { return m_ptr != NULL; }
// navigation
// ----------
// advance the iterator to the next pixel, prefix version
Iterator& operator++()
{
--m_bit;
m_ptr += (m_bit < 0);
m_bit &= 0x7;
return *this;
}
// postfix (hence less efficient -- don't use it unless you
// absolutely must) version
Iterator operator++(int)
{
Iterator p(*this);
++* this;
return p;
}
// move x pixels to the right and y down
//
// note that the rows don't wrap!
void Offset(const PixelData& data, int x, int y)
{
m_ptr += data.GetRowStride() * y;
x += 7 - m_bit;
m_ptr += x >> 3;
m_bit = 7 - (x & 0x7);
}
// move x pixels to the right (again, no row wrapping)
void OffsetX(const PixelData& WXUNUSED(data), int x)
{
x += 7 - m_bit;
m_ptr += x >> 3;
m_bit = 7 - (x & 0x7);
}
// move y rows to the bottom
void OffsetY(const PixelData& data, int y)
{
m_ptr += data.GetRowStride() * y;
}
// go to the given position
void MoveTo(const PixelData& data, int x, int y)
{
Reset(data);
Offset(data, x, y);
}
// data access
// -----------
// access to individual pixels
Reference Pixel() { return Reference(*this); }
// private: -- see comment in the beginning of the file
// I don't see a way to bit-twiddle without two fields
wxByte* m_ptr;
wxInt8 m_bit;
};
// ctor associates this pointer with a bitmap and locks the bitmap for
// raw access, it will be unlocked only by our dtor and so these
// objects should normally be only created on the stack, i.e. have
// limited life-time
wxPixelDataIn(wxBitmap& bmp) : m_bmp(bmp), m_pixels(bmp, *this)
{
}
wxPixelDataIn(wxBitmap& bmp, const wxRect& rect)
: m_bmp(bmp), m_pixels(bmp, *this)
{
InitRect(rect.GetPosition(), rect.GetSize());
}
wxPixelDataIn(wxBitmap& bmp, const wxPoint& pt, const wxSize& sz)
: m_bmp(bmp), m_pixels(bmp, *this)
{
InitRect(pt, sz);
}
// we evaluate to true only if we could get access to bitmap data
// successfully
operator bool() const { return m_pixels.IsOk(); }
// get the iterator pointing to the origin
Iterator GetPixels() const { return m_pixels; }
// dtor unlocks the bitmap
~wxPixelDataIn()
{
if ( m_pixels.IsOk() )
{
m_bmp.UngetRawData(*this);
}
// else: don't call UngetRawData() if GetRawData() failed
}
// private: -- see comment in the beginning of the file
// the bitmap we're associated with
wxBitmap m_bmp;
// the iterator pointing to the image origin
Iterator m_pixels;
private:
void InitRect(const wxPoint& pt, const wxSize& sz)
{
m_pixels.Offset(*this, pt.x, pt.y);
m_ptOrigin = pt;
m_width = sz.x;
m_height = sz.y;
}
};
#endif
#endif //wxUSE_GUI #endif //wxUSE_GUI
template <class Image, template <class Image,
@@ -712,6 +944,10 @@ typedef wxPixelData<wxImage> wxImagePixelData;
typedef wxPixelData<wxBitmap, wxNativePixelFormat> wxNativePixelData; typedef wxPixelData<wxBitmap, wxNativePixelFormat> wxNativePixelData;
typedef wxPixelData<wxBitmap, wxAlphaPixelFormat> wxAlphaPixelData; typedef wxPixelData<wxBitmap, wxAlphaPixelFormat> wxAlphaPixelData;
#if defined(__WXMSW__)
typedef wxPixelData<wxBitmap, wxMonoPixelFormat> wxMonoPixelData;
#endif
#endif //wxUSE_GUI #endif //wxUSE_GUI
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------

View File

@@ -70,7 +70,7 @@ public:
#if wxUSE_WXDIB #if wxUSE_WXDIB
// Creates a new bitmap (DDB or DIB) from the contents of the given DIB. // Creates a new bitmap (DDB or DIB) from the contents of the given DIB.
void CopyFromDIB(const wxDIB& dib); void CopyFromDIB(const wxDIB& dib, int depth = -1);
// Takes ownership of the given DIB. // Takes ownership of the given DIB.
bool AssignDIB(wxDIB& dib); bool AssignDIB(wxDIB& dib);
@@ -318,14 +318,15 @@ void wxBitmapRefData::InitFromDIB(const wxDIB& dib, HBITMAP hbitmap)
m_hBitmap = (WXHBITMAP)hbitmap; m_hBitmap = (WXHBITMAP)hbitmap;
} }
void wxBitmapRefData::CopyFromDIB(const wxDIB& dib) void wxBitmapRefData::CopyFromDIB(const wxDIB& dib, int depth /* = -1 */)
{ {
wxCHECK_RET( !IsOk(), "bitmap already initialized" ); wxCHECK_RET( !IsOk(), "bitmap already initialized" );
wxCHECK_RET( dib.IsOk(), wxT("invalid DIB in CopyFromDIB") ); wxCHECK_RET( dib.IsOk(), wxT("invalid DIB in CopyFromDIB") );
HBITMAP hbitmap; HBITMAP hbitmap;
#ifdef SOMETIMES_USE_DIB #ifdef SOMETIMES_USE_DIB
hbitmap = dib.CreateDDB(); // MemoryHDC defaults to monochrome
hbitmap = dib.CreateDDB(depth == 1 ? HDC(MemoryHDC()) : NULL);
#else // ALWAYS_USE_DIB #else // ALWAYS_USE_DIB
hbitmap = NULL; hbitmap = NULL;
#endif // SOMETIMES_USE_DIB/ALWAYS_USE_DIB #endif // SOMETIMES_USE_DIB/ALWAYS_USE_DIB
@@ -845,7 +846,7 @@ bool wxBitmap::CreateFromImage(const wxImage& image, int depth, WXHDC hdc)
const int h = image.GetHeight(); const int h = image.GetHeight();
const int w = image.GetWidth(); const int w = image.GetWidth();
wxDIB dib(image); wxDIB dib(image, wxDIB::PixelFormat_PreMultiplied, depth);
if ( !dib.IsOk() ) if ( !dib.IsOk() )
return false; return false;
@@ -1269,7 +1270,7 @@ void wxBitmap::MSWBlendMaskWithAlpha()
{ {
wxBitmap bmpMask = GetMask()->GetBitmap(); wxBitmap bmpMask = GetMask()->GetBitmap();
wxNativePixelData maskData(bmpMask); wxMonoPixelData maskData(bmpMask);
wxCHECK_RET(maskData, "No access to bitmap mask data"); wxCHECK_RET(maskData, "No access to bitmap mask data");
wxAlphaPixelData bmpData(*this); wxAlphaPixelData bmpData(*this);
@@ -1278,17 +1279,17 @@ void wxBitmap::MSWBlendMaskWithAlpha()
const int w = GetWidth(); const int w = GetWidth();
const int h = GetHeight(); const int h = GetHeight();
wxNativePixelData::Iterator maskRowStart(maskData); wxMonoPixelData::Iterator maskRowStart(maskData);
wxAlphaPixelData::Iterator bmpRowStart(bmpData); wxAlphaPixelData::Iterator bmpRowStart(bmpData);
for ( int y = 0; y < h; y++ ) for ( int y = 0; y < h; y++ )
{ {
wxNativePixelData::Iterator pMask = maskRowStart; wxMonoPixelData::Iterator pMask = maskRowStart;
wxAlphaPixelData::Iterator pBmp = bmpRowStart; wxAlphaPixelData::Iterator pBmp = bmpRowStart;
for ( int x = 0; x < w; x++, ++pBmp, ++pMask ) for ( int x = 0; x < w; x++, ++pBmp, ++pMask )
{ {
// Masked pixel is not drawn i.e. is transparent, // Masked pixel is not drawn i.e. is transparent,
// non-masked pixel is untouched. // non-masked pixel is untouched.
if ( pMask.Red() == 0 ) if ( pMask.Pixel() == 0 )
{ {
pBmp.Red() = pBmp.Green() = pBmp.Blue() = 0; // pre-multiplied pBmp.Red() = pBmp.Green() = pBmp.Blue() = 0; // pre-multiplied
pBmp.Alpha() = wxALPHA_TRANSPARENT; pBmp.Alpha() = wxALPHA_TRANSPARENT;
@@ -1378,6 +1379,12 @@ void *wxBitmap::GetRawData(wxPixelDataBase& data, int bpp)
// no bitmap, no data (raw or otherwise) // no bitmap, no data (raw or otherwise)
return NULL; return NULL;
} }
if ( bpp == 1 && GetDepth() != 1 )
{
wxFAIL_MSG( wxT("use wxQuantize if you want to convert color wxBitmap to mono") );
return NULL;
}
// if we're already a DIB we can access our data directly, but if not we // if we're already a DIB we can access our data directly, but if not we
// need to convert this DDB to a DIB section and use it for raw access and // need to convert this DDB to a DIB section and use it for raw access and
@@ -1388,7 +1395,7 @@ void *wxBitmap::GetRawData(wxPixelDataBase& data, int bpp)
wxCHECK_MSG( !GetBitmapData()->m_dib, NULL, wxCHECK_MSG( !GetBitmapData()->m_dib, NULL,
wxT("GetRawData() may be called only once") ); wxT("GetRawData() may be called only once") );
wxDIB *dib = new wxDIB(*this); wxDIB *dib = new wxDIB(*this, bpp);
if ( !dib->IsOk() ) if ( !dib->IsOk() )
{ {
delete dib; delete dib;
@@ -1462,7 +1469,8 @@ void wxBitmap::UngetRawData(wxPixelDataBase& WXUNUSED(data))
GetBitmapData()->m_dib = NULL; GetBitmapData()->m_dib = NULL;
GetBitmapData()->Free(); GetBitmapData()->Free();
GetBitmapData()->CopyFromDIB(*dib); int depth = GetDepth() == 1 && dib->GetDepth() != GetDepth() ? 1 : -1;
GetBitmapData()->CopyFromDIB(*dib, depth);
delete dib; delete dib;
} }

View File

@@ -37,6 +37,8 @@
#endif //WX_PRECOMP #endif //WX_PRECOMP
#include "wx/file.h" #include "wx/file.h"
#include "wx/quantize.h"
#include "wx/scopedarray.h"
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@@ -49,9 +51,12 @@
// private functions // private functions
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
namespace
{
// calculate the number of palette entries needed for the bitmap with this // calculate the number of palette entries needed for the bitmap with this
// number of bits per pixel // number of bits per pixel
static inline WORD GetNumberOfColours(WORD bitsPerPixel) inline WORD GetNumberOfColours(WORD bitsPerPixel)
{ {
// only 1, 4 and 8bpp bitmaps use palettes (well, they could be used with // only 1, 4 and 8bpp bitmaps use palettes (well, they could be used with
// 24bpp ones too but we don't support this as I think it's quite uncommon) // 24bpp ones too but we don't support this as I think it's quite uncommon)
@@ -59,7 +64,7 @@ static inline WORD GetNumberOfColours(WORD bitsPerPixel)
} }
// wrapper around ::GetObject() for DIB sections // wrapper around ::GetObject() for DIB sections
static inline bool GetDIBSection(HBITMAP hbmp, DIBSECTION *ds) inline bool GetDIBSection(HBITMAP hbmp, DIBSECTION *ds)
{ {
// note that GetObject() may return sizeof(DIBSECTION) for a bitmap // note that GetObject() may return sizeof(DIBSECTION) for a bitmap
// which is *not* a DIB section and the way to check for it is // which is *not* a DIB section and the way to check for it is
@@ -68,6 +73,25 @@ static inline bool GetDIBSection(HBITMAP hbmp, DIBSECTION *ds)
ds->dsBm.bmBits; ds->dsBm.bmBits;
} }
// for monochrome bitmaps, need bit twiddling functions to get at pixels
inline bool MonochromeLineReadBit(const unsigned char* srcLineStart, int index)
{
const unsigned char* byte = srcLineStart + (index >> 3);
int bit = 7 - (index & 7);
unsigned char mask = 1 << bit;
return (*byte & mask) != 0;
}
inline void MonochromeLineWriteBit(unsigned char* dstLineStart, int index, bool value)
{
unsigned char* byte = dstLineStart + (index >> 3);
int bit = 7 - (index & 7);
unsigned char mask = ~(1 << bit);
unsigned char newValue = value << bit;
(*byte &= mask) |= newValue;
}
}
// ============================================================================ // ============================================================================
// implementation // implementation
// ============================================================================ // ============================================================================
@@ -81,7 +105,7 @@ bool wxDIB::Create(int width, int height, int depth)
// we don't support formats using palettes right now so we only create // we don't support formats using palettes right now so we only create
// either 24bpp (RGB) or 32bpp (RGBA) bitmaps // either 24bpp (RGB) or 32bpp (RGBA) bitmaps
wxASSERT_MSG( depth, wxT("invalid image depth in wxDIB::Create()") ); wxASSERT_MSG( depth, wxT("invalid image depth in wxDIB::Create()") );
if ( depth < 24 ) if ( depth != 1 && depth < 24 )
depth = 24; depth = 24;
// allocate memory for bitmap structures // allocate memory for bitmap structures
@@ -126,7 +150,7 @@ bool wxDIB::Create(int width, int height, int depth)
return true; return true;
} }
bool wxDIB::Create(HBITMAP hbmp) bool wxDIB::Create(HBITMAP hbmp, int depth /* = -1 */)
{ {
wxCHECK_MSG( hbmp, false, wxT("wxDIB::Create(): invalid bitmap") ); wxCHECK_MSG( hbmp, false, wxT("wxDIB::Create(): invalid bitmap") );
@@ -158,7 +182,7 @@ bool wxDIB::Create(HBITMAP hbmp)
return false; return false;
} }
int d = bm.bmBitsPixel; int d = depth >= 1 ? depth : bm.bmBitsPixel;
if ( d <= 0 ) if ( d <= 0 )
d = wxDisplayDepth(); d = wxDisplayDepth();
@@ -246,14 +270,36 @@ bool wxDIB::Save(const wxString& filename)
const size_t sizeHdr = ds.dsBmih.biSize; const size_t sizeHdr = ds.dsBmih.biSize;
const size_t sizeImage = ds.dsBmih.biSizeImage; const size_t sizeImage = ds.dsBmih.biSizeImage;
// provide extra space so we can verify that
// monochrome DIB's color table is size 2
RGBQUAD monoBmiColors[3];
UINT nColors = 0;
if ( ds.dsBmih.biBitCount == 1 )
{
MemoryHDC hDC;
SelectInHDC sDC(hDC, m_handle);
nColors = GetDIBColorTable(hDC, 0, WXSIZEOF(monoBmiColors), monoBmiColors);
if ( nColors != 2 )
{
wxLogLastError(wxT("GetDIBColorTable"));
return false;
}
}
const size_t colorTableSize = ds.dsBmih.biBitCount == 1
? nColors * sizeof(monoBmiColors[0])
: 0;
bmpHdr.bfType = 0x4d42; // 'BM' in little endian bmpHdr.bfType = 0x4d42; // 'BM' in little endian
bmpHdr.bfOffBits = sizeof(BITMAPFILEHEADER) + ds.dsBmih.biSize; bmpHdr.bfOffBits = sizeof(BITMAPFILEHEADER);
bmpHdr.bfOffBits += ds.dsBmih.biSize;
bmpHdr.bfOffBits += colorTableSize;
bmpHdr.bfSize = bmpHdr.bfOffBits + sizeImage; bmpHdr.bfSize = bmpHdr.bfOffBits + sizeImage;
// first write the file header, then the bitmap header and finally the // first write the file header, then the bitmap header and finally the
// bitmap data itself // bitmap data itself
ok = file.Write(&bmpHdr, sizeof(bmpHdr)) == sizeof(bmpHdr) && ok = file.Write(&bmpHdr, sizeof(bmpHdr)) == sizeof(bmpHdr) &&
file.Write(&ds.dsBmih, sizeHdr) == sizeHdr && file.Write(&ds.dsBmih, sizeHdr) == sizeHdr &&
(!colorTableSize || file.Write(monoBmiColors, colorTableSize)) &&
file.Write(ds.dsBm.bmBits, sizeImage) == sizeImage; file.Write(ds.dsBm.bmBits, sizeImage) == sizeImage;
} }
} }
@@ -391,7 +437,10 @@ HBITMAP wxDIB::ConvertToBitmap(const BITMAPINFO *pbmi, HDC hdc, const void *bits
HBITMAP hbmp = ::CreateDIBitmap HBITMAP hbmp = ::CreateDIBitmap
( (
hdc ? hdc // create bitmap compatible hdc
? hdc // create bitmap compatible
: pbmih->biBitCount == 1
? (HDC) MemoryHDC()
: (HDC) ScreenHDC(), // with this DC : (HDC) ScreenHDC(), // with this DC
pbmih, // used to get size &c pbmih, // used to get size &c
CBM_INIT, // initialize bitmap bits too CBM_INIT, // initialize bitmap bits too
@@ -586,7 +635,7 @@ wxPalette *wxDIB::CreatePalette() const
#if wxUSE_IMAGE #if wxUSE_IMAGE
bool wxDIB::Create(const wxImage& image, PixelFormat pf) bool wxDIB::Create(const wxImage& image, PixelFormat pf, int dstDepth)
{ {
wxCHECK_MSG( image.IsOk(), false, wxT("invalid wxImage in wxDIB ctor") ); wxCHECK_MSG( image.IsOk(), false, wxT("invalid wxImage in wxDIB ctor") );
@@ -595,17 +644,64 @@ bool wxDIB::Create(const wxImage& image, PixelFormat pf)
// if we have alpha channel, we need to create a 32bpp RGBA DIB, otherwise // if we have alpha channel, we need to create a 32bpp RGBA DIB, otherwise
// a 24bpp RGB is sufficient // a 24bpp RGB is sufficient
// but use monochrome if requested (to support wxMask)
const bool hasAlpha = image.HasAlpha(); const bool hasAlpha = image.HasAlpha();
const int bpp = hasAlpha ? 32 : 24; wxCHECK_MSG(!hasAlpha || dstDepth != 1, false, "alpha not supported in monochrome bitmaps");
const int srcBpp = hasAlpha ? 32 : 24;
dstDepth = dstDepth != -1 ? dstDepth : srcBpp;
if ( !Create(w, h, bpp) ) if ( !Create(w, h, dstDepth) )
return false; return false;
// if requested, convert wxImage's content to monochrome
wxScopedArray<unsigned char> eightBitData;
if ( dstDepth == 1 )
{
wxImage quantized;
wxPalette* tempPalette;
unsigned char* tempEightBitData;
if ( !wxQuantize::Quantize(
image,
quantized,
&tempPalette,
2,
&tempEightBitData,
wxQUANTIZE_RETURN_8BIT_DATA) )
{
return false;
}
wxScopedPtr<wxPalette> palette(tempPalette);
eightBitData.reset(tempEightBitData);
// use palette's colors in result bitmap
MemoryHDC hDC;
SelectInHDC sDC(hDC, m_handle);
RGBQUAD colorTable[2];
for ( UINT i = 0; i < WXSIZEOF(colorTable); ++i )
{
if ( !palette->GetRGB(i,
&colorTable[i].rgbRed,
&colorTable[i].rgbGreen,
&colorTable[i].rgbBlue) )
{
return false;
}
colorTable[i].rgbReserved = 0;
}
UINT rc = SetDIBColorTable(hDC, 0, WXSIZEOF(colorTable), colorTable);
if ( rc != WXSIZEOF(colorTable))
{
wxLogLastError(wxT("SetDIBColorTable"));
return false;
}
}
// DIBs are stored in bottom to top order (see also the comment above in // DIBs are stored in bottom to top order (see also the comment above in
// Create()) so we need to copy bits line by line and starting from the end // Create()) so we need to copy bits line by line and starting from the end
const int srcBytesPerLine = w * 3; // N.B.: srcBytesPerLine varies with dstDepth because dstDepth == 1 uses quantized input
const int dstBytesPerLine = GetLineSize(w, bpp); const int srcBytesPerLine = dstDepth != 1 ? w * 3 : w;
const unsigned char *src = image.GetData() + ((h - 1) * srcBytesPerLine); const int dstBytesPerLine = GetLineSize(w, dstDepth);
const unsigned char *src = (dstDepth != 1 ? image.GetData() : eightBitData.get()) + ((h - 1) * srcBytesPerLine);
const unsigned char *alpha = hasAlpha ? image.GetAlpha() + (h - 1)*w const unsigned char *alpha = hasAlpha ? image.GetAlpha() + (h - 1)*w
: NULL; : NULL;
unsigned char *dstLineStart = (unsigned char *)m_data; unsigned char *dstLineStart = (unsigned char *)m_data;
@@ -650,6 +746,8 @@ bool wxDIB::Create(const wxImage& image, PixelFormat pf)
} }
else // no alpha channel else // no alpha channel
{
if ( dstDepth != 1 )
{ {
for ( int x = 0; x < w; x++ ) for ( int x = 0; x < w; x++ )
{ {
@@ -659,6 +757,15 @@ bool wxDIB::Create(const wxImage& image, PixelFormat pf)
src += 3; src += 3;
} }
} }
else
{
for ( int x = 0; x < w; x++ )
{
MonochromeLineWriteBit(dstLineStart, x, src[0] != 0);
++src;
}
}
}
// pass to the previous line in the image // pass to the previous line in the image
src -= 2*srcBytesPerLine; src -= 2*srcBytesPerLine;
@@ -716,6 +823,8 @@ wxImage wxDIB::ConvertToImage(ConversionFlags flags) const
{ {
// copy one DIB line // copy one DIB line
const unsigned char *src = srcLineStart; const unsigned char *src = srcLineStart;
if ( bpp != 1 )
{
for ( int x = 0; x < w; x++ ) for ( int x = 0; x < w; x++ )
{ {
dst[2] = *src++; dst[2] = *src++;
@@ -759,6 +868,21 @@ wxImage wxDIB::ConvertToImage(ConversionFlags flags) const
dst += 3; dst += 3;
} }
}
else
{
for ( int x = 0; x < w; x++ )
{
unsigned char value = MonochromeLineReadBit(srcLineStart, x)
? 255
: 0;
dst[2] = value;
dst[1] = value;
dst[0] = value;
dst += 3;
}
}
// pass to the previous line in the image // pass to the previous line in the image
dst -= 2*dstBytesPerLine; dst -= 2*dstBytesPerLine;

View File

@@ -22,6 +22,7 @@
#include "wx/graphics.h" #include "wx/graphics.h"
#endif // wxUSE_GRAPHICS_CONTEXT #endif // wxUSE_GRAPHICS_CONTEXT
#include "testfile.h"
#include "testimage.h" #include "testimage.h"
#define ASSERT_EQUAL_RGB(c, r, g, b) \ #define ASSERT_EQUAL_RGB(c, r, g, b) \
@@ -54,6 +55,52 @@ typedef wxNativePixelData wxNative32PixelData;
// tests // tests
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
TEST_CASE("BitmapTestCase::Monochrome", "[bitmap][monochrome]")
{
#ifdef __WXGTK__
WARN("Skipping test known not to work in wxGTK.");
#else
wxBitmap color;
color.LoadFile("horse.bmp", wxBITMAP_TYPE_BMP);
REQUIRE(color.IsOk());
REQUIRE(color.GetDepth() == 32);
wxImage imgQuant = color.ConvertToImage();
wxBitmap bmpQuant(imgQuant, 1);
REQUIRE(bmpQuant.GetDepth() == 1);
TempFile mono_horse("mono_horse.bmp");
REQUIRE(bmpQuant.SaveFile(mono_horse.GetName(), wxBITMAP_TYPE_BMP));
wxBitmap mono;
REQUIRE(mono.LoadFile(mono_horse.GetName(), wxBITMAP_TYPE_BMP));
REQUIRE(mono.IsOk());
REQUIRE(mono.GetDepth() == 1);
// wxMonoPixelData only exists in wxMSW
#if defined(__WXMSW__)
// draw lines on top and left, but leaving blank top and left lines
{
wxMonoPixelData data(mono);
wxMonoPixelData::Iterator p(data);
p.OffsetY(data, 1);
for ( int i = 0; i < data.GetWidth() - 2; ++i )
{
++p;
p.Pixel() = 0;
}
p.MoveTo(data, 1, 1);
for ( int i = 0; i < data.GetHeight() - 3; ++i )
{
p.OffsetY(data, 1);
p.Pixel() = 1;
}
}
TempFile mono_lines_horse("mono_lines_horse.bmp");
REQUIRE(mono.SaveFile(mono_lines_horse.GetName(), wxBITMAP_TYPE_BMP));
#endif // __WXMSW__
#endif // !__WXGTK__
}
TEST_CASE("BitmapTestCase::Mask", "[bitmap][mask]") TEST_CASE("BitmapTestCase::Mask", "[bitmap][mask]")
{ {
wxBitmap bmp(10, 10); wxBitmap bmp(10, 10);
@@ -726,6 +773,7 @@ TEST_CASE("BitmapTestCase::SubBitmapNonAlphaWithMask", "[bitmap][subbitmap][nona
wxColour maskClrBottomRight; wxColour maskClrBottomRight;
// Fetch sample original mask pixels // Fetch sample original mask pixels
{ {
REQUIRE(bmpMask.GetDepth() == 1);
wxNativePixelData data(bmpMask); wxNativePixelData data(bmpMask);
REQUIRE(data); REQUIRE(data);
wxNativePixelData::Iterator p(data); wxNativePixelData::Iterator p(data);
@@ -742,11 +790,44 @@ TEST_CASE("BitmapTestCase::SubBitmapNonAlphaWithMask", "[bitmap][subbitmap][nona
p.OffsetX(data, w / 2); // bottom-right point p.OffsetX(data, w / 2); // bottom-right point
maskClrBottomRight = wxColour(p.Red(), p.Green(), p.Blue()); maskClrBottomRight = wxColour(p.Red(), p.Green(), p.Blue());
} }
REQUIRE(bmpMask.GetDepth() == 1);
CHECK(maskClrTopLeft == *wxWHITE); CHECK(maskClrTopLeft == *wxWHITE);
CHECK(maskClrTopRight == *wxWHITE); CHECK(maskClrTopRight == *wxWHITE);
CHECK(maskClrBottomLeft == *wxBLACK); CHECK(maskClrBottomLeft == *wxBLACK);
CHECK(maskClrBottomRight == *wxBLACK); CHECK(maskClrBottomRight == *wxBLACK);
// wxMonoPixelData only exists in wxMSW
#if defined(__WXMSW__)
bool maskValueTopLeft;
bool maskValueTopRight;
bool maskValueBottomLeft;
bool maskValueBottomRight;
// Fetch sample original mask pixels
{
REQUIRE(bmpMask.GetDepth() == 1);
wxMonoPixelData data(bmpMask);
REQUIRE(data);
wxMonoPixelData::Iterator p(data);
p.OffsetY(data, h / 4);
wxMonoPixelData::Iterator rowStart = p;
p.OffsetX(data, w / 4); // top-left point
maskValueTopLeft = p.Pixel();
p.OffsetX(data, w / 2); // top-right point
maskValueTopRight = p.Pixel();
p = rowStart;
p.OffsetY(data, h / 2);
p.OffsetX(data, w / 4); // bottom-left point
maskValueBottomLeft = p.Pixel();
p.OffsetX(data, w / 2); // bottom-right point
maskValueBottomRight = p.Pixel();
}
REQUIRE(bmpMask.GetDepth() == 1);
CHECK(maskValueTopLeft == true);
CHECK(maskValueTopRight == true);
CHECK(maskValueBottomLeft == false);
CHECK(maskValueBottomRight == false);
#endif // __WXMSW__
wxBitmap subBmpMask = subBmp.GetMask()->GetBitmap(); wxBitmap subBmpMask = subBmp.GetMask()->GetBitmap();
// Check sub bitmap mask attributes // Check sub bitmap mask attributes
REQUIRE(subBmpMask.GetWidth() == subBmp.GetWidth()); REQUIRE(subBmpMask.GetWidth() == subBmp.GetWidth());
@@ -758,6 +839,7 @@ TEST_CASE("BitmapTestCase::SubBitmapNonAlphaWithMask", "[bitmap][subbitmap][nona
REQUIRE_FALSE(subBmpMask.GetMask()); REQUIRE_FALSE(subBmpMask.GetMask());
// Check sub bitmap mask pixels // Check sub bitmap mask pixels
{ {
REQUIRE(subBmpMask.GetDepth() == 1);
wxNativePixelData data(subBmpMask); wxNativePixelData data(subBmpMask);
REQUIRE(data); REQUIRE(data);
wxNativePixelData::Iterator p(data); wxNativePixelData::Iterator p(data);
@@ -774,6 +856,30 @@ TEST_CASE("BitmapTestCase::SubBitmapNonAlphaWithMask", "[bitmap][subbitmap][nona
p.OffsetX(data, w2 / 2); // bottom-right point p.OffsetX(data, w2 / 2); // bottom-right point
ASSERT_EQUAL_COLOUR_RGB(p, maskClrBottomRight); ASSERT_EQUAL_COLOUR_RGB(p, maskClrBottomRight);
} }
REQUIRE(subBmpMask.GetDepth() == 1);
// wxMonoPixelData only exists in wxMSW
#if defined(__WXMSW__)
{
REQUIRE(subBmpMask.GetDepth() == 1);
wxMonoPixelData data(subBmpMask);
REQUIRE(data);
wxMonoPixelData::Iterator p(data);
p.OffsetY(data, h2 / 4);
wxMonoPixelData::Iterator rowStart = p;
p.OffsetX(data, w2 / 4); // top-left point
CHECK(p.Pixel() == maskValueTopLeft);
p.OffsetX(data, w2 / 2); // top-right point
CHECK(p.Pixel() == maskValueTopRight);
p = rowStart;
p.OffsetY(data, h2 / 2);
p.OffsetX(data, w2 / 4); // bottom-left point
CHECK(p.Pixel() == maskValueBottomLeft);
p.OffsetX(data, w2 / 2); // bottom-right point
CHECK(p.Pixel() == maskValueBottomRight);
}
REQUIRE(subBmpMask.GetDepth() == 1);
#endif // __WXMSW__
} }
TEST_CASE("BitmapTestCase::SubBitmapAlphaWithMask", "[bitmap][subbitmap][alpha][withmask]") TEST_CASE("BitmapTestCase::SubBitmapAlphaWithMask", "[bitmap][subbitmap][alpha][withmask]")
@@ -870,6 +976,7 @@ TEST_CASE("BitmapTestCase::SubBitmapAlphaWithMask", "[bitmap][subbitmap][alpha][
wxColour maskClrBottomRight; wxColour maskClrBottomRight;
// Fetch sample original mask pixels // Fetch sample original mask pixels
{ {
REQUIRE(bmpMask.GetDepth() == 1);
wxNativePixelData data(bmpMask); wxNativePixelData data(bmpMask);
REQUIRE(data); REQUIRE(data);
wxNativePixelData::Iterator p(data); wxNativePixelData::Iterator p(data);
@@ -886,11 +993,44 @@ TEST_CASE("BitmapTestCase::SubBitmapAlphaWithMask", "[bitmap][subbitmap][alpha][
p.OffsetX(data, w / 2); // bottom-right point p.OffsetX(data, w / 2); // bottom-right point
maskClrBottomRight = wxColour(p.Red(), p.Green(), p.Blue()); maskClrBottomRight = wxColour(p.Red(), p.Green(), p.Blue());
} }
REQUIRE(bmpMask.GetDepth() == 1);
CHECK(maskClrTopLeft == *wxWHITE); CHECK(maskClrTopLeft == *wxWHITE);
CHECK(maskClrTopRight == *wxWHITE); CHECK(maskClrTopRight == *wxWHITE);
CHECK(maskClrBottomLeft == *wxBLACK); CHECK(maskClrBottomLeft == *wxBLACK);
CHECK(maskClrBottomRight == *wxBLACK); CHECK(maskClrBottomRight == *wxBLACK);
// wxMonoPixelData only exists in wxMSW
#if defined(__WXMSW__)
bool maskValueTopLeft;
bool maskValueTopRight;
bool maskValueBottomLeft;
bool maskValueBottomRight;
// Fetch sample original mask pixels
{
REQUIRE(bmpMask.GetDepth() == 1);
wxMonoPixelData data(bmpMask);
REQUIRE(data);
wxMonoPixelData::Iterator p(data);
p.OffsetY(data, h / 4);
wxMonoPixelData::Iterator rowStart = p;
p.OffsetX(data, w / 4); // top-left point
maskValueTopLeft = p.Pixel();
p.OffsetX(data, w / 2); // top-right point
maskValueTopRight = p.Pixel();
p = rowStart;
p.OffsetY(data, h / 2);
p.OffsetX(data, w / 4); // bottom-left point
maskValueBottomLeft = p.Pixel();
p.OffsetX(data, w / 2); // bottom-right point
maskValueBottomRight = p.Pixel();
}
REQUIRE(bmpMask.GetDepth() == 1);
CHECK(maskValueTopLeft == true);
CHECK(maskValueTopRight == true);
CHECK(maskValueBottomLeft == false);
CHECK(maskValueBottomRight == false);
#endif // __WXMSW__
wxBitmap subBmpMask = subBmp.GetMask()->GetBitmap(); wxBitmap subBmpMask = subBmp.GetMask()->GetBitmap();
// Check sub bitmap mask attributes // Check sub bitmap mask attributes
REQUIRE(subBmpMask.GetWidth() == subBmp.GetWidth()); REQUIRE(subBmpMask.GetWidth() == subBmp.GetWidth());
@@ -902,6 +1042,7 @@ TEST_CASE("BitmapTestCase::SubBitmapAlphaWithMask", "[bitmap][subbitmap][alpha][
REQUIRE_FALSE(subBmpMask.GetMask()); REQUIRE_FALSE(subBmpMask.GetMask());
// Check sub bitmap mask pixels // Check sub bitmap mask pixels
{ {
REQUIRE(subBmpMask.GetDepth() == 1);
wxNativePixelData data(subBmpMask); wxNativePixelData data(subBmpMask);
REQUIRE(data); REQUIRE(data);
wxNativePixelData::Iterator p(data); wxNativePixelData::Iterator p(data);
@@ -918,6 +1059,30 @@ TEST_CASE("BitmapTestCase::SubBitmapAlphaWithMask", "[bitmap][subbitmap][alpha][
p.OffsetX(data, w2 / 2); // bottom-right point p.OffsetX(data, w2 / 2); // bottom-right point
ASSERT_EQUAL_RGB(p, maskClrBottomRight.Red(), maskClrBottomRight.Green(), maskClrBottomRight.Blue()); ASSERT_EQUAL_RGB(p, maskClrBottomRight.Red(), maskClrBottomRight.Green(), maskClrBottomRight.Blue());
} }
REQUIRE(subBmpMask.GetDepth() == 1);
// wxMonoPixelData only exists in wxMSW
#if defined(__WXMSW__)
{
REQUIRE(subBmpMask.GetDepth() == 1);
wxMonoPixelData data(subBmpMask);
REQUIRE(data);
wxMonoPixelData::Iterator p(data);
p.OffsetY(data, h2 / 4);
wxMonoPixelData::Iterator rowStart = p;
p.OffsetX(data, w2 / 4); // top-left point
CHECK(p.Pixel() == maskValueTopLeft);
p.OffsetX(data, w2 / 2); // top-right point
CHECK(p.Pixel() == maskValueTopRight);
p = rowStart;
p.OffsetY(data, h2 / 2);
p.OffsetX(data, w2 / 4); // bottom-left point
CHECK(p.Pixel() == maskValueBottomLeft);
p.OffsetX(data, w2 / 2); // bottom-right point
CHECK(p.Pixel() == maskValueBottomRight);
}
REQUIRE(subBmpMask.GetDepth() == 1);
#endif // __WXMSW__
} }
namespace Catch namespace Catch