As I\'m using the Emacs Org mode as a research log, sometime I want to keep track of something via screenshot images, and I definitely don\'t want to save them. So I\'m wonderin
The exact functionality you want isn't currently implemented, but I would be skeptical of saving lots of images into a research log if your opinion is that you "definitely don't want to save them."
Anyways, the functionality you desire has been expressed in the org-mode mailing list a couple of times in recent years - check
http://comments.gmane.org/gmane.emacs.orgmode/33770
http://www.mail-archive.com/emacs-orgmode@gnu.org/msg50862.html
The first link includes some code to launch a screenshot utility (via ImageMagick) to [uniquely] save the file and insert an inline link in your org-mode buffer.
As stated in that thread, the code was improved upon and added to org-mode hacks page - which has lots of useful gems:
http://orgmode.org/worg/org-hacks.html
(defun my-org-screenshot ()
"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)
(setq filename
(concat
(make-temp-name
(concat (buffer-file-name)
"_"
(format-time-string "%Y%m%d_%H%M%S_")) ) ".png"))
(call-process "import" nil nil nil filename)
(insert (concat "[[" filename "]]"))
(org-display-inline-images))
For Windows 10 users, I modified the answer provided by @assem to work with out-of-the-box tools:
(defun my-org-screenshot ()
"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)
(setq filename
(concat
(make-temp-name
(concat (buffer-file-name)
"_"
(format-time-string "%Y%m%d_%H%M%S_")) ) ".png"))
(shell-command "snippingtool /clip")
(shell-command (concat "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('" filename "',[System.Drawing.Imaging.ImageFormat]::Png); Write-Output 'clipboard content saved as file'} else {Write-Output 'clipboard does not contain image data'}\""))
(insert (concat "[[file:" filename "]]"))
(org-display-inline-images))
The whole write-up can be found here
I like the images and other material that belongs together with an org file to be located in a clearly associated directory. I am using the org-attachment mechanism for this and wrote a few years back a package org-attachment-screenshot that is also available from MELPA. It allows providing a function for defining the directory name (e.g. document name + some postfix) for the attachments, and will also support the standard attachment functionalities, e.g. if you want to define attachments based on entries, and inheriting within entries. Also provides customizable variable for the snapshot command to be executed. Please have a look.
(If of use), I used @assem's code (thank you v.much btw) and created a version that instead creates a sub-folder (if it doesn't exist) like : ${filename}IMG/
Then puts an image like img_date.png into that folder
and then inserts a relative path into the buffer like:
[[ ./${filename}IMG/img_2015...png ]]
Here is code:
(defun my-org-screenshot ()
"Take a screenshot into a time stamped unique-named file in the
sub-directory (%filenameIMG) as the org-buffer and insert a link to this file."
(interactive)
(setq foldername (concat (buffer-file-name) "IMG/"))
(if (not (file-exists-p foldername))
(mkdir foldername))
(setq imgName (concat "img_" (format-time-string "%Y_%m_%d__%H_%M_%S") ".png"))
(setq imgPath (concat (buffer-file-name) "IMG/" imgName))
(call-process "import" nil nil nil imgPath)
(setq relativeFilename (concat "./"
(buffer-name) "IMG/" imgName))
(insert (concat "[[" relativeFilename "]]"))
(org-display-inline-images)
)
In the mean time, I found the above doesn't work well. Technically it works, but it generates a lot of folder names that match the file names, making it tedious to select files as there are folder that have a similar name.
Instead I settled with a solution where I keep all my org files in the same folder, and have an 'img' sub folder' with all my images.
I use code like this to capture images:
(defun my/img-maker ()
"Make folder if not exist, define image name based on time/date"
(setq myvar/img-folder-path (concat default-directory "img/"))
; Make img folder if it doesn't exist.
(if (not (file-exists-p myvar/img-folder-path)) ;[ ] refactor thir and screenshot code.
(mkdir myvar/img-folder-path))
(setq myvar/img-name (concat "img_" (format-time-string "%Y_%m_%d__%H_%M_%S") ".png"))
(setq myvar/img-Abs-Path (concat myvar/img-folder-path myvar/img-name)) ;Relative to workspace.
(setq myvar/relative-filename (concat "./img/" myvar/img-name))
(insert "[[" myvar/relative-filename "]]" "\n")
)
(defun my/org-screenshot ()
"Take a screenshot into a time stamped unique-named file in the
sub-directory (%filenameIMG) as the org-buffer and insert a link to this file."
(interactive)
(my/img-maker)
;(make-frame-invisible)
(lower-frame)
(call-process "import" nil nil nil myvar/img-Abs-Path)
(raise-frame)
;(make-frame-visible)
(org-display-inline-images)
)
and a little bash script in my org/img folder to archive unreferenced images:
#!/bin/sh
cd ~/git/LeoUfimtsev.github.io/org/img/
mkdir ~/git/LeoUfimtsev.github.io/org/img/archive
FILES=~/git/LeoUfimtsev.github.io/org/img/*.png
for f in $FILES
do
var_grep_out=$(grep --directories=skip $(basename $f) ../*)
if [ -z "$var_grep_out" ];
then
echo "Archiving: $f"
mv $f archive/
fi
done
For OS X users. The following compliments Assem's answer.
The import
command was not working for me, so I swapped it to screencapture
with an -i
argument. I also find it more convenient to use a relative path for the link rather than the absolute.
(defun my-org-screenshot ()
"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)
(setq filename
(concat
(make-temp-name
(concat (file-name-nondirectory (buffer-file-name))
"_"
(format-time-string "%Y%m%d_%H%M%S_")) ) ".png"))
(call-process "screencapture" nil nil nil "-i" filename)
(insert (concat "[[./" filename "]]"))
(org-display-inline-images))
I have recently improved on the script a little. It now places screenshots in a subdirectory, creates the directory if required, and only inserts the link into emacs if the image was actually created (i.e., does nothing if you hit ESC). It also works on both Ubuntu and OS X.
(defun my-org-screenshot ()
"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)
(org-display-inline-images)
(setq filename
(concat
(make-temp-name
(concat (file-name-nondirectory (buffer-file-name))
"_imgs/"
(format-time-string "%Y%m%d_%H%M%S_")) ) ".png"))
(unless (file-exists-p (file-name-directory filename))
(make-directory (file-name-directory filename)))
; take screenshot
(if (eq system-type 'darwin)
(call-process "screencapture" nil nil nil "-i" filename))
(if (eq system-type 'gnu/linux)
(call-process "import" nil nil nil filename))
; insert into file if correctly taken
(if (file-exists-p filename)
(insert (concat "[[file:" filename "]]"))))
My solution is for the Windows platform. Many thanks to Assem for giving his solution on which I based mine.
I wrote a C# console app CbImage2File
that writes the image on the clipboard (if there is one) to a path given as the command line argument. You will need a reference to System.Windows.Forms
assembly.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CbImage2File
{
class Program
{
private static int FAILURE = 1;
private static void Failure(string description)
{
Console.Error.WriteLine("Clipboard does not contain image data");
Environment.Exit(FAILURE);
}
[STAThread]
static void Main(string[] args)
{
var image = System.Windows.Forms.Clipboard.GetImage();
if (image == null)
{
Failure("Clipboard does not contain image data");
}
if (args.Length == 0)
{
Failure("You have not specified an output path for the image");
}
if (args.Length > 1)
{
Failure("You must only specify a file path (1 argument)");
}
var filePath = args[0];
var folderInfo = new System.IO.FileInfo(filePath).Directory;
if (!folderInfo.Exists)
{
System.IO.Directory.CreateDirectory(folderInfo.FullName);
}
image.Save(filePath);
}
}
}
I use Greenshot to take the screenshot, which (via its configuration) automatically copies the screenshot to the clipboard.
I then use a shortcut C-S-v
to paste the image in the org mode buffer using the function org-insert-image-from-clipboard
:
(defun org-insert-image-from-clipboard ()
(interactive)
(let* ((the-dir (file-name-directory buffer-file-name))
(attachments-dir (concat the-dir "attachments"))
(png-file-name (format-time-string "%a%d%b%Y_%H%M%S.png"))
(png-path (concat attachments-dir "/" png-file-name))
(temp-buffer-name "CbImage2File-buffer"))
(call-process "C:/_code/CbImage2File/CbImage2File/bin/Debug/CbImage2File.exe" nil temp-buffer-name nil png-path)
(let ((result (with-current-buffer temp-buffer-name (buffer-string))))
(progn
(kill-buffer temp-buffer-name)
(if (string= result "")
(progn
(insert (concat "[[./attachments/" png-file-name "]]"))
(org-display-inline-images))
(insert result))))))
(define-key org-mode-map (kbd "C-S-v") 'org-insert-image-from-clipboard)
This function will work out a file path, then invoke the C# app to create png
file, then it will add the image tag and then call org-display-inline-images
to show the image. If there is no image on the clipboard, it will paste in the response from the C# app.