{"id":483,"date":"2020-09-08T23:08:08","date_gmt":"2020-09-08T23:08:08","guid":{"rendered":"http:\/\/www.scrootchme.com\/bmc\/?p=483"},"modified":"2025-07-10T17:17:34","modified_gmt":"2025-07-10T17:17:34","slug":"undo-onto-others","status":"publish","type":"post","link":"http:\/\/www.scrootchme.com\/bmc\/undo-onto-others\/","title":{"rendered":"UNDO onto others&#8230;"},"content":{"rendered":"\n<p><a href=\"http:\/\/www.scrootchme.com\/bmc\/tip-the-scale\/\" data-type=\"post\" data-id=\"466\">Last time<\/a> we covered the critical question of maintaining a scaled editing area so that we can work on a 16 bit resolution ILDA file on lower resolution screens. As I mentioned in that post, one of the other areas we want to address sooner rather than later is &#8216;undo&#8217;\/&#8217;redo&#8217;. All modern editors are expected to do this well and users have become accustomed to relying on the feature.<\/p>\n\n\n\n<p>Instead of starting from scratch, our good friend JUCE has an <a href=\"https:\/\/docs.juce.com\/master\/classUndoManager.html\">UndoManager class<\/a>. It is a specialized container class that holds instances of an <a href=\"https:\/\/docs.juce.com\/master\/classUndoableAction.html\">UndoableAction class<\/a>. One of the things I like about this setup is you add UndoableAction instances to the manager by &#8220;performing&#8221; them. That is, the operation is &#8216;done&#8217; initially by exactly the same code that will be executed for a &#8216;redo&#8217; operation. This eliminates a common pitfall with undo\/redo systems bolted on after the fact, where do and redo are different bodies of code that don&#8217;t always stay in perfect sync.<\/p>\n\n\n\n<p>Up to now our FrameEditor worker class has basically had public <em>get<\/em> and <em>set<\/em> functions. For example, from FrameEditor.h:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>float getRefOpacity() { return refOpacity; }\nvoid setRefOpacity (float opacity);<\/code><\/pre>\n\n\n\n<p>Step one to implement our Undo\/Redo system is to make this into three functions, get, undoable set, and destructive set:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>float getRefOpacity() { return refOpacity; }\nvoid setRefOpacity (float opacity);\nvoid _setRefOpacity (float opacity);<\/code><\/pre>\n\n\n\n<p>The function _setRefOpacity is just our original setRefOpacity function renamed. This is our &#8220;destructive&#8221; version of the operation. Our new setRefOpacity function is the undoable version, the one regular clients call. Instead of calling _setRefOpacity directly, it creates an instance of this class and performs it with the UndoManager derived class:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>class UndoableSetRefAlpha : public UndoableAction\n{\npublic:\n    UndoableSetRefAlpha (FrameEditor* editor, float alpha)\n    : newAlpha (alpha), frameEditor (editor) {;}\n    \n    bool perform() override\n    {\n        oldAlpha = frameEditor->getRefOpacity();\n        frameEditor->_setRefOpacity (newAlpha);\n        return true;\n    }\n    \n    bool undo() override\n    {\n        frameEditor->_setRefOpacity (oldAlpha);\n        return true;\n    }\n    \nprivate:\n    float oldAlpha;\n    float newAlpha;\n    FrameEditor* frameEditor;\n};\n<\/code><\/pre>\n\n\n\n<p>When the class is instantiated it saves the desired new value and a pointer to our FrameEditor worker class. The perform() method saves the old (current) value and sets the new one, using the destructive _setRefOpacity. Undo uses the same function to set the old, preserved value.<\/p>\n\n\n\n<p>One concern with a scheme like this is the scope, or life, of objects. We have an instance of this undoable object class that points to our FrameEditor class. We want to make sure that we never get into a situation where we try to &#8216;undo&#8217; something after our FrameEditor has already been deleted. I chose to elliminate this possibility by making our FrameEditor and the UndoManager the same object.  FrameEditor &#8220;inherits&#8221; from UndoManager:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>class FrameEditor  : public ActionBroadcaster,\n                     public UndoManager<\/code><\/pre>\n\n\n\n<p>In various object oriented programming paradigms, this is called the &#8220;Is-A&#8221; vs &#8220;Has-A&#8221; question. Or, &#8220;the entity relationship model&#8221;, if you want to sound even more obnoxious. To me it just made sense that our FrameEditor worker class, which we can now ask to perform undoable operations, is the same object we use to undo them.<\/p>\n\n\n\n<p>But, regardless, either having our FrameEditor <em>be<\/em> an UndoManager or <em>have<\/em> an UndoManager as a member variable solves our scope problem. The scope, or object life, of our worker class, the UndoManager, and any saved UndoableAction classes that point back to our worker class, are all tied together.<\/p>\n\n\n\n<p>With the Is-A approach, our undoable set function looks something like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>void FrameEditor::setRefOpacity (float opacity)\n{\n    if (opacity &lt; 0)\n        opacity = 0;\n    else if (opacity > 1.0)\n        opacity = 1.0;\n    \n    if (opacity != refOpacity)\n\n        perform(new UndoableSetRefAlpha (this, opacity), \"Background Opacity\");\n}<\/code><\/pre>\n\n\n\n<p>We make sure the caller isn&#8217;t passing in an out-of-range value, check if the request actually changes anything, and, if yes, create and perform the UndoableAction class we defined for this operation (automatically saving it into the UndoManager maintained list).<\/p>\n\n\n\n<p>This works, but we run into one headache. If we grab the opacity slider and play with it, we get a zillion of these events stacked up to undo. Generally, that&#8217;s not what the user expects. They have a starting opacity, fiddled with the slider, then stopped. From their point of view, all that fiddling is just one undoable adjustment.<\/p>\n\n\n\n<p>The JUCE UndoManager has a mechanism called &#8220;Transactions&#8221;, meant to lump multiple UndoableActions into a single undo\/redo operation. But it also has a nice rollback feature, which helps us here:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>void FrameEditor::setRefOpacity (float opacity)\n{\n    if (opacity &lt; 0)\n        opacity = 0;\n    else if (opacity > 1.0)\n        opacity = 1.0;\n    \n    if (opacity != refOpacity)\n    {\n        if (getCurrentTransactionName() == \"Background Opacity\")\n            undoCurrentTransactionOnly();\n        \n        beginNewTransaction (\"Background Opacity\");\n        perform(new UndoableSetRefAlpha (this, opacity));\n    }\n}<\/code><\/pre>\n\n\n\n<p>If we are doing a series of opacity changes, we keep rolling back the transaction to our original value, then adjust to the new one. This turns slider fiddling into a single undo\/redo step.<\/p>\n\n\n\n<p>At this point our background layer looks a lot like last time:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"719\" src=\"http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-5-1024x719.png\" alt=\"\" class=\"wp-image-490\" srcset=\"http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-5-1024x719.png 1024w, http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-5-300x211.png 300w, http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-5-768x539.png 768w, http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-5-1536x1078.png 1536w, http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-5-2048x1437.png 2048w, http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-5-676x474.png 676w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>I added a button to clear (delete) the background image and reference grid you can turn on and off, but the big difference is that we can now undo and redo all the adjustments you make:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"660\" height=\"158\" src=\"http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/Screen-Shot-2020-09-08-at-11.44.15-AM.png\" alt=\"\" class=\"wp-image-491\" srcset=\"http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/Screen-Shot-2020-09-08-at-11.44.15-AM.png 660w, http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/Screen-Shot-2020-09-08-at-11.44.15-AM-300x72.png 300w\" sizes=\"auto, (max-width: 660px) 100vw, 660px\" \/><\/figure>\n\n\n\n<p>Now that we have a scheme for undo\/redo and a way to scale, draw, and interact with ILDA resolution data, it is time to open some!<\/p>\n\n\n\n<p>The <a href=\"https:\/\/www.ilda.com\/resources\/StandardsDocs\/ILDA_IDTF14_rev011.pdf\">ILDA image exchange format<\/a> is pretty bare bones. It doesn&#8217;t have a mechanism to include vendor specific information. So we will need a to support two formats for opening:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>ILDA (.ild) files<\/li><li>JSE (.jse) files<\/li><\/ul>\n\n\n\n<p>A JSE file will contain all the ILDA information, plus all the other items we are tacking on, like our reference (background) and sketch layers. For the time being I&#8217;ve decided not to embed any selected background images, thumbnails, etc. in our JSE file. Background images will be kept as a media reference (file name\/location), and thumbnails will be reconstructed at load time.<\/p>\n\n\n\n<p>Without raster graphics content, storing coordinates, sketch objects, etc. as ASCII text seems fine to me. JUCE has <a href=\"https:\/\/docs.juce.com\/master\/group__juce__core-xml.html\">great support for reading and creating XML files<\/a>, so we&#8217;ll start there. This should make our files easy to examine and fiddle with by eye in a normal text editor. If file sizes becomes an issue we could use <a href=\"https:\/\/docs.juce.com\/master\/group__juce__core-zip.html\">JUCE&#8217;s wrappers for zlib<\/a> to compress our XML files in the GNU zip format.<\/p>\n\n\n\n<p>But first things first, let&#8217;s just parse an ILDA file and put it into one or more Frame objects for us to manipulate with our FrameEditor helper class!<\/p>\n\n\n\n<p>The ILDA load function in IldaLoader.cpp looks a lot like our ILDA code in our scan system firmware:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>bool IldaLoader::load (ReferenceCountedArray&lt;Frame>&amp; frameArray, File&amp; file)\n{\n    FileInputStream input (file);\n    if (input.failedToOpen())\n        return false;\n\n    frameArray.clear();\n    \n    \/\/ Loop until we are out of frames\n    do\n    {\n        Frame::Ptr frame = new Frame;\n        \n        ILDA_HEADER header;\n\n        \/\/ Read the header\n        if (input.read (&amp;header, sizeof(header)) != sizeof(header)) break;\n\n        \/\/ Valid?\n        if (header.ilda&#91;0] != 'I' || header.ilda&#91;1] != 'L'\n                || header.ilda&#91;2] != 'D' || header.ilda&#91;3] != 'A') break;\n\n        uint16 rCount;\n        rCount = header.numRecords.b&#91;0];\n        rCount &lt;&lt;= 8;\n        rCount += header.numRecords.b&#91;1];\n\n        \/\/ 0 records marks end\n        if (!rCount) break;\n\n        int n;\n        for (n = 0; n &lt; rCount; ++n)\n        {\n            Frame::XYPoint newPoint;\n\n            \/\/ We have 5 different handlers for the 5 different\n            \/\/ ILDA data formats (ugh)\n            if (header.format == 0)\n            {\n                ILDA_FORMAT_0 in;\n\n                \/\/ Try to read the next point\n                if (input.read (&amp;in, sizeof(in)) != sizeof(in)) break;\n\n                \/\/ Change endian and store X, Y and Z\n                newPoint.x.b&#91;1] = in.x.b&#91;0];\n                newPoint.x.b&#91;0] = in.x.b&#91;1];\n                newPoint.y.b&#91;1] = in.y.b&#91;0];\n                newPoint.y.b&#91;0] = in.y.b&#91;1];\n                newPoint.z.b&#91;1] = in.z.b&#91;0];\n                newPoint.z.b&#91;0] = in.z.b&#91;1];\n\n                \/\/ Store status (minus last frame indicator!)\n                newPoint.status = (in.status &amp; 0x7F);\n\n                \/\/ Lookup and store colors\n                newPoint.red = IldaColors&#91;in.colorIdx].red;\n                newPoint.green = IldaColors&#91;in.colorIdx].green;\n                newPoint.blue = IldaColors&#91;in.colorIdx].blue;\n            }\n            else if (header.format == 1)\n            {\n                ILDA_FORMAT_1 in1;\n\n                \/\/ Try to read the next point\n                if (input.read (&amp;in1, sizeof(in1)) != sizeof(in1)) break;\n\n                \/\/ Change endian and store X, Y and Z\n                newPoint.x.b&#91;1] = in1.x.b&#91;0];\n                newPoint.x.b&#91;0] = in1.x.b&#91;1];\n                newPoint.y.b&#91;1] = in1.y.b&#91;0];\n                newPoint.y.b&#91;0] = in1.y.b&#91;1];\n\n                newPoint.z.w = 0;\n\n                \/\/ Store status (minus last frame indicator!)\n                newPoint.status = (in1.status &amp; 0x7F);\n\n                \/\/ Lookup and store colors\n                newPoint.red = IldaColors&#91;in1.colorIdx].red;\n                newPoint.green = IldaColors&#91;in1.colorIdx].green;\n                newPoint.blue = IldaColors&#91;in1.colorIdx].blue;\n            }\n            else if (header.format == 2)\n            {\n                \/\/ Color Palette\n                ILDA_FORMAT_2 in2;\n                \/\/ Try to read the next point\n                if (input.read (&amp;in2, sizeof(in2)) != sizeof(in2)) break;\n            }\n            else if (header.format == 4)\n            {\n                ILDA_FORMAT_4 in4;\n\n                \/\/ Try to read the next point\n                if (input.read (&amp;in4, sizeof(in4)) != sizeof(in4)) break;\n\n                \/\/ Change endian and store X, Y and Z\n                newPoint.x.b&#91;1] = in4.x.b&#91;0];\n                newPoint.x.b&#91;0] = in4.x.b&#91;1];\n                newPoint.y.b&#91;1] = in4.y.b&#91;0];\n                newPoint.y.b&#91;0] = in4.y.b&#91;1];\n                newPoint.z.b&#91;1] = in4.z.b&#91;0];\n                newPoint.z.b&#91;0] = in4.z.b&#91;1];\n\n                \/\/ Store status (minus last frame indicator!)\n                newPoint.status = (in4.status &amp; 0x7F);\n\n                \/\/ Store colors\n                newPoint.red = in4.red;\n                newPoint.green = in4.green;\n                newPoint.blue = in4.blue;\n            }\n            else if (header.format == 5)\n            {\n                ILDA_FORMAT_5 in5;\n\n                \/\/ Try to read the next point\n                if (input.read (&amp;in5, sizeof(in5)) != sizeof(in5)) break;\n\n                \/\/ Change endian and store X, Y and Z\n                newPoint.x.b&#91;1] = in5.x.b&#91;0];\n                newPoint.x.b&#91;0] = in5.x.b&#91;1];\n                newPoint.y.b&#91;1] = in5.y.b&#91;0];\n                newPoint.y.b&#91;0] = in5.y.b&#91;1];\n\n                newPoint.z.w = 0;\n\n                \/\/ Store status (minus last frame indicator!)\n                newPoint.status = (in5.status &amp; 0x7F);\n\n                \/\/ Store colors\n                newPoint.red = in5.red;\n                newPoint.green = in5.green;\n                newPoint.blue = in5.blue;\n            }\n            else\n                break;\n            \n            \/\/ Store the point\n            frame->addPoint (newPoint);\n        }\n\n        if (n != rCount) break;\n\n        \/\/ Don't store palettes!\n        if (header.format != 2)\n        {\n            frame->buildThumbNail();\n            frameArray.add (frame);\n        }\n    }\n    while (1);\n\n    if (frameArray.size())\n        return true;\n    \n    return false;\n}\n<\/code><\/pre>\n\n\n\n<p>All read coordinates are converted into 16 bit X, Y, and Z data with truecolor (8 bits R, G, and B) data.  If you look at frame.h, the Frame::XYPoint data type is just the ILDA_FORMAT_4 under a different name:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>    typedef ILDA_FORMAT_4 XYPoint;\n    typedef enum {\n        BlankedPoint = 0x40,\n        LastPoint = 0x80\n    } Status;<\/code><\/pre>\n\n\n\n<p>If you skim the code above, a few things might stand out. First, we ignore custom palettes. If a file uses indexed colors, you get the ILDA standard palette. If this turns out to be a real problem, it wouldn&#8217;t be hard to fix. I am just reluctant to code something that I don&#8217;t even have a suitable sample file to test with!<\/p>\n\n\n\n<p>Another thing that might catch your eye is that we discard the &#8220;LastPoint&#8221; status bit. We don&#8217;t need it in the editor, we keep all the coordinates for a Frame in a juce::Array container class:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>    Array&lt;XYPoint> framePoints;<\/code><\/pre>\n\n\n\n<p>This way, we can copy, paste, and reorder at will without worrying about keeping the flag in the right spot. We just have to be sure to add it back in when we export our point array to an ILDA file!<\/p>\n\n\n\n<p>The code above also might look like it leaks memory. We allocate a new Frame class with &#8220;new&#8221; at the top of every iteration of our do loop:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>    \/\/ Loop until we are out of frames\n    do\n    {\n        Frame::Ptr frame = new Frame;\n<\/code><\/pre>\n\n\n\n<p>But we never delete it and only conditionally add it to the frame array that the caller passed in. This is a variation on RAII techniques we <a href=\"http:\/\/www.scrootchme.com\/bmc\/care-for-some-juce\/\" data-type=\"post\" data-id=\"432\">discussed before<\/a> with std::unique_ptr and juce::ScopedPointer.<\/p>\n\n\n\n<p>Those classes delete what they point to when they go out of scope, which isn&#8217;t what we want here. Just as FrameEditor &#8220;Is-A&#8221; UndoManager above, every instance of the Frame class now &#8220;Is-A&#8221; <a href=\"https:\/\/docs.juce.com\/master\/classReferenceCountedObject.html\">ReferenceCountedObject<\/a>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>class Frame : public ReferenceCountedObject\n<\/code><\/pre>\n\n\n\n<p>These are pretty much what the name implies. The class has a counter. When the counter is decremented to zero, the object is deleted. When our object is created, we put it in a special kind of Pointer class called &#8220;Frame::Ptr&#8221;. This is just shorthand for the long winded:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ReferenceCountedObjectPtr&lt;Frame>;\n<\/code><\/pre>\n\n\n\n<p>This incremented our count from 0 to 1. IF we add it to the frame array, which you can see is a long winded reference counting version too:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ReferenceCountedArray&lt;Frame>&amp; frameArray<\/code><\/pre>\n\n\n\n<p>The count is incremented from 1 to 2. The next time we assign a new pointer at the top of the do loop, the <em>previous<\/em> object is decremented (2 to 1 if saved, 1 to 0 and deleted if not). Similarly, when our function ends, any object in the pointer is decremented and conditionally deleted when the pointer class itself is destroyed.<\/p>\n\n\n\n<p>We didn&#8217;t have to use reference counted objects just for our ILDA loader. The std::unique_ptr class has a release() method specifically to transfer ownership of a pointer to another container. But reference counted Frame objects are also useful for undo\/redo. Instead of making fresh copies of every frame we delete, etc., we can just hand off the pointer to the UndoableAction class variant and release our reference count on it. Whenever the UndoManager releases the UndoableAction class, the reference will be again decremented and, unless it is still referenced by other instances of undo operations, deleted.<\/p>\n\n\n\n<p>In addition to an ILDA file loader, I added a helper to build thumbnail images, which the loader indirectly invokes for each frame saved:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>        \/\/ Don't store palettes!\n        if (header.format != 2)\n        {\n            frame->buildThumbNail();\n            frameArray.add (frame);\n        }\n<\/code><\/pre>\n\n\n\n<p>I then modified the FrameList component so that it &#8220;Is-A&#8221; <a href=\"https:\/\/docs.juce.com\/master\/classListBoxModel.html\">ListBoxModel<\/a>, for a <a href=\"https:\/\/docs.juce.com\/master\/classListBox.html\">ListBox<\/a> added to it. We are going to have to make our own specialized version of ListBox later when we add advanced Frame operations, but this at least gives us a way to preview and select Frames now:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"719\" src=\"http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-6-1024x719.png\" alt=\"\" class=\"wp-image-494\" srcset=\"http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-6-1024x719.png 1024w, http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-6-300x211.png 300w, http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-6-768x539.png 768w, http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-6-1536x1078.png 1536w, http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-6-2048x1437.png 2048w, http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-6-676x474.png 676w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"719\" src=\"http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-7-1024x719.png\" alt=\"\" class=\"wp-image-495\" srcset=\"http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-7-1024x719.png 1024w, http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-7-300x211.png 300w, http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-7-768x539.png 768w, http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-7-1536x1078.png 1536w, http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-7-2048x1437.png 2048w, http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-7-676x474.png 676w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>I actually made the generated thumbnails have transparent backgrounds and made the frame labels a semi transparent bar over the main working area. I wanted the thumbnails to be useful without being too visually distracting. Particularly when an image is in color:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"719\" src=\"http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-8-1024x719.png\" alt=\"\" class=\"wp-image-496\" srcset=\"http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-8-1024x719.png 1024w, http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-8-300x211.png 300w, http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-8-768x539.png 768w, http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-8-1536x1078.png 1536w, http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-8-2048x1437.png 2048w, http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-8-676x474.png 676w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>And, believe it or not, this Barney animation is even worse when you project it. But it&#8217;s better than anything I have ever drawn, is colorized, and Hanna Barbara likely won&#8217;t sue me, so&#8230; here it is.<\/p>\n\n\n\n<p>Anyway, I also added some controls for ILDA layer display. Here is with the reference layer grid turned off:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"719\" src=\"http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-9-1024x719.png\" alt=\"\" class=\"wp-image-497\" srcset=\"http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-9-1024x719.png 1024w, http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-9-300x211.png 300w, http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-9-768x539.png 768w, http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-9-1536x1078.png 1536w, http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-9-2048x1437.png 2048w, http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-9-676x474.png 676w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>You can turn off the blanked points:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"719\" src=\"http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-10-1024x719.png\" alt=\"\" class=\"wp-image-498\" srcset=\"http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-10-1024x719.png 1024w, http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-10-300x211.png 300w, http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-10-768x539.png 768w, http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-10-1536x1078.png 1536w, http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-10-2048x1437.png 2048w, http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-10-676x474.png 676w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>Or hide the point to point connecting lines:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"719\" src=\"http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-11-1024x719.png\" alt=\"\" class=\"wp-image-499\" srcset=\"http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-11-1024x719.png 1024w, http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-11-300x211.png 300w, http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-11-768x539.png 768w, http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-11-1536x1078.png 1536w, http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-11-2048x1437.png 2048w, http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-11-676x474.png 676w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>It&#8217;s worth reiterated that this is being drawn in our WorkingArea component at full ILDA resolution. The only math we do for coordinates is the arithmetic to shift from center 0,0 to top left 0,0:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Draw the dots\nif (point.status &amp; Frame::BlankedPoint)\n{\n    if (frameEditor->getIldaShowBlanked())\n    {\n        g.setColour (Colours::darkgrey);\n        g.drawEllipse((float)(point.x.w + (32768 - halfDotSize)),\n                   (float)((32768 - halfDotSize) - point.y.w), dotSize, dotSize, activeInvScale);\n    }\n}\nelse\n{\n    \n    g.setColour (Colour (point.red, point.green, point.blue));\n    g.fillEllipse(point.x.w + (32768 - halfDotSize), (32768 - halfDotSize) - point.y.w, dotSize, dotSize);\n}\n<\/code><\/pre>\n\n\n\n<p>This is great for maintaining registration of everything we draw, etc. but what about our circle, dot, and line sizes? We want those to relate back to something that makes sense to the user on the screen, not ILDA resolution. For now I scale them to a target number of pixels on the display:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>float dotSize = 3.0f * activeInvScale;\nfloat halfDotSize = dotSize \/ 2.0f;\n<\/code><\/pre>\n\n\n\n<p>This makes things look at little thick when we make the window really small:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"830\" src=\"http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-12-1024x830.png\" alt=\"\" class=\"wp-image-500\" srcset=\"http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-12-1024x830.png 1024w, http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-12-300x243.png 300w, http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-12-768x623.png 768w, http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-12-676x548.png 676w, http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-12.png 1416w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>And a little thin when we maximize on a big monitor:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large is-resized\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-13-1024x576.png\" alt=\"\" class=\"wp-image-501\" width=\"1024\" height=\"576\" srcset=\"http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-13-1024x576.png 1024w, http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-13-300x169.png 300w, http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-13-768x432.png 768w, http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-13-1536x864.png 1536w, http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-13-2048x1152.png 2048w, http:\/\/www.scrootchme.com\/bmc\/wp-content\/uploads\/2020\/09\/screenshot-13-676x380.png 676w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>But it is a good starting point. We can add a 2nd order proportional adjustment later. After we spend some time editing content. Speaking of which, <a href=\"http:\/\/www.scrootchme.com\/bmc\/be-selective\/\" data-type=\"post\" data-id=\"507\">next stop<\/a>, let&#8217;s start selecting and editing ILDA content and get our Sketch layer going!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Last time we covered the critical question of maintaining a scaled editing area so that we can work on a 16 bit resolution ILDA file on lower resolution screens. As I mentioned in that post, one of the other areas we want to address sooner rather than later is &#8216;undo&#8217;\/&#8217;redo&#8217;. All modern editors are expected [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":485,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_coblocks_attr":"","_coblocks_dimensions":"","_coblocks_responsive_height":"","_coblocks_accordion_ie_support":"","footnotes":""},"categories":[],"tags":[],"class_list":["post-483","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","post-preview"],"_links":{"self":[{"href":"http:\/\/www.scrootchme.com\/bmc\/wp-json\/wp\/v2\/posts\/483","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/www.scrootchme.com\/bmc\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/www.scrootchme.com\/bmc\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/www.scrootchme.com\/bmc\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/www.scrootchme.com\/bmc\/wp-json\/wp\/v2\/comments?post=483"}],"version-history":[{"count":10,"href":"http:\/\/www.scrootchme.com\/bmc\/wp-json\/wp\/v2\/posts\/483\/revisions"}],"predecessor-version":[{"id":525,"href":"http:\/\/www.scrootchme.com\/bmc\/wp-json\/wp\/v2\/posts\/483\/revisions\/525"}],"wp:featuredmedia":[{"embeddable":true,"href":"http:\/\/www.scrootchme.com\/bmc\/wp-json\/wp\/v2\/media\/485"}],"wp:attachment":[{"href":"http:\/\/www.scrootchme.com\/bmc\/wp-json\/wp\/v2\/media?parent=483"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/www.scrootchme.com\/bmc\/wp-json\/wp\/v2\/categories?post=483"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/www.scrootchme.com\/bmc\/wp-json\/wp\/v2\/tags?post=483"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}