65.9K
CodeProject 正在变化。 阅读更多。
Home

在 ReactOS(以及由此衍生的 Windows XP 及更新版本,使用 Win32 API)中将位图粘贴到图标图像的方法

starIconstarIconstarIconstarIconstarIcon

5.00/5 (4投票s)

2020 年 12 月 28 日

CPOL

6分钟阅读

viewsIcon

5849

downloadIcon

145

将位图(或其一部分)插入图标图像的分步说明和源代码片段。

引言

本技巧总结了在两个程序之间交换位图的许多零散信息。所有这些信息都已在互联网上的各种文章中讨论过,但要找到正确的文章(而不是被错误的干扰)并将它们包含的信息成功地结合起来,这才至关重要。我使用我自写的图标编辑器(参见文章“在 ReactOS(以及由此衍生的 Windows XP 及更新版本)上运行的基本图标编辑器”)作为其中一个程序,Windows 画图作为另一个程序。此方法已在 ReactOS 0.4.13 上的 Code::Blocks 和 Windows 10 64 位(在 32 位子系统中)上的 Microsoft Visual Studio 2019 上进行了测试。

我写这个技巧是为了让其他开发者免于收集关于位图转换的所有必要信息的麻烦。

背景

图标

图标(*.ico 文件)包含一个或多个图像。图标图像由元数据(位图信息头)、彩色位图(XOR 位图)、蒙版位图(AND 位图)以及可选的颜色调色板组成。任何可能的像素格式(32bpp / 24bpp = 真彩色,16bpp = 高彩色,8bpp = 256 色调色板,4bpp = 16 色调色板)都可以用于彩色位图。蒙版位图始终是 1bpp = 黑/白。

通常,图标图像使用 8bpp = 256 色调色板(标准调色板颜色)。我的图标编辑器偏好 4bpp = 16 色调色板(自定义/用户采纳的颜色)。这是因为大多数图标可以用 16 种自定义颜色比 256 种标准颜色更紧凑、更精确地定义。

这是我用于图标图像的结构

/// <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;

请阅读文章“无资源嵌入式图标简介 - 在 ReactOS 上”以获取有关图标的更多详细信息。

提示:XOR(彩色)图像和 AND(蒙版)图像的 DIB 位图的步幅(stride)对齐固定为 4 字节(== 2 == 双字)。

位图

当使用位图一词时,如果您想深入了解位图的内部细节,事情会变得令人困惑。一方面,位图一词用作 DDBitmap(设备相关位图/设备兼容位图)和 DIBSection(设备无关位图/设备不兼容位图)的通用术语。另一方面,它也用作 DDBitmapDIBSection 的缩写形式。不幸的是,Win32 API 文档也是如此。

这种混淆还源于 Win32 结构 DIBSECTION 可以轻松地转换为 Win32 结构 BITMAP(因为 BITMAPDIBSECTION 的第一个成员),因此并不常见。

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;

此外,虽然 DDBitmap 有一个句柄 HBITMAP,但 DIBSection 没有句柄。相反,HBITMAP 通常用于两者。

更糟糕的是,还可以使用 16bpp、24bpp 和 32bpp 的像素格式创建 DIBSection

提示:目前,我为 DIBSection 使用固定 4 字节(== 2 == 双字)的步幅(stride)对齐,而 DDBitmap 的步幅(stride)对齐是计算得出的,通常为 2 字节(== )。

我的解决方案

我发现这非常令人困惑,并且花了很多时间来正确处理位图。然后,我为 DIBSection 引入了一个单独的句柄 HDIBSECTION,并在代码中清楚地区分了 class CDDBitmap : public CBitmapclass 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);

因此,如果我的代码中有一个 HDIBSECTIONCDIBSection 实例,我可以确信该实例是基于 ::CreateDibSection() 调用创建的。如果我的代码中有一个 HBITMAPCDDBitmap 实例,我可以确信它是基于 ::CreateCompatibleBitmap() 调用创建的。

结论

最终,DDBitmap 永远没有颜色调色板,并且始终使用相应设备的颜色空间。

DIBSection 的深层意义在于独立于任何设备的颜色空间 - 因此它们需要与 1bpp、4bpp 和 8bpp 像素格式中的颜色调色板相关联。

将位图粘贴到图标图像中的方法

要从剪贴板粘贴到您自己的程序中的位图的像素格式无法预先确定。剪贴板区分 CF_BITMAP(用于 DDBitmap)和 CF_DIB(用于 DIBSection),但即使这样,也无法预先确定像素格式。由于使用 Win32 API 将 DIBSection 转换为 DDBitmap 非常简单,我决定采用以下方法:

  1. 将剪贴板中的 DDBitmapDIBSection 转换为本地 DDBitmap,并具有可用的最大颜色深度(以尽可能无损)。
    1. 如果剪贴板格式为 CF_DIB,则处理 DIBSection 源数据。
      1. 如果源数据为 16bpp、24bpp 或 32bpp,则创建本地 DDBitmap,并假定像素数据可以“按原样”处理。
      2. 或者,如果源数据为 4bpp 或 8bpp,则创建本地 DDDBitmap,并假定像素数据位于相应设备的颜色空间中。
    2. 或者,如果剪贴板格式为 CF_BITMAP,则处理 DDBitmap 源数据。
      1. 创建本地 DDDBitmap,并假定像素数据位于相应设备的颜色空间中。
  2. 将本地 DDBitmap 转换为本地 DIBSection,其颜色深度与目标图标图像相同。
  3. 匹配本地 DIBSection 和目标图标图像的颜色调色板。
    1. 准备源调色板和目标调色板。
    2. 将源调色板颜色集成到目标调色板中。
  4. 将本地 DIBSection 的像素传输到目标图标图像。

Using the Code

我的方法的最后两个步骤由 CBitmap::FromClipBoard() 实现。

1. 将剪贴板中的 DDBitmap 或 DIBSection 转换为本地 DDBitmap,并具有可用的最大颜色深度(以尽可能无损)。测试并准备先决条件。

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. 如果剪贴板格式为 CF_DIB,则处理 DIBSecttion 源数据。

    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. 如果源数据为 16bpp、24bpp 或 32bpp,则创建本地 DDBitmap,并假定像素数据可以“按原样”处理。
        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. 或者,如果源数据为 4bpp 或 8bpp,则创建本地 DDDBitmap,并假定像素数据位于相应设备的颜色空间中。
        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. 或者,如果剪贴板格式为 CF_BITMAP,则处理 DDBitmap 源数据。

    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. 创建本地 DDDBitmap,并假定像素数据位于相应设备的颜色空间中。
            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. 将本地 DDBitmap 转换为本地 DIBSection,其颜色深度与目标图标图像相同。

    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;
}

CDDBitmap 构造函数和 CDDBitmap::CopyTo4bpp()(此处重复,应为 CopyTo8bpp() 或其他)隐藏了大量代码。下载内容包含完整的源代码。

我的方法的最后两个步骤由 CPixelEdit::ReplaceRegion() 实现。

3. 匹配本地 DIBSection 和目标图标图像的颜色调色板。测试并准备先决条件。

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 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 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. 将本地 DIBSection 的像素传输到目标图标图像。

    // ------------------------------------------------------
    // 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. 将本地 DIBSection 的像素传输到目标图标图像。完成。

    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;
}

CBitmap::FindBestMatchingIndexARGB()CIcon::BmpXORgetColor...bpp()CIcon::BmpANDgetMask() 隐藏了大量代码。此外,对颜色调色板和像素位的更改不是直接进行的,而是通过撤销/重做操作实现的。下载内容包含完整的源代码(其中也包含隐藏的代码)。

关注点

不幸的是,Win32 API 追求尽可能通用的设计,导致其使用容易出错。但是,一旦弄清楚了 BitmapDDBDIBSection,其余的就只是勤奋的问题了。

历史

  • 2020 年 12 月 28 日:初始技巧
© . All rights reserved.