4
$\begingroup$

When I work with lots of images, I don't always want them cooked into my notebook. This adds to the file bloat and if I change things externally I have to copy them back in.

Can I efficiently link my images dynamically via a TemplateBox construct? That way I can bind this to a stylesheet.

$\endgroup$

1 Answer 1

3
$\begingroup$

Here's an example for how you can do such things via TemplateBox.

Linking in Action

We'll start with a demonstration then get to how it works.

First in a blank notebook (or one where overwriting the StyleDefinitions doesn't matter), evaluate:

SetOptions[EvaluationNotebook[], 
  StyleDefinitions -> 
   Get["https://github.com/b3m2a1/mathematica-tools/raw/master/Stylesheets/LinkedImage.nb"]
  ];

Then we'll define a formatting Head so we can access the TemplateBox:

Format[LinkedImage[url_, alt : _String | Automatic : Automatic]] :=
  
  RawBoxes@
   TemplateBox[
    {
     url,
     If[alt === Automatic, 
      If[FileExistsQ@url, 
       FileNameTake[url],
       URLParse[url, "Path"][[-1]]
       ], 
      alt
      ]
     }, "LinkedImage"];

Then we can get one of these to load:

LinkedImage["https://i.sstatic.net/t8a4tt.png"]

monkey

But if we look at the boxes generated for that we see no excess data is baked into the notebook:

LinkedImage["https://i.sstatic.net/t8a4tt.png"] // ToBoxes

TemplateBox[{"https://i.sstatic.net/t8a4tt.png", "t8a4tt.png"}, "LinkedImage"]

This also works for local files:

LinkedImage[BTools`Paclets`AppPath["BTools", "PacletIconBig.png"]]

boop

And we can change what file or alt text we want by clicking on the image:

click

We can also extend this to images that stand in for links:

Format[LinkedImageLink[link_, 
   url : _String | Automatic : Automatic, 
   alt : _String | Automatic : Automatic]
  ] :=
 RawBoxes@
  TemplateBox[
   { 
    link,
    If[url === Automatic,  link, url],
    If[alt === Automatic, 
     If[FileExistsQ@link, 
      FileNameTake[link],
      URLParse[link, "Path"][[-1]]
      ],
     alt
     ]
    },
    "LinkedImageLink"
   ]

Then we can use a smaller image in our notebook, but link to a larger one which can be accessed via Command (or Control) click:

LinkedImageLink["https://i.sstatic.net/t8a4tl.png", "https://i.sstatic.net/t8a4tt.png", "monkey"]

linker

How do it do

We use a combination of TemplateBox system, DynamicBox with Refresh, and caching of Imported results.

The heart of this is making a StyleData cell in our stylesheet called "LinkedImage" and binding to it the DisplayFunction:

Function@
 TooltipBox[
  DynamicBox[
   Refresh[
    EventHandler[
     ToBoxes@
      Quiet[
       Check[Import[#],
         Framed[
         Style[#, FontFamily -> "Helvetica Serif"], 
         RoundingRadius -> 5, Background -> GrayLevel[.95], 
         FrameStyle -> Gray]]
       ],
     {
      "MouseClicked" :>
       Replace[
        DialogInput[
         {alt = #2, link = #},
         Grid[
          {
           {"Image URL:", 
            EventHandler[
             InputField[Dynamic[link], String, BoxID -> "image-url"],
             "ReturnKeyDown" :>
              
              If[StringQ@alt && 
                AllTrue[{alt, link}, StringLength[#] > 0 &],
               DialogReturn[{link, alt}],
               FrontEndExecute@
                FrontEnd`MoveCursorToInputField[
                 EvaluationNotebook[],
                 "alt-text"
                 ]
               ]
             ]},
           {"Alt Text:", 
            EventHandler[
             InputField[Dynamic[alt], String, BoxID -> "alt-text"],
             "ReturnKeyDown" :>
              
              If[StringQ@link && 
                AllTrue[{alt, link}, StringLength[#] > 0 &],
               DialogReturn[{link, alt}],
               FrontEndExecute@
                FrontEnd`MoveCursorToInputField[
                 EvaluationNotebook[],
                 "image-url"
                 ]
               ]
             ]
             },
           {Spacer[{10, 10}]}
           },
          Alignment -> Left,
          Spacings -> {1, 1}
          ],
         WindowSize -> {300, All},
         WindowMargins ->
          Function[
            {{#[[1]], Automatic }, {Automatic, #[[2]]}}
            ][
           MousePosition[]
           ]
         ],
        {
          link_String,
          alt_String
          } :>
         NotebookWrite[
          Nest[ParentBox, EvaluationBox[], 3],
          TemplateBox[
           {
            link,
            alt
            },
           "LinkedImage"
           ]
          ]
        ],
      Method -> "Queued"
      }
     ],
    None
    ],
   TrackedSymbols :> {}
   ],
  #2
  ]

Now when one of these TemplateBoxes comes onto the screen the DynamicBox fires, tries to download the specified URL, and formats a little EventHandler that allows you to change its innards if you so choose. The results aren't cached beyond the Refresh[..., None] because we might want to change our file. Say I put something at "https://www.wolframcloud.com/objects/b3m2a1.testing/static_image.png" and link against it:

LinkedImage["https://www.wolframcloud.com/objects/b3m2a1.testing/static_image.png"]

enter image description here

But then if I change it I want to be able to reload and get the new image:

%

enter image description here

This is really all there is to it. It's a pretty simple concept, but with a pretty nice result.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.