Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Approach to Paste a Bitmap into an Icon Image on ReactOS (and Consequently on Windows XP and Newer Versions, using Win32 API)

0.00/5 (No votes)
27 Dec 2020 1  
Step-by-step instructions and source code snippets for inserting a bitmap (or a section of it) into an icon image.
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:

/// <summary>
/// The ICONIMAGE structure provides all display data associated to one single image 
/// within an icon. Hint: Icons can contain multiple images!
/// </summary>
typedef struct tagICONIMAGE
{
    // ATTENTION: This structure is probably mirrored within the application.
    // Don't change this structure!

    /// <summary>
    /// The bitmap meta data for XOR (color) and AND (mask) bitmaps, 
    /// except that mask bitmap has fixed and always 1 plane as well as 1 bpp format.
    /// </summary>
    BITMAPINFOHEADER     iiHeader; // DIB header

    /// <summary>
    /// The color table, if any. Hint: Sometimes Windows 
    /// creates a color table of three colors - even if no color table is used at all.
    /// </summary>
    AARRGGBB*            iiColors; // Color table as DWORD[]: AARRGGBB format

    /// <summary>
    /// The DIB bits for XOR (color) image. Hint: Currently supported are 4bpp, 
    /// 8bpp or 32bpp format.
    /// </summary>
    BYTE*                iiXOR;    // DIB bits for color pixel

    /// <summary>
    /// The DIB bits for AND (mask) image. Hint: Always 1 plane and as 1 bpp format.
    /// </summary>
    BYTE*                iiAND;    // DIB bits for AND mask
} 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 DDBitmaps (device dependent bitmaps / device compatible bitmaps) and DIBSections (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 DDBitmaps, HBITMAP, there is no handle for DIBSections. Instead, HBITMAP is typically used for both.

And to top it all off, it is also possible to create DIBSections with the pixel formats 16bpp, 24bpp and 32bpp.

Hint: Currently, I use a stride alignment for DIBSections of fixed 4 BYTE (== 2 WORD == DWORD) while the stride alignment for DDBitmaps 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 DIBSections, HDIBSECTION, and made a clear separation in code between class CDDBitmap : public CBitmap and class CDIBSection : public CBitmap.

/// <summary>
/// This is a real <c>HBITMAP</c> but created by ::<c>CreateDIBSection()</c>.
/// Which means ::<c>GetObject()</c> can be called with <c>DIBSECTION</c> (and <c>BITMAP</c>).
/// </summary>
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 DDBitmaps never have a color palette and always use the color space of the respective device.

While the deeper sense of DIBSections 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 DDBitmaps and CF_DIB for DIBSections, 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:

  1. Convert DDBitmap or DIBSection from clipboard to a local DDBitmap with maximum available color depth (so it is as lossless as possible).
    1. Process DIBSection source data in case of CF_DIB clipboard format.
      1. Create a local DDBitmap in case source data are in 16bpp, 24bpp or 32bpp and assume the pixel data can be processed 'as is'.
      2. 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.
    2. Or process DDBitmap source data in case of CF_BITMAP clipboard format.
      1. Create a local DDDBitmap and assume the pixel data are in the color space of the respective device.
  2. Convert the local DDBitmap to a local DIBSection with the same color depth as the target icon image.
  3. Match color palettes of the local DIBSection and the target icon image.
    1. Prepare source and target color palettes.
    2. Integrate source palette colors into target palette.
  4. 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;

    // ----------------------------------
    // Incorporate clipboard bitmap data.
    // ----------------------------------
    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)
{
    // --------------------------
    // Process DIBSection source.
    GLOBALHANDLE      hClipboardData  = (GLOBALHANDLE)::GetClipboardData(uiClipBordFormat);
    BITMAPINFOHEADER* pbmihSource     = (BITMAPINFOHEADER*)::GlobalLock(hClipboardData);
    BYTE*             pbyDIBBits      = (BYTE*)(pbmihSource + 1);

    // ----------------------------------------------------------------------------------
    // To prevent palette color loss, an intermediate DD bitmap GDI object is needed
    // (since DI bitmap GDI objects must be in sync with device logical palette).
    // ----------------------------------------------------------------------------------
    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)
{
    // ------------------------------------------------
    // Process high color and true color source bitmap.
    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 // (pbmihSource->biBitCount == 32)
        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
    {
        // ------------------------------------
        // Process indexed color source bitmap.
        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 // (uiClipBordFormat == CF_BITMAP)
{
    // --------------------------
    // Process DDBitmap source.
    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)
            {
                // Copy 8/16/24/32bpp ==> 4bpp.
                hbmpTarget = pbmpIntermediate->CopyTo4bpp();
            }
            else if (nTargetPixelFormat == PixelFormat::Format8bppIndexed && 
                                           pbmiIntermediate->bmiHeader.biBitCount >= 8)
            {
                // Copy 16/24/32bpp ==> 8bpp.
                hbmpTarget = pbmpIntermediate->CopyTo8bpp();
            }
            else
            {
                // Copy 16/24/32bpp ==> 8bpp.
                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)
{
    // -------------------
    // Test prerequisites.
    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;
    }

    // ----------------------------
    // Get source DIB section data.
    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.

// -----------------------------
// Prepare source color palette.
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]);

// -----------------------------
// Prepare target color palette.
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.

// ------------------------------------------------------------------------
// Integrate source palette colors into target palette as long as possible.
// Otherwise translate the source color to the best matching target color.
std::map<BYTE, BYTE> aSourceToTargetColorTranslations;
for (auto it = oSourceUsedColors.begin(); it != oSourceUsedColors.end(); it++)
{
    // Integrate by exact source to target color match.
    float fDistance      = 0.0f;
    BYTE  byInitialMatch = CBitmap::FindBestMatchingIndexARGB
                           (_pIconImage->iiColors, nMaxColors, it->second, &fDistance);
    if (fDistance == 0.0f)
    {
        aSourceToTargetColorTranslations[it->first] = byInitialMatch;
    }
    // Integrate by source to target color transfer.
    else if (oTargetUsedColors.size() < nMaxColors - 1)
    {
        BYTE byFirstUnusedKey = oTargetUsedColors.FindFirstUnunsedKey();
        oTargetUsedColors[byFirstUnusedKey] = it->second;
        aSourceToTargetColorTranslations[it->first] = byFirstUnusedKey;
    }
    // Integrate by best possible source to target color match.
    else
    {
        BYTE byBestMatch = CBitmap::FindBestMatchingIndexARGB
                           (_pIconImage->iiColors, nMaxColors, it->second);
        aSourceToTargetColorTranslations[it->first] = byBestMatch;
    }
}

while (_aUndoActionChain.Count() >= _nPreferredUndoRedoStackSize)
    _aUndoActionChain.Shrink();

// ----------------------------------------------------------------------
// Refresh target palette colors after source palette colors integration.
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.

// ------------------------------------------------------
// Transfer source DIB section bits to target icon image.
DWORD dwSourcePixelBytesPerRow  =
          bmpSource.bmWidthBytes; // CBitmap::BytesPerRow(bmpSource.bmWidth,
          //bmpSource.bmPlanes, bmpSource.bmBitsPixel, CDIBSection::StrideAlignment());
DWORD dwTargetPixelBytesPerRow  = CBitmap::BytesPerRow(_pIconImage->iiHeader.biWidth,
                                  _pIconImage->iiHeader.biPlanes,
                                  _pIconImage->iiHeader.biBitCount,
                                  CIcon::StrideAlignment());

// Transfer source color bitmap bits.
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 // (bmpSource.bmBitsPixel == 1)
        {
            // Every BYTE stores the color index of eight pixel.
            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 // (lCol % 8 == 7)
                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 // (_pIconImage->iiHeader.biBitCount)
            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)

    // -----------------------------
    // Success: Execute all changes.
    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

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here