Writing/Reading Rendered Image to and from Text
-
@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 theBaseBitmap
(Γ la the Pythonread
method for reading bytes from a binary file).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! -
Hi,
@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:
- Move away from string serialisation, as it is quite inefficient when it comes to most performance metrics (space, time, etc.). The purpose of string serialisation is to be human readable and to be as barrier-free as possible for data exchange. There is nothing wrong with string serialisations, I like them too, you just have to pay the costs.
- If you do not want to implement a more complex image compression algorithm or use one of the existing 3rd party Python modules, you could employ one of the all purpose compression algorithms, like for example LZW. It won't be as effective a wavelet compression, but in some cases, it will cut down the file size significantly. Python has the
zlib
module, which implements an algorithm that is very similar to LZW. The file formats GIF and TIF use LZW-compression internally.
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 methodSetPixelCnt
to write consecutive blocks of pixels, there is even an example for it in the docs).Cheers,
zipit -
@zipit Thank you again for the thorough explanation.
The existing 3rd party Python modules to which you are referring are
pickle
andmarshal
? -
Hi again
no,
pickle
andmarshal
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 bepil
or its successorpillow
. 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: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])
Cheers,
zipit -
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? -
Hi,
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 installpillow
in the first place, as it has a long list of dependencies. With that I mean in Cinema's fakesite-packages
folder that is meant for third party modules. You could also try to includepillow
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.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 actuallypillow
code that ran on Python 3.9. The naming history of the package is not the smartest, to put it mildlyCheers,
zipit -
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
?Thank you.
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 -
Hoi again,
- With cleaning up I was just referring to what these local import modules / hacks usually do. I haven't looked at Nikklas' code, but I was implying that it was doing it for you.
- Yes, that is the path I meant.
site-packages
is normally the directory where you place 3rd party packages in the library of a Python installation. I called it fake, because Maxon moved the path out of the Python installation that comes with Cinema. Probably not the best choice of words on my end. - Yes, I would not go that route due to the fact that I seems like a lot of work. But that does not mean that it is impossible. I would be confident to find another solution, but if you are not, there is nothing inherently wrong with installing
pillow
in Cinema's Python. It will probably be just a lot of tinkering until you have come up with a custom installer, since there is nopip
in Cinema's Python. The first thing to check would be if you can drag and drop installpillow
and its dependencies you need. Python packages come sometimes with an installer that is run bypip
when you install the package via the command line and sometimes the execution of that installer is actually required for the package to work properly.
PS: Also, when you install
pillow
globally insite-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.Cheers,
zipit -
@zipit Thank you for all of your guidance. You've been very helpful.
-
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. -
@m_adam Thank you, Maxime.
I still learned a lot from your help, @zipit , thank you too.