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. So I wrote iimg.el. For my note files, I really want to pack all the stuff into one single file and not worry about keeping the paths intact.
1 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-insert
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):
2 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.
3 Show me the code
4 後日談: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.
(with-temp-buffer (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)
5 後日談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.