Melange_Getting the Rendered Image's ... [SOLVED]



  • On 15/09/2016 at 09:34, xxxxxxxx wrote:

    User Information:
    Cinema 4D Version:    
    Platform:   Windows  ;   
    Language(s) :     C++  ;

    ---------
    Hi,

    I'm trying to get the rgba colors from a render done with a .c4d file using Melange.
    AFAIK. This is done by using the rgba_data in the UpdateImage() callback. But it's returning jibberish values for me.
    What am I doing wrong?
    How do I get the correct rgba color values for each pixel in the rendered image?

    Here is a very simple (but complete) example that I've built for testing purposes:

    //This is a very simple example that uses only one thread to render a .c4d file  
    //NOTE: the file paths to my c4d.exe server and .c4d file are hard coded to make the code simpler  
    //In the .c4d file being rendered. There is no image output path set in the render settings. The rendered image is created in memory only  
    //I'm just rendering a cube with a dark colored material on it as a test case  
    //I'm trying to get the rgba color values from the rendered image that's in memory. So I can write a copy of it in my Qt application  
    //The renderconnection example does this somehow with win32 code using the WinUpdateImage() callback function  
    //I cannot figure out how this is being done.  How do I get the rgba colors from this callback?  
    //The rgba_data values I get from it are jibberish!!  
      
      
    #include <windows.h>  
    #include <iostream>  
    using namespace std;  
      
    #include "c4d_file.h"  
    #include "default_alien_overloads.h"  
    #include <c4d_renderconnection.h>   //Needed for rendering  
    using namespace melange;  
      
    enum  
    {  
      APPID = 1234567   //Temporary ID ONLY!!!  
    };  
      
    void GetWriterInfo(Int32 &id, String &appname)  
    {  
      id = APPID;  
      appname = "My Melange Example";  
    }  
      
    void MyUpdateImage(void *userdata, Int32 xpos, Int32 ypos, Int32 xcnt, ColorResolution bpp, UChar *rgba_data)  
    {  
      //How do I use this thing?!!!  
       
      //cout << xpos << "," << ypos <<endl;  
      
      //if (bpp == 4) cout << "float" << endl;  
      //else  cout << "Int" << endl;  
      
      
      //These values do not make any sense  
      //They produce values like 124,99,210 for a scene rendering a black object?!!!  
      int r = static_cast<int>(rgba_data[0]);  
      int g = static_cast<int>(rgba_data[1]);  
      int b = static_cast<int>(rgba_data[2]);  
      cout << r << "," << g <<  "," << b  <<endl;  
    }  
      
    //Thread used to manage the rendering  
    class RenderThread : public IpCommunicationThread  
    {  
      public:  
      
      int counter = 0;  //frame counter...Is there another way to get the current frame being rendered in the .c4d file?  
      
      virtual void Main()  
      {  
          //The file to render  
          String renderedFile = "C:\\Users\\user\\Desktop\\myfile.c4d";  
          BaseDocument *c4dDoc = LoadDocument(renderedFile, SCENEFILTER_OBJECTS);          
          RenderData *rdata = c4dDoc->GetFirstRenderData();  
      
          //Get the render dimensions from the render settings  
          GeData sizeX;  
          rdata->GetParameter(RDATA_XRES, sizeX);  
          Int32 sx = sizeX.GetInt32();  
      
          GeData sizeY;  
          rdata->GetParameter(RDATA_YRES, sizeY);  
          Int32 sy = sizeY.GetInt32();  
      
          //Get the last frame to render from the render settings  
          Float32 fps = c4dDoc->GetFps();      
          GeData d;  
          rdata->GetParameter(RDATA_FRAMETO, d);          
          BaseTime to = d.GetTime();  
          Int32 frameTo = to.GetFrame(fps);          
      
          Int32 rendererror = RENDERERROR_OK, renderphase = PROGRESSTYPE_BEFORERENDERING;  
          Float percent_done = 0.0;  
          Bool running = true;  
      
          // open communication to the server  
          // (local host + port used when starting, UID of your application, initial timeout, connection timeout)  
          Open("127.0.0.1:1234", APPID, 10, 60);  
      
          // send the scene to the renderer  
          // (pass path of CINEMA 4D file or an optional BaseDocument reference as second parameter)              
          Bool file = SendScenefile(renderedFile);  
          if (!file) { cout << "No .c4d file found!" << endl; return; }  
      
          //Start rendering  
          //(X resolution, Y resolution, bitmap color mode, allow saving the render image, high priority)  
          StartRender(sx, sy, COLORRESOLUTION_FLOAT, false, false);  
      
          //Get the render image and progress while rendering         
          while (!TestBreak() && running)  
          {              
              //printf("\n running");  
              //printf(this->GetThreadName());                  
      
              //Calls your callback "UpdateImage" to get the image update  
              GetImageUpdate(MyUpdateImage, nullptr, &running);  
      
              //Check for error and how much rendering is done  
              GetRenderStatus(&running, &rendererror, &percent_done, &renderphase);  
      
              //The percent rendering done for each frame in the scene file  
              //if (renderphase == PROGRESSTYPE_DURINGRENDERING)  
                  //cout << "Work Done: " << percent_done*100.0 << endl;  
      
              //Count the number of frames being rendered (better way to do this?)  
              if (renderphase == PROGRESSTYPE_AFTERRENDERING)  
                  counter++;  
                
              //cout << counter << endl;  
              if (counter > frameTo) running = false;  //Stop the thread when done rendering all the frames in the scene  
          }  
      
          //Free the session on the server and close the thread  
          FreeSession();  
          Close(true);  
      
          //Free Melange network and thread  
          GeIpCloseNetwork();  
          GeEndThreads();  
          cout << "Finished" << endl;  
      }  
    };  
      
      
    RenderThread *rThread = nullptr;  
      
    int main(int argc, Char* argv[])  
    {  
      BaseDocument *doc = BaseDocument::Alloc();  
      
      GeInitThreads();  
      GeIpInitNetwork();  
      Bool server = StartRenderServer("C:\\Program Files\\MAXON\\CINEMA 4D R13\\CINEMA 4D 64 Bit.exe", 1234);  
      if (!server) { cout << "No Server" << endl;  return 0; }  
      rThread = NewObj(RenderThread);  
      rThread->Start(true);  
      
      //Note: The OS will reclaim the memory from rThread when the console closes   
      //      So there is no need to create another thread just to free it  
      //      Free it yourself only if you are creating an app that stays open and runs for a while (to prevent memory leaks)  
      
      system("pause");  
      return 0;  
    }
    

    -ScottA



  • On 16/09/2016 at 08:53, xxxxxxxx wrote:

    Hi Scott,

    the best suggestion I can give is to have a look at the render connection example in function renderconnection_win.cpp:WinImageUpdate(). The code in there is pretty much doing, what you want and all there is to it, is a bit of pointer arithmetic.

    I really don't see the point in reinventing this code.

    The thing is you are given an array with pixel data (basically a chunk of memory, rgba_data points to the beginning of the chunk). The type of this pixel data is defined by the bpp parameter. This dictates how you have to access this data. As also stated in the example, if bpp equals four (I agree, this should be COLORRESOLUTION_FLOAT), then the array contains 32-Bit floating point values (see how the pointer gets cast in this case in the example), one float per r, g, b and a (in other words four float per pixel). What you do with these depends of course on your use case. The example also shows, how to convert these (ConvFtoL()), if you are interested in rgb integer values ranging from 0 to 255. A simple cast (as in your posted code) is of course not enough, as floating point color values range from 0.0 to 1.0. What's supposed to happen if a floating point value in this range gets cast to integer? Can only be 0 or 1... You can also see there, that you may have to swap the rgb values depending on your target format.

    I hope this helps.



  • On 16/09/2016 at 09:39, xxxxxxxx wrote:

    This is very low level stuff. Very complicated with all the maths going on.
    I get the general idea. But I'm having a very hard time stripping out the custom WinBitmap class in the example.
    That class is proprietary to win32. So I can't use it in anything else.
    I need to convert the rgba_data values into rgba color values using local variables.

    I need to get the local versions of UChar *adr & UChar *dst.
    without using that custom WinBitmap class.

        melange::UChar *adr = (melange::UChar* )global_bmp->Bits + (global_bmp->yres-1-ypos) * global_bmp->xres * 3;  
      
      if (xpos+xcnt > global_bmp->xres)  
          xcnt = global_bmp->xres-xpos;  
      
      melange::UChar *dst = adr+xpos*3;
    

    I get that I can use the ConvFtoL() function to convert the values.
    I get that I need to swap the arrays (although I don't know why this needs to be done?).
    But I don't know how to do that locally inside the Callback. Without using the variables from that custom WinBitmap class?
    That WinBitmap class is getting in my way. And I can't figure out how to strip it out and get the color values locally in my own example.

    -ScottA



  • On 17/09/2016 at 11:48, xxxxxxxx wrote:

    I think I have the color conversion part solved. But the xpos & ypos values do not return all of the xy values of the rendered image
    They seem to only return a fraction of the object being rendered in the scene?

    Take this senario:
    Suppose you have a red cube in the .c4d scene file you want to render. And that has the render settings set to 200x200
    In my Melange code. I can render this file without problems. But there are only approx. 60 (xpos & ypos) pixel values being returned
    What are these values? They can't be pixels because there's not enough of them.
    How do I take the rendered image and create a 200x200 2d array of pixels from it so I can write an image from it?

    static long ConvFtoL(float f)  
    {  
      f *= 256.0;  
      if (f < 0.0) return 0;  
      else if (f > 255.0) return 255;  
      
      return (long)f;  
    }  
      
    void MyUpdateImage(void *userdata, Int32 xpos, Int32 ypos, Int32 xcnt, ColorResolution bpp, UChar *rgba_data)  
    {   
      if (bpp == 4) cout << "float" << endl;  
      else  cout << "Int" << endl;  
      
      if (bpp == 4)  
      {  
          float *src = (float* )rgba_data;  
          while (xcnt -->0)  
          {  
              cout << xcnt << endl; //<---prints 0-60 5 times. Why??  
      
              rgba_data[0] = ConvFtoL(src[0]);  
              rgba_data[1] = ConvFtoL(src[1]);  
              rgba_data[2] = ConvFtoL(src[2]);  
              src += 4;  
          }  
      
          //The converted color rgb values(0-255) for the rendered image  
          int r = static_cast<int>(rgba_data[0]);  
          int g = static_cast<int>(rgba_data[1]);  
          int b = static_cast<int>(rgba_data[2]);  
      
          //This seems to get the color values correctly..But I'm not 100% sure?  
          //However...xpos & ypos only seem to return only a few of the values of the object being rendered in the scene?  
          //There seems to be no returned values for areas in the scene where an object does not exist?   
          cout << xpos << "," << ypos << ",    " << r << "," << g << "," << b  <<endl;  
      
      
      
          //This is wrong. But it roughly shows what I'm trying to do  
          //I'm trying to build a 2d array of pixels data from the rendered image so this can be used to write an image file  
          //How do we do that with the param. data supplied by this callback function?  
      
          melange::Vector color;  
      
          for (int x = 0; x < 200; ++x)  
          {  
              for (int y = 0; y < 200; ++y)  
              {    
                  if (x != xpos && y != ypos)  
                  {  
                      color.x = 0;  
                      color.y = 0;  //Use black for any areas not returned by xpos & ypos   
                      color.z = 0;                      
                  }  
                  else  
                  {  
                      color.x = r;  
                      color.y = g;  
                      color.z = b;  
                  }  
      
                //Store these color values in a 2d array so that I can write an image file using it?  
              }  
          }  
      }  
    }
    

    -ScottA



  • On 19/09/2016 at 10:25, xxxxxxxx wrote:

    Hi Scott,

    I'm sorry, but your code is completely wrong. And you are still writing into the array pointed to by rgba_data. Although I have mentioned several times, you are not supposed to do this.

    So, what is your first while loop doing?

            float *src = (float* )rgba_data; // assigns beginning of array to a second pointer called src
            while (xcnt-- > 0) // loop number of delivered pixel times (correct)
            {
                // The following three lines are complete bogus (sorry to say and I hope you don't mind)
                // While your conversion is right and the right side of the assignments is as well,
                // you are then writing all the delivered pixels over the first pixel!
                // rgba_data is pointing to the array of _delivered_ pixels. I mean,
                // you just assigned rgba_data to src before the loop...
                // You want to assign the pixel to your destination bitmap, whatever type it is.
                // Just like the example is doing in WinUpdateImage().
                rgba_data[0] = ConvFtoL(src[0]);
                rgba_data[1] = ConvFtoL(src[1]);
                rgba_data[2] = ConvFtoL(src[2]);
      
                // Advance to the next pixel in the rgba_data array by advancing the pointer by 4
                // as there a four float values per pixel (r, g, b and a) if bpp equals four (correct)
                src += 4;
            }
    

    As already mentioned in the Getting Started with Melange thread, you are receiving not only one pixel per MyUpdateImage() call, but xcnt pixels. You need to see xpos, ypos and xcnt in combination. xpos and ypos tell you the coordinate of the first pixel passed via the rgba_data array. And xcnt tells you the amount of pixels contained in the array. So I'd expect the sum of all xcnt over all MyUpdateImage() calls should equal the number of pixels in your image.
    That's why in the example code there is the while loop, transferring _all_ received pixels. You are for some reason having a while loop only iterating over the src (rgba_data) array, doing a bogus assignment in the place of this source array, storing the values of all pixels in just one place. And in the end you are already saying that your loops are wrong, and to be honest they don't show to me what you are trying to do.

    I hope you don't mind.

    Why am I not posting correct code?
    Because the example already contains the code, which is doing exactly what you want. In a pretty standard way and how one would do it in C/C++. Your problem has nothing to do with Melange, but it's more a general programming question. I suggest to search the net for explanations or tutorials about C/C++ pointer arithmetic. I'm pretty sure afterwards you will say "I see" and there will be a light bulb moment. A whole new tool set is waiting for you. But it's way out of scope of this forum to provide such a lesson. Sorry. On the other hand I'm sure, there are tutorials out there, which explain this matter way more comprehensibly than I ever could.



  • On 19/09/2016 at 16:34, xxxxxxxx wrote:

    Originally posted by xxxxxxxx

    you are receiving not only one pixel per MyUpdateImage() call, but xcnt pixels. You need to see xpos, ypos and xcnt in combination. xpos and ypos tell you the coordinate of the first pixel passed via the rgba_data array. And xcnt tells you the amount of pixels contained in the array. So I'd expect the sum of all xcnt over all MyUpdateImage() calls should equal the number of pixels in your image.

    Nope. It does not. At least not in my code. I wish it was that simple.
    The sum of xcnt is not returning the number of pixels. And the xpos and ypos values are not returning the pixel positions for every pixel in the rendered image.
    They are only returning a fraction of the pixels in the rendered image. Only the ones that seem to have any color value.

    Example 1:
    Using a front facing camera. Render a cube with it in the middle of the editor window.
    The first xpos&ypos returned will have a number roughly where the top left corner of the cube is in the rendered image.
    Move the cube to the top left of the editor window. Then run it again. And the first xpos&ypos will be nearly 0,0.
    Makes sense so far. But...The callback does not return the xpos&ypos values for every pixel in the rendered image. Only a small fraction of the needed pixels.

    Example 2:
    Render a cube with it at the center of the editor window. Then note how many xpos, ypos, and xcnt returned values you get from the callback.
    Now zoom the camera out. Or make the cube smaller. Then do it again.
    Notice that the smaller the rendered object is. The fewer returned callback values you get.
    If the callback returns "all of the rendered image's data". Then number of values should stay constant.
    What is being rendered in the scene should not effect how many values return. Only their color values should change.

    I need to somehow get the color values for each pixel in the rendered image.
    The WinBitmap class in the example is using a CreateDIBSection() function and memset code to process the callback's data. And I need to be able to get the image's pixels myself, without using them.
    But the callback is returning values that frankly don't make any sense to me.

    -ScottA



  • On 20/09/2016 at 00:13, xxxxxxxx wrote:

    I said, the xcnt summed up over _all_ WinImageUpdate() calls will sum up to the number of pixels in your image. Of course you don't get xpos or ypos for every pixel. Why should you? xpos and ypos provide the start coordinate for _all_ pixels provided by one WinImageUpdate() call (xcnt many).

    Several times now I have told you, that you are provided with the pixel in WinImageUpdate().
    All the information you are looking for is there. I tried to explain to you, what is happening in there, all the parameters, even what is fundamentally wrong with your code.

    Did you fix your code to behave like the WinImageUpdate() in the example?



  • On 20/09/2016 at 09:22, xxxxxxxx wrote:

    If I use the same code that's in the WinUpdateImage callback in my project.  It doesn't work because of the member variables pointing to the WinBitmap class. Which I don't have in my project.

    melange::UChar *adr = (melange::UChar* )global_bmp->Bits + (global_bmp->yres-1-ypos) * global_bmp->xres * 3;  
      
    melange::UChar *dst = adr+xpos*3;
    

    I have tried to re-write these 2 lines of code to work locally without the WinBitmap class stuff. But so far I have not been successful.

    What I think I understand correctly at this point is:
    - The UpdateImage callback chunks the image into data blocks. Which get stored in the rgba_data parameter (an array)
    - Every time the callback executes. It grabs the next chunk in the image (but I don't know how yet? possibly this is what src +=4 is doing?)
    - The xpos&ypos parameters get the first rgba_data array element (per chunk?)
    - These rgba_data chunks (arrays) can be of different sizes. So the xcnt parameter is use to get the size of each one (each chunk)

    So putting it all together. The callback returns:
      Several chunks of image data
      The x&y positions where each chunk starts
      The size of each chunk

    So to get all of the actual pixels from this data. We have to use maths that I don't fully understand yet.
    I'm guessing that is where the height-1 + width * 3 stuff is coming into play some how?

    -ScottA

    Edit- Out of curiosity. I looked at the mac version of this to see how they do this. And holy cow is it much simpler. They just create a MacBitmap and then dump the callback into it using global_bmp->Update( userdata, xpos, ypos, xcnt, bpp, rgb_data ). Done!!!
    So then I thought to myself. I wonder if we can do this same thing with a BaseBitmap in Windows?
    That would be great. Because I think I already have code to parse the color values of a BaseBitmap.



  • On 21/09/2016 at 08:59, xxxxxxxx wrote:

    DOH!
    I think the light bulb finally went on.

    I've been struggling to figure out how to get the pixels from the rendered image. Because I wanted a non-proprietary way to get the image data. Because I use many different image handling libraries (Qt, OpenCV, FreeImage, ect..) in my projects. And only rarely use Win32.
    And this whole rgb_data block thing has been a very confusing, and very frustrating for me to figure out how to get the pixels out of it (which I still can't do yet Angry).

    So I'm sitting here pounding my head on my keyboard. And wondering why the heck the developer wrote such a strange callback, using these God awful data block things. When I happened to google Bit Blit. **** And what do I see?  Bit Blit stands for _bit block transfer!
    https://en.wikipedia.org/wiki/Bit_blit
    _
    There's those annoying blocks again!

    So I'm guessing that this callback was written specifically to be used in combination with a BitBlit type function.
    So even if it is technically possible. The developer never intended for us grab the individual pixels with it. It was designed to only work in conjunction with a BitBlit type function.
    So If I want to use it with one of my image libraries. That library needs to have a BitBlit function in it in order to use it as the developer intended.

    -ScottA



  • On 21/09/2016 at 13:33, xxxxxxxx wrote:

    I haven't looked at any code examples at all but I think its a miss understanding of how it works. This code may not work but I hope it helps you get where you need to go.

    If bpp is 4 then its a float so...

    float *colors = (float* )rgba_data;

    //Apparently xcnt is the number of pixels you have been told are ready to read. You need to read these from a starting position in the buffer.

    //The starting point inside the buffer would be given by this...

    //If you know the width of your image, lets call it WIDTH, then we can work out where to start.

    int start = (xpos + ypos *WIDTH) * 4; //There are 4 components to the color r,b,g and a.

    for (int i=0; i < xcnt; i++)
    {
    Int pos = start + i * 4; //the start moved along by the required number of components.

    float r = colors[pos];
    float g= colors[pos + 1];
    float b = colors[pos + 2];
    float a = colors[pos + 3];

    Int imageXPos = pos % (WIDTH*4);
    Int imageYPos = pos / (WIDTH*4);

    }

    I can't guarantee any of this code works, but I took a stab at the code based on reading the docs I found here:

    https://developers.maxon.net/docs/MelangeSDK/html/group__group__c4d__renderconnection.html#gaedda4588b466218969e2037d9181a47c

    This is nothing specific to windows or bitblit. Its just a big long list of rgba values either as chars, words or floats. And the xpos and ypos just tell you where to start reading from in the array and the xcnt tells you how far to go.

    All other colors may be total garbage during rendering since they have not yet been written to. But once the image has finished rendering then all the color values in the buffer will be correct.

    Might be worth re-checking the example Andreas suggested: renderconnection_win.cpp:WinImageUpdate(). probably does something similar to what I just wrote.

    Good luck!



  • On 21/09/2016 at 15:56, xxxxxxxx wrote:

    Thanks kbar,
    I tried it, but it the colors pointer crashes with access violation errors. Just like my own attempts.
    The renderconnect example provide by MAXON uses a separate WinBitmap class and a void type pointer to hold the data. But I have not been able to write my own version of this that doesn't crash with access violations.

    But here's the thing. I don't think I should be trying to write my own version.
    Because in the renderconnect example I was able to get all of the pixels by pushing the rgb values into vector arrays. Then looping through them. But it was very slow. Too slow to be usable.
    So I'm guessing that the main reason that these pass data using chunks (blocks), and read data from chunks functions are used is because it's much faster than reading every single pixel with a nested for loop.
    So I think I should probably use block reading code like BitBlt() instead of reading each pixel by hand.

    I use 3rd party image handling libraries for this stuff. So this callback really confused the heck out of me.
    I'm not used to doing these kinds of low level image handling tasks.

    -ScottA

    -Edit:
    I got your code to work if I changed all of the *4 instances to *3. But unfortunately it is returning strange values.
    I tried using this on them: float r = ConvFtoL(colors[pos]); but the values are still wrong. And this only returns the data blocks. Not all of the pixels.
    I'm sure it's my fault though.



  • On 23/09/2016 at 03:11, xxxxxxxx wrote:

    Hi ScottA,

    considering the length the discussion is reaching without approaching to a real final solution, please find here a simple but comprehensive example matching for the most the one you've presented in the very first post.
    The only change required to the code is to properly set the path to the cinema executable and place inside the folder containing your solution a testscene.c4d file which will be used by the application for rendering.
    AS you can experiment the callback function is invoked by the RenderThread instance from time to time to spool the data arriving from CINEMA and has the responsibility to take this data and save into a local resource (in my case a BaseBitmap instance to get advantage of MELANGE SDK). One important aspect of the way CINEMA streams back the rendered data is that only empty render buckets are not provided to the callback function thus resulting in certain situation that xpos and ypos might not start from an "expected" 0,0.
    Last but not least you can see that render data can be saved back on the bitmap as a chunk or as pixel-per-pixel .
     
    Hoping this example finally answers to all your opened points, give best.
    Riccardo



  • On 23/09/2016 at 07:41, xxxxxxxx wrote:

    Awesome!
    Thanks Riccardo. 🍺

    Not only does this example answer my question about how to get the rgb values.
    But it also shows how to incorporate BaseBitmap & SetPixel() with it in Melange.
    I've used those a lot with the c4d sdk. But I'm new with the Melange sdk and I'm struggling with some of the differences.
    This helps me out a lot.

    Thanks,
    -ScottA


Log in to reply