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:
@@ -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
|
||||
|
||||
|
@@ -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
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
@@ -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;
|
||||
}
|
||||
|
150
src/msw/dib.cpp
150
src/msw/dib.cpp
@@ -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,14 +270,36 @@ 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 &&
|
||||
(!colorTableSize || file.Write(monoBmiColors, colorTableSize)) &&
|
||||
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
|
||||
(
|
||||
hdc ? hdc // create bitmap compatible
|
||||
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
|
||||
@@ -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;
|
||||
@@ -650,6 +746,8 @@ bool wxDIB::Create(const wxImage& image, PixelFormat pf)
|
||||
|
||||
}
|
||||
else // no alpha channel
|
||||
{
|
||||
if ( dstDepth != 1 )
|
||||
{
|
||||
for ( int x = 0; x < w; x++ )
|
||||
{
|
||||
@@ -659,6 +757,15 @@ bool wxDIB::Create(const wxImage& image, PixelFormat pf)
|
||||
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
|
||||
src -= 2*srcBytesPerLine;
|
||||
@@ -716,6 +823,8 @@ wxImage wxDIB::ConvertToImage(ConversionFlags flags) const
|
||||
{
|
||||
// copy one DIB line
|
||||
const unsigned char *src = srcLineStart;
|
||||
if ( bpp != 1 )
|
||||
{
|
||||
for ( int x = 0; x < w; x++ )
|
||||
{
|
||||
dst[2] = *src++;
|
||||
@@ -759,6 +868,21 @@ wxImage wxDIB::ConvertToImage(ConversionFlags flags) const
|
||||
|
||||
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
|
||||
dst -= 2*dstBytesPerLine;
|
||||
|
@@ -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
|
||||
|
Reference in New Issue
Block a user