Your browser does not seem to support JavaScript. As a result, your viewing experience will be diminished, and you have been placed in read-only mode.
Please download a browser that supports JavaScript, or enable it if it's disabled (i.e. NoScript).
Hello, I am trying to write/read a C4D-rendered image file to & from a string. In this example, I am able to use Base64 to write the image but I have to save to disk in order to convert it to a string. The same goes for reading it back from a string to a BaseBitmap.
BaseBitmap
import c4d, os, base64 from c4d import storage desktop = storage.GeGetC4DPath(c4d.C4D_PATH_DESKTOP) rd = doc.GetActiveRenderData() imageString = "" def ReadImage(b64str): image_64_decode = base64.decodestring(b64str) bmp2Path = os.path.join(desktop,"Test.jpg") with open(bmp2Path, "rb") as imageFile: imageString = base64.b64encode(imageFile.read()) print imageString image_result = open(bmp2Path, 'wb') image_result.write(image_64_decode) bmp = c4d.bitmaps.BaseBitmap() bmp.InitWith(bmp2Path) def WriteImage(bmp,bmpPath): if bmp.Save(bmpPath, c4d.FILTER_JPG) == c4d.IMAGERESULT_OK: print "Bitmap saved to: %s"%bmpPath with open(bmpPath, "rb") as imageFile: imageString = base64.b64encode(imageFile.read()) return imageString return False def main(doc): bmp = c4d.bitmaps.BaseBitmap() bmp.Init(250,250,24) bmpPath = os.path.join(desktop,"Test.jpg") if c4d.documents.RenderDocument(doc, rd.GetData(), bmp, c4d.RENDERFLAGS_EXTERNAL) != c4d.RENDERRESULT_OK: raise RuntimeError("Failed to render the document.") else: imgString = WriteImage(bmp,bmpPath) ReadImage(imgString) if __name__=='__main__': main(doc)
Thank you.
Hi sorry to come a bit late to the party, but you are not forced to rely on 3rd part solution you can export your BaseBitmap to a bitsequence then it's up to you to convert this bitsequence in a way that can be represented in a file.
Find an example in read_write_memory_file_bitmap_r13.
Cheers, Maxime.
Hi,
I am a bit confused. In your example you are serialising your image as a JPEG to disk. Cinema's bitmap format is obviously a raw and lossless one, so there is that discrepancy for once.
While Python offers with pickle and marshal two modules for byte-serialisation and with json one for string serialisation, the language lacks the capability to serialise arbitrary objects like you can for example in C# via XmlSerializer (but even there you have to fulfil certain criteria and cannot just serialise anything).
pickle
marshal
json
XmlSerializer
So you will have to come up with your own serialisation scheme, since BaseBitmap does not have its own format. There is also BaseBitmap.SetPixelCnt which lets you write from a byte buffer into the bitmap, but this is only a performance feature, you can also just serialise and deserialise your data pixel by pixel.
BaseBitmap.SetPixelCnt
For the serialisation: There are no inherently wrong ways to do this. It is common practice to reserve a fixed amount of bytes for the header, where you put the important metadata of the file (for a bitmap that could be the bit depth and the number of pixel rows and columns). Then you have to define the data in the body. For an uncompressed bitmap that could be simply enumerating the RGB triples. You could look at other formats, but file formats, even the simple ones, are usually quite complex. If you want to include multiple layers and/or alpha channels, things would get more complicated, as you would not only have to serialise the additional bitmap data, but also the relation they are in.
Cheers, zipit
@zipit Thank you for the reply and info regarding byte-serialisation. Yes, you're right about the serialising my image as a JPG to disk being confusing because, as I mentioned in the original post, I don't know how to write to text or read it back in Cinema 4D without saving it as an image (in this case) with base64. I'm aware this is not the way to do it & want to skip the image step in both functions entirely by getting the bitmap data from the BaseBitmap (à la the Python read method for reading bytes from a binary file).
base64
read
My images will not have alpha channels or metadata. I tried what you mentioned with writing the RGB of each pixel and the text file was 800KB for a 250x250px image compared to the 6KB .jpg! I had no idea the difference would be so great.
pixels = list() for wPixel in xrange(bmp.GetBw()): for hPixel in xrange(bmp.GetBh()): pixels.append(bmp.GetPixel(wPixel, hPixel)) filePath = os.path.join(desktop,"Image.txt") f= open(filePath,"w") rgb_values = ','.join(str(p) for p in pixels) f.write(rgb_values) f.close()
I imagine reading 818361 characters back, converting them to lists, then writing them with SetPixel will be a slow operation? I'm guessing because of the image compression, when I saved the base64 byte string to a text file, it was only 8KB, but again, I had to save as an image to disk first.
Could I get the compressed .jpg data without saving the image first, perhaps, or is there really no way to get this data from BaseBitmap? Can anyone suggest a faster,smaller way of doing this? Thanks!
@blastframe said in Writing/Reading Rendered Image to and from Text:
... because, as I mentioned in the original post, I don't know how to write to text or read it back without saving it as an image (in this case) with base64.
You can not, because BaseBitmap is only being serialiseable via the offered common image formats, and this serialisation is, depending on the chosen format, also lossy (a JPEG cannot have layers for example). BaseBitmap is a complex data type, think of it as a diagram, that has no inherent information about how to be expressed as an ordered tuple of values, i.e. how to be serialised. base64 is also just a number system, so you are just expressing the existing data in a slightly more convenient way (you could also save binary, i.e. base 2, as text, it just would be very long).
Could I get the compressed .jpg data without saving the image first, perhaps, or is there really no way to get this data from BaseBitmap? Thanks!
The lossy image formats are quite efficient at what they do, so no, you won't get things as small without using something specialised like a wavelet compression for example. Things you can do:
zlib
edit: Yes, SetPixel is probably quite slow (never tested the performance myself, but array insertion operations are expensive, so this method is most likely quite expensive too. I mentioned in my first post the method SetPixelCnt to write consecutive blocks of pixels, there is even an example for it in the docs).
SetPixel
SetPixelCnt
@zipit Thank you again for the thorough explanation.
The existing 3rd party Python modules to which you are referring are pickle and marshal?
Hi again
no, pickle and marshal are 'bultin' modules of Python and both general purpose serialisation modules (pickling and marshling are both just odd synonyms for serialisation). A common candidate for such 3rd party image module would be pil or its successor pillow. Both libraries accept byte objects when invoking their save functionality, which would allow you to serialise something into the image format of your choice without having to go the route of saving it to disk. It would go something like that:
pil
pillow
import io from PIL import Image # The source of the bitmap would obviously different, I am just not # in the mood to install pillow in Cinema. You can create Image objects # from a byte buffer, which would allow you to instiate an Image object # from a raw pixel array fed by Cinemas BaseBitmap. image = Image.open("test.png") # We need to get rid of the alpha channel first, or pillow will complain when # we try to save the image as a JPEG. image = image.convert("RGB") # The buffer object. buffer = io.BytesIO() # We just save the image with the buffer object in place of the file path. image.save(buffer, format="JPEG") # The first 10 bytes of the image in JPEG format. print (buffer.getvalue()[:9])
Hi @zipit ! Thank you again.
Yes, I saw the pil module, but didn't pursue it because I don't know the recommended way for including a 3rd party module with my plugins. How would you include the module? Using Niklas Rosenstein's localimport or is there another way to do it?
AFAIK there is no recommended way. Due to the fact the MAXON decided to rip out pip and not deliver a package manager with Cinema's Python, it would be quite some work to install pillow in the first place, as it has a long list of dependencies. With that I mean in Cinema's fake site-packages folder that is meant for third party modules. You could also try to include pillow locally with your plugin, but that would probably require a decent amount of monkey patching to get things to work. If you only import the module locally in the interpreter and then clean up after yourself (i.e. use Nikklas importer thingy), is probably only a detail in these considerations.
pip
site-packages
If you want to do this in a plugin intended for distribution, I would look for another solution.
PS: And just to be clear. Although it says PIL in my example code, this is actually pillow code that ran on Python 3.9. The naming history of the package is not the smartest, to put it mildly
PIL
Hi @zipit , Thank you for the clarification and sorry for all the questions.
What do you mean, clean up after myself? Would I need to remove something after importing it?
Yes, the plugin is meant for distribution...so you suggest I look for a solution other than pillow & Niklas' localimport?
localimport
P.S. It seems like C4D is recognizing modules in this folder for me... C:\Program Files\Maxon Cinema 4D R22\resource\modules\python\libs\win64\python27.vs2008.framework\lib\site-packages I'm now trying to create Image objects from a byte buffer
C:\Program Files\Maxon Cinema 4D R22\resource\modules\python\libs\win64\python27.vs2008.framework\lib\site-packages
Hoi again,
PS: Also, when you install pillow globally in site-packages , you won't need Nikklas' package, since it is already sitting in the global module directory then, so it does not make sense to import it locally then.
@zipit Thank you for all of your guidance. You've been very helpful.
@m_adam Thank you, Maxime.
I still learned a lot from your help, @zipit , thank you too.