RSS | Source | License

Embed Images in Text Files

Table of Contents

I’ve always wanted to take notes with images in Emacs: to actually embed images into the text file and manipulate and view them as easy as in other rich text editors. Org mode can display images but its image links are quite limited: 1) it is really a file path and you have to keep the file path in sync 2) changing the display size is not convenient, and you can’t control the size of individual images.

Since I’m moving from Notes.app to Deft to take notes, I have to have a comparable image support in Emacs. For that I wrote iimg.el. Now, if you think Org Mode’s link image are good enough and is the \(Right Way^{TM}\) to do it, I hold no objection. My blogs are written in Org Mode and the images are linked. However, for my notes, I really want to pack all the stuff into one single file and not worry about keeping the paths intact.

Introducing iimg.el

iimg.el provides these features:

  • embedding images as base64 text in text files
  • easy control of the size of each individual image
  • rendering the images

Now I can insert an image by iimg-resize and change its displayed size by typing s on the image, and toggle thumbnail display by typing t (inspired by Notes.app). I can resize a image’s width/height to be n characters, n pixels, or n percent of the window width/height. Drag-and-drop is also supported. Lastly, I can export the embedded images out if I want to.

Here is a demo (demo link) (sorry for the flickering, Emacs has bad image scrolling):

Implementation details

At first I thought of simply inserting the base64 string, but then there will be this wall of nonsense between meaningful text if you don’t render the image. That doesn’t sound like a good idea: what if someone else needs to view the file and don’t have iimg.el, or I need to view the file on some other places where iimg.el or even Emacs isn’t available? So I split the image into two parts: link and data. Data are the base64 strings and are placed at the end of each file. Links are inline with other normal text, and are rendered as images. This design adds a bit of hair to the implementation but I think it’s worth it.

I store the meta data (size, thumbnail state, etc) as plists in the links. A link looks like this:

({iimg-link (:name "hooks" :size (width char 70) :thumbnail t)})

I can simply use read and get all the information about the image and render it accordingly. And all these settings are persistent because they are directly saved to the file.

Data looks similar:

({iimg-data (:name "hooks" :data "/9j/4AAQSkZJRgAAEaAAUA...")})

I stored the base64 string literally as a string, and let read do the hard work.

Show me the code

後日談:Better integration with Deft

Deft stores file contents to cache, and storing the gibberish multi-megabyte image data to cache probably isn’t a good idea—it takes up memory and slows down searching. This is what we do:

First, define a function that prunes image data in a buffer:

(defun iimg-clean-data ()
    "Clear any iimg-data in current buffer."
    (goto-char (point-min))
    (while (re-search-forward iimg--data-regexp nil t)
      (let ((inhibit-read-only t))
        (delete-region (match-beginning 0) (match-end 0)))))

Second, add a hook to Deft to run our function before saving the file to cache. I forked deft.el and modified deft-cache-newer-file to run a hook before saving buffer content.

  (insert-file-contents file nil nil nil t)
  (run-hook-with-args 'deft-cache-file-hook)
  (setq contents (buffer-substring-no-properties
                  (point-min) (point-max))))

The definition of the hook:

(defvar deft-cache-file-hook nil
  "Hook run before a file is saved to the cache.
Run in a temp buffer with the file’s content with no argument.")

Finally, put our function in that hook:

(add-hook 'deft-cache-newer-file #'iimg-clean-data)

後日談2:Smooth scrolling over images

Because Emacs cannot partially display a line, inline images jumps in and out of the screen, which is super annoying. One solution is to display the image in multiple lines, so each line displays a strip of the image. I added this feature to iimg.el and now you can toggle between single and multi-line display by typing m on the image. Here is a demo scrolling over multi-line images (video link):

My Emacs finally looks like a modern editor, yay!

Here is another local backup.

Written by Yuan Fu

First Published on 2020-08-13 Thu 16:52

Last modified on 2020-09-14 Mon 21:39

Send your comment to archive.casouri.cat@gmail.com