This tip summarizes what I've learned about copy & paste bitmaps from/to my icon editor in exchange with Paint. It also clarifies basic things about Win32 bitmaps that are only implicit in the Win32 API documentation.
Introduction
This tip summarizes a lot of little information about exchanging bitmaps between two programs. All of them have already been covered in various articles on the Internet, but to find the right articles (and not to be confused by the wrong ones) and to combine the information they contain successfully makes all the difference. I use my self-written Icon Editor (see article "A basic icon editor running on ReactOS (and consequently on Windows XP and newer versions)") as one of the programs and Windows Paint as the other one. This approach is tested with Code::Blocks on ReactOS 0.4.13 and Microsoft Visual Studio 2019 on Microsoft Windows 10 64 bit (in 32 bit sub system).
I wrote this tip to save other developers the hassle of gathering all the necessary information about bitmap transformation.
Background
Icons
Icons (*.ico files) contain one or more image(s). An icon image consists of meta data (bitmap info header), a color bitmap (XOR bitmap), a mask bitmap (AND bitmap) and optionally a color palette. Any possible pixel format (32bpp / 24bpp = true color, 16bpp = high color, 8bpp = 256 color palette, 4bpp = 16 color palette) can be used for the color bitmap. The mask bitmap is always 1bpp = black/white.
Typically, icon images use 8bpp = 256 color palette (standard palette colors). My Icon Editor prefers 4bpp = 16 color palette (individual/user adopted colors). This is because most of the icons can be defined more compact and precise with 16 individual colors than with 256 standard colors.
This is the structure I use for icon images:
typedef struct tagICONIMAGE
{
BITMAPINFOHEADER iiHeader;
AARRGGBB* iiColors;
BYTE* iiXOR;
BYTE* iiAND;
} ICONIMAGE, *LPICONIMAGE;
Please read the tip "Introduction to Embedded Icons without Resource on ReactOS" for more details on icons.
Hint: The stride alignment for the DIB bits of XOR (color) image and AND (mask) image is fixed 4 BYTE
(== 2 WORD
== DWORD
).
Bitmaps
When using the term bitmap, things get confusing if you want to take a closer look at the inner details of a bitmap. On the one hand, the term bitmap is used as a generic term for DDBitmap
s (device dependent bitmaps / device compatible bitmaps) and DIBSection
s (device independent bitmaps / device incompatible bitmaps). On the other hand, it is also used in a shortened form for DDBitmap
and DIBSection
. Unfortunately, this is also true for the Win32 API documentation.
This confusion is also promoted by the fact that the Win32 structure DIBSECTION
can easily be casted into the Win32 structure BITMAP
(since a BITMAP
is the first member of a DIBSECTION
) and thus not very common.
typedef struct tagBITMAP
{
LONG bmType;
LONG bmWidth;
LONG bmHeight;
LONG bmWidthBytes;
WORD bmPlanes;
WORD bmBitsPixel;
LPVOID bmBits;
} BITMAP, *PBITMAP, NEAR *NPBITMAP, FAR *LPBITMAP;
typedef struct tagDIBSECTION
{
BITMAP dsBm;
BITMAPINFOHEADER dsBmih;
DWORD dsBitfields[3];
HANDLE dshSection;
DWORD dsOffset;
} DIBSECTION, FAR *LPDIBSECTION, *PDIBSECTION;
Furthermore, although there is a handle for DDBitmap
s, HBITMAP
, there is no handle for DIBSection
s. Instead, HBITMAP
is typically used for both.
And to top it all off, it is also possible to create DIBSection
s with the pixel formats 16bpp, 24bpp and 32bpp.
Hint: Currently, I use a stride alignment for DIBSection
s of fixed 4 BYTE
(== 2 WORD
== DWORD
) while the stride alignment for DDBitmap
s is calculated and typically 2 BYTE
(== WORD
).
My Solution
I found this very confusing and struggled for a long time with the correct handling of bitmaps. Then, I introduced a separate handle for DIBSection
s, HDIBSECTION
, and made a clear separation in code between class CDDBitmap : public CBitmap
and class CDIBSection : public CBitmap
.
DECLARE_HANDLE(HDIBSECTION);
So if I have a HDIBSECTION
or an instance of CDIBSection
in my code, I can be sure that this instance is based on a ::CreateDibSection()
call. And if I have a HBITMAP
or an instance of CDDBitmap
in my code, I can be sure that it is based on a ::CreateCompatibleBitmap()
call.
The Bottom Line
What remains in the end is that DDBitmap
s never have a color palette and always use the color space of the respective device.
While the deeper sense of DIBSection
s is to be independent from the color space of any device - thus they need an associated color palette in the pixel formats 1bpp, 4bpp and 8bpp.
The Approach to Paste a Bitmap Into an Icon Image
The pixel format of the bitmap to be pasted from the clipboard into your own program cannot be determined in advance. The clipboard distinguishes between CF_BITMAP
for DDBitmap
s and CF_DIB
for DIBSection
s, but even with this, the pixel format cannot be determined in advance. Because the conversion of a DIBSection
into a DDBitmap
with the Win32 API is very simple, I decided to follow this approach:
- Convert
DDBitmap
or DIBSection
from clipboard to a local DDBitmap
with maximum available color depth (so it is as lossless as possible).
- Process
DIBSection
source data in case of CF_DIB
clipboard format.
- Create a local
DDBitmap
in case source data are in 16bpp, 24bpp or 32bpp and assume the pixel data can be processed 'as is'. - Or create a local
DDDBitmap
in case source data are in 4bpp or 8bpp and assume the pixel data are in the color space of the respective device.
- Or process
DDBitmap
source data in case of CF_BITMAP
clipboard format.
- Create a local
DDDBitmap
and assume the pixel data are in the color space of the respective device.
- Convert the local
DDBitmap
to a local DIBSection
with the same color depth as the target icon image. - Match color palettes of the local
DIBSection
and the target icon image.
- Prepare source and target color palettes.
- Integrate source palette colors into target palette.
- Transfer the pixels of the local
DIBSection
to the target icon image.
Using the Code
The first two steps of my approach are realized by CBitmap::FromClipBoard()
.
1. Convert DDBitmap or DIBSection from clipboard to a local DDBitmap with maximum available color depth (so as lossless as possible). Test and prepare prerequisites.
HDIBSECTION CBitmap::FromClipBoard(UINT uiClipBordFormat,
PixelFormat nTargetPixelFormat, int iTargetMaxWidth, int iTargetMaxHeight) noexcept
{
if (uiClipBordFormat != CF_DIB && uiClipBordFormat != CF_BITMAP)
return NULL;
if(::OpenClipboard(NULL) == FALSE)
{
CConsole::WriteText(CConsole::__ERROR, L"CBitmap::FromClipBoard()
Unable to open clip board (for paste).\n");
return NULL;
}
BOOL bCopyResult = false;
std::unique_ptr<CDDBitmap, CDDBitmapDeleter>
pbmpIntermediate(nullptr, CDDBitmapDeleter());
1.a. Process DIBSecttion source data in case of CF_DIB clipboard format.
if (uiClipBordFormat == CF_DIB)
{
GLOBALHANDLE hClipboardData = (GLOBALHANDLE)::GetClipboardData(uiClipBordFormat);
BITMAPINFOHEADER* pbmihSource = (BITMAPINFOHEADER*)::GlobalLock(hClipboardData);
BYTE* pbyDIBBits = (BYTE*)(pbmihSource + 1);
CDDBitmap* pbmpIntermediateBitmap = NULL;
1.a.I. Create a local DDBitmap in case source data are in 16bpp, 24bpp or 32bpp and assume the pixel data can be processed 'as is'.
if (pbmihSource->biBitCount == 16 || pbmihSource->biBitCount == 24 ||
pbmihSource->biBitCount == 32)
{
if (pbmihSource->biBitCount == 16)
NEW_REGISTRATION_CALL___DBG(pbmpIntermediateBitmap =
new CDDBitmap(pbmihSource->biWidth, pbmihSource->biHeight,
CBitmap::PixelFormat::Format16bppRgb565, (AARRGGBB*)NULL,
(RGBQUAD*)NULL, pbyDIBBits));
else if (pbmihSource->biBitCount == 24)
NEW_REGISTRATION_CALL___DBG(pbmpIntermediateBitmap =
new CDDBitmap(pbmihSource->biWidth, pbmihSource->biHeight,
CBitmap::PixelFormat::Format24bppRgb, (AARRGGBB*)NULL,
(RGBQUAD*)NULL, pbyDIBBits));
else
NEW_REGISTRATION_CALL___DBG(pbmpIntermediateBitmap =
new CDDBitmap(pbmihSource->biWidth, pbmihSource->biHeight,
CBitmap::PixelFormat::Format32bppArgb, (AARRGGBB*)NULL,
(RGBQUAD*)NULL, pbyDIBBits));
::GlobalUnlock(hClipboardData);
pbmpIntermediate.reset(pbmpIntermediateBitmap);
if (pbmpIntermediateBitmap == NULL ||
pbmpIntermediateBitmap->GetHBITMAP() == NULL)
CConsole::WriteText(CConsole::__ERROR, L"CBitmap::FromClipBoard()
Unable to create intermediate bitmap.\n");
else
bCopyResult = TRUE;
}
1.a.II. Or create a local DDDBitmap in case source data are in 4bpp or 8bpp and assume the pixel data are in the color space of the respective device.
else
{
CClientDC dcScreen(NULL);
CMemoryDC dcSource(dcScreen.CreateCompatibleDC());
HBITMAP hbmpBufSource = ::CreateBitmap(pbmihSource->biWidth,
pbmihSource->biHeight, pbmihSource->biPlanes, pbmihSource->biBitCount, NULL);
HGDIOBJ hgdiOldSource = dcSource.SelectObject(hbmpBufSource);
bCopyResult = ::StretchDIBits(dcSource.GetHDC(),
0, 0, pbmihSource->biWidth, pbmihSource->biHeight,
0, 0, pbmihSource->biWidth, pbmihSource->biHeight,
pbyDIBBits, (BITMAPINFO*)pbmihSource,
DIB_RGB_COLORS, SRCCOPY);
::GlobalUnlock(hClipboardData);
dcSource.SelectObject(hgdiOldSource);
NEW_REGISTRATION_CALL___DBG(pbmpIntermediateBitmap = new CDDBitmap(hbmpBufSource));
pbmpIntermediate.reset(pbmpIntermediateBitmap);
if (pbmpIntermediateBitmap == NULL ||
pbmpIntermediateBitmap->GetHBITMAP() == NULL)
{
CConsole::WriteText(CConsole::__ERROR, L"CBitmap::FromClipBoard()
Unable to create intermediate bitmap.\n");
bCopyResult = FALSE;
}
}
}
1.b. Or process DDBitmap source data in case of CF_BITMAP clipboard format.
else
{
HBITMAP hbmpClipboard = (HBITMAP)::GetClipboardData(uiClipBordFormat);
BITMAP bmClipboard;
if (::GetObjectW(hbmpClipboard, sizeof(BITMAP), &bmClipboard) == 0)
CConsole::WriteText(CConsole::__ERROR, L"CBitmap::FromClipBoard()
Unable to access the clipboard's bitmap meta data.\n");
else
{
1.b.I. Create a local DDDBitmap and assume the pixel data are in the color space of the respective device.
CClientDC dcScreen(NULL);
CMemoryDC dcSource(dcScreen.CreateCompatibleDC());
CMemoryDC dcTarget(dcScreen.CreateCompatibleDC());
HBITMAP hbmpBufTarget = dcScreen.CreateCompatibleBitmap
(bmClipboard.bmWidth, bmClipboard.bmHeight);
HGDIOBJ hgdiOldSource = dcSource.SelectObject(hbmpClipboard);
HGDIOBJ hgdiOldTarget = dcTarget.SelectObject(hbmpBufTarget);
bCopyResult = ::BitBlt(dcTarget.GetHDC(), 0, 0, bmClipboard.bmWidth,
bmClipboard.bmHeight, (HDC)dcSource, 0, 0, SRCCOPY);
dcTarget.SelectObject(hgdiOldTarget);
dcSource.SelectObject(hgdiOldSource);
CDDBitmap* pbmpIntermediateBitmap = NULL;
if (bCopyResult == TRUE)
NEW_REGISTRATION_CALL___DBG(pbmpIntermediateBitmap =
new CDDBitmap(hbmpBufTarget));
pbmpIntermediate.reset(pbmpIntermediateBitmap);
if (pbmpIntermediateBitmap == NULL || pbmpIntermediateBitmap->GetHBITMAP() == NULL)
{
CConsole::WriteText(CConsole::__ERROR, L"CBitmap::FromClipBoard()
Unable to create intermediate bitmap.\n");
bCopyResult = FALSE;
}
}
}
::CloseClipboard();
2. Convert the local DDBitmap to a local DIBSection with the same color depth as the target icon image.
HDIBSECTION hbmpTarget = NULL;
if (bCopyResult == TRUE)
{
BITMAPINFO* pbmiIntermediate = NULL;
if ((pbmiIntermediate = pbmpIntermediate->CreateCompatibleBitmapInfo()) != NULL)
{
if (nTargetPixelFormat == PixelFormat::Format4bppIndexed &&
pbmiIntermediate->bmiHeader.biBitCount >= 4)
{
hbmpTarget = pbmpIntermediate->CopyTo4bpp();
}
else if (nTargetPixelFormat == PixelFormat::Format8bppIndexed &&
pbmiIntermediate->bmiHeader.biBitCount >= 8)
{
hbmpTarget = pbmpIntermediate->CopyTo8bpp();
}
else
{
CConsole::WriteText(CConsole::__ERROR, L"CBitmap::FromClipBoard()
Unable to copy from intermediate bitmap's bits
to target bitmap's bits for ?bpp ==> ?bpp.\n");
}
FREE_CODE_BLOCK___DBG(pbmiIntermediate)
}
else
CConsole::WriteText(CConsole::__ERROR, L"CBitmap::FromClipBoard()
Unable to investigate intermediate bitmap's BITMAPINFO.\n");
}
else
CConsole::WriteErrorMessageFromID(CConsole::__ERROR, L"CBitmap::FromClipBoard()
Unable to copy from clip board bitmap's bits to intermediate
bitmap's bits! Error code is: %d\n", ::GetLastError());
return hbmpTarget;
}
There is a lot of code hidden by the CDDBitmap
constructor and CDDBitmap::CopyTo4bpp()
or CDDBitmap::CopyTo4bpp()
. The download contains the complete sources.
The last two steps of my approach are realized by CPixelEdit::ReplaceRegion()
.
3. Match color palettes of the local DIBSection and the target icon image. Test and prepare prerequisites.
bool CPixelEdit::ReplaceRegion(HDIBSECTION hdibsSource, RECT rcImageRegion, bool bMirror)
{
if (_pIconImage == NULL || hdibsSource == NULL)
return false;
if (rcImageRegion.right <= rcImageRegion.left ||
rcImageRegion.bottom <= rcImageRegion.top)
return false;
BITMAP bmpSource;
if (::GetObjectW(hdibsSource, sizeof(BITMAP), &bmpSource) == 0)
{
CConsole::WriteText(CConsole::__ERROR, L"CPixelEdit::ReplaceRegion()
Unable to determine the source bitmap's 'BITMAP' structure.\n");
return false;
}
if (bmpSource.bmBitsPixel != 8 && bmpSource.bmBitsPixel != 4 &&
bmpSource.bmBitsPixel != 1)
{
CConsole::WriteText(CConsole::__ERROR, L"CPixelEdit::ReplaceRegion()
Unable to process a source bitmap that is not 8bpp,
4bpp or 1bpp, because it uses direct colors
rather than a color palette.\n");
return false;
}
BITMAPINFO* pbmiSource = NULL;
RGBQUAD* prgbqSourceColorPalette = NULL;
BYTE* pbySourceBits = NULL;
int iPaletteSize = CDIBSection::GetDIBitmapData(hdibsSource,
&pbmiSource, &prgbqSourceColorPalette, &pbySourceBits);
if (iPaletteSize < 0)
{
if (pbySourceBits != NULL)
FREE_CODE_BLOCK___DBG(pbySourceBits)
if (prgbqSourceColorPalette != NULL)
FREE_CODE_BLOCK___DBG(prgbqSourceColorPalette)
if (pbmiSource != NULL)
FREE_CODE_BLOCK___DBG(pbmiSource)
CConsole::WriteText(CConsole::__ERROR, L"CPixelEdit::ReplaceRegion()
Unable to determine the source bitmap's meta data,
color palette and/or pixel bytes.\n");
return false;
}
3.a. Prepare source and target color palettes.
std::vector<BYTE> aSourceColorIndexVector = CDIBSection::DetermineUsedColorIndexes
((BYTE*)bmpSource.bmBits, bmpSource.bmBitsPixel, bmpSource.bmWidth, bmpSource.bmHeight);
if (prgbqSourceColorPalette == NULL)
{
CConsole::WriteText(CConsole::__ERROR, L"CPixelEdit::ReplaceRegion()
Unable to determine the source bitmap's used color palette indexes.\n");
return false;
}
CByteToAarrggbbMap oSourceUsedColors;
for (auto it = aSourceColorIndexVector.begin(); it < aSourceColorIndexVector.end(); it++)
oSourceUsedColors[*it] = RGBQUADtoAARRGGBB(prgbqSourceColorPalette[*it]);
size_t nMaxColors = 16;
CByteToAarrggbbMap oTargetUsedColors;
if (_pIconImage->iiHeader.biBitCount == 8)
{
nMaxColors = 256;
for (int nRowCount = 0; nRowCount < _pIconImage->iiHeader.biHeight; nRowCount++)
{
for (int nColCount = 0; nColCount < _pIconImage->iiHeader.biWidth; nColCount++)
{
BYTE byColorIndex = CIcon::BmpXORgetColor8bpp
(_pIconImage->iiXOR, nColCount, nRowCount,
_pIconImage->iiHeader.biWidth, _pIconImage->iiHeader.biHeight);
oTargetUsedColors[byColorIndex] = _pIconImage->iiColors[byColorIndex];
}
}
}
else if (_pIconImage->iiHeader.biBitCount == 4)
{
nMaxColors = 16;
for (int nRowCount = 0; nRowCount < _pIconImage->iiHeader.biHeight; nRowCount++)
{
for (int nColCount = 0; nColCount < _pIconImage->iiHeader.biWidth; nColCount++)
{
BYTE byColorIndex = CIcon::BmpXORgetColor4bpp
(_pIconImage->iiXOR, nColCount, nRowCount,
_pIconImage->iiHeader.biWidth, _pIconImage->iiHeader.biHeight);
oTargetUsedColors[byColorIndex] = _pIconImage->iiColors[byColorIndex];
}
}
}
else
{
nMaxColors = 2;
for (int nRowCount = 0; nRowCount < _pIconImage->iiHeader.biHeight; nRowCount++)
{
for (int nColCount = 0; nColCount < _pIconImage->iiHeader.biWidth; nColCount++)
{
BYTE byColorIndex = CIcon::BmpXORgetColor1bpp
(_pIconImage->iiXOR, nColCount, nRowCount,
_pIconImage->iiHeader.biWidth, _pIconImage->iiHeader.biHeight);
oTargetUsedColors[byColorIndex] = _pIconImage->iiColors[byColorIndex];
}
}
}
3.b. Integrate source palette colors into target palette.
std::map<BYTE, BYTE> aSourceToTargetColorTranslations;
for (auto it = oSourceUsedColors.begin(); it != oSourceUsedColors.end(); it++)
{
float fDistance = 0.0f;
BYTE byInitialMatch = CBitmap::FindBestMatchingIndexARGB
(_pIconImage->iiColors, nMaxColors, it->second, &fDistance);
if (fDistance == 0.0f)
{
aSourceToTargetColorTranslations[it->first] = byInitialMatch;
}
else if (oTargetUsedColors.size() < nMaxColors - 1)
{
BYTE byFirstUnusedKey = oTargetUsedColors.FindFirstUnunsedKey();
oTargetUsedColors[byFirstUnusedKey] = it->second;
aSourceToTargetColorTranslations[it->first] = byFirstUnusedKey;
}
else
{
BYTE byBestMatch = CBitmap::FindBestMatchingIndexARGB
(_pIconImage->iiColors, nMaxColors, it->second);
aSourceToTargetColorTranslations[it->first] = byBestMatch;
}
}
while (_aUndoActionChain.Count() >= _nPreferredUndoRedoStackSize)
_aUndoActionChain.Shrink();
CPixelEditUndoRedoAction* pUndoRedoAction = NULL;
for (auto it = oTargetUsedColors.begin(); it != oTargetUsedColors.end(); it++)
{
if (_pIconImage->iiColors[it->first] != it->second)
{
if (pUndoRedoAction == NULL)
{
NEW_REGISTRATION_CALL___DBG(pUndoRedoAction =
new CPixelEditUndoRedoAction(it->first,
_pIconImage->iiColors[it->first], it->second));
_aUndoActionChain.Push(pUndoRedoAction);
}
else
pUndoRedoAction->AddPaletteManipulation(it->first,
_pIconImage->iiColors[it->first], it->second);
}
}
4. Transfer the pixels of the local DIBSection to the target icon image.
DWORD dwSourcePixelBytesPerRow =
bmpSource.bmWidthBytes;
DWORD dwTargetPixelBytesPerRow = CBitmap::BytesPerRow(_pIconImage->iiHeader.biWidth,
_pIconImage->iiHeader.biPlanes,
_pIconImage->iiHeader.biBitCount,
CIcon::StrideAlignment());
for (LONG lRow = 0; lRow < _pIconImage->iiHeader.biHeight; lRow++)
{
if (lRow < rcImageRegion.top || lRow > rcImageRegion.bottom)
continue;
if (lRow - rcImageRegion.top >= bmpSource.bmHeight)
continue;
DWORD dwSourceRowBytesOffset = dwSourcePixelBytesPerRow *
(bMirror == true ? bmpSource.bmHeight - 1 -
(lRow - rcImageRegion.top) : (lRow - rcImageRegion.top));
for (LONG lCol = 0; lCol < _pIconImage->iiHeader.biWidth; lCol++)
{
if (lCol < rcImageRegion.left || lCol > rcImageRegion.right)
continue;
if (lCol - rcImageRegion.left >= bmpSource.bmWidth)
continue;
BYTE byOldPalIndex = 0;
BYTE byNewPalIndex = 0;
if (bmpSource.bmBitsPixel == 8)
{
byNewPalIndex = pbySourceBits[dwSourceRowBytesOffset +
(lCol - rcImageRegion.left) ];
}
else if (bmpSource.bmBitsPixel == 4)
{
byNewPalIndex = pbySourceBits[dwSourceRowBytesOffset +
(lCol - rcImageRegion.left) / 2];
if ((lCol - rcImageRegion.left) % 2 == 0)
byNewPalIndex = (byNewPalIndex & 0xF0) >> 4;
else
byNewPalIndex = (byNewPalIndex & 0x0F);
}
else
{
byNewPalIndex = pbySourceBits[dwSourceRowBytesOffset +
lCol / 8 - rcImageRegion.left / 8];
if (lCol % 8 == 0)
byNewPalIndex = (byNewPalIndex & 0x80) >> 7;
else if (lCol % 8 == 1)
byNewPalIndex = (byNewPalIndex & 0x40) >> 6;
else if (lCol % 8 == 2)
byNewPalIndex = (byNewPalIndex & 0x20) >> 5;
else if (lCol % 8 == 3)
byNewPalIndex = (byNewPalIndex & 0x10) >> 4;
else if (lCol % 8 == 4)
byNewPalIndex = (byNewPalIndex & 0x08) >> 3;
else if (lCol % 8 == 5)
byNewPalIndex = (byNewPalIndex & 0x04) >> 2;
else if (lCol % 8 == 6)
byNewPalIndex = (byNewPalIndex & 0x02) >> 1;
else
byNewPalIndex = (byNewPalIndex & 0x01);
}
if (aSourceToTargetColorTranslations.find(byNewPalIndex)
!= aSourceToTargetColorTranslations.end())
byNewPalIndex = aSourceToTargetColorTranslations[byNewPalIndex];
if (_pIconImage->iiHeader.biBitCount == 8)
byOldPalIndex = CIcon::BmpXORgetColor8bpp(_pIconImage->iiXOR, lCol,
lRow, _pIconImage->iiHeader.biWidth, _pIconImage->iiHeader.biHeight);
else if (_pIconImage->iiHeader.biBitCount == 4)
byOldPalIndex = CIcon::BmpXORgetColor4bpp(_pIconImage->iiXOR, lCol,
lRow, _pIconImage->iiHeader.biWidth, _pIconImage->iiHeader.biHeight);
else
byOldPalIndex = CIcon::BmpXORgetColor1bpp(_pIconImage->iiXOR, lCol,
lRow, _pIconImage->iiHeader.biWidth, _pIconImage->iiHeader.biHeight);
if (byOldPalIndex != byNewPalIndex)
{
bool bOldMaskFlag = CIcon::BmpANDgetMask(_pIconImage->iiAND,
lCol, lRow, _pIconImage->iiHeader.biWidth, _pIconImage->iiHeader.biHeight);
if (pUndoRedoAction == NULL)
{
NEW_REGISTRATION_CALL___DBG(pUndoRedoAction =
new CPixelEditUndoRedoAction(lCol, lRow, byOldPalIndex,
byNewPalIndex, bOldMaskFlag, false));
_aUndoActionChain.Push(pUndoRedoAction);
}
else
pUndoRedoAction->AddPixelManipulation(lCol, lRow, byOldPalIndex,
byNewPalIndex, bOldMaskFlag, false);
}
}
}
4. Transfer the pixels of the local DIBSection to the target icon image. Finalize.
if (pbySourceBits != NULL)
FREE_CODE_BLOCK___DBG(pbySourceBits)
if (prgbqSourceColorPalette != NULL)
FREE_CODE_BLOCK___DBG(prgbqSourceColorPalette)
if (pbmiSource != NULL)
FREE_CODE_BLOCK___DBG(pbmiSource)
if (pUndoRedoAction != NULL)
{
pUndoRedoAction->ExecuteAll(this);
_aRedoActionChain.Clear();
}
return true;
}
There is a lot of code hidden by the CBitmap::FindBestMatchingIndexARGB()
, CIcon::BmpXORgetColor...bpp()
or CIcon::BmpANDgetMask()
. In addition, the changes to the color palette and the pixel bits are not carried out directly, but are realized via Undo/Redo actions. The download contains the complete sources (that also include the hidden code).
Points of Interest
Unfortunately, the claim of the Win32 API to be as generic as possible leads to the fact that its use is error-prone. But once light has been shed on Bitmap
, DDB
and DIBSection
, the rest is just a task of diligence.
History
- 28th December, 2020: Initial tip