Storing Images in Scene Files?
-
THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED
On 21/12/2012 at 11:35, xxxxxxxx wrote:
Thanks Robert.
I look at your words. And I try to use them.But this is what happens :
-ScottA
-
On 08/01/2013 at 21:42, xxxxxxxx wrote:
Robert.
I've manged to get to the point where I can save String values into a scene's HF.
The changed string value sticks when the scene is saved and opened as expected without any problems.
But I'm getting access violation crashes when I try to do the same thing with a BaseBitmap.
You mentioned saving a copy of the bitmap. And I'm thinking that might be the final missing piece of the puzzle I need to make this finally work.Could you please look at this code and let me know what you think?
#include "c4d.h" #include "c4d_symbols.h" #include "tsimpletag.h" #include "customgui_priority.h" //needed for the priority stuff to work // be sure to use a unique ID obtained from www.plugincafe.com #define PLUGIN_ID 1000010 class SimpleTag : public TagData { INSTANCEOF(SimpleTag,TagData) public: BaseBitmap *bmp; virtual Bool Read(GeListNode *node, HyperFile *hf, LONG level); virtual Bool Write(GeListNode *node, HyperFile *hf); virtual Bool CopyTo(NodeData* dest, GeListNode* snode, GeListNode* dnode, COPYFLAGS flags, AliasTrans* trn); void createImage(void); virtual Bool Message(GeListNode *node, LONG type, void *t_data); virtual Bool Init(GeListNode *node); virtual EXECUTIONRESULT Execute(BaseTag *tag, BaseDocument *doc, BaseObject *op, BaseThread *bt, LONG priority, EXECUTIONFLAGS flags); static NodeData *Alloc(void) { return gNew SimpleTag; } }; Bool SimpleTag::Read(GeListNode *node, HyperFile *hf, LONG level) { return hf->ReadImage(bmp); } Bool SimpleTag::Write(GeListNode *node, HyperFile *hf) { hf->WriteImage(bmp,FILTER_JPG,NULL,SAVEBIT_0); return TRUE; } Bool SimpleTag::CopyTo(NodeData* dest, GeListNode* snode, GeListNode* dnode, COPYFLAGS flags, AliasTrans* trn) { //What do I write here? return TRUE; } void SimpleTag::createImage(void) { BaseDocument *doc = GetActiveDocument(); bmp = BaseBitmap::Alloc(); LONG x = 100; //The X size of the final image LONG y = 100; //The Y size of the final image bmp->Init(x, y); RenderData *rd = doc->GetActiveRenderData(); //Get the active render data BaseContainer rdcopy = rd->GetData(); rdcopy.SetLong(RDATA_XRES, x); rdcopy.SetLong(RDATA_YRES, y); rdcopy.SetLong(RDATA_RENDERENGINE, RDATA_RENDERENGINE_STANDARD); RenderDocument(doc, rdcopy, NULL, NULL, bmp, RENDERFLAGS_EXTERNAL, NULL); } Bool SimpleTag::Message(GeListNode *node, LONG type, void *data) { BaseTag *tag = (BaseTag* )node; //Get the tag and assign it to a variable BaseContainer *bc = ((BaseList2D* )node)->GetDataInstance(); //Get the container for the tag switch (type) { case MSG_DESCRIPTION_COMMAND: // MSG_DESCRIPTION_COMMAND is sent when button is clicked { DescriptionCommand *dc = (DescriptionCommand* )data; // data contains the description ID of the button LONG button = dc->id[0].id; // get the ID of the button switch (button) { //This button executes a method that renders the scene into the bmp BaseBitmap variable case BUTTON1: createImage(); break; //This button shows the current image stored in the bmp BaseBitmap variable in the image viewer case BUTTON2: ShowBitmap(bmp); break; } } } tag->SetDirty(DIRTYFLAGS_DATA); //Used to update a Tag's AM GUI items return TRUE; } Bool SimpleTag::Init(GeListNode *node) { bmp = NULL; //This code sets up the expression priority options that most things have in c4d GeData d; if (node->GetParameter(DescLevel(EXPRESSION_PRIORITY),d,DESCFLAGS_GET_0)) { PriorityData *pd = (PriorityData* )d.GetCustomDataType(CUSTOMGUI_PRIORITY_DATA); if (pd) pd->SetPriorityValue(PRIORITYVALUE_CAMERADEPENDENT,GeData(FALSE)); node->SetParameter(DescLevel(EXPRESSION_PRIORITY),d,DESCFLAGS_SET_0); } return TRUE; } EXECUTIONRESULT SimpleTag::Execute(BaseTag *tag, BaseDocument *doc, BaseObject *op, BaseThread *bt, LONG priority, EXECUTIONFLAGS flags) { return EXECUTIONRESULT_OK; } Bool RegisterSimpleTag(void) { String path=GeLoadString(IDS_SIMPLETAG); if (!path.Content()) return TRUE; return RegisterTagPlugin(PLUGIN_ID,path,TAG_EXPRESSION|TAG_VISIBLE,SimpleTag::Alloc,"tsimpletag",AutoBitmap("myicon.tif"),0); }
The crash is coming from the Read() function when trying to open the saved file.
Thanks,
-ScottA -
On 08/01/2013 at 23:41, xxxxxxxx wrote:
Yeah. There is more that you have to do and where it is done is important. Here is a zip download that contains the source and res that works:
http://www.kuroyumes-developmentzone.com/wp-content/uploads/2013/01/Z_PluginTestProject.zip
Note that you have to always have Alloc()'d the BaseBitmap, even for hf->ReadImage().
Note also that I define C4D_RXX as preprocessor directives to rectify SDK version differences. You should be able to get away with defining only C4D_R12 and C4D_R13 (depending on the version you are building for).
-
On 09/01/2013 at 02:39, xxxxxxxx wrote:
The reason you are getting a crash on Read() is that you haven't allocated a bitmap for the data to be loaded into. You have a pointer but it's just a pointer to nothing - it probably has a null value - so you get an access violation the moment it's used.
If you AutoAlloc a BaseBitmap as a class-level variable, you can use that and it shouldn't crash on read.
-
On 09/01/2013 at 08:29, xxxxxxxx wrote:
God bless you Robert.
You have no idea how many days of hair pulling and dead ends I've gone through trying to figure this out.It turns out that I don't need to use the CopyTo() function at all. But it's nice to see an example of how to use it if I do need it some day. The example given in the docs is of no help at all to someone who hasn't used it before. Your code example is much more clear.
All I needed to do to make it work was put BaseBitmap::Alloc(); in the Init() function.
Now it's working and I'm on my way to the next step.@Steve,
Thank you for your advice too.-ScottA
-
On 09/01/2013 at 23:07, xxxxxxxx wrote:
Originally posted by xxxxxxxx
It turns out that I don't need to use the CopyTo() function at all. But it's nice to see an example of how to use it if I do need it some day.
If the user copies and pastes the tag (in the same document or a new one), they might get a BaseBitmap allocated through Init() but CopyTo() ensures that they get a copy that has been initialized and contains the same image (as was presented in my code). In other words, never assume that the user will never do something that you don't think that they will do - they expect a lot and usually to our (programmer's) demise ;). It may be that they will not expect the image copied and will use a new image - but it is most likely that the user expects that the copied plugin retains all of the information contained in the original.
-
On 10/01/2013 at 08:35, xxxxxxxx wrote:
Oh. OK.
Thanks for explaining that.-ScottA
-
On 24/01/2013 at 12:07, xxxxxxxx wrote:
I've got everything working like I want now with one bitmap image stored in the tag.
But I'm having a very hard time doing it with multiple images.ASFAIK. The overridden Read() & Write() methods can't read images from an array. Since all my attempts to do so have failed misearbly.
So I was wondering if maybe I could use the level option in place of an array?
Maybe I could write each bitmap to it's own level. Then use those levels as index numbers to assign them to a specific bitmap button?The problem is I can't even test this out. Because the SDK doesn't say how to Write a level.
It only shows how to read a level.
Can anyone tell me how to write a new hyperfile level in the overriden Write() method?-ScottA
-
On 24/01/2013 at 12:23, xxxxxxxx wrote:
Hi Scott,
Try something like this:
Bool MyObjectData::Write(HyperFile* hf) { Bool result = ObjectData::Write(hf); if (!result) return result; /* Assuming `bitmaps` is a GeDynamicArray<BaseBitmap>* object. */ /* Write the number of bitmaps in the array to the file, so we */ /* later know how many bitmaps to read. */ LONG count = bitmaps->GetCount(); if (!hf->WriteLong(count)) return FALSE; /* Iterate over all bitmaps and write them to the HyperFile. */ for (LONG i=0; i < count; i++) { BaseBitmap* bmp = (*bitmaps)[i]; if (!hf->WriteImage(bmp, FILTER_PNG, NULL, SAVEBITS_ALPHA)) return FALSE; } return TRUE; }
Reading should be similiarly simple.
Best,
NiklasNote: I did not compile this code yet, serves just a demonstrational purpse.
-
On 24/01/2013 at 15:03, xxxxxxxx wrote:
I'm not able to make that work yet...But still trying.
I think I'm trying to do something that just isn't supported.
I don't think the built in hyperfile is designed to serve up multiple images like this.-ScottA
-
On 24/01/2013 at 15:09, xxxxxxxx wrote:
Can you upload your current code?
-
On 24/01/2013 at 16:06, xxxxxxxx wrote:
Sure:https://sites.google.com/site/scottayersmedia/Dialog_StorageTag.zip
This plugin works exactly how I want. But only with one button and one image per StorageTag.
Let me explain a little bit about what it does:-When the plugin's dialog is open. If the currently selected object also has my "StorageTag" on it. And the bitmap button is blank. Pressing the bitmap button will render the current scene and place that image on the button.
-If there is an image already on the button. It will not re-render a new image( this is intentional by design)
-If the object with a Storage tag has children. The children are also rendered
If the object has no children. Only the object itself is rendered-Selecting different objects in the OM with StorageTags on them makes the bitmap buttons automatically change respective to the selected object.
-If the scene is saved. And then opened. The old image will still be on the dialog button
All of this is working nicely just how I want. But It's limited to one button. And one image per StorageTag.
My final version will have multiple dynamically created bitmap buttons on it. So I need a way to store and retrieve multiple images from the hyperfile. And some kind of way to index them so they can be assigned to specific dialog bitmap buttons as they are added or deleted.-ScottA
-
On 24/01/2013 at 20:01, xxxxxxxx wrote:
It is assuredly possible to store as many images as you would like to the hyperfile.
Two important points you should consider:
1. You should create a LONG description in your tag to store the number of bitmaps currently in the array so that upon reading, you know the number that were saved and need to be read (otherwise, how would your code know? - this will surely end poorly if that number is not known).
2. For retaining button-image assignments, you will need to store that information somewhere. You could create a separate array that mirrors the bitmap array to store, say, the button ID. Or you could create a simple class that stores a BaseBitmap* and the button ID that you create as an array of instances. You would then go through the simple class array to store the bitmap (WriteImage()) and ID association (WriteLong()).
Realize that the reading-writing process is in tandem. That is, the order that you write data is the EXACT order that you must read it. The C4D file format is not random access - it is linear data (possibly chunked, but not important). The good thing though, is that static descriptions are always read before Read() is called so that they are available in Read() by default.
Finally, you need some way to do this. Yes, you will need to figure out what information is required to retain the bitmaps and their correct button associations and write-read-copyto that information for it to be retained in file. Also, upon reading, you need to allocate any dynamic structures (arrays, class arrays) that hold this information and data just like when the tag has it allocated otherwise. If you have an array of BaseBitmap* in which you store the images, you will need to allocate an array of BaseBitmap* in Read() not only to read in the images but to store them just like when images are added, say, by the user. Read() needs to reestablish all of the member data as it was while live in the tag when in use (as long as the member isn't just for some intermediate processing).
-
On 24/01/2013 at 22:12, xxxxxxxx wrote:
@Scott: You don't have to explain me, I did already write a you a full
plugin doin' what you wanted it to do earlier. I'll look into it at a later
minute.@Robert: Why does he need to create a LONG description? This would
let the user modify it. And even when hiding it, the value could be set
via a script or similar. Preventing that with overriding Message() would
just be an in-ideal design. The number of bitmaps stored in the tag
should be known always during run-time. This value can then be written
to the HyperFile (just as I did above).I do not understand why he would need to write the ID of the button in
the dialog to the file, but that might depend on the requirements.-Nik
-
On 25/01/2013 at 07:51, xxxxxxxx wrote:
Originally posted by xxxxxxxx
@Scott: You don't have to explain me, I did already write a you a full
plugin doin' what you wanted it to do earlier. I'll look into it at a later
minute.It's kind of important that you read that explanation. And look at the plugin I posted. Because the plugin you made earlier worked completely differently. It did just about everything except what I wanted.
You had images saved in the plugin's folder swapping each other to be displayed on a button.
What I'm doing is showing images(or no image at all) on buttons. Depending on the saved images in the object's StorageTag.
The plugin you posted was still very helpful for me though. I learned something new about creating custom messages with it that I didn't know before.I'd still like to know how to write new levels into a hyperfile.
Why does the SDK show how to read them. But not how to write them?-ScottA
-
On 25/01/2013 at 08:39, xxxxxxxx wrote:
Originally posted by xxxxxxxx
I'd still like to know how to write new levels into a hyperfile.Why does the SDK show how to read them. But not how to write them?-ScottA
You don't write a new level. It's a different concept that takes a moment to get your head round.
Suppose you write a plugin which saves some stuff to the hyperfile using Write(). You read that back in again with Read(). Now suppose you write a new version of your plugin which during Write() saves some more data in addition to the original stuff.
There's a problem now because two versions of your plugin are circulating. The new version can read files created by the old version and the new version, but the old version doesn't know anything about your new version. So when it comes to read a file created by the new version, it will read data which is the 'extra' data written out by the new version, thinking that this is the data it wants. This could have various effects depending on what the data is - anything from working incorrectly up to a crash.
Levels are the answer to this. If the plugin is level 0, it will ignore any level 1 data it finds. If it's level 1, it will read the level 0 and the level 1 data. Likewise a level 1 plugin will ignore data created by a level 2 plugin, and so on.
You don't have to write the level to the hyperfile, Cinema does it for you. It knows the plugin level because you told it when you registered the plugin - look at the disklevel parameter in RegisterObjectPlugin() for example, that's what it's for. Mostly that parameter is ignored, but it's vital if you are using Write/Read and you release a different version of the plugin which stores more data.
-
On 25/01/2013 at 09:59, xxxxxxxx wrote:
Ah shoot. Another dead end that won't help me at all.
Thanks for the clarification Steve.I hope the Maxon developers are taking note of this thread. There are situations where storing images to HD files just won't work.
There are times that we need a way to store images locally in tags or objects so a dialog can use them interactively.
Nitroman for example uses a camera and view panels in his NitroPose plugin to do what I'm trying to do with a storage tag.
It would be much better and cleaner if we could store our GUI images locally in a hidden tag, object, or UserData.-ScottA
-
On 25/01/2013 at 10:47, xxxxxxxx wrote:
Originally posted by xxxxxxxx
I hope the Maxon developers are taking note of this thread. There are situations where storing images to HD files just won't work.
There are times that we need a way to store images locally in tags or objects so a dialog can use them interactively.How can you say it's not possible to save images within tags or objects plugins?
I've just seen your code and if you remove the 2 'if level' conditions in the Read(), it should work as expected.
-
On 25/01/2013 at 12:19, xxxxxxxx wrote:
Hi Yannick,
Good to know you're watching this.
Yes we can save images to the hyperfile. But it's a FIFO stack with no way if getting or changing their indexes.
In order to be able to assign specific saved hf images to specific GUI items like buttons. We need a way to index them to tell them apart. And to use those indexes to assign them to gui items.Robert is talking about writing a custom indexing system to solve this problem. Which is rather complex for the average coder like me. I don't think I can pull it off.
It would be nice to see something in future versions that would possibly make it easier for the average user trying to save things locally. Like maybe some sort of storage tag class with indexing options.-ScottA
-
On 25/01/2013 at 15:49, xxxxxxxx wrote:
Scott,
I think part of the problem I'm having is visualising exactly what it is you're trying to do. I *think* you want the user to be able to pick which images get assigned to a set of BitmapButtons, so that your hyperfile needs to be able to store a series of images. Presumably that could be a fixed number (e.g. there are always going to be n bitmaps) or it's variable so the user can add more bitmaps. It doesn't matter either way.
Now, if that's that case, it's very easy to do. What I would do is this:
* store the pointers to all the bitmaps I'm going to use in an array of pointers to BaseBitmap(s)
* I can now access each bitmap by means of its index in the array
* each BitmapButton has a value which is the index into the array of the bitmap it's going to show - it's easiest to store this as a hidden field in the description because then I don't have to worry about saving or copying this value
* I use that hidden value as an index into the bitmap pointer array to get the pointer to the bitmap I want and assign that bitmap to the buttonWhen I Write() the hyperfile, I would do this:
* first, write a LONG which is the number of bitmaps I have in total - say it's 6, for example
* then write 6 bitmaps using the 6 pointers in my arrayWhen I Read() the hyperfile, I do this:
* read the LONG which tells me the number of bitmaps I have to read
* allocate the 6 BaseBitmaps so I have somewhere to store the data
* store the pointers to those BaseBitmaps into my array of pointers
* read the 6 bitmaps from the hyperfile
* the hidden value in the button description then marries up with the correct bitmap - I'd need to check that the value was valid in case something had gone wrong, but other than that I can forget about it; when MSG_DESCRIPTION_GETBITMAP is sent for a particular button, all I'd need to do is retrieve the hidden value in its description and use that as an index into the array of pointers to bitmapsThat's basically it. I suppose I'd have to provide some method in the interface for the user to be able to select which bitmap he wanted, but that's simple. The only other thing I would have to do is implement the CopyTo() function to copy the bitmaps, array of pointers, and the value holding the total number of bitmaps I have into the cloned tag.
I just don't see the problem. It doesn't need lots of extra SDK functions to do this.