Update, 2023-04-05:

By popular demand, I updated the below solution to include a second function which stores the screenshots in a subfolder.

The Final Result

Let me start this article by showing you my usage of the functionality described. I work as a Data Scientist and use org-mode in Emacs for a large number of every day tasks. One of them is the documentation of new findings within datasets or other software's documentation or websites etc. In order to easily collect all these informations into a single reference, I like to use screenshots:

The story of this painting is really fascinating, please do check out the book 'A History of the World in 10 1/2 Chapters' by Julian Barnes (Ch. 5)

An example usage of the code presented here.

The only thing "hidden" from this clip is the fact that I hit the combination C-c s right after finishing my sentence. Everything else ran automatically.

The Step-by-Step Guide (there is only one step)

Assuming you run Emacs on Windows 10, the result is extremely simple to achieve. For Windows 7 and 8, I assume that this approach works perfectly fine, I couldn't put this assumption to the test, though. Here is the singe step needed: edit your .emacs or init.el to include the following block:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
(defvar my-org-screenshot-default-directory nil
  "Default directory for saving screenshots.")

(defun my-org-screenshot (&optional prompt relative-folder)
  "Take a screenshot into a time stamped unique-named file in the
same directory as the org-buffer and insert a link to this file."
  (interactive)
  (unless (eq major-mode 'org-mode)
    (error "Not in org-mode buffer"))

  (let* ((buffer-file (buffer-file-name))
         (default-directory (or my-org-screenshot-default-directory
                                (and buffer-file (file-name-directory buffer-file))
                                default-directory))
         (filename (if (and (null prompt) (null relative-folder))
                       (concat buffer-file
                               (format-time-string "%Y%m%d_%H%M%S_")
                               ".png")
                     (if prompt
                         (setq filename (read-png-file-name default-directory))
                       (setq filename (concat
                                        (concat default-directory
                                                (or relative-folder "")
						"_"
                                                (format-time-string "%Y%m%d_%H%M%S_"))))
                       (setq filename (concat filename ".png"))))))
    (make-screenshot-windows filename)
    (if (file-exists-p filename)
        (progn
          (my-org-screenshot-insert-link filename)
          (message "Screenshot inserted"))
      (message "No screenshot was created, aborting."))))

(defun make-screenshot-windows (filename)
  "Take a screenshot using the Windows Snipping Tool and save it to the given filename."
  (shell-command "snippingtool /clip")
  (shell-command
   (format "powershell -command \"Add-Type -AssemblyName System.Windows.Forms;if ($([System.Windows.Forms.Clipboard]::ContainsImage())) {$image = [System.Windows.Forms.Clipboard]::GetImage();[System.Drawing.Bitmap]$image.Save('%s',[System.Drawing.Imaging.ImageFormat]::Png); Write-Output 'clipboard content saved as file'} else {Write-Output 'clipboard does not contain image data'}\"" filename)))

;; Define make-screenshot for other operating systems here

(defun my-org-screenshot-insert-link (filename)
  "Insert the link to the specified screenshot filename at point."
  (unless (file-exists-p filename)
    (error "Screenshot file doesn't exist"))
  (insert (concat "[[file:" filename "]]")))

(defun read-png-file-name (&optional default-directory)
  (let* ((file-name (read-file-name "Enter a new PNG file name: " default-directory nil nil ".png"))
         (extension (file-name-extension file-name)))
    (if (and (string= extension "png") (not (file-exists-p file-name)))
        file-name
        (error "Invalid file format. Please enter a non-existing PNG file."))))

Copy it in, evaluate it, and you're ready to go: just hit C-c s whenever you are inside an org-mode-buffer and take screenshots at any whim. They will be saved inside your org-mode folder and displayed inline.

Alternative solution with subfolders

If you want to store your screenshots in a subfolder "images", relative to the location of the org-file, you simply need to replace the function with the second one in the code snippet above.

The (a little more) detailed description

So, what does the magic function do and how does it do it? There are three major parts:

Creating a Unique Name for the Image

This is the first 4 lines, in which the filename of the buffer, the current time stamp and a random combination of letters (via make-temp_name) is created.

Getting the Screenshot into the Clipboard

This is the function of the line

(shell-command "snippingtool /clip")

which calls the wonderful "SnippingTool" inside Windows and skips all preliminary steps with the command line argument /clip.

Saving the Contents of the Clipboard to Disk

This part took me the longest to figure out. The core idea is that powershell.exe, when called from CMD, has the functionality to save Clipboard contents to file. Hence, the command given here

  • Adds appropriate types for Powershell to understand
  • Checks if the Clipboard contains an image, and if so
  • Saves it to filename, given in step 3.