Emacs Lisp: Writing a image-linkify Function

, , …,

This page shows a example of writing a elisp command that creates a customized HTML inline image link.

Problem Description

We want a command that changes image path under cursor into a HTML image link.

~/web/xahlee_org/emacs/i/emacs_logo.png▮
 ⇓
<img src="i/emacs_logo.png" alt="emacs logo" width="302" height="260">▮

A image path will become a HTML inline image link, with {alt, height, width} attributes all filled in.

Note: emacs's html-mode provides the command html-imageCtrl+c Ctrl+c i】. But it doesn't let you nagivate the path, and doesn't add width and height attributes.

Solution

We'll need:

We proceed to write these. First, we write the string processing function. Here's the code.

Parse File Name

Parsing file name can be done with regex.

(replace-regexp-in-string "_" " " (file-name-sans-extension "my_cats.png") )

; result: "my cats"

(info "(elisp) Search and Replace")

We need this as the default value of the “alt” attribute.

Get Image Pixel Dimension

To get image width and height, we can use this unix command from ImageMagick.

# get image width and height
identify -format "%w %h" ‹image path›

Its output is like this: 312 208. 〔☛ ImageMagick/GraphicsMagick Tutorial

To call a shell command from elisp, we can use shell-command-to-string, like this:

(shell-command-to-string
  (concat "identify -format \"%w %h\" " ‹imgage path›))

(info "(elisp) Synchronous Processes")

Now, here's the code for getting the image width and height.

(defun get-image-dimensions-imk (img-file-path)
  "Returns a image file's width and height as a vector.
This function requires ImageMagick's “identify” shell command."
  (let ( widthHeightList )
    (setq widthHeightList (split-string (shell-command-to-string (concat "identify -format \"%w %h\" " img-file-path))) )
    (vector
     (string-to-number (elt widthHeightList 0))
     (string-to-number (elt widthHeightList 1)) ) ))

We use split-string to split the shell output into a list. Then, we extract each element, change it from string to number, then return the width height pair as a vector of 2 elements. (a “vector” is similar to a list. 〔☛ Emacs Lisp Tutorial: List & Vector〕)

Final Code: image-linkify

Now, we are ready to write a wrapper function to put the whole thing together.

(defun image-linkify ()
  "Replace a path to image file with a HTML img tag.
Example, if cursor is on the word “emacs_logo.png”, then it will became
<img src=\"emacs_logo.png\" alt=\"emacs logo\" width=\"123\" height=\"456\">

This function requires the “identify” command from ImageMagick.com."
  (interactive)
  (let (imgPath pathBoundaries imgDimen iWidth iHeight altText myResult)
    (setq imgPath (thing-at-point 'filename))
    (setq pathBoundaries (bounds-of-thing-at-point 'filename))
    (setq altText imgPath)
    (setq altText (replace-regexp-in-string "\\.[A-Za-z]\\{3,4\\}$" "" altText t t))
    (setq altText (replace-regexp-in-string "_" " " altText t t))
    (setq imgDimen (get-image-dimensions-imk imgPath))
    (setq iWidth (number-to-string (elt imgDimen 0)))
    (setq iHeight (number-to-string (elt imgDimen 1)))
    (setq myResult (concat "<img src=\"" imgPath "\""
                           " "
                           "alt=\"" altText "\""
                           " "
                           "width=\"" iWidth "\" "
                           "height=\"" iHeight "\">"))
    (save-excursion
      (delete-region (car pathBoundaries) (cdr pathBoundaries))
      (insert myResult))
    ))

The key in this wrapper, are the functions thing-at-point, bounds-of-thing-at-point, save-excursion, delete-region, “insert.” All these are very frequently used functions.

thing-at-point will return a string by grabbing the text around the current cursor position. The “thing” can be {word, line, sentence, paragraph, or file path, URL, sexp, …}. It saves you the time to actually write code to move around and grab the string you want. 〔☛ Emacs Lisp: Using thing-at-point

Now, we are done. Now i can assign this function a shortcut (global-set-key (kbd "<f5>") 'image-linkify). So if i want to insert a inline image, i paste into my buffer:

emacs_logo.png

press the F5, then i got:

<img src="emacs_logo.png" alt="emacs logo" width="65" height="82">

Emacs ♥

Pure Elisp Solution

In the above, we relied on ImageMagick's command line tool “identify”. Later on i realized it can be done in elisp itself. The following pure elisp code will get the image's width and height.

(defun get-image-dimensions (img-file-relative-path)
  "Returns a image file's width and height as a list."
  (let (tmp dimen)
    (clear-image-cache)
    (setq tmp
          (create-image (concat default-directory img-file-relative-path)))
    (setq dimen (image-size tmp t))
    (list (car dimen) (cdr dimen))
  )
)

However, it depends on whether your emacs is compiled to support images. On Microsoft Windows, usually not.

blog comments powered by Disqus