A Basic Icon Editor Running on ReactOS (and Consequently on Windows XP and Newer Versions)






4.96/5 (17 votes)
Creation of a basic icon editor with as little code as possible, that is running on ReactOS and Windows, to check out the stability of application development capabilities on ReactOS
- Download ReactOS/CodeBlocks souce and debug compilation V0.6 (C#/C++)
Older versions: V0.5 (C++), V0.4 (C++), V0.3 (C++), V0.2 (C++), V0.1 (C++) - Download Windows/VisualStudio souce and debug compilation V0.6_(C#/C++)
Older versions: V0.5 (C++), V0.4 (C++), V0.3 (C++), V0.2 (C++), V0.1 (C++) - Download documentation generated with DoxyGen V0.6_Doxygen_Editor.zip and V0.6_Doxygen_OGWW.zip;
Older versions: V0.5_Doxygen_Editor.zip, 0.4_Doxygen_Editor.zip and V0.5_Doxygen_OGWW.zip, V0.4_Doxygen_OGWW.zip
Contents
- Introduction
- Using the Code
- The Inside of an Icon
- Flickering Minimization
- Memory Leak Detection
- Microsoft Visual Studio (VC++) Compatibility
- Point of Interest
- History
Introduction
ReactOS is an open source alternative to the Windows operation system. Even if the first version of ReactOS dates back to 1998, there is still no 'stable' version of ReactOS. Maybe, the most important reason is a lack of attention.
Driven by the tip, Introduction to Embedded Icons without Resource on ReactOS, I wanted to dive deeper into the topic of Windows icons and looked for a free icon editor for ReactOS, that was simple enough to use without a long study of the docs. The best match I've found has been the Junior Icon Editor. This icon editor works fine on all Microsoft Windows versions I've tested and it can be easily installed and started on ReactOS as well. The only drawback is that the color palet for 4 bpp icons (16 color icons) does not work (tested on ReactOS version 0.4.11 with Junior Icon Editor version 4.39).
The Junior Icon Editor's color palet for a 8 bpp icon (256 color icon) on ReactOS: |
| The Junior Icon Editor's color palet for a 4 bpp icon (16 color) on ReactOS: |
|
My primary focus is on 4 bpp icons. Although it is more likely that the Junior Icon Editor's color palette problem is caused by a missing 4bpp default palette in ReactOS than a bug in the Junior Icon Editor itself, it is hardly possible to create or edit 4bpp icons.
Because of this drawback and since I wanted to test the findings from the tips, Introduction to OpenGL with C/C++ on ReactOS and Introduction to C# on ReactOS on a real application, I started to program my own basic icon editor - the ReactOS Icon Editor. This application is inspired by the simplicity and intuitive handling of the Visual Studio Icon Editor.
Updates
Version 0.2
- New: The 16 colors of the 4bpp color palette can be individualized. A double click on the color opens the color dialog of ReactOS and the color value can be set individually. Advantage: The 4bpp palette can also be used to create appealing icons. Disadvantage: Not all icon editors support individualized colors and reset the colors to the default palette - e.g. the Visual Studio icon editor.
- New: The Pipette Tool is implemented. Now colors can also be selected within the image.
- New: The menu items now also support icons (13x13 pixels) including transparency. They are automatically switched to ownerdraw. Advantage: The native support of menu item bitmaps is originally intended for 1bpp images and does not support transparency for color images. With icons, transparency is also supported for colored images. Disadvantage: The switch to ownerdraw can lead to inconsistent results when using themes.
- Fix: The "Save as" dialog works now correctly / generates no longer a segmentation fault (wrong parameter 4 in call to
wcsncpy_s()
fixed). - Fix: Segmentation faults have been fixed, that occurred when working with icons whose width/height is not a multiple of 8 (e.g. icons with 13x13 pixels for menu items).
- Upgrade: Improved
OWNERDRAW
menus, which support accelerators now. Provision of accelerators for many menu items.
Version 0.3
This update took quite a long time - but that has to do with my learning curve. Firstly, I found the wonderful Win32++ from David Nash (whose simplicity and clarity impressed me as much as the preemption of inheritance and object orientation in the K&R C Athena Widget Set more than 30 years ago) and secondly, this was the first time I seriously looked into DoxyGen.
- New: An
ASSERT
macro is integrated now, see tip Automatically disappearing dialog, used to provide VERIFY and ASSERT on ReactOS. - New: Findings on
OWNERDRAW
menus are integrated now, see tip Yet another fully functional ownerdraw menu. - Fix: Tool bar separator width has been corrected, see tip Guarantee correct spacing for toolbar separators on multiple TB_ADDBUTTONS calls.
- Upgrade: Complete revision of message processing code - almost all
WM_xxx
messages now callvirtual LRESULT OnXxx()
methods. This results in clearer code, which can easily be read with a little knowledge about Microsoft's MFC or David Nash's Win32++.
You may ask me: Why is my icon editor not based on Win32++?
- I think Win32++ is cool, but I discovered Win32++ very late.
- I am seriously considering to switch to Win32++ - especially because of the already existing stability - but I am not entirely happy with the implementation of the
MenuBar
andToolBar
in mainframe window.- The design of Win32++ impresses me with its clarity and simplicity - but the need for synchronization when developing own controls is still a bit daunting.
Maybe this will change the more professional - and thus the more similar in the program structure to Win32++ - my icon editor becomes.
- New: My first attempt of a color picker uses one
Button
control per color. This is OK for 4bpp (16 colors) mode but leads to 256 controls for 8bpp (256 color) mode. To be ready for 8bpp, I implemented the new controlColorPicker
, that is set up with 8 columns and 2 rowsSetCellCount(SET_SIZE(8, 2))
for 4bpp (and looks the same as the previous solution).and will probably set up with 14 columns and 18 rows
SetCellCount(SET_SIZE(14, 18))
for 8bpp (which makes the icon editor ready to be extended to 8bpp).
- NEW: The fill tool is implemented now
.
- NEW: All code comments in the whole
Ogww
Library are revised in such a way that a meaningful documentation can be generated with DoxyGen. I use the DoxyBlocks plugin from Code::Blocks (and I'm very pleased about the resulting documentation). See also the section "Adding a documentation generator" in my tip Introduction to OpenGL with C/C++ on ReactOS for details on this. - FIX: Memory violation when using
GetDIBits()
has been fixed - at least on Microsoft Windows. Sometimes (based on my observation on color depths that require a color palette)GetDIBits()
writes a minimum color palette into theBITMAPINFOHEADER
. According to the Microsoft Forum, they are 3 colors (3* sizeof(WORD)
). If this is not taken into account, memory violations occur.
Version 0.4
- FIX: The overlapping display (outdated position) of the image buttons with wrong size/position after opening an icon file has been fixed, which must formerly be rectified manually by resizing the window. Fixed by the handover of the window size to
::SendMessage(hWnd, WM_SIZE, (WPARAM)SIZE_RESTORED, ...)
after opening an icon file. - FIX: The display of the old color (outdated RGB value) in
ColorPicker
andPixelEdit
after changing a color of the color palette was fixed, which must formerly be rectified manually by resizing the window. Fixed by a re-paint of theColorPicker
andPixelEdit
with::InvalidateRect(_pweakColorPicker->GetHWnd(), NULL, TRUE)
and::InvalidateRect(_pweakPixelEdit->GetHWnd(), NULL, TRUE)
. - NEW: Added memory leak detection. I've read the great article A Cross-Platform Memory Leak Detector and wondered how good my application is in terms of memory management. See also the section "Memory leak detection" in this article.
- NEW: Added palette color change to the undo/redo chain.
- NEW: All code comments in the whole
IconEditor
is revised in such a way that a meaningful documentation can be generated with DoxyGen. I use the DoxyBlocks plugin from Code::Blocks (and I'm very pleased about the resulting documentation). See also the section "Adding a documentation generator" in my tip Introduction to OpenGL with C/C++ on ReactOS for details on this. - NEW: Ownerdraw buttons support
BS_RADIOBUTTON
andBS_AUTORADIOBUTTON
. This is a function which is supported by the original window controls for autodraw buttons only (because the bit mask ofBS_OWNERDRAW
overlaps other button styles and can't be used simultaneously withBS_CHECKBOX
,BS_AUTOCHECKBOX
,BS_RADIOBUTTON
andBS_AUTORADIOBUTTON
). - NEW: Added initial handling of multi-image icons: Load/save/save as for multi-image icons work. Images can be added to an existing icon (but currently not removed) and the image order can be changed.
Version 0.5
- NEW: Delete image from icon is implemented (the last remaining image can't be removed).
- NEW: Tool bar buttons support tool tips.
- IMPROVED: Methods, that return dynamically allocated strings, no longer use
StringMediator*
butString
. This ensures an automatic garbage collection as before and the code is immediately transparent for every C++ programmer. For details, see tip How to return a string class from a C++ function/method. - NEW: As an alternative to the simple
ToolBar
, it is now possible to use severalToolBar
s within oneReBar
. The firstToolBar
of theReBar
is automatically created as a defaultToolBar
. - IMPROVED: The image of disabled menu items and toolbar buttons are calculated as a gray scale image now, instead of an gray/white image with cast shadow.
- FIXED: The short cuts for UNDO (Ctrl + z) and REDO (Ctrl + y) work now.
- NEW: The select (lasso) tool has been added to capture a rectangle area of pixels as selected (the images below this list show the tool bar button and a sample rectangle area of selected pixels).
- NEW: Basic COPY (Ctrl + c) of the current icon image (e.g., to Paint) and basic PASTE (Ctrl + v) into the current icon image (e.g., from Paint) are added. If a rectangle area of pixels is selected, PASTE affects the selection only. For details, see tip Approach to Paste a Bitmap into an Icon Image on ReactOS (and Consequently on Windows XP and Newer Versions, using Win32 API).
Version 0.6
- FIXED: The methods
CDDBitmap::CopyTo4bppColors()
andCDDBitmap::CopyTo8bppColors()
work now (they have sometimes thrown exceptions during redurction of the color vector for the final color table). - NEW: Small and big icons can be set now for the application farme window.
- IMPROVED: The
C
API has been cleaned up to supportC#
P/Invoke (interop) for the whole API. - NEW: A
C#
equivalent to theC++
application hast been added for ReactOS (editor: Notepad++ with NppExec plugin, compiler: MONO installation mono-4.3.2.467-gtksharp-2.12.30.1-win32-0.msi, see tip Introduction to C# on ReactOS for details) and Windows (Visual Studio). - IMPROVED: The undo/redo chains is no longer connected to the pixel editor control, and thus generic for all icon images, but every icon image has now a local undo/redo chains. In addition, the redo/undo chains remain intact when the icon image is changed.
- IMPROVED: The application now has its own icon - the first meaningful icon created with the application itself.
- FIXED: A memory leak during PASTE (Ctrl + v) into the current icon image has been eliminated.
- NEW: The selected rectangle area of pixels can be scaled now.
Using the Code
The Application
The ReactOS Icon Editor is based on the OpenGL Windows Wrapper (Ogww) DLL, introduced by the tip Introduction to OpenGL with C/C++ on ReactOS. Meanwhile, the DLL has evolved to meet significantly more requirements on a professional UI, but the DLL is still far away from a release state. However, it is still designed to support application development with C/C++ and C#.
Now, the inclined reader will ask: Why Ogww
- yet another wrapper around the Win32 API?
Simple answer: Because I discovered Win32++ only later.
Win32++ does a great job! I will try to replace Ogww
as completely as possible by Win32++ bit by bit. Although I already know that there will be challenges: CString
is not based on CObject
and CObject
offers no typeof()
operator / no GetType()
method or any alternative easy access to RTTI.
The ReactOS Icon Editor is currently very limited but designed to be developed step by step into a full-fledged icon editor. The current limitations are:
- support for 4bpp (16 colors) mode only
- support for pen, eraser, fill and pipette tool only (copy/paste tools are planned)
And that's how it currently looks:
The Tool Bar
Starting with version 0.5, it's possible to choose between a simple ToolBar
and a ReBar
, that provides the option to handle multiple ToolBar
s. This is the code, that shows both opportunities at once:
void CIconEditMainFrame::AddToolBar(HWND hWnd)
{
HBITMAP hColorBmp = NULL;
HBITMAP hMaskBmp = NULL;
#ifdef USE_REBAR
LPVOID pweakReBarImp = ReBarCreateAndRegister(hWnd, TOOL_BAR_DEFAULT_ID, 16, 5);
CReBar aReBar(pweakReBarImp);
_pweakToolBar = new CToolBar(aReBar.GetFistToolBarImplementation());
#else
LPVOID pweakToolBarImp = ToolBarCreateAndRegister(hWnd, TOOL_BAR_DEFAULT_ID, 16, 5);
_pweakToolBar = new CToolBar(pweakToolBarImp);
#endif
CIcon::LoadIconBitmapsFromBytes(ICO_NEW2_16_Bytes(), ICO_NEW2_16_ByteCount(),
16, 16, true, &hColorBmp, &hMaskBmp);
_pweakToolBar->AddButton(hColorBmp, hMaskBmp, MENU_FILE_NEW_ID,
TBSTATE_ENABLED, TBSTYLE_BUTTON);
::DeleteObject(hColorBmp);
::DeleteObject(hMaskBmp);
_pweakToolBar->SetButtonToolTip(MENU_FILE_NEW_ID, _(L"MAINFRAME|Toolbar",
L"New\n\nStart with a new\ninitial icon file.", L"Tool bar item", __FILE__));
...
_pweakToolBar->Show();
}
The Pixel Edit Control
I have added the new window class PixelEditWindow
to the OpenGL Windows Wrapper (Ogww) DLL - the picture above shows the control in action, right in the center of the application. It is based on the ICONIMAGE
structure (see chapter "The inside of an icon" below) and designed to display and edit the image pixels.
Masked pixels (pixels that are not displayed) are shown in the defined color but crossed out. Unmasked pixels (pixels that are displayed) are shown in the defined color.
The masking is set with the erase tool - it also sets the color.
The pen tool sets the color and deletes the masking.
I have decided
- to set the defined color when masking a pixel, and
- to show masked pixels in the defined color and not in the complementary color or any fixed color.
Pencil tool and erase tool work when the left mouse button is held down - as long as the mouse button is held down, multiple pixels can be set via mouse move.
Icon Design Recommendations
![]() | Since disabled toolbar buttons and disabled menu items typically display their images gray/white with cast shadow, toolbar and menu item images should always be designed to keep the bottom and right pixel stripes unused. |
![]() | The image to the left illustrates the new procedure by which disabled images (second row) are automatically calculated from normal images (first row). |
The Projects
I've developed the ReactOS Icon Editor entirely using Code::Blocks on ReactOS. The solution currently has the following structure:
![]() | The download (at the top of this article) contains a folder structure with the three folders OGWW, OGWW_Wrapper and ReactOS_Icon_Editor. The Code::Blocks solution consists of these two projects
To open the projects on a new environment for the first time, these two files must be opened:
The
The ReactOS Icon Editor project also integrates the folder OGWW_Wrapper, that holds all wrapper classes to provide an object oriented interface of the OGWW DLL. |
In order to check whether the current build environment is well configured, here is an example call to g++:
mingw32-g++.exe -Wall -std=c++11 -pg -g -D_UNICODE -DUNICODE -D__MSVCRT__ -Wall -g -DBUILD_DLL -c C:\Projects\CodeBlocks\OGWW\Console.cpp -o obj\Debug\Console.o
The -std=c++11
switch lifts the g++ to the ANSI C++2011 standard. This enables among other thing the use of
snwprintf()
instead ofswprintf()
,std::chrono::high_resolution_clock::now()
instead ofGetTickCount()
andauto
.
The -D_UNICODE
and -DUNICODE
switch ensure the general use of wchar_t
instead of char
.
The -D__MSVCRT__
switch announces the availability of msvcrt.dll
, that enables among other things, the use of _wgetenv()
instead of getenv()
.
The Inside of an Icon
The most important class of the ReactOS Icon Editor is the class OgwwIconData
. This class provides convenient access to the current image and manages the selected mask as well as the selected color. It is derived from the class OgwwIcon
, that manages the icon as a whole. The basic structure of an icon is:
● Icon directory | stored to ICONDIR structure |
● Icon directory entries | stored to ICONDIRENTRY[] within ICONDIR structure |
□ Image directory entry 1...n | stored to ICONDIRENTRY structure |
● Images | stored to ICONIMAGE[] |
□ Image 1...n | stored to ICONIMAGE structure |
◦ Bitmap info header | stored to BITMAPINFOHEADER within ICONIMAGE structure |
◦ Bitmap color palet | stored to AARRGGBB[] within ICONIMAGE structure |
◦ Color bitmap bytes | stored to BYTE[] within ICONIMAGE structure |
◦ Mask bitmap bytes | stored to BYTE[] within ICONIMAGE structure |
Only BITMAPINFOHEADER
is a standard Windows data structure. I would like to briefly introduce the other structures and types ICONDIR
, ICONDIRENTRY
and ICONIMAGE
:
typedef struct tagICONDIR
{
WORD idReserved; // Reserved (must be 0)
WORD idType; // Resource Type (1 for icons)
WORD idCount; // How many images?
LPICONDIRENTRY idEntries; // One entry for each image.
} ICONDIR, *LPICONDIR;
typedef struct tagICONDIRENTRY
{
BYTE deWidth; // Width, in pixels, of the image
BYTE deHeight; // Height, in pixels, of the image
BYTE deColorCount; // Number of colors in image (0 if >=8bpp)
BYTE deReserved; // Reserved ( must be 0)
WORD dePlanes; // Color Planes
WORD deBitCount; // Bits per pixel
DWORD deBytesInRes; // How many bytes are in this image?
DWORD deImageOffset; // Where in the file is this image?
} ICONDIRENTRY, *LPICONDIRENTRY;
The size of an image (deBytesInRes
) can be calculated this way: sizeof(BITMAPINFOHEADER) + sizeof(ICONIMAGE::iiColors) + sizeof(ICONIMAGE::iiXOR) + sizeof(ICONIMAGE::iiAND)
typedef DWORD AARRGGBB;
#define AARRGGBBtoCOLORREF(v) ((DWORD) (((0xFF000000 & ((DWORD)v)) >> 0) | \\
((0x00FF0000 & ((DWORD)v)) >> 16) | \\
((0x0000FF00 & ((DWORD)v)) << 0) | \\
((0x000000FF & ((DWORD)v)) << 16)) )
#define AARRGGBBtoLUMINANCE(v) ((WORD) (((0x00FF0000 & ((DWORD)v)) >> 16) + \\
((0x0000FF00 & ((DWORD)v)) >> 8) + \\
((0x000000FF & ((DWORD)v)) >> 0)) ) \\
typedef struct tagICONIMAGE
{
BITMAPINFOHEADER iiHeader; // DIB header
AARRGGBB* iiColors; // Color table as DWORD[]: AARRGGBB format
BYTE* iiXOR; // DIB bits for XOR image: 4bpp, 8bpp or 32bpp format
BYTE* iiAND; // DIB bits for AND mask
} ICONIMAGE, *LPICONIMAGE;
To get full control over all data of an icon, I implemented my own methods for reading OgwwIcon::ConstructFromFile(LPCWSTR wszFilePath)
and writing OgwwIcon::SaveAs(LPCWSTR wszFilePath)
icons.
At first, I focused on supporting 16 color images. A further development to 256 and 16777216 colors is already planned.
Flickering Minimization
There are two essential problem areas regarding the flickering:
- Redrawing of the controls (e.g., during resize)
- Editing the image pixels (window class
PixelEditWindow
/ controlOgwwPixelEdit
)
Since redrawing the controls happens relatively rarely, my focus is on editing the image pixels. To edit the image pixels, the control OgwwPixelEdit
is used, which is an owner-drawn control. Either the WS_EX_COMPOSITED
window style does not solve the problem or it does not work as expected on ReactOS (double-buffering). The solution to this problem is an optimized implementation of the message handlers for WM_ERASEBKGND
and WM_PAINT
, as shown below:
case WM_ERASEBKGND: // 20
{
// Minimize flickering - part I:
// * Move background erasing to WM_PAINT to integrate it with foreground drawing.
return 0;
}
case WM_PAINT: // 15
{
PAINTSTRUCT ps;
::BeginPaint(hWnd, &ps);
RECT clientRect;
::GetWindowRect(hWnd, &clientRect);
clientRect.right -= clientRect.left; clientRect.left = 0;
clientRect.bottom -= clientRect.top; clientRect.top = 0;
OgwwPaintArgs paintArgs(&ps, clientRect);
OnPaint(paintArgs);
::EndPaint(hWnd, &ps);
//Console::WriteText(Console::GRAY, L"Handle WM_PAINT for blank %d.\n", (int)hWnd);
return 0;
}
I moved the message handler for WM_PAINT
to the OnPaint()
method:
/// <summary>
/// Processes the <see cref="WM_PAINT"> message.
/// </summary>
/// <param name="paintArgs">The <see cref="OgwwPaintArgs"> arguments.</param>
void OgwwPixelEdit::OnPaint(OgwwPaintArgs& paintArgs)
{
RECT rcClientRect = paintArgs.GetClientRect();
LPBITMAPINFOHEADER bmiHeader = NULL;
// --- DRAW BACKGROUND
// ===================
// Minimize flickering - part II:
// * Erase only the background, that will NOT be affected by foreground drawing.
COLORREF crBackground = ::GetSysColor(COLOR_BTNFACE);
HBRUSH hbrush = CreateSolidBrush(crBackground);
if (_pIconImage == NULL)
{
::FillRect(paintArgs.GetDC(), &rcClientRect, hbrush);
}
else
{
bmiHeader = &(_pIconImage->iiHeader);
if (_rcPadding.left > 0)
{
RECT bgClipLeftArea = rcClientRect;
bgClipLeftArea.right = _rcPadding.left;
bgClipLeftArea.bottom = bmiHeader->biHeight * (1 + _nZoom) + 1 + _rcPadding.top;
::FillRect(paintArgs.GetDC(), &bgClipLeftArea, hbrush);
}
if (_rcPadding.top > 0)
{
RECT bgClipTopArea = rcClientRect;
bgClipTopArea.bottom = _rcPadding.top;
bgClipTopArea.right = bmiHeader->biWidth * (1 + _nZoom) + 1 + _rcPadding.left;
::FillRect(paintArgs.GetDC(), &bgClipTopArea, hbrush);
}
RECT bgClipBottomArea = rcClientRect;
bgClipBottomArea.top = bmiHeader->biHeight * (1 + _nZoom) + 1 + _rcPadding.top;
bgClipBottomArea.right = bmiHeader->biWidth * (1 + _nZoom) + 1 + _rcPadding.left;
if (bgClipBottomArea.top < bgClipBottomArea.bottom)
::FillRect(paintArgs.GetDC(), &bgClipBottomArea, hbrush);
RECT bgClipRightArea = rcClientRect;
bgClipRightArea.left = bmiHeader->biWidth * (1 + _nZoom) + 1 + _rcPadding.left;
if (bgClipRightArea.left < bgClipRightArea.right)
::FillRect(paintArgs.GetDC(), &bgClipRightArea, hbrush);
}
::DeleteObject(hbrush);
// --- PREVENT VIOLATION
// =====================
if (_pIconImage == NULL)
return;
// DRAW PIXELS
// ===========
if (bmiHeader->biWidth > 0 && bmiHeader->biHeight > 0)
{
HPEN oldPen = (HPEN)::SelectObject(paintArgs.GetDC(), _hDarkMaskPen);
int x = rcClientRect.left + _rcPadding.left;
int y = rcClientRect.top + _rcPadding.top;
for (int nRowCount = 0; nRowCount < bmiHeader->biHeight; nRowCount++)
{
for (int nColCount = 0; nColCount < bmiHeader->biWidth; nColCount++)
{
// -- DETERMINE COLOR
// ==================
WORD luminance = 0;
HBRUSH currentBrush = NULL;
if ((bmiHeader->biBitCount == 4) && (_pIconImage->iiColors != NULL))
{
DWORD colorIndex = OgwwIcon::BmpXORgetColor4bpp (_pIconImage->iiXOR,
nColCount, nRowCount, bmiHeader->biWidth, bmiHeader->biHeight);
luminance = AARRGGBBtoLUMINANCE(_pIconImage->iiColors[colorIndex]);
currentBrush = ::CreateSolidBrush(
AARRGGBBtoCOLORREF(_pIconImage->iiColors[colorIndex]));
}
else if ((bmiHeader->biBitCount == 8) && (_pIconImage->iiColors != NULL))
{
DWORD colorIndex = OgwwIcon::BmpXORgetColor8bpp (_pIconImage->iiXOR,
nColCount, nRowCount, bmiHeader->biWidth, bmiHeader->biHeight);
luminance = AARRGGBBtoLUMINANCE(_pIconImage->iiColors[colorIndex]);
currentBrush = ::CreateSolidBrush(
AARRGGBBtoCOLORREF(_pIconImage->iiColors[colorIndex]));
}
else
{
int pixelIndex = nRowCount * bmiHeader->biWidth + nColCount;
luminance = AARRGGBBtoLUMINANCE(_pIconImage->iiXOR[pixelIndex]);
currentBrush = ::CreateSolidBrush(
AARRGGBBtoCOLORREF(_pIconImage->iiXOR[pixelIndex]));
}
// -- DRAW PIXEL FACE
// ==================
// Minimize flickering - part III:
// * Prevent drawing pixel face over pixel border
// (by inflating the pixel face rectangle).
RECT currentRC;
currentRC.left = x; currentRC.top = y;
currentRC.right = x + (1 + _nZoom); currentRC.bottom = y + (1 + _nZoom);
// Inflate left/top but keep right/bottom.
// FillRect doesn't include the right coordinate.
currentRC.left += 1;
currentRC.top += 1;
::FillRect(paintArgs.GetDC(), ¤tRC, currentBrush);
// Restore the left/bottom.
currentRC.left -= 1;
currentRC.top -= 1;
::DeleteObject(currentBrush);
// -- DRAW MASK ON PIXEL FACE
// ==========================
bool bMasked = OgwwIcon::BmpXORgetMask(_pIconImage->iiAND, nColCount,
nRowCount, bmiHeader->biWidth, bmiHeader->biHeight);
if (bMasked)
{
if (luminance < 192)
::SelectObject(paintArgs.GetDC(),_hLightMaskPen);
else if (luminance < 384)
::SelectObject(paintArgs.GetDC(),(HGDIOBJ)::GetStockObject(WHITE_PEN));
else if (luminance < 576)
::SelectObject(paintArgs.GetDC(),(HGDIOBJ)::GetStockObject(BLACK_PEN));
else
::SelectObject(paintArgs.GetDC(),_hDarkMaskPen);
::MoveToEx(paintArgs.GetDC(), x, y, NULL);
::LineTo (paintArgs.GetDC(), x + (1 + _nZoom), y + (1 + _nZoom));
if (_nZoom >= 6)
{
int nHalf = _nZoom / 2;
::MoveToEx(paintArgs.GetDC(), x + nHalf, y, NULL);
::LineTo (paintArgs.GetDC(), x + (1+_nZoom), y - nHalf +(1+_nZoom));
::MoveToEx(paintArgs.GetDC(), x, y + nHalf, NULL);
::LineTo (paintArgs.GetDC(), x - nHalf + (1+_nZoom), y +(1+_nZoom));
}
}
x += (1 + _nZoom);
}
x = rcClientRect.left + _rcPadding.left;
y += (1 + _nZoom);
}
// -- DRAW PIXEL BORDER
// ====================
int nLineWidth = 1 + (1 + _nZoom) * bmiHeader->biWidth;
int nLineHeight = 1 + (1 + _nZoom) * bmiHeader->biHeight;
HPEN newPen = GetStockPen(BLACK_PEN);
::SelectObject(paintArgs.GetDC(), newPen);
x = rcClientRect.left + _rcPadding.left;
y = rcClientRect.top + _rcPadding.top;
for (int nRowCount = 0; nRowCount <= bmiHeader->biHeight; nRowCount++)
{
::MoveToEx(paintArgs.GetDC(), x, y, NULL);
::LineTo(paintArgs.GetDC(), x + nLineWidth, y);
y += (1 + _nZoom);
}
x = rcClientRect.left + _rcPadding.left;
y = rcClientRect.top + _rcPadding.top;
for (int nColCount = 0; nColCount <= bmiHeader->biWidth; nColCount++)
{
::MoveToEx(paintArgs.GetDC(), x, y, NULL);
::LineTo(paintArgs.GetDC(), x, y + nLineHeight);
x += (1 + _nZoom);
}
::SelectObject(paintArgs.GetDC(), oldPen);
}
}
I have added comments at the three most important points of the optimization:
- DON'T DRAW on
WM_ERASEBKGND
(integrate background drawing intoWM_PAINT
instead) - DRAW BACKGROUND (only outside the pixel field - it will be updated later)
- DRAW PIXEL FACE (without any overlap with the borders)
There is a good article "Flicker free drawing of any control" about this topic, which I used as a basis for my solution. The use of clip regions as described in the article "A Guide to WIN32 Clipping Regions" could be an equivalent alternative.
Memory Leak Detection
After I accidentally came across the article, A Cross-Platform Memory Leak Detector, I wondered how good my application is in terms of storage management. The answer was disillusioning. I almost lost faith in myself and in C++. But C++ wouldn't be C++ if it couldn't pull itself out of this mess.
First and foremost: Respect to all programmers who write stable professional applications in C or C++, which do a hard job for days, weeks or months without complaint and without having to be restarted!
If my Icon Editor (before I started to revise the memory management) would be an application that is in use continuously for a long time and processes large amounts of data, the operating system would certainly run out of breath at some point. As it turned out that it was mainly about small omissions, the memory leaks were cleared away when the application was terminated and were not noticed any further. In the long run, however, the memory leaks would still cause considerable memory consumption.
I've learned the following things while searching for memory leaks:
- You definitely need easy-to-use yet effective tools. The on-board tools of gcc or VS are not enough. Special versions (with logging/tracing) of
::GlobalAlloc()
and::GlobalFree()
as well asnew
anddelete
are highly recommended. - Make a simple determination for using
::GlobalAlloc()
and::GlobalFree()
respectivenew
anddelete
- and stick to it! I usednew
anddelete
only for objects that have constructor(s) / destructors. In all other cases, I used::GlobalAlloc()
and::GlobalFree()
(because I'll never know whether I have to pasds (parts) of the memory block to Window API calls). - Design all dynamic memory usage intuitively! Any method that allocates dynamic memory should have a natural/intuitive counterpart to release the dynamic memory and both should be found instinctively.
- Never stop improving memory management. Even 2 months after I first thought all memory leaks were fixed, I still found new ones.
I finally ended up with 4 macros and one class, with which I could detect all memory leaks - fixing them was a breeze afterwards. They are much simpler and far from being as beautiful as described in A Cross-Platform Memory Leak Detector - but they do their job well. Here is a screenshot from the final phase of my fixes:
The macros
ALLOC_REGISTRATION_CALL___DBG
(wraps::GlobalAlloc()
) /FREE_CODE_BLOCK___DBG
(replaces::GlobalFree()
) andNEW_REGISTRATION_CALL___DBG
(wrapsnew
) /DELETE_CODE_BLOCK___DBG
(replacesdelete
)
are declared in MemoryDebug.hpp and OgwwAPI.hpp and are used for all requests or releases of dynamic memory. And here exemplary use cases for ::GlobalAlloc()
/::GlobalFree()
and new
/delete
:
...
// Not very elegant, but 100% transparent (the application of ::GlobalAlloc() is visible):
// Separation of declaration and initialization not needed - because ::GlobalAlloc() returns
// an untyped memory block. (Yes. there is room for optimization.)
BYTE* pbIconBytes = (BYTE*)ALLOC_REGISTRATION_CALL___DBG(::GlobalAlloc(GPTR, dwBufferSize));
...
// Less transparent but very short: The ::GlobalFree() call is hidden within an {...} block.
if (pbIconBytes != NULL)
FREE_CODE_BLOCK___DBG(pbIconBytes)
// I do that in principle. When reusing the variable it is clear, if it is already initialized.
pbIconBytes = NULL;
...
...
// Not very elegant, but 100% transparent (the application of new operator is visible):
// I separate the declaration from the initialization and only the initialization is monitored -
// because the new operator returns a typed memory block. (Yes, there is room for optimization.)
CColLayouter* pColLayouter = NULL;
NEW_REGISTRATION_CALL___DBG(pColLayouter = new CColLayouter());
...
// Less transparent but very short: The delete operator call is hidden within an {...} block.
if (pColLayouter != NULL)
DELETE_CODE_BLOCK___DBG(pColLayouter)
// I do that in principle. When reusing the variable it is clear, if it is already initialized.
pColLayouter = NULL;
...
Microsoft Visual Studio (VC++) Compatibility
I have written the DLL and the application completely in Code::Blocks 17.12 on ReactOS 0.4.11. Now I was wondering if this can be transferred 1:1 to Microsoft World with Windows 10 and Visual Studio 2017. After some initial difficulties, the creation of full compatibility for the entire source code of the DLL and the application took one day. For those of you who have something similar in mind, here are some hints on how to make the necessary adjustments.
1. First, I created a new solution with Visual Studio and two new projects. One project based on the template "Other Languages | Visual C++ | Windows Desktop Application - Visual C++" for the application and one project based on the template "Other Languages | Visual C++ | Dynamic-Link Library (DLL) - Visual C++" for the DLL.
1.1. Any new Visual Studio C++ project is prepared to use precompiled headers. This is a feature I don't use with Code::Blocks. To remove this feature, the project properties must be edited for both projects.
Switch the "Configuration Properties | C/C++ | Precompiled Headers" property "Precompiled Headers" from "Create (/Yc)" to "Not Using Precompiled Headers" and empty the properties "Precompiled Header File" and "Precompiled Header Output File" for "All Configurations".
1.2. To resolve all external references, two libraries must be added to both new solutions: opengl32.lib;comctl32.lib;
This is done via the "Configuration Properties | Linker | Input" property "Additional Dependencies" for "All Configurations".
2. All code and resource files except dllmain.cpp, generated with the two project templates, must be deleted from the projects and all code files from Code::Blocks 17.12 must be registered to the appropriate project. Within dllmain.cpp, the #include "stdafx.h"
must be replaced by #include "windows.h"
.
3. Unfortunately, Code::Blocks comes with an older version of MinGW that does not yet include the security enhancements of string functions. However, Visual Studio demands the use of these functions and therefore a solution must be found. A local deactivation using "#pragma warning (disable: 4996)
" does not work for Code::Blocks. And I couldn't warm up for a global deactivation. So I decided to emulate the security enhancements for the demanded string functions in Code::Blocks.
These are the additional declarations within SimpleWString.hpp:
#if defined(__GNUC__) || defined(__MINGW32__)
typedef int errno_t;
/// <summary>
/// Copies not more than <c>nCopyCnt</c> characters of the character array pointed to
/// by <c>wszSrc</c> to the character array pointed to by <c>wszDst<c>, stopping at the
/// first <c>0</c> character. Zeroes out the rest of the character array pointed to
/// by <c>wszDst</c>, which can be a performance concern.
/// </summary>
/// <param name="wszDst">The copy target. Must not be <c>NULL</c>.</param>
/// <param name="nDstSize">The max. capacity of the copy target.</param>
/// <param name="wszSrc">The copy source. Can be <c>NULL</c>.</param>
/// <param name="nCopyCnt">The max. number of characters to copy.</param>
/// <returns>Returns <c>0</c> on success, or an error number otherwise.</returns>
errno_t wcsncpy_s(wchar_t* wszDst, size_t nDstSize, const wchar_t* wszSrc, size_t nCopyCnt);
/// <summary>
/// Copies the character array pointed to by <c>wszSrc</c> to the character array pointed to
/// by <c>wszDst<c>, stopping at the first <c>0</c> character. Zeroes out the rest of
/// the character array pointed to by <c>wszDst</c>, which can be a performance concern.
/// </summary>
/// <param name="wszDst">The copy target. Must not be <c>NULL</c>.</param>
/// <param name="nDstSize">The max. capacity of the copy target.</param>
/// <param name="wszSrc">The copy source. Can be <c>NULL</c>.</param>
/// <returns>Returns <c>0</c> on success, or an error number otherwise.</returns>
errno_t wcscpy_s(wchar_t* wszDst, size_t nDstSize, const wchar_t* wszSrc);
#define swprintf_s(wszDst, nDstSize, wszFormat, ...) swprintf(wszDst, wszFormat, __VA_ARGS__)
#endif
And this is how the additional implementations look like within SimpleWString.cpp:
#if defined(__GNUC__) || defined(__MINGW32__)
errno_t wcsncpy_s(wchar_t* wszDst, size_t nDstSize, const wchar_t* wszSrc, size_t nCopyCnt)
{
// Prevent segment violation.
if (wszDst == NULL)
return ENOMEM;
// Prevent overlapping.
if (&(wszDst[nDstSize]) > wszSrc && wszDst < &(wszSrc[nCopyCnt]))
return EPERM;
if (&(wszSrc[nCopyCnt]) > wszDst && wszSrc < &(wszDst[nDstSize]))
return EPERM;
// Process the standard case.
if (nDstSize > nCopyCnt)
{
wcsncpy(wszDst, wszSrc, nCopyCnt);
for (size_t nIndex = nCopyCnt; nIndex < nDstSize; nIndex++)
wszDst[nIndex] = (wchar_t)0;
return 0;
}
// Prevent overrun (by missing string termination).
if (nDstSize == nCopyCnt && wszSrc[nCopyCnt] == (wchar_t)0)
{
wcsncpy(wszDst, wszSrc, nCopyCnt);
return 0;
}
// Prevent overflow.
return ERANGE;
}
errno_t wcscpy_s(wchar_t* wszDst, size_t nDstSize, const wchar_t* wszSrc)
{
return wcsncpy_s(wszDst, nDstSize, wszSrc, wcslen(wszSrc));
}
#endif
4. What remains now is diligent work to remove the warnings within Visual Studio because the Microsoft compiler checks the syntax much stricter than MinGW.
Points of interest
After years of exclusive programming in C#, it was once again great fun to program in C++ and to have full control over every bit and every function call - even if you have to act more carefully than in C#.
It is a pity that the C/C++ Windows libraries are either over-engineered (e.g. MFC) or/and not equipped with a comfort comparable to C# (e.g. LINQ).
History
- 12th December, 2019: Initial version
- 9th January, 2020: Update to version 0.2
- 10th June, 2020: Update to version 0.3
- 10th October, 2020: Update to version 0.4
- 27th November, 2020: Update to version 0.5
- 31st January, 2021: Update to version 0.6