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__
// create a DIB from the DDB
wxDIB(const wxBitmap& bmp)
{ Init(); (void)Create(bmp); }
wxDIB(const wxBitmap& bmp, int depth = -1)
{ Init(); (void)Create(bmp, depth); }
#endif // __WXMSW__
// create a DIB from the Windows DDB
@@ -58,9 +58,9 @@ public:
// same as the corresponding ctors but with return value
bool Create(int width, int height, int depth);
#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
bool Create(HBITMAP hbmp);
bool Create(HBITMAP hbmp, int depth = -1);
bool Load(const wxString& filename);
// 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
// pre-multiplication for the DIB to be usable with ImageList_Draw() which
// does pre-multiplication internally.
wxDIB(const wxImage& image, PixelFormat pf = PixelFormat_PreMultiplied)
wxDIB(const wxImage& image, PixelFormat pf = PixelFormat_PreMultiplied, int depth = -1)
{
Init();
(void)Create(image, pf);
(void)Create(image, pf, depth);
}
// 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

View File

@@ -153,6 +153,24 @@ typedef wxPixelFormat<unsigned char, 24, 0, 1, 2> wxImagePixelFormat;
typedef wxPixelFormat<unsigned char, 24, 2, 1, 0> wxNativePixelFormat;
#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__)
// under Mac, first component is unused but still present, hence we use
// 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
template <class Image,
@@ -712,6 +944,10 @@ typedef wxPixelData<wxImage> wxImagePixelData;
typedef wxPixelData<wxBitmap, wxNativePixelFormat> wxNativePixelData;
typedef wxPixelData<wxBitmap, wxAlphaPixelFormat> wxAlphaPixelData;
#if defined(__WXMSW__)
typedef wxPixelData<wxBitmap, wxMonoPixelFormat> wxMonoPixelData;
#endif
#endif //wxUSE_GUI
// ----------------------------------------------------------------------------

View File

@@ -70,7 +70,7 @@ public:
#if wxUSE_WXDIB
// 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.
bool AssignDIB(wxDIB& dib);
@@ -318,14 +318,15 @@ void wxBitmapRefData::InitFromDIB(const wxDIB& dib, HBITMAP 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( dib.IsOk(), wxT("invalid DIB in CopyFromDIB") );
HBITMAP hbitmap;
#ifdef SOMETIMES_USE_DIB
hbitmap = dib.CreateDDB();
// MemoryHDC defaults to monochrome
hbitmap = dib.CreateDDB(depth == 1 ? HDC(MemoryHDC()) : NULL);
#else // ALWAYS_USE_DIB
hbitmap = NULL;
#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 w = image.GetWidth();
wxDIB dib(image);
wxDIB dib(image, wxDIB::PixelFormat_PreMultiplied, depth);
if ( !dib.IsOk() )
return false;
@@ -1269,7 +1270,7 @@ void wxBitmap::MSWBlendMaskWithAlpha()
{
wxBitmap bmpMask = GetMask()->GetBitmap();
wxNativePixelData maskData(bmpMask);
wxMonoPixelData maskData(bmpMask);
wxCHECK_RET(maskData, "No access to bitmap mask data");
wxAlphaPixelData bmpData(*this);
@@ -1278,17 +1279,17 @@ void wxBitmap::MSWBlendMaskWithAlpha()
const int w = GetWidth();
const int h = GetHeight();
wxNativePixelData::Iterator maskRowStart(maskData);
wxMonoPixelData::Iterator maskRowStart(maskData);
wxAlphaPixelData::Iterator bmpRowStart(bmpData);
for ( int y = 0; y < h; y++ )
{
wxNativePixelData::Iterator pMask = maskRowStart;
wxMonoPixelData::Iterator pMask = maskRowStart;
wxAlphaPixelData::Iterator pBmp = bmpRowStart;
for ( int x = 0; x < w; x++, ++pBmp, ++pMask )
{
// Masked pixel is not drawn i.e. is transparent,
// non-masked pixel is untouched.
if ( pMask.Red() == 0 )
if ( pMask.Pixel() == 0 )
{
pBmp.Red() = pBmp.Green() = pBmp.Blue() = 0; // pre-multiplied
pBmp.Alpha() = wxALPHA_TRANSPARENT;
@@ -1378,6 +1379,12 @@ void *wxBitmap::GetRawData(wxPixelDataBase& data, int bpp)
// no bitmap, no data (raw or otherwise)
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
// 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,
wxT("GetRawData() may be called only once") );
wxDIB *dib = new wxDIB(*this);
wxDIB *dib = new wxDIB(*this, bpp);
if ( !dib->IsOk() )
{
delete dib;
@@ -1462,7 +1469,8 @@ void wxBitmap::UngetRawData(wxPixelDataBase& WXUNUSED(data))
GetBitmapData()->m_dib = NULL;
GetBitmapData()->Free();
GetBitmapData()->CopyFromDIB(*dib);
int depth = GetDepth() == 1 && dib->GetDepth() != GetDepth() ? 1 : -1;
GetBitmapData()->CopyFromDIB(*dib, depth);
delete dib;
}

View File

@@ -37,6 +37,8 @@
#endif //WX_PRECOMP
#include "wx/file.h"
#include "wx/quantize.h"
#include "wx/scopedarray.h"
#include <stdio.h>
#include <stdlib.h>
@@ -49,9 +51,12 @@
// private functions
// ----------------------------------------------------------------------------
namespace
{
// calculate the number of palette entries needed for the bitmap with this
// 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
// 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
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
// 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;
}
// 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
// ============================================================================
@@ -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
// either 24bpp (RGB) or 32bpp (RGBA) bitmaps
wxASSERT_MSG( depth, wxT("invalid image depth in wxDIB::Create()") );
if ( depth < 24 )
if ( depth != 1 && depth < 24 )
depth = 24;
// allocate memory for bitmap structures
@@ -126,7 +150,7 @@ bool wxDIB::Create(int width, int height, int depth)
return true;
}
bool wxDIB::Create(HBITMAP hbmp)
bool wxDIB::Create(HBITMAP hbmp, int depth /* = -1 */)
{
wxCHECK_MSG( hbmp, false, wxT("wxDIB::Create(): invalid bitmap") );
@@ -158,7 +182,7 @@ bool wxDIB::Create(HBITMAP hbmp)
return false;
}
int d = bm.bmBitsPixel;
int d = depth >= 1 ? depth : bm.bmBitsPixel;
if ( d <= 0 )
d = wxDisplayDepth();
@@ -246,15 +270,37 @@ bool wxDIB::Save(const wxString& filename)
const size_t sizeHdr = ds.dsBmih.biSize;
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.bfOffBits = sizeof(BITMAPFILEHEADER) + ds.dsBmih.biSize;
bmpHdr.bfOffBits = sizeof(BITMAPFILEHEADER);
bmpHdr.bfOffBits += ds.dsBmih.biSize;
bmpHdr.bfOffBits += colorTableSize;
bmpHdr.bfSize = bmpHdr.bfOffBits + sizeImage;
// first write the file header, then the bitmap header and finally the
// bitmap data itself
ok = file.Write(&bmpHdr, sizeof(bmpHdr)) == sizeof(bmpHdr) &&
file.Write(&ds.dsBmih, sizeHdr) == sizeHdr &&
file.Write(ds.dsBm.bmBits, sizeImage) == sizeImage;
(!colorTableSize || file.Write(monoBmiColors, colorTableSize)) &&
file.Write(ds.dsBm.bmBits, sizeImage) == sizeImage;
}
}
#else // !wxUSE_FILE
@@ -391,8 +437,11 @@ HBITMAP wxDIB::ConvertToBitmap(const BITMAPINFO *pbmi, HDC hdc, const void *bits
HBITMAP hbmp = ::CreateDIBitmap
(
hdc ? hdc // create bitmap compatible
: (HDC) ScreenHDC(), // with this DC
hdc
? hdc // create bitmap compatible
: pbmih->biBitCount == 1
? (HDC) MemoryHDC()
: (HDC) ScreenHDC(), // with this DC
pbmih, // used to get size &c
CBM_INIT, // initialize bitmap bits too
bits, // ... using this data
@@ -586,7 +635,7 @@ wxPalette *wxDIB::CreatePalette() const
#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") );
@@ -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
// a 24bpp RGB is sufficient
// but use monochrome if requested (to support wxMask)
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;
// 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
// Create()) so we need to copy bits line by line and starting from the end
const int srcBytesPerLine = w * 3;
const int dstBytesPerLine = GetLineSize(w, bpp);
const unsigned char *src = image.GetData() + ((h - 1) * srcBytesPerLine);
// N.B.: srcBytesPerLine varies with dstDepth because dstDepth == 1 uses quantized input
const int srcBytesPerLine = dstDepth != 1 ? w * 3 : w;
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
: NULL;
unsigned char *dstLineStart = (unsigned char *)m_data;
@@ -651,12 +747,23 @@ bool wxDIB::Create(const wxImage& image, PixelFormat pf)
}
else // no alpha channel
{
for ( int x = 0; x < w; x++ )
if ( dstDepth != 1 )
{
*dst++ = src[2];
*dst++ = src[1];
*dst++ = src[0];
src += 3;
for ( int x = 0; x < w; x++ )
{
*dst++ = src[2];
*dst++ = src[1];
*dst++ = src[0];
src += 3;
}
}
else
{
for ( int x = 0; x < w; x++ )
{
MonochromeLineWriteBit(dstLineStart, x, src[0] != 0);
++src;
}
}
}
@@ -716,48 +823,65 @@ wxImage wxDIB::ConvertToImage(ConversionFlags flags) const
{
// copy one DIB line
const unsigned char *src = srcLineStart;
for ( int x = 0; x < w; x++ )
if ( bpp != 1 )
{
dst[2] = *src++;
dst[1] = *src++;
dst[0] = *src++;
if ( bpp == 32 )
for ( int x = 0; x < w; x++ )
{
// wxImage uses non premultiplied alpha so undo
// premultiplication done in Create() above
const unsigned char a = *src;
*alpha++ = a;
dst[2] = *src++;
dst[1] = *src++;
dst[0] = *src++;
// Check what kind of alpha do we have.
switch ( a )
if ( bpp == 32 )
{
case 0:
hasTransparent = true;
break;
// wxImage uses non premultiplied alpha so undo
// premultiplication done in Create() above
const unsigned char a = *src;
*alpha++ = a;
default:
// Anything in between means we have real transparency
// and must use alpha channel.
hasAlpha = true;
break;
// Check what kind of alpha do we have.
switch ( a )
{
case 0:
hasTransparent = true;
break;
case 255:
hasOpaque = true;
break;
default:
// Anything in between means we have real transparency
// and must use alpha channel.
hasAlpha = true;
break;
case 255:
hasOpaque = true;
break;
}
if ( a > 0 )
{
dst[0] = (dst[0] * 255) / a;
dst[1] = (dst[1] * 255) / a;
dst[2] = (dst[2] * 255) / a;
}
src++;
}
if ( a > 0 )
{
dst[0] = (dst[0] * 255) / a;
dst[1] = (dst[1] * 255) / a;
dst[2] = (dst[2] * 255) / a;
}
src++;
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;
dst += 3;
}
}
// pass to the previous line in the image

View File

@@ -22,6 +22,7 @@
#include "wx/graphics.h"
#endif // wxUSE_GRAPHICS_CONTEXT
#include "testfile.h"
#include "testimage.h"
#define ASSERT_EQUAL_RGB(c, r, g, b) \
@@ -54,6 +55,52 @@ typedef wxNativePixelData wxNative32PixelData;
// 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]")
{
wxBitmap bmp(10, 10);
@@ -726,6 +773,7 @@ TEST_CASE("BitmapTestCase::SubBitmapNonAlphaWithMask", "[bitmap][subbitmap][nona
wxColour maskClrBottomRight;
// Fetch sample original mask pixels
{
REQUIRE(bmpMask.GetDepth() == 1);
wxNativePixelData data(bmpMask);
REQUIRE(data);
wxNativePixelData::Iterator p(data);
@@ -742,11 +790,44 @@ TEST_CASE("BitmapTestCase::SubBitmapNonAlphaWithMask", "[bitmap][subbitmap][nona
p.OffsetX(data, w / 2); // bottom-right point
maskClrBottomRight = wxColour(p.Red(), p.Green(), p.Blue());
}
REQUIRE(bmpMask.GetDepth() == 1);
CHECK(maskClrTopLeft == *wxWHITE);
CHECK(maskClrTopRight == *wxWHITE);
CHECK(maskClrBottomLeft == *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();
// Check sub bitmap mask attributes
REQUIRE(subBmpMask.GetWidth() == subBmp.GetWidth());
@@ -758,6 +839,7 @@ TEST_CASE("BitmapTestCase::SubBitmapNonAlphaWithMask", "[bitmap][subbitmap][nona
REQUIRE_FALSE(subBmpMask.GetMask());
// Check sub bitmap mask pixels
{
REQUIRE(subBmpMask.GetDepth() == 1);
wxNativePixelData data(subBmpMask);
REQUIRE(data);
wxNativePixelData::Iterator p(data);
@@ -774,6 +856,30 @@ TEST_CASE("BitmapTestCase::SubBitmapNonAlphaWithMask", "[bitmap][subbitmap][nona
p.OffsetX(data, w2 / 2); // bottom-right point
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]")
@@ -870,6 +976,7 @@ TEST_CASE("BitmapTestCase::SubBitmapAlphaWithMask", "[bitmap][subbitmap][alpha][
wxColour maskClrBottomRight;
// Fetch sample original mask pixels
{
REQUIRE(bmpMask.GetDepth() == 1);
wxNativePixelData data(bmpMask);
REQUIRE(data);
wxNativePixelData::Iterator p(data);
@@ -886,11 +993,44 @@ TEST_CASE("BitmapTestCase::SubBitmapAlphaWithMask", "[bitmap][subbitmap][alpha][
p.OffsetX(data, w / 2); // bottom-right point
maskClrBottomRight = wxColour(p.Red(), p.Green(), p.Blue());
}
REQUIRE(bmpMask.GetDepth() == 1);
CHECK(maskClrTopLeft == *wxWHITE);
CHECK(maskClrTopRight == *wxWHITE);
CHECK(maskClrBottomLeft == *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();
// Check sub bitmap mask attributes
REQUIRE(subBmpMask.GetWidth() == subBmp.GetWidth());
@@ -902,6 +1042,7 @@ TEST_CASE("BitmapTestCase::SubBitmapAlphaWithMask", "[bitmap][subbitmap][alpha][
REQUIRE_FALSE(subBmpMask.GetMask());
// Check sub bitmap mask pixels
{
REQUIRE(subBmpMask.GetDepth() == 1);
wxNativePixelData data(subBmpMask);
REQUIRE(data);
wxNativePixelData::Iterator p(data);
@@ -918,6 +1059,30 @@ TEST_CASE("BitmapTestCase::SubBitmapAlphaWithMask", "[bitmap][subbitmap][alpha][
p.OffsetX(data, w2 / 2); // bottom-right point
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