Maxon Developers Maxon Developers
    • Documentation
      • Cinema 4D Python API
      • Cinema 4D C++ API
      • Cineware API
      • ZBrush GoZ API
      • Code Examples on Github
    • Forum
    • Downloads
    • Support
      • Support Procedures
      • Registered Developer Program
      • Plugin IDs
      • Contact Us
    • Categories
      • Overview
      • News & Information
      • Cinema 4D SDK Support
      • Cineware SDK Support
      • ZBrush 4D SDK Support
      • Bugs
      • General Talk
    • Unread
    • Recent
    • Tags
    • Users
    • Login

    GeClipMap and init(BaseBitmap)

    Cinema 4D SDK
    c++ sdk
    4
    13
    1.6k
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • WickedPW
      WickedP
      last edited by

      Hi folks,

      I'm working on an interface and am passing a Bitmap around to a number of separate objects, each with their own draw function. In doing so, I'm noticing some strange behaviour, and I think I may know what's causing it. But I'm seeking some guidance first in case it's something else.

      If I do this:

      BaseBitmap *bmp = etc..
      
      // then, I do this somewhere else
      GeClipMap *cm = GeClipMap::Alloc();
      cm->init(bmp);
      

      is this copying the bmp into the clip map? Or is the clip map now working on the original bitmap?

      If it is being copied, can we cast to a clip map instead so there's no copying, and where the original 'bmp' pointer remains in tack also?

      WP.

      wickedp.com

      ferdinandF 1 Reply Last reply Reply Quote 0
      • fwilleke80F
        fwilleke80
        last edited by fwilleke80

        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

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

        1 Reply Last reply Reply Quote 0
        • ferdinandF
          ferdinand @WickedP
          last edited by ferdinand

          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;
          }
          

          MAXON SDK Specialist
          developers.maxon.net

          WickedPW 1 Reply Last reply Reply Quote 0
          • fwilleke80F
            fwilleke80
            last edited by

            Ah, learned something, too 🙂

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

            1 Reply Last reply Reply Quote 0
            • WickedPW
              WickedP @ferdinand
              last edited by

              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.

              wickedp.com

              ferdinandF 1 Reply Last reply Reply Quote 0
              • ferdinandF
                ferdinand @WickedP
                last edited by

                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

                MAXON SDK Specialist
                developers.maxon.net

                WickedPW 1 Reply Last reply Reply Quote 0
                • WickedPW
                  WickedP @ferdinand
                  last edited by

                  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.

                  wickedp.com

                  ferdinandF 1 Reply Last reply Reply Quote 0
                  • ferdinandF
                    ferdinand @WickedP
                    last edited by

                    Hello @wickedp,

                    Okay, I will keep an eye on this thread.

                    Cheers,
                    Ferdinand

                    MAXON SDK Specialist
                    developers.maxon.net

                    1 Reply Last reply Reply Quote 0
                    • WickedPW
                      WickedP
                      last edited by

                      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.

                      wickedp.com

                      ferdinandF 1 Reply Last reply Reply Quote 0
                      • ferdinandF
                        ferdinand @WickedP
                        last edited by

                        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

                        MAXON SDK Specialist
                        developers.maxon.net

                        WickedPW 1 Reply Last reply Reply Quote 0
                        • WickedPW
                          WickedP @ferdinand
                          last edited by

                          @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.

                          wickedp.com

                          ferdinandF 1 Reply Last reply Reply Quote 0
                          • ferdinandF
                            ferdinand @WickedP
                            last edited by ferdinand

                            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

                            MAXON SDK Specialist
                            developers.maxon.net

                            1 Reply Last reply Reply Quote 0
                            • WickedPW
                              WickedP
                              last edited by

                              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.

                              wickedp.com

                              1 Reply Last reply Reply Quote 0
                              • First post
                                Last post