The TDib(int width, int height, uint32 nColors, ...) constructor wasn't originally written to support high-color (> 8-bit) DIBs. It takes the number of colors as a parameter, assuming this number to specify the palette size for a low-color DIB.
Partial support for high-color was added in early OWLNext by setting biClrUsed to 0 for biBitCount greater than 8 [r7], and testing the RASTERCAPS of the device before attempting system palette copying [r5].
//
/// Creates a DIB object with the given width, height, number of colors, mode
/// values.
//
TDib::TDib(int width, int height, uint32 nColors, uint16 mode)
{
...
infoHeader.biBitCount = static_cast<WORD>(NBits(nColors));
...
infoHeader.biClrUsed = infoHeader.biBitCount > 8 ? 0 : nColors;
...
if (Mode == DIB_PAL_COLORS) {
...initialize nColors palette entries (indexes)...
}
else if(nColors){
...
TScreenDC dc;
if(dc.GetDeviceCaps(RASTERCAPS) & RC_PALETTE){
...initialize nColors palette entries (RGB)...
}
}
}
These changes allow a high-color DIB to be created by passing an artificial high number for the nColors parameter, effectively repurposing the parameter as a bit-depth selector. For example:
enum TBitDepth : uint
{
b1 = 1 << 1,
b4 = 1 << 4,
b8 = 1 << 8,
b16 = 1 << 16,
b24 = 1 << 24,
b32 = INT_MAX
};
auto MakeDib(TSize d, TBitDepth b) -> TDib
{
return TDib{d.cx, d.cy, b};
}
However, the implementation still assumes that nColors reflects the size of the allocated color table. This means that, for bit counts greater than 8, if the screen device is in a low-color mode, the TDib constructor will try to copy system palette entries for which no space has been allocated.
Proposed fix
The fix is simple; just use biClrUsed rather than nColors in the palette copying part of the code. biClrUsed already represents the number of allocated color table entries and is set to zero for high-color DIBs (since [r7]). Using it consistently for color-table initialization prevents palette-copy operations when no color table exists.
// Initialize the color table--either Palette or RGB
//
const uint paletteSize = infoHeader.biClrUsed;
Mode = mode;
if (Mode == DIB_PAL_COLORS) {
// Generate a 1:1 palette relative color table- it can later be translated
// to RGB given a palette.
//
for (uint i = 0; i < paletteSize; i++)
(reinterpret_cast<uint16*>(Colors))[i] = static_cast<uint16>(i);
}
else if (paletteSize > 0) {
// Get the system palette and convert to RGB quad format if exist support.
//
TScreenDC dc;
if(dc.GetDeviceCaps(RASTERCAPS) & RC_PALETTE){
uint clrCnt = ::GetSystemPaletteEntries(dc, 0, paletteSize, reinterpret_cast<LPPALETTEENTRY>(Colors));
for (uint32 i = 0; i < clrCnt; i++) {
std::swap(Colors[i].rgbRed, Colors[i].rgbBlue);
Colors[i].rgbReserved = 0;
}
}
}
A patch for trunk [r8901] is attached.
Google Gemini suggests:
Something like this, perhaps:
Gemini review:
Overall, the code is well-protected against integer overflows and invalid dimensions, but there is a notable design inconsistency regarding how palette sizes are handled compared to other errors.
1. Strengths in Validation
d.cx <= 0,d.cy == 0, and explicitly guarding againstLONG_MINprevents immediate logical failures and undefined behavior withabs().uint64arithmetic forpitchand validating bothpitchandsizeImageagainstin_range<DWORD>successfully prevents buffer overflow vulnerabilities when passing large dimensions to Windows GDI.2. Areas for Evaluation & Potential Issues
A. Palette Size Handling (
WARNvs.throw)TXOwlexception. However, for an invalidpaletteSize(e.g., negative or greater thanmaxPaletteSize), the code uses aWARN(...)macro and silently clamps the value usingclamp(paletteSize, 0, maxPaletteSize).WARNonly logs a message and execution continues, callers passing invalid palette sizes will not experience a hard failure. Depending on your library's error-handling philosophy, this might mask caller bugs rather than enforcing strict validation. If strictness is preferred, out-of-range palette sizes should throw an exception just like invalid dimensions.B. Enum Cast Safety (
TBitDepth&TPaletteMode)enum class), which naturally restricts inputs to defined values. However, if a caller uses a C-style cast or static cast to pass an arbitrary integer (e.g.,static_cast<TBitDepth>(999)),NColors(bitCount)must be robust enough to return a negative number or handle the invalid bit depth safely. (Your code does checkif (maxPaletteSize < 0) throw TXOwl{...}, which handles this correctly ifNColorsreturns negative for invalid counts).3. Example of Arguments Causing Non-Throwing Behavior
If a caller passes an out-of-range
paletteSize:TDib(TSize{100, 100}, TBitDepth::Bpp8, 300, TPaletteMode::Rgb)(whereBpp8allows a maximum palette size of 256).TXOwl, the constructor triggers theWARNmacro, clampspaletteSizeto256, and proceeds to create the DIB.Recommendation
If silent recovery via clamping is intended behavior, the current implementation is robust and safe. If you want strict validation consistent with the rest of the constructor, replace the
WARNmacro check with an exception throw:Last edit: Vidar Hasfjord 1 day ago