From: Xah Lee
Subject: Emacs lisp Lesson: Creating Image Tag
Date: 
Message-ID: <1193237676.504223.294810@t8g2000prg.googlegroups.com>
Elisp Lesson: Creating Image Tag

Xah Lee, 2007-10-24

This article shows a example of writing a emacs lisp function that
creates a customized HTML inline image link. If you don't know elisp,
first take a gander at Emacs Lisp Basics.

(
A HTML version of this essay with colors and links is at:
http://xahlee.org/emacs/elisp_image_tag.html
)

--------------------------------
The Problem

--------------------------------
Summary

I want to write a command, so that, when invoked, emacs will turn the
current line into a HTML inline image link, with width and height
attributes and a alt text based on the file name. (the current line
being a image file's path)

This lesson will also show you how to call shell commands and process
their results.

--------------------------------
Detail

I work a lot with HTML in emacs. Often, i need to create a inline
image. Emac's html-mode provides a shortcut “Ctrl+c Ctrl+c i” (invokes
the command html-image), which will prompt you to type in a file path,
then will insert the tag like this:

<img src="«fpath»" alt="">

and place your cursor in between the quotes in alt. However, this is
relatively inconvenient and inadequate for few reasons. It is
inconvenient because when it prompts for a path, it doesn't lets the
user navigate a directory to find the file. To work around this, the
user should use dired and copy the file path into the clipboard (kill-
ring) first, so that when prompted, she can paste it.

This method is inadequate because it doesn't generate the width and
height attributes. Width and height attributes are important because
they allow browser to quickly settle down on a layout before actual
loading the image. Without the width and height attribute, a loading
webpage's layout will jump around as it discovers the actual
dimensions of inline images. (unless the inline images are set inside
tables ...)

Ideally, i'd like to type in a path name on the current buffer, such
as “../img/emacs_logo.png”, then press a button, and emacs will
automatically turn that line into a inline image, with width and
height attributes, and with alt partially filled by using the file
name and removing suffix and replacing underscore by space. We proceed
to write this function.

--------------------------------
Solution

First we break down the problem into a few necessary functional units.
We well need:

    * Given a file path, return a string with the file suffix removed
and underscores replaced by spaces.
    * Given a file path to a image file, return the image's width and
height as a list.
    * A wrapper that grabs the current line, delete it, and insert a
replacement. (calling the previous 2 functions to generate the
insertion text)

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

(defun xx ()
  "temp func to experiment and learn"
  (interactive)
  (let (x)
    (setq x "my_love_some.png")
    (setq x (replace-regexp-in-string "\\.[A-Za-z]\\{3,4\\}$" "" x t
t))
    (setq x (replace-regexp-in-string "_" " " x t t))
    (insert x)
    )
)

The key in writing this function is to find or realize that string
replacement for a given string, is “replace-regexp-in-string”. This is
found under the elisp manual under section “34 Searching and
Matching”. There's no magic in locating this function, just good old
experience and doc reading.

Reference: Elisp Manual: Search-and-Replace.

The function takes 3 required parameters: a regex string, a
replacement string, a string to replace with. The function takes 3
optional parameters. The first 2 optional parameters are “fixedcase”
and “literal”. If “fixedcase” is true, than emacs will not try to
change the replacement string's case (i.e. uppercase/lowercase) based
on the case of the matched string. If “literal” is true, the replace
string is taken literally. i.e. “//1” won't mean the first matched
string.

One thing interesting to note here is that backslashes are doubled,
because a backslash needs a backslash to represent itself, then this
is passed into emacs regex engine, which requires a backslash for some
constructs. (See also: Emacs Regex).

With this function, you can just run it “Alt+x xx” and see the result
inserted into the buffer. Undo to undo the effect. Using a temp
function like this is a easy way develop in elisp. After you know how
required functions work, you can gather them and put them together
into your final code.

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

(defun xGetDimention ()
  "temp func to experiment and learn"
  (interactive)
  (let (img-file-path cmd-name sh-output width height)
    (setq img-file-path "~/web/emacs/i/emacs_text_menu.png")
    (setq cmd-name "identify")
    (setq sh-output
          (shell-command-to-string (concat cmd-name " " img-file-
path)))
    ;; sample output from “identify”:
    ;; menu.png PNG 520x429+0+0 DirectClass 8-bit 9.1k 0.0u 0:01
    (string-match "^[^ ]+ [^ ]+ \\([0-9]+\\)x\\([0-9]+\\)" sh-output)
    (setq width (match-string 1 sh-output))
    (setq height (match-string 2 sh-output))
    (insert (concat width " " height))
))

Here, we call the command line tool “identify”, which is from the
ImageMagic suite. (See: ImageMagick Tutorial) The command “identify”
will return a string like this: “menu.png PNG 520x429+0+0 DirectClass
8-bit 9.1k 0.0u 0:01”, where the first 2 numbers after the PNG is the
width and height of the image. (see bottom of this page for a pure
elisp version)

The basic plan for xGetDimention is to call the shell command
“identify”, parse its result, and return the height and width.

The key function here is “shell-command-to-string”. Its output is
parsed by a regex match using “string-match”.

Reference: Elisp Manual: Synchronous-Processes.

Now, we clean this function up into a final form.

(defun get-image-dimensions (img-file-path)
  "Returns a image file's width and height as a list.
This function requires ImageMagic's “identity” shell command."
  (let (cmd-name sh-output width height)
    (setq cmd-name "identify")
    (setq sh-output (shell-command-to-string (concat cmd-name " " img-
file-path)) )
; sample output from “identify”:
; some.png PNG 520x429+0+0 DirectClass 8-bit 9.1k 0.0u 0:01
(string-match "^[^ ]+ [^ ]+ \\([0-9]+\\)x\\([0-9]+\\)" sh-output)
(setq width (match-string 1 sh-output))
(setq height (match-string 2 sh-output))
    (list (string-to-number width) (string-to-number height))
))

Note here that now get-image-dimensions takes a img-file-path
argument, and returns a list of 2 elements, both are numbers.

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

(defun tag-image ()
  "Replace a image file name 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
    (img-file-path bounds img-dim width height altText myResult)
    (setq img-file-path (thing-at-point 'filename))
    (setq bounds (bounds-of-thing-at-point 'filename))
    (setq altText img-file-path)
    (setq altText (replace-regexp-in-string "\\.[A-Za-z]\\{3,4\\}$" ""
altText t t))
    (setq altText (replace-regexp-in-string "_" " " altText t t))
    (setq img-dim (get-image-dimensions2 img-file-path))
    (setq width (number-to-string (car img-dim)))
    (setq height (number-to-string (car (last img-dim))))
    (setq myResult (concat "<img src=\"" img-file-path "\""
                           " "
                           "alt=\"" altText "\""
                           " "
                           "width=\"" width "\" "
                           "height=\"" height "\">"))
    (save-excursion
      (delete-region (car bounds) (cdr bounds))
      (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 a word, line, sentence, paragraph, or file path, url, sexp,
etc. It saves you the time actually write code to move around and grab
the string you want. The bounds-of-thing-at-point returns a pair of
the positions of the thing, so that you can delete it easily in the
buffer.

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

emacs_logo.png

press the f3, then i got:

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

this is of tremendous help if you work with manually-crafted sites
with thousands of inline images. For example, my website Visual
Dictionary of Special Plane Curves, Gallery of Famous Surfaces, Visual
Arts Gallery.

Here is a practical account showing how i used this function in my
work. One time i need to create a web gallery of images generated by a
batgirl drawing craze that happened on the blog site livejournal.com.
The general process is to collect the images from various blog sites
(onto my hard-drive), create html pages, link these images as inline
images, gather permission and write comment on them (such as the
artist and source). Literally there were thousands of images i need to
exam and ends up having to about 80 images in the final gallery. Such
work is naturally not within any automation system's reach, and will
take me actually far more time if done with a blog site or wiki
system. (See here for the page: Batgirl Craze)

So, i put some images that i decided should all be on one page of the
gallery, into a directory, then type “Ctrl+u Alt+! ls dir_name/*png”,
so that emacs lists them in my buffer like this:

_p/bg/0004kt1h.png
_p/bg/batgirl-1.png
_p/bg/batgirl.png
_p/bg/batgirl6mc.png
_p/bg/batgirl_abs.png
_p/bg/batgirl_squat.png
_p/bg/bg2.png
_p/bg/dadsgonnakillme.png
_p/bg/truebatgirl.png
...

Then, i just move the cursor to a line, press a button, then press
arrow down, and hit the button again, and so on, and each time a line
is turned into a link. (occationally i record it as a macro then do
apply-macro-to-region-lines, but usually not worth the trouble)

Emacs is beautiful!
Addendum

In the above, we relied on ImageMagic's command line tool “identify”.
However, it is not necessary. The following pure elisp code will get
the image's width and height.

(defun get-image-dimensions2 (img-file-relative-path)
  "Returns a image file's width and height as a list."
  (let (temp-img img-dim)
    (setq temp-img
          (create-image (concat default-directory img-file-relative-
path)))
    (setq img-dim (image-size temp-img t))
    (list (car img-dim) (cdr img-dim))
  )
)

I'm not sure, but i think a problem i ran into using this is that when
i did a image, then i changed the image's size (without changing the
image's file name), then use tag-image again, the width and height
won't reflect the new file's size, probably because emacs for some
reason has cached it... This is why i'm currently using the
ImageMagic's version, because it happens often that i need to re-edit
and re-link images.

  Xah
  ···@xahlee.org
∑ http://xahlee.org/