SOLVED GeClipMap and init(BaseBitmap)

Hi,

the SDK documentation says about GeClipMap::Init(BaseBitmap* bm):

Loads the clip map bitmap from bm. Any previous data is lost.

and about parameter bm:

The bitmap to initialize the clip map with. The caller owns the pointed bitmap.

Sounds like it's copying the bitmap. Casting probably won't work, as GeClipMap and BaseBitmap are not related by inheritance. To get to the clipmap's bitmap, you need to use GeClipMap::GetBitmap().

Cheers,
Frank

Hello @wickedp,

Thank you for reaching out to us. For GeClipMap::Init(BaseBitmap* bm):

  1. Is this copying the bmp into the clip map?
  2. Is the clip map now working on the original bitmap?
  1. No
  2. Yes

This also means that you cannot deallocate the bitmap for the lifetime of the GeClipmap, or more specifically, as long as you intend to call methods on it. This is what is meant by 'the caller owns the pointed bitmap'; it is a polite way of saying 'make sure that this thing stays alive'.

Regarding the casting subject. Inheritance is not always necessary for casting to work, I assume this is where your question came from, and the relevant factor is memory layout. Sometimes we construct types to be layout compliant to other types to allow for casting (without an inheritance relation), the pairs (VPBuffer, MultiPassBitmap) and (Filename, maxon::Url) would be two examples which can be cast without an inheritance relation, for the latter it is however not advisable, you should use MaxonConvert. For GeClipmap this is not true, it just carries a private field for the managed bitmap.

Find below an example which demonstrates the behavior.

Cheers,
Ferdinand

Result (shown is here the first bitmap, i.e., source, it also contains the red square):
Screenshot 2022-12-07 at 14.21.36.png

source == result = true

Code:

/// @brief Demonstrates the ownership of bitmaps associated with a GeClipMap drawing canvas and the 
/// bitmap object lifetime guarantees a caller must make.
static maxon::Result<void> RunGeClipmapTest(BaseDocument* doc)
{
  // The bitmap we are going to use as a source. We are not using scope based memory management, 
  // AutoAlloc, but handle the bitmap ourselves, to make a point about ownership.
  BaseBitmap* source;
  
  iferr_scope;
  finally
  {
    // Free #source when we exit the function normally or through an error.
    BaseBitmap::Free(source);
  };
  
  // Allocate memory for #source and load an image into it.
  Filename file {"/Users/f_hoppe/Documents/matcopy.gif"_s};
  source = BaseBitmap::Alloc();
  if (!source)
    return maxon::OutOfMemoryError(MAXON_SOURCE_LOCATION);
   
  if (source->Init(file) != IMAGERESULT::OK)
    return maxon::UnexpectedError(MAXON_SOURCE_LOCATION, "Could not load image file into bitmap."_s);
  
  // Allocate a GeClipMap canvas to draw into and init it with #source, it will copy the pointer
  // and not the object/memory itself.
  AutoAlloc<GeClipMap> canvas;
  if (!canvas)
    return maxon::OutOfMemoryError(MAXON_SOURCE_LOCATION);
  
  if (canvas->Init(source) != IMAGERESULT::OK)
    return maxon::UnexpectedError(MAXON_SOURCE_LOCATION);
  
  // Doing something like this will lead to crashes in release mode or stops in debug mode, #canvas
  // relies on the object, the memory region, pointed to by #source for the calls made below.
  // BaseBitmap::Free(source);
  // ---> Thread 1: EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
   
  canvas->BeginDraw();
  canvas->SetColor(255, 0, 0);
  canvas->FillRect(0, 0, 100, 100);
  canvas->EndDraw();
 
  BaseBitmap* const result = canvas->GetBitmap();
  if (!result)
    return maxon::UnexpectedError(MAXON_SOURCE_LOCATION);
  
  // Both source and result will contain the red square in the top left corner because they are the 
  // same object.
  ApplicationOutput("source == result = @"_s, source == result);
  ShowBitmap(source);
  ShowBitmap(result);
  
  return maxon::OK;
}

Ah, learned something, too 🙂

Thanks @ferdinand, that's really helpful. Your words explain what I see.

I dug a little deeper, and since found my issue was bit-depth related. I've changed my Bitmap to 32bit now. That's fixed that part.

However, it's introduced another issue. If I put any text in the clip map it shows up as a blank rectangle full of the set colour. The text only appears if I do something like draw a FillRect() underneath it first. Is this bit-depth related as well?

Text without FillRect():
42676991-9fb3-4797-9e52-e855836343cd-image.png

Text with FillRect():
317230fa-9931-42f1-9bf4-e7057f4e97fc-image.png

[Note: just to put the two images above into context for you, the second one is like a 'mouse-over' effect of the first. But the text only reads in the second one, when the FillRect() is drawn 'underneath', or before, the call to TextAt().]

WP.

Hey @wickedp,

Yeah, that could be the case, there is some weird hacking going on in GeClipMap regarding the bit depth of the managed bitmap and I remember a comment warning about stuff being buggy or slow for the bit depth X (I do not recall the exact number).

But I do not think that this is the case here, I assume you are using .TextAt() and drawing into an empty bitmap canvas. I recognized this before myself too: TextAt() seems to need pixels it can blend into, i.e., you cannot have text over a transparent background. When I stumbled upon this a while ago, I briefly play around with SetDrawMode with little luck and then simply assumed that you cannot have text over a transparent background. I did not dig any deeper then due to lack of time, so my assumption could have been false.

It depends on what you want to do here. If this is just a question out of curiosity, I would leave it at this hint of mine. If you need text over a transparent background, I could have a deeper look, but I will probably only happen next week due to time restrictions and this probably being quite time consuming to find out (GeClipMap is itself only a frontend interface which operates on older and more low-level types).

Cheers,
Ferdinand

Leave it with me @ferdinand, I'll dig a bit more tomorrow and over the weekend, and if no luck I'll get back to you (or I'll try a different drawing method).

I'll let you know.

WP.

Hello @wickedp,

Okay, I will keep an eye on this thread.

Cheers,
Ferdinand

OK, I had to jump through some hoops for one solution here.

I created a separate GeClipMap, just the size of the text width and height. I added the text into this in a white colour. I then iterated over this text map and if the pixel was greater than black, I blended it in with the main clip map and the text colour needed. Not an ideal solution as it involves creating another temporary clip map, but they're only the size of the text so fairly small.

While I'm on this one, is it safe to have two clip maps inside the BeginDraw() and EndDraw() functions at the same time?

WP.

Hey @wickedp,

great to hear that you found a solution and thank you for sharing your approach.

While I'm on this one, is it safe to have two clip maps inside the BeginDraw() and EndDraw() functions at the same time?

I am not quite sure how this is meant. Let's assume you have a BaseBitmap B and two GeClipMap instance CA and CB.

  • Starting or stopping drawing operations for CA will have no impact on CB and vice versa.
  • You can start and stop operations on CA and then start and stop then again on CA. This can for example be useful when you want to figure out which width and height a string S has before you define the size of a drawing canvas (so that S will fit into it). This also means that you can initialize the same canvas again.
  • I would not recommend opening two canvases on the same bitmap source. As far as I know, a GeClipMap does not acquire a lock on its managed bitmap. So, when you then try to operate the two bitmaps without access boundaries as for example running them consecutively in a thread, you might run into crashes or garbage output when they both try to write the same data.

If this does not answer your question, I would ask for a clarification on what you mean, ideally with dummy code.

Cheers,
Ferdinand

@ferdinand said in GeClipMap and init(BaseBitmap):

Starting or stopping drawing operations for CA will have no impact on CB and vice versa

This might be the answer. But just to be sure...

The docs say that to use both Get/SetPixelRGBA() they have to be enclosed in BeginDraw/EndDraw(). So if we're handling two clip maps at the same time, for example reading from one and writing to another, can they both be 'active' between Begin/EndDraw() at the same time?

In my case, something like this:

/* pseudo code */

GeClipMap *CA = GeClipMap::Alloc()...
// init CA etc. We'll draw text into this one

GeClipMap *CB = GeClipMap::Alloc()...
CB->init(bmp); // init this one with another Bitmap

CA->BeginDraw();
CB->BeginDraw();

/* Add some text into our first clip map */
CA->SetColor(...);
CA->TextAt(...);

/* 'for' loop here to iterate over CA, and copy into CB if colour from CA is not black */
LONG r,g,b,a;
for(int rows = 0; etc...)
{
    for(int cols = 0; etc...)
    {
        /* Get rgba from CA */
        CA->GetPixelRGBA(....);

        /* Check if red colour is greater than 0 */
        if(r != 0)
        {
            /* Set/blend pixel colour into CB */
            CB->SetRGBA(...);
        }
    }
}

CA->EndDraw();
CB->EndDraw();

In this case, we have to use GetPixelRGBA on the first clip map with the text, and are using SetPixelRGBA on the second clip map that's the BaseBitmap we're adding text into. They'd both be between Begin/EndDraw() at the same time in my example above. Is this OK?

For what it's worth, it seems to work in my plugin. But I want to check that it's safe to do.

WP.

Hey @wickedp,

Generally speaking, I would say this is safe, but I am a bit reluctant to make such a broad statement. The specific code you have posted is however definitely safe.

As stated before, GeClipMap is just a front-end interface with a lower-level interface sitting behind it, let us call it Backend. Backend operates on the data managed by the GeClipMap, i.e., when you call Backend::DrawLine, you pass in a memory region, and it then does what it is told to do. Since each clipmap has its own managed memory unless you deliberately initialized it with the same memory, there will be no problems.

But there are methods like Backend::TextWidth or Backend::TextAt which then use OS resources. It seems unlikely that this will cause problems, but I cannot categorically deny it, as this then leaks into Windows, mac OS, and Linux OS code. I.e., two GeClipMap instances try to measure the width of a font from two threads at the same time. Something could go wrong there, depending on what the access restrictions of these OS resources are. But since we use GeClipMap also internally a lot, a Cinema 4D is drawing a lot of text, it seems very unlikely that there is any problem with that.

But your main concern seems to be if GeClipMap acts as a sort of singleton, i.e., that you can only have (or operate on) one instance at a time. That is not the case. You can have as many instances as you want. But when you open GeClipMap instances on the same memory, you can obviously run into problems, when you try to operate them in parallel. But that is not the case in your code, you are running everything consecutively, so both maps CA and CB could manage the same memory in this case.

Cheers,
Ferdinand

Sounds like what I'm doing should be OK then. If I run into any problems, I'll pop back in for further advice.

Thanks @ferdinand, your help is always appreciated. We can close this one.

WP.