Transparency Issue with MultipassBitmap in GeUserArea
-
Hello,
I'm encountering an issue with transparency when drawing a MultipassBitmap over a background in a custom GeUserArea. The transparent areas appear black unless I save the bitmap before drawing it.
Code Snippet:
// ARGBfbuf is a Float32 image buffer with ARGB data // w = width, h = height auto Bmp = MultipassBitmap::Alloc(w, h, COLORMODE::ARGBf); if (!Bmp) { // Handle allocation failure return; } Bmp->SetColorProfile(ColorProfile::GetDefaultSRGB()); // Set parameters for the MultipassBitmap Bmp->SetParameter(MPBTYPE_NAME, "bitmap"); Bmp->SetParameter(MPBTYPE_SAVE, FALSE); Bmp->SetParameter(MPBTYPE_SHOW, TRUE); // Add root alpha channel to the MultipassBitmap auto rootAlpha = Bmp->AddAlpha(nullptr, COLORMODE::GRAYf); if (!rootAlpha) { // Handle error return; } // Set parameters for the root alpha rootAlpha->SetParameter(MPBTYPE_NAME, "rootAlpha"); rootAlpha->SetParameter(MPBTYPE_SAVE, FALSE); rootAlpha->SetParameter(MPBTYPE_SHOW, TRUE); auto layer = Bmp->GetLayerNum(0); if (!layer) { // Handle error return; } // Set parameters for the layer layer->SetParameter(MPBTYPE_NAME, "layer0"); layer->SetParameter(MPBTYPE_SAVE, FALSE); layer->SetParameter(MPBTYPE_SHOW, TRUE); // Add alpha channel to the layer auto alpha = layer->AddAlpha(nullptr, COLORMODE::GRAYf); if (!alpha) { // Handle error return; } // Set parameters for the alpha layer alpha->SetParameter(MPBTYPE_NAME, "alpha0"); alpha->SetParameter(MPBTYPE_SAVE, FALSE); alpha->SetParameter(MPBTYPE_SHOW, TRUE); for (Int32 y = 0; y < h; ++y) { Float32* ptr = ARGBfbuf + (y * w * 4); // Copy image line to the layer layer->SetPixelCnt(0, y, w, reinterpret_cast<UChar*>(ptr), COLORBYTES_ARGBf, COLORMODE::ARGBf, PIXELCNT_0); // Copy alpha line to the alpha layer alpha->SetPixelCnt(0, y, w, reinterpret_cast<UChar*>(ptr), COLORBYTES_ARGBf, COLORMODE::GRAYf, PIXELCNT_0); } // In the GeUserArea render function: // Draw background image DrawBitmap(Background, 0, 0, GetWidth(), GetHeight(), 0, 0, Background->GetBw(), Background->GetBh(), BMP_NORMAL); // Draw bitmap with transparency DrawBitmap(Bmp, 0, 0, GetWidth(), GetHeight(), 0, 0, Bmp->GetBw(), Bmp->GetBh(), BMP_NORMAL | BMP_APPLY_COLORPROFILE | BMP_TRANSPARENTALPHA);
Issue:
Transparent areas (where alpha = 0.0f) appear black instead of showing the background.
Workaround:Saving the bitmap before drawing fixes the issue:
// Save and delete the file String path = GeGetStartupWritePath() + "/temp.tga"; Bmp->Save(path, FILTER_TGA, nullptr, SAVEBIT_ALPHA); GeFKill(path); // Then draw as before DrawBitmap(Bmp, ...);
Additional Information:
- I tried using BaseBitmap instead of MultipassBitmap, but BaseBitmap does not support floating-point color modes (COLORMODE::ARGBf), which I need for my application.
- Even when using BaseBitmap and saving the image, the transparency issue persists.
Questions:
- Why does saving the MultipassBitmap make the transparency work?
- How can I achieve proper transparency without saving the bitmap?
- Is there a way to handle floating-point ARGB data with transparency using BaseBitmap, or is MultipassBitmap the only option?
- Do the SetParameter calls on the Bmp, Layer, root alpha, and alpha layers have any effect on this issue, and am I using them correctly?
Any insights or solutions would be greatly appreciated!
-
Welcome to the Maxon developers forum and its community, it is great to have you with us!
Getting Started
Before creating your next postings, we would recommend making yourself accustomed with our forum and support procedures. You did not do anything wrong, we point all new users to these rules.
- Forum Overview: Provides a broad overview of the fundamental structure and rules of this forum, such as the purpose of the different sub-forums or the fact that we will ban users who engage in hate speech or harassment.
- Support Procedures: Provides a more in detail overview of how we provide technical support for APIs here. This topic will tell you how to ask good questions and limits of our technical support.
- Forum Features: Provides an overview of the technical features of this forum, such as Markdown markup or file uploads.
It is strongly recommended to read the first two topics carefully, especially the section Support Procedures: Asking Questions.
About your First Question
Hey, you did a great job for your first your topic(s), I assume you did read our forum rules - thanks. But you should really avoid having more than one question in a topic, because things tend to become very hard to answer then and our answers very long (as evident by my answer here). Try to find a singular main question you have. You can then optionally ask thematically closely related follow-up questions once the main question has been answered.
Before we go here into details, I would recommend having a look at the topic Overlapping images with transparency with BaseBitmap as it is quite tangential to what you seem to be trying to do.
The general problem is that this "writing multiple layers with embedded alpha values" scenario is not something the Cinema Image API (
BaseBitmap
,MultipassBitmap
) has been really geared for. The major use case for creating multiple layers with alpha values in the Cinema API, Bodypaint, is covered by cinema::PaintBitmap. But that is sort of its own eco-system.BaseBitmap
andMultipassBitmap
are meant for loading and displaying data.- The reasons why your code is likely not working, is because you do not write the master alpha of the multipass bitmap. That was also what I was struggling with in the other thread. You create the channel as
auto rootAlpha = Bmp->AddAlpha(nullptr, COLORMODE::GRAYf);
but never write to it. Probably in the assumption that this would happen automatically, but it does not. - That is probably due to the Maxon Image API image saver maxon::ImageSaverClasses::TGA or generally the switch to the underlying Image API (BaseBitmap::GetImageRef()) making some things snap into place there. You must understand that the Cinema API types
BaseBitmap
andMultipassBitmap
are just a shallow wrapper for the underlying Maxon Image API. - You could save into one of our in-memory UrlSchemes, e.g., an MFS or Ramdisk, when it is the disk activity you fear. When you want to avoid the general overhead of having to serialize and deserialize the image, and want to stay in the Cinema Image API, you will probably have to curate your master alpha channel manually. Or just do what I did in the other thread and manually blend things. There are also some options in the Maxon Image API, but my answer is getting longer and longer ...
- I don't think that this is true. Both
BaseBitmap
and the underlyingImageInterface
(which you usually encounter as anImageRef
in code) support floating point image data. They are defined under the Graphics label asmaxon:Pix...
- They are largely correct but neither necessary nor the cause (not sure why you set
MPBTYPE_SAVE
tofalse
though).
This topic is riddled a bit by too many questions. Let's try to take a more direct angle here and just try to solve what you are trying to do. I assume you want to have some form of UI that stacks multiple images with transparent areas, similar to what the Asset Browser is doing here to display overlay icons over its assets.
The Asset browser simply achieves this by drawing the images on top of each other via cinema::GeUserArea::DrawImageRef, i.e., it uses the Maxon Image API directly. Here you could also load in SVG data via
maxon::VectorImageClasses
if you wanted to. We also have specialized buffer handlers for pixel blending or you could manage a multi layer image in the Maxon Image API via maxon::ImageBaseInterface. But I would avoid these two latter options when possible.Maybe you could describe what you want to achieve concretely?
Given the 32bit/float requirement, I assume you want to draw some texture tiles with some meta information superimposed on them? Please also try to share executable code with us, so that I have a starting point when I write a code example. Please also share demo data when necessary (e.g., an HDRI texture).
Cheers,
Ferdinand -
Thank you for your quick reply and guidance on using the forum.
I'm working on an existing plugin that needs to render an image in RGBAf format in real-time within the User Area. Currently, it achieves this by converting the buffer to ARGB, then to a BaseBitmap, and finally using DrawBitmap to display it on the User Area. This method is slow, and now I also need to add a background, which isn’t possible with BaseBitmap.
Following your guidance, I understand I should use the Maxon Image API (maxon::ImageRef) instead of BaseBitmap and use cinema::GeUserArea::DrawImageRef to display it in the User Area instead of DrawBitmap.
Is there a way to work directly with RGBAf format rather than converting to ARGBf to improve performance?
Thank you!
-
Hey @T1001,
yes, you can do this, converting between pixel formats or use different pixel formats. I will post here a code example in the course of this week which highlights doing what you want to do. As a warning (as I already wrote most of the example but won't have time the next days to finish it),
cinema::GeUserArea::DrawImageRef
is currently misssing the enummaxon::IMAGEINTERPOLATIONMODE
in the public API. You can of course just define it yourself but you (currently) won't find it in the public API.Cheers,
Ferdinand -
Hey @ferdinand ,
Thank you for the clarification and for offering to share a code example—I'm looking forward to it!
I’ve been exploring maxon::PixelFormats::RGBA, and it seems like it might fit my use case. Could you confirm if that’s the correct approach?
Here’s what I aim to achieve:
- Transparency: Drawing a background image first, then overlaying another image with an alpha channel (transparency).
- Cropping: Rendering only a specific portion of an image within the User Area.
- Resizing and other image operations.
If possible, I’d like to make all image processing hardware-accelerated or leverage parallel processing for optimal performance using the Maxon Image API.
Could you provide any guidance or examples on how to effectively utilize the API for these purposes? Any insights would be greatly appreciated.
Thanks again for your support and expertise. Looking forward to your example whenever you get a chance!
Cheers,
T1001 -
Hey @T1001,
yes, you are on the right track there, to initialize an image as RGBA F32, you would do something like this:
static const Int32 size = 1024; const ImageRef image = ImageClasses::IMAGE().Create() iferr_return; const maxon::ColorProfile profile = ColorSpaces::RGBspace().GetDefaultNonlinearColorProfile(); const PixelFormat format = PixelFormats::RGBA::F32(); const Int32 channelCount = format.GetChannelCount(); // Init the bitmap with its size, storage convention, and pixel format. Whe choose here the // "normal", i.e., consecutive layout. An alternative would be a planar layout. image.Init(size, size, ImagePixelStorageClasses::Normal(), format) iferr_return; image.Set(IMAGEPROPERTIES::IMAGE::COLORPROFILE, profile) iferr_return;
Regarding the rest, I roughly cover this in my example. A little GeUserArea which wraps around some render buffer data. I rather not yet share it here, as it "misbehaves" still in one aspect, and I am not sure if that is my or the API's fault (will have to catch one of the Image API devs for that).
You can also have a look at the Color Management and OCIO manual as I covered there quite a bit of pixel format and buffer handling subjects. Last but not least regarding hardware acceleration: The Image API does not have much drawing functions, that is primarily done in the semi-public Drawport API. It depends a bit on what you want to accelerate, filling a buffer cannot be accelerated, you either fill things row by row, or just wrap around some existing memory.
When you need hardware accelerated drawing functions like drawing a circle, etc., then you indeed must use the Drawport API. To get access to it as an external, you would have to apply for Maxon Registered Developer membership, as this would also grant access to the semi-public Drawport API. As its name implies, the Drawport API is the API with wich viewports are drawn in Cinema 4D. You might be familar with
BaseDraw
, the Cinema API type which is used to draw into view ports. Similar to how you can get anImageRef
from aBaseBitmap
, you can get aDrawPortRef
from aBaseDraw
. Under the hood, draw ports are also used to draw into bitmaps, and there is also a mechanism with which one can attach a draw port to a dialog (a draw port user area).But not all parts of the Drawport API are semi-public, some are fully private. I would first have to check, if the bitmap drawing part is semi-public and I know for a fact that the user area is not semi-public (but we wanted to expose it at some point but I think it never happend).
So, long story short, when you think you need all that, I would recommend reaching out via our contact form or directly apply for registered developer membership.
Cheers,
Ferdinand -
Hey @T1001,
find below an example for a user area which draws multiple semi-transparent bitmaps on top of each other. I will likely add this code example to the SDK at some point, as this is a common task. Please provide feedback when you think things are missing.
The example focuses on the core problem of having an 'alien' app which places bitmap data in memory and the Image API wrapping around that data with transparencies. What I have not yet done here, is that I avoided all copying altogether and directly operate on the raw memory managed and owned by the alien app. Which is possible to do in the public API I think, but one would have to write a component for
ImageBaseInterface
so that one can modify theGet/SetPixelHandler
's. This would have been out of scope for this example. But I have it on my backlog, as I can see how this would be a desirable feature for render engine vendors and similar plugin types.Cheers,
FerdinandResult
Code
/* Demonstrates how to implement a dialog that wraps around image buffers provided by an alien application. The buffers are of type RGBA-32 but could also follow any other pixel format the Image API supports. The buffers are drawn with their transparencies on top of each other in a user area and automatically scaled to the size of the user area. The example contains the following sections: * namespace alien: Contains code that generates image data in memory, mocking an alien app. * namespace cinema: Contains the code to wrap that alien image data in Cinema 4D with: * class ImageLayersArea: The UI element which wraps the buffers concretely. * class ImageLayersAreaDialog: The dialog implementation which uses an #ImageLayersArea. * class ImageLayersAreaCommand: The command which wraps the dialog as a menu/palette item which can be invoked by the user to open the dialog. */ // std is here only used to simulate an alien app, do not use std in your regular Cinema projects. #include <vector> #include <algorithm> #include "c4d_basedocument.h" #include "c4d_colors.h" #include "c4d_commandplugin.h" #include "c4d_general.h" #include "c4d_gui.h" #include "maxon/gfx_image.h" #include "maxon/gfx_image_colorprofile.h" #include "maxon/gfx_image_colorspaces.h" #include "maxon/gfx_image_pixelformats.h" #include "maxon/gfx_image_storage.h" #include "maxon/mediasession_image_export.h" // Must be included before the specialization. #include "maxon/mediasession_image_export_psd.h" #include "maxon/lib_math.h" // The plugin ID of the #ImageLayersAreaCommand. You must register unique plugin IDs for your plugins // via https://developers.maxon.net/. static const cinema::Int32 g_image_layers_command = 1064549; // The definition of bitmap buffers generated by the alien application. static const cinema::Int32 g_buffer_height = 2048; static const cinema::Int32 g_buffer_width = 2048; static const cinema::Int32 g_buffer_channel_count = 4; static const cinema::Int32 g_buffer_line_size = g_buffer_width * g_buffer_channel_count; static const cinema::Int32 g_buffer_size = g_buffer_line_size * g_buffer_height; // Title used by the dialog and command, and the IDs of the dialog gadgets. #define CMD_TITLE "C++ SDK: Image Layers Area"_s #define ID_GRP_BUTTONS 1000 #define ID_UA_IMAGE_LAYERS 2000 #define ID_BTN_ADD 2001 #define ID_BTN_REMOVE 2002 #define ID_BTN_SAVE 2003 /// We need this because IMAGEINTERPOLATIONMODE is currently not exposed in the public API, I /// will fix this in a future release. namespace maxon { enum class IMAGEINTERPOLATIONMODE { NEARESTNEIGHBOR, ///< worst quality, no interpolation at all LINEAR, ///< linear interpolation between pixels BICUBIC, ///< best quality using bicubic interpolation } MAXON_ENUM_LIST(IMAGEINTERPOLATIONMODE); } // end of namespace maxon // --- Alien Application Code /// This namespace is meant to simulate some alien application which places bitmap data in memory /// which we are going to wrap with the Image API. The example is using here the std library for /// the sole purpose of simulating an alien application. PLEASE NOTE THAT THE USE OF STD IN CINEMA /// PROJECTS IS STRONGLY DISCOURAGED. namespace alien { // A list of alien bitmap buffers held and managed by an alien application. Each float* in the // vector is a head to a buffer as defined by the g_buffer_ constants. std::vector<float*> g_alien_buffers; // A random number generator used by the alien application. maxon::LinearCongruentialRandom<maxon::Float32> g_random; /// Places an RGBA buffer in memory which holds a horizontal gradient in an otherwise largely /// transparent bitmap. This could for example be a buffer of an render engine which stores /// its data as #float. /// /// We are going to draw something like this, a horizontal gradient that only covers a portion /// of the height of the bitmap buffer, leaving the rest of the buffer entirely transparent. /// /// ---------------------------------------- /// /// /// @@@@@%%%%#####****+++++====-----::::.... /// /// /// ---------------------------------------- float* AddAlienBuffer() { iferr_scope; // The starting position and height of the horizontal gradient bar. const int32_t gradientHeight = std::max( 3, int32_t (g_random.Get01() * float(g_buffer_height) * 0.333)); const int32_t gradientStart = std::min( g_buffer_height - gradientHeight, int32_t (g_random.Get01() * float(g_buffer_height))); const int32_t gradientEnd = gradientStart + gradientHeight - 1; // The knots of the gradient. We either interpolate from opaque to transparent or vice versa. We // dip here our toes a bit into the Maxon API with ColorA32 so that we later use its color // interpolation function. const float alphaA = g_random.Get01() > .5 ? 0.0 : 1.0; const float alphaB = alphaA > 0.5 ? 0.0 : 1.0; const maxon::ColorA32 gradientColorA( g_random.Get01(), g_random.Get01(), g_random.Get01(), alphaA); const maxon::ColorA32 gradientColorB( g_random.Get01(), g_random.Get01(), g_random.Get01(), alphaB); // Allocate a new buffer. float* buffer = new float[g_buffer_size]; // Now we write a gradient into an otherwise fully transparent image. We write RGBA float data // in an at this point undefined color profile. We will then on the Cinema 4D side interpret // this data as PixelFormats::RGBA::F32() with an sRGB-2.1 profile. for (int32_t y = 0; y < g_buffer_height; ++y) { // We are outside of the gradient strip, we fill the line with fully transparent pure black. if (MAXON_LIKELY((y < gradientStart) || (y > gradientEnd))) { for (int32_t x = 0; x < g_buffer_width; x++) { const int32_t i = (y * g_buffer_line_size) + (x * g_buffer_channel_count); buffer[i + 0] = 0.0; // R buffer[i + 1] = 0.0; // G buffer[i + 2] = 0.0; // B buffer[i + 3] = 0.0; // A } } // We are inside the gradient strip, we write a gradient into the buffer. else { for (int32_t x = 0; x < g_buffer_width; x++) { const int32_t i = (y * g_buffer_line_size) + (x * g_buffer_channel_count); const maxon::ColorA32 color = maxon::BlendColor( gradientColorA, gradientColorB, float(x) / float(g_buffer_width)); buffer[i + 0] = color.r; buffer[i + 1] = color.g; buffer[i + 2] = color.b; buffer[i + 3] = color.a; } } } // Append the buffer to the global buffers and return the buffer. g_alien_buffers.push_back(buffer); return buffer; } /// Removes the last element from the alien buffer list and frees its memory. void PopAlienBuffer() { if (g_alien_buffers.empty()) return; float* last = g_alien_buffers.back(); delete[] last; g_alien_buffers.pop_back(); } } // end of alien // --- Cinema 4D Application Code namespace cinema { using namespace maxon; // --- UserArea Implementation -------------------------------------------------------------------- /// Implements a custom GUI that wraps around the bitmap buffers of an alien application. class ImageLayersArea : public GeUserArea { private: // Holds the images wrapping the alien buffers. Since we later want save out these buffers as // multi layer PSD files, we right away pick ImageLayerRef and not ImageRef as our image class. BaseArray<ImageLayerRef> _layers; public: // --- Custom Methods // Adds an image layer to the user area from the given #buffer. We rely here on the globally defined // buffer metadata, otherwise we would have to describe the layout of #buffer with more arguments. Result<void> AddLayer(float* buffer) { iferr_scope; // Allocate a new maxon Image API (layer) bitmap that uses the default non-linear RGB profile // (sRGB 2.1) and an RGBA four bytes per channel float pixel format, i.e., 16 bytes per pixel. // We could also import here an ICC profile from disk to match an exotic color profile used // by alien app. But when that is the case, we would have to convert colors here to sRGB 2.1 as // shown in the example_color_management examples. const ImageLayerRef layer = ImageLayerClasses::RASTER().Create() iferr_return; const maxon::ColorProfile profile = ColorSpaces::RGBspace().GetDefaultNonlinearColorProfile(); const PixelFormat format = PixelFormats::RGBA::F32(); const Int32 channelCount = format.GetChannelCount(); // Init the bitmap with its size, storage convention, and pixel format. Whe choose here the // "normal", i.e., consecutive layout. An alternative could be a planar layout. layer.Init(g_buffer_width, g_buffer_height, ImagePixelStorageClasses::Normal(), format) iferr_return; // Set some metadata on the image layer. Setting a name is irrelevant, the rest is not. layer.Set(IMAGEPROPERTIES::NAME, FormatString("layer_@", _layers.GetCount() + 1)) iferr_return; layer.Set(IMAGEPROPERTIES::IMAGE::COLORPROFILE, profile) iferr_return; layer.Set(IMAGEPROPERTIES::TYPE, IMAGEPROPERTIES::ITYPE::LAYER) iferr_return; // Get the pixel handler to write data into the image. const maxon::SetPixelHandlerStruct handler = layer.SetPixelHandler( format, format.GetChannelOffsets(), profile, maxon::SETPIXELHANDLERFLAGS::NONE) iferr_return; // Copy the data line by line. for (Int32 y = 0; y < g_buffer_height; ++y) { // Construct a Pix, i.e., UChar , line buffer pointer for our current buffer pointer. The // Image API handles all data at its lowest level as UChar. E.g., a line with 10 RGBA::F32 // pixels, i.e, 10 * 4, elements will be actually stored as, 10 * 4 * 4 elements, as an F32 // is decomposed into four UChar bytes. Pix* head = reinterpret_cast<Pix*>(buffer); // Write the line buffer into the image. Despite its name, an ImagePos can express up to // a line in an image. handler.SetPixel( ImagePos(0, y, g_buffer_width), PixelMutableBuffer(head, format.GetBitsPerPixel()), SETPIXELFLAGS::NONE) iferr_return; // Advance the buffer to the next line. buffer += g_buffer_line_size; } // Append our layer to list of layers. _layers.Append(layer) iferr_return; return OK; } /// Pops the last layer from the list of layers. Bool PopImage() { return _layers.Pop(); } /// Saves the whole layer list as a PSD to disk. Result<void> Save() { iferr_scope; // Check that we are on the main thread and then ask the user for a save location. if (!GeIsMainThreadAndNoDrawThread()) return IllegalStateError( MAXON_SOURCE_LOCATION, "This function cannot be run off main-thread"_s); Filename file; if (!file.FileSelect( FILESELECTTYPE::IMAGES, FILESELECT::SAVE, "Select file path:"_s, "psd"_s)) return OK; // Insatiate an ImageTextureRef, the root level bitmap type required to save images. // A texture can only hold entries of type IMAGEHIERARCHY::IMAGE, adding multiple images // to a ImageTextureRef will not result in layers, but all data being put into the "background" // layer of the image. const ImageTextureRef texture = ImageTextureClasses::TEXTURE().Create() iferr_return; const ImageRef base = ImageClasses::IMAGE().Create() iferr_return; base.Init(g_buffer_width, g_buffer_height, ImagePixelStorageClasses::Normal(), PixelFormats::RGBA::F32()) iferr_return; base.Set(IMAGEPROPERTIES::IMAGE::COLORPROFILE, maxon::ColorProfile()) iferr_return; texture.AddChildren(IMAGEHIERARCHY::IMAGE, base, ImageBaseRef()) iferr_return; // Now iterate over our layers with transparencies and add them one by one to the image. for (const ImageLayerRef layer : _layers) { // It is really importan that #layer has IMAGEPROPERTIES::TYPE set to LAYER. base.AddChildren(IMAGEHIERARCHY::LAYER, layer, ImageBaseRef()) iferr_return; } // Save #texture as a PSD. const MediaOutputUrlRef psd = ImageSaverClasses::Psd().Create() iferr_return; texture.Save(MaxonConvert( file, MAXONCONVERTMODE::NONE), psd, MEDIASESSIONFLAGS::NONE) iferr_return; return OK; } // --- GeUserArea Methods /// Called by Cinema 4D to request the minium #width and #height for the area. Bool GetMinSize(Int32& w, Int32& h) { w = h = 256; return true; } /// Called by Cinema 4D to let the area draw its content. void DrawMsg(Int32 x1, Int32 y1, Int32 x2, Int32 y2, const BaseContainer& msg) { // Enable some drawing optimizations. OffScreenOn(); SetClippingRegion(x1, y1, x2, y2); // Draw the background with the default background color. DrawSetPen(COLOR_BG); DrawRectangle(x1, y1, x2, y2); Float32 wx = Float32(x1); Float32 wy = Float32(y1); Float32 ww = Float32(x2 - x1); Float32 wh = Float32(y2 - y1); // Draw our layers on top of each other with bicubic interpolation. for (ImageBaseRef image : _layers) DrawImageRef(image, wx, wy, ww, wh, 1.0, IMAGEINTERPOLATIONMODE::BICUBIC); } }; // --- Dialog Implementation ---------------------------------------------------------------------- /// Realizes a dialog that displays a bitmap with multiple layers with transparencies. class ImageLayersAreaDialog : public GeDialog { private: // The custom user area and the default number of layers which are being added. ImageLayersArea _layers; const Int32 _defaultLayerCount = 3; public: // --- Custom Methods /// Adds an alien buffer and wraps it with an image in the #ImageLayersArea in tandem. Result<void> AddBuffer() { iferr_scope; float* buffer = alien::AddAlienBuffer(); _layers.AddLayer(buffer) iferr_return; return OK; } /// Pops an alien buffer and its wrapping image in the #ImageLayersArea in tandem. Result<void> PopBuffer() { _layers.PopImage(); alien::PopAlienBuffer(); return OK; } // --- GeDialog Methods /// Called by Cinema 4D to let the dialog populate itself with gadgets. Bool CreateLayout() { Bool result = true; static const Int32 margin = 5; // Set the title and the margin between the group and the border and between items in the // group. We are defining here the implicitly existing outmost group. SetTitle(CMD_TITLE); result &= GroupBorderSpace(margin, margin, margin, margin); result &= GroupSpace(margin, margin); // Add the image layers user area. result &= AddUserArea(ID_UA_IMAGE_LAYERS, BFH_SCALEFIT | BFV_SCALEFIT, 0, 200) != nullptr; result &= AttachUserArea(_layers, ID_UA_IMAGE_LAYERS); // Add a group with holds three elements pre row (opposed to the one element by row of the // default group and add the three buttons. result &= GroupBegin(ID_GRP_BUTTONS, BFH_SCALEFIT, 3, 0, ""_s, 0); { result &= GroupSpace(margin, margin); result &= AddButton(ID_BTN_ADD, BFH_SCALE, 0, 0, "Add"_s) != nullptr; result &= AddButton(ID_BTN_REMOVE, BFH_SCALE, 0, 0, "Remove"_s) != nullptr; result &= AddButton(ID_BTN_SAVE, BFH_SCALE, 0, 0, "Save"_s) != nullptr; } result &= GroupEnd(); return result; } /// Called by Cinema 4D to let the dialog init its values once its UI has been built. Bool InitValues() { // We implemented many methods here as Result<T>, the error system of the Maxon API. This method // is not of type Result<T> and we must therefore terimate the error handling in this function. // When an error is bubbling up through the 'iferr_return' of the #AddBuffer call, the function // will exit through this scope handler with #err being the error which is raised. iferr_scope_handler { // This is for demo purposes only, please avoid console spam in production code, use loggers // like DiagnosticsOutput instead. ApplicationOutput("@ failed with error: @", MAXON_FUNCTIONNAME, err); return false; }; // Add a few layers by default. for (Int32 i = 0; i < _defaultLayerCount; i++) { AddBuffer() iferr_return; } return true; } /// Called by Cinema 4D when the user clicks elements in the UI. /// /// Propagated are here only true click events, to realize things like scrubbing, mouse-over, /// etc, one has to implement GeDialog::Message (where one can also handle clicks). Bool Command(Int32 cid, const BaseContainer& msg) { iferr_scope_handler { // This is for demo purposes only, please avoid console spam in production code, use loggers // like DiagnosticsOutput instead. #err is the error exposed to the scope handler. ApplicationOutput("@ failed with error: @", MAXON_FUNCTIONNAME, err); return false; }; // We handle the buttons, when we add or pop layers, we force the user area to redraw. if (cid == ID_BTN_ADD) { AddBuffer() iferr_return; _layers.Redraw(); } else if (cid == ID_BTN_REMOVE) { PopBuffer() iferr_return; _layers.Redraw(); } else if (cid == ID_BTN_SAVE) { _layers.Save() iferr_return; } return true; } }; // --- Command Implementation --------------------------------------------------------------------- /// Realizes the command with which the user can open and close an the #ImageLayersAreaDialog. class ImageLayersAreaCommand : public CommandData { private: // The dialog of the command, we keep using the same instance over the life time of Cinema 4D. ImageLayersAreaDialog _dialog; public: /// Returns an instance of the #ImageLayersCommand. static ImageLayersAreaCommand* Alloc() { return NewObjClear(ImageLayersAreaCommand); } /// Called by Cinema 4D when the user invokes the command. Bool Execute(BaseDocument* doc, GeDialog* parentManager) { // Fold the dialog when open and unfolded, otherwise unfold it (Open both opens a never opened // dialog and unfolds a folded dialog). if (_dialog.IsOpen() && !_dialog.GetFolding()) _dialog.SetFolding(true); else _dialog.Open(DLG_TYPE::ASYNC, g_image_layers_command); return true; } /// Called by Cinema 4D to restore the UI associated with a command on layout switches. Bool RestoreLayout(void* secret) { return _dialog.RestoreLayout(g_image_layers_command, 0, secret); } }; } // namespace cinema /// Registers the #ImageLayersAreaCommand as a CommandData plugin. /// /// Must be called in the main.cpp of this module when #PluginStart() is emitted. cinema::Bool RegisterImageLayersAreaExample() { return cinema::RegisterCommandPlugin( g_image_layers_command, CMD_TITLE, 0, nullptr, "Opens a dialog holding a custom UI element that stacks multiple bitmaps with transparencies."_s, cinema::ImageLayersAreaCommand::Alloc()); }
-
F ferdinand forked this topic 21 days ago