Solved Overlaying icons (like Alembic)

Hi,

here's the next icon-related question from me 😉

Is there any ready-to-use function in the API to overlay icons, like the Alembic import does?

Screenshot 2020-09-02 at 10.49.43.png

I need to do exactly the same, so I better ask if there's an easy way, before I start fiddling around with bitmaps and their alpha channels.

Thanks in advance!

Greetings,
Frank

www.frankwilleke.de
Only asking personal code questions here.

Hi Frank, this is done via the MSG_GETCUSTOMICON and the GetCustomIcon with the callback.

//----------------------------------------------------------------------------------------
/// Writes an overlay icon bitmap into another bitmap at given coordinates and dimensions.
/// @param[in] overlay Source icon bitmap.
/// @param[in] dest    Destination icon bitmap.
/// @param[in] x       Starting horizontal coordinate.
/// @param[in] y       Starting vertical coordinate.
/// @param[in] bw      Width of source bitmap region to write.
/// @param[in] bh      Height of source bitmap region to write.
//----------------------------------------------------------------------------------------
inline void BlitOverlayIcon(IconData* overlay, IconData* dest, Int32 x, Int32 y, Int32 bw, Int32 bh, Int32 xOffset = 0, Int32 yOffset = 32)
{
	Int32	 px, py;
	UInt16 aa, a, rr, r, gg, g, bb, b;

	if (!overlay || !overlay->bmp || !dest || !dest->bmp)
		return;

	BaseBitmap* channel = overlay->bmp->GetInternalChannel();
	BaseBitmap* destAlphaChannel = dest->bmp->GetInternalChannel();

	if (!channel)
		return;

	Int32 destW = dest->w;
	Int32 destH = dest->h;
	if (bw > destW)
		bw = destW;
	if (bh + yOffset > destH)
		bh = destH - yOffset;
	if (bw + xOffset > destW)
		bw = destW - xOffset;

	aa = a = rr = r = gg = g = bb = b = 0xff;
	for (py = 0; py < bh; py++)
	{
		for (px = 0; px < bw; px++)
		{
			// get color and alpha from overlay icon
			overlay->bmp->GetPixel(x + px, y + py, &r, &g, &b);
			overlay->bmp->GetAlphaPixel(channel, x + px, y + py, &a);

			// get color and alpha from background icon if available
			dest->bmp->GetPixel(px + xOffset, py + yOffset, &rr, &gg, &bb);
			if (destAlphaChannel)
			{
				dest->bmp->GetAlphaPixel(destAlphaChannel, px + xOffset, py + yOffset, &aa);
			}

			// blend overlay color against existing icon background color
			Float blend = a / 255.0;
			dest->bmp->SetPixel(px + xOffset, py + yOffset, (Int32)Blend(rr, r, blend), (Int32)Blend(gg, g, blend), (Int32)Blend(bb, b, blend));

			// use only the overlay alpha if the opacity is higher to keep the original icon complete
			// and only in case the background has an alpha at all
			if (destAlphaChannel && aa < a)
			{
				dest->bmp->SetAlphaPixel(destAlphaChannel, px + xOffset, py + yOffset, a);
			}
		}
	}
}


// In the message
case MSG_GETCUSTOMICON:
{
    GetCustomIconData* cid = (GetCustomIconData*)data;
    if (!cid)
        return false;

    BaseContainer* bc = ((BaseObject*)node)->GetDataInstance();

    CustomIconDrawDelegate drawCallback = [this, bc](IconData& dat) -> void
    {
        IconData overlayIcon;
        Bool overlayIconloaded = false;

        // According to a value in the BaseContainer change the loaded overlay
        if (bc->GetBool(10000))
            overlayIconloaded = GetIcon(RESOURCEIMAGE_OK, &overlayIcon);
        else
            overlayIconloaded = GetIcon(RESOURCEIMAGE_CANCEL, &overlayIcon);


        if (overlayIconloaded)
        {
            BlitOverlayIcon(&overlayIcon, &dat, overlayIcon.x, overlayIcon.y, overlayIcon.w, overlayIcon.h);
        }
    };

    // Get the default custom icons and settings
    CustomIconSettings customIconSettings;
    ((BaseObject*)node)->Message(MSG_GETCUSTOMICON_SETTINGS, &customIconSettings);
    customIconSettings._defaultIconId = bc->GetInt32(Ocube, Onull);
    customIconSettings._fillDefault = true;

    // Set cid with the passed customIconSettings and callback
    GetCustomIcon(*cid, customIconSettings, false, nullptr, &drawCallback);

    break;
}

Cheers,
Maxime.

Nice, thank you! 🙂

www.frankwilleke.de
Only asking personal code questions here.

Hmm, I'm not sure how to use this properly.

My plugin objects' icons are loaded from one big TIFF. When I use your BlitOverlayIcon() to draw an overlay over an icon, it basically works, but seems to change the bitmap that all the objects use. Drawing the overlay in one of the objects changes the icon for all object of a kind. Also, the bitmap is not reset, so everything I ever overlay remains there. With every call of MSG_GETCUSTOMICON I get more and more stuff drawn on top of it.

I guess, I need to use CustomIconSettingsand GetCustomIcon() to avoid changing the common bitmap for all objects, but those are not explained in the SDK. Do I have to use all that color-related stuff? What's in that BaseContainer in your code that it returns something from the ID Ocube?

I have several objects, and each object should be able to change its own icon (not the icon of all object of the same type) by overlaying a second icon on it. Could you elaborate on your code a little?

Thanks in advance!

Cheers,
Frank

www.frankwilleke.de
Only asking personal code questions here.

Unfortunately, I'm not able to reproduce your issue, here the atom example with the Message method adapted like so

inline void BlitOverlayIcon(IconData* overlay, IconData* dest, Int32 x, Int32 y, Int32 bw, Int32 bh, Int32 xOffset = 0, Int32 yOffset = 32)
{
	Int32	 px, py;
	UInt16 aa, a, rr, r, gg, g, bb, b;

	if (!overlay || !overlay->bmp || !dest || !dest->bmp)
		return;

	BaseBitmap* channel = overlay->bmp->GetInternalChannel();
	BaseBitmap* destAlphaChannel = dest->bmp->GetInternalChannel();

	if (!channel)
		return;

	Int32 destW = dest->w;
	Int32 destH = dest->h;
	if (bw > destW)
		bw = destW;
	if (bh + yOffset > destH)
		bh = destH - yOffset;
	if (bw + xOffset > destW)
		bw = destW - xOffset;

	aa = a = rr = r = gg = g = bb = b = 0xff;
	for (py = 0; py < bh; py++)
	{
		for (px = 0; px < bw; px++)
		{
			// get color and alpha from overlay icon
			overlay->bmp->GetPixel(x + px, y + py, &r, &g, &b);
			overlay->bmp->GetAlphaPixel(channel, x + px, y + py, &a);

			// get color and alpha from background icon if available
			dest->bmp->GetPixel(px + xOffset, py + yOffset, &rr, &gg, &bb);
			if (destAlphaChannel)
			{
				dest->bmp->GetAlphaPixel(destAlphaChannel, px + xOffset, py + yOffset, &aa);
			}

			// blend overlay color against existing icon background color
			Float blend = a / 255.0;
			dest->bmp->SetPixel(px + xOffset, py + yOffset, (Int32)Blend(rr, r, blend), (Int32)Blend(gg, g, blend), (Int32)Blend(bb, b, blend));

			// use only the overlay alpha if the opacity is higher to keep the original icon complete
			// and only in case the background has an alpha at all
			if (destAlphaChannel && aa < a)
			{
				dest->bmp->SetAlphaPixel(destAlphaChannel, px + xOffset, py + yOffset, a);
			}
		}
	}
}

Bool AtomObject::Message(GeListNode* node, Int32 type, void* t_data)
{
	if (type == MSG_DESCRIPTION_VALIDATE)
	{
		BaseContainer* data = ((BaseObject*)node)->GetDataInstance();
		CutReal(*data, ATOMOBJECT_CRAD, 0.0, data->GetFloat(ATOMOBJECT_SRAD));
	}
	if (type == MSG_GETCUSTOMICON)
	{
		GetCustomIconData* cid = (GetCustomIconData*)t_data;
		if (!cid)
			return false;

		BaseContainer* bc = ((BaseObject*)node)->GetDataInstance();

		CustomIconDrawDelegate drawCallback = [this, bc](IconData& dat) -> void
		{
			IconData overlayIcon;
			Bool overlayIconloaded = false;

			// According to a value in the BaseContainer change the loaded overlay
			if (bc->GetBool(10000))
				overlayIconloaded = GetIcon(RESOURCEIMAGE_OK, &overlayIcon);
			else
				overlayIconloaded = GetIcon(RESOURCEIMAGE_CANCEL, &overlayIcon);


			if (overlayIconloaded)
			{
				BlitOverlayIcon(&overlayIcon, &dat, overlayIcon.x, overlayIcon.y, overlayIcon.w, overlayIcon.h);
			}
		};

		// Get the default custom icons and settings
		CustomIconSettings customIconSettings;
		((BaseObject*)node)->Message(MSG_GETCUSTOMICON_SETTINGS, &customIconSettings);
		customIconSettings._defaultIconId = bc->GetInt32(Ocube, Onull);
		customIconSettings._fillDefault = true;

		// Set cid with the passed customIconSettings and callback
		GetCustomIcon(*cid, customIconSettings, false, nullptr, &drawCallback);
	}

	return true;
}

Then if you call in python op[10000] = True, only this instance will be enabled.
f860b2fd-e602-475e-85ae-a0718bea5386-image.png
Maybe you can share your code with us, or try to adapt BlitOverlayIcon to fit your need, but here I can't reproduce your issue.

Cheers,
Maxime.

Oh right, that really works. Hmmm, no idea what I did before to make it behave so strange.
I'll build upon this code and come back if there are any more questions.

Thanks a lot! I'll mark this solved for now.

Cheers,
Frank

www.frankwilleke.de
Only asking personal code questions here.

Here's another question about this...

The solution offered in this thread works in R21, S22, and R23. But it does not compile in R20, because there's no customgui_iconchooser.h and no struct CustomIconSettings.

How would one do this in R20?

Thanks in advance!
Cheers,
Frank

www.frankwilleke.de
Only asking personal code questions here.

My first try was this:
(R20 code)

BaseContainer* bc = ((BaseObject*)node)->GetDataInstance();

IconData overlayIcon;

// According to a value in the BaseContainer change the loaded overlay
const Int32 iconId = MyFunctionToGetTheIconId();
const Bool overlayIconloaded = GetIcon(iconId, &overlayIcon);
if (overlayIconloaded && iconId != NOTOK)
{
	BlitOverlayIcon(&overlayIcon, cid->dat, overlayIcon.x, overlayIcon.y, overlayIcon.w, overlayIcon.h, 16, 16);
	cid->filled = true;
}

This did draw my overlay icon, but the underlying icon is just black. It seems that cid->dat->bmp does not contain anything.

Next try:
(R20 code)

BaseContainer* bc = ((BaseObject*)node)->GetDataInstance();

// Load node's icon
if (!GetIcon(nodeType, cid->dat))
	return false;

IconData overlayIcon;

// According to a value in the BaseContainer change the loaded overlay
const Int32 iconId = MyFunctionToGetTheIconId();
const Bool overlayIconloaded = GetIcon(iconId, &overlayIcon);
if (overlayIconloaded && iconId != NOTOK)
{
	BlitOverlayIcon(&overlayIcon, cid->dat, overlayIcon.x, overlayIcon.y, overlayIcon.w, overlayIcon.h, 16, 16);
	cid->filled = true;
}

This didn't change anything. Still a black icon with my overlay on top.

Maybe GetIcon() doesn't work for some reason? Let's try AutoBitmap().

This crashes:
(R20 code)

BaseContainer* bc = ((BaseObject*)node)->GetDataInstance();

const Int32 nodeType = node->GetType();
BaseBitmap *bmp = AutoBitmap(nodeType);

bmp->CopyTo(cid->dat->bmp); // <-- crashes here!

IconData overlayIcon;

// According to a value in the BaseContainer change the loaded overlay
const Int32 iconId = MyFunctionToGetTheIconId();
const Bool overlayIconloaded = GetIcon(iconId, &overlayIcon);
if (overlayIconloaded && iconId != NOTOK)
{
	BlitOverlayIcon(&overlayIcon, cid->dat, overlayIcon.x, overlayIcon.y, overlayIcon.w, overlayIcon.h, 16, 16);
	cid->filled = true;
}

OK, so that crashes on CopyTo().

Last try, use AutoBitmap() to load the icon directly into cid->dat->bmp.
(R20 code)

BaseContainer* bc = ((BaseObject*)node)->GetDataInstance();
				
const Int32 nodeType = node->GetType();
cid->dat->bmp = AutoBitmap(nodeType);
if (!GetIcon(nodeType, cid->dat))
	return false;
				
IconData overlayIcon;
				
// According to a value in the BaseContainer change the loaded overlay
const Int32 iconId = MyFunctionToGetTheIconId();
const Bool overlayIconloaded = GetIcon(iconId, &overlayIcon);
if (overlayIconloaded && iconId != NOTOK)
{
	BlitOverlayIcon(&overlayIcon, cid->dat, overlayIcon.x, overlayIcon.y, overlayIcon.w, overlayIcon.h, 16, 16);
	cid->filled = true;
}

This does not crash, but while cid->dat has the correct x, y, b, and h values, cid->dat->bmp has size 0,0. For some reason, the normal icon shows up in the Object Manager.

How can I solve this in R20?

Thanks for any tips!!

Cheers,
Frank

www.frankwilleke.de
Only asking personal code questions here.

OK, I got it. Once it's all figured out, the solution seems laughably simple.

In case anybody else needs to maintain R20 compatibility and wants to use custom icon overlays, here's a solution:

In MyObject class declaration:
(R20 code)

class MyObject : public ObjectData
{
	INSTANCEOF(MyObject, ObjectData);

	// ...

private:
#if API_VERSION < 21000
	BaseBitmap *_customIcon; ///< Used to store a node's custom icon in R20
#endif
};

public:
#if API_VERSION < 21000
	MyObject() : _customIcon(nullptr)
	{
	}

	~ MyObject()
	{
		if (_customIcon)
			BaseBitmap::Free(_customIcon);
	}
#endif

In MyObject::Message():
(R20 code)

const BaseContainer &dataRef = op->GetDataInstanceRef();

//
// 1. Copy default icon into customIcon
//

// Load default icon
IconData defaultIconData;
if (!GetIcon(node->GetType(), &defaultIconData))
	return false;

// Free _customIcon if it has been allocated before, because
// it will now receive the pointer to a newly allocated BaseBitmap.
if (_customIcon)
	BaseBitmap::Free(_customIcon);

// Get the actual bitmap that is our icon
_customIcon = defaultIconData.GetClonePart();
if (!_customIcon)
	return false;

//
// 2. Blit overlay into customIcon
//

// Load overlay icon
IconData overlayIcon;
const Int32 overlayIconId = MyFunctionToGetTheIconId();
if (overlayIconId == NOTOK)
	return false;
const Bool overlayIconloaded = GetIcon(overlayIconId, &overlayIcon);
if (overlayIconloaded)
{
	BlitOverlayBitmap(overlayIcon.bmp, _customIcon, overlayIcon.x, overlayIcon.y, overlayIcon.w, overlayIcon.h, 32, 32);

	//
	// 3. Set cid->dat to use customIcon
	//
	cid->dat->bmp = _customIcon;
	cid->dat->x = 0;
	cid->dat->y = 0;
	cid->dat->w = _customIcon->GetBw();
	cid->dat->h = _customIcon->GetBh();
	cid->filled = true;
}

And BlitOverlayBitmap(), a variation of your BlitOverlayIcon():

void BlitOverlayBitmap(BaseBitmap *overlay, BaseBitmap *dest, Int32 x, Int32 y, Int32 bw, Int32 bh, Int32 xOffset, Int32 yOffset)
{
	if (!overlay || !dest)
		return;

	BaseBitmap *overlayAlpha = overlay->GetInternalChannel();
	BaseBitmap *destAlpha = dest->GetInternalChannel();

	const Int32 destW = dest->GetBw();
	const Int32 destH = dest->GetBh();
	if (bw > destW)
		bw = destW;
	if (bh + yOffset > destH)
		bh = destH - yOffset;
	if (bw + xOffset > destW)
		bw = destW - xOffset;

	UInt16 aa, a, rr, r, gg, g, bb, b;
	aa = a = rr = r = gg = g = bb = b = 0xff;

	for (Int32 py = 0; py < bh; py++)
	{
		for (Int32 px = 0; px < bw; px++)
		{
			// get color and alpha from overlay icon
			overlay->GetPixel(x + px, y + py, &r, &g, &b);
			overlay->GetAlphaPixel(overlayAlpha, x + px, y + py, &a);

			// get color and alpha from background icon if available
			dest->GetPixel(px + xOffset, py + yOffset, &rr, &gg, &bb);
			if (destAlpha)
			{
				dest->GetAlphaPixel(destAlpha, px + xOffset, py + yOffset, &aa);
			}

			// blend overlay color against existing icon background color
			Float blend = a / 255.0;
			dest->SetPixel(px + xOffset, py + yOffset, (Int32)Blend(rr, r, blend), (Int32)Blend(gg, g, blend), (Int32)Blend(bb, b, blend));

			// use only the overlay alpha if the opacity is higher to keep the original icon complete
			// and only in case the background has an alpha at all
			if (destAlpha && aa < a)
			{
				dest->SetAlphaPixel(destAlpha, px + xOffset, py + yOffset, a);
			}
		}
	}
}

Thanks for patience & support!

Cheers,
Frank

www.frankwilleke.de
Only asking personal code questions here.