在不保存图像的情况下将剪贴板上的图像粘贴到 Emacs Org 模式文件中

42

作为研究日志,我使用Emacs Org模式。有时我想通过屏幕截图来跟踪某些内容,但我绝对不想将它们保存下来。因此我想知道是否有办法将这些图片插入到我的org模式文件中,就像从剪贴板复制文字一样?

8个回答

27

目前尚未实现您所需的确切功能,但如果您认为“绝对不想保存它们”,我会对将大量图像保存到研究日志中持怀疑态度。

无论如何,您所需的功能在最近几年的org-mode邮件列表中已经被提出了几次 - 请查看:

http://comments.gmane.org/gmane.emacs.orgmode/33770

http://www.mail-archive.com/emacs-orgmode@gnu.org/msg50862.html

第一个链接包括一些代码来启动一个截屏实用程序(通过ImageMagick)来[唯一]保存文件并在您的org-mode缓冲区中插入内联链接。

正如该线程中所述,该代码得到改进,并添加到org-mode hacks页面中 - 该页面具有许多有用的技巧:

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))

谢谢,这很有效。除了我仍然在努力让'org-display-inline-images'在Mac上的Aquamacs中工作。另外,我想知道,既然据说这种方法只适用于基于Unix的系统,您是否知道Windows上有没有任何好的替代方法? - lina
1
@lina 这段代码在我使用的cygwin和ImageMagick下运行良好。ImageMagick也有一个Windows二进制版本,但我承认我没有尝试过。您可以将call-process中的import替换为任何可以指定输出文件名的快照实用程序。 - assem
@assem 我认为将图像嵌入文档中的好处在于,当你的研究笔记有很多变动时,可以像lina所说的那样保留图像。如果你删除了包含文件的笔记,图像仍将保留在你的文件系统中。你认为有没有办法修改脚本(例如使用原始PNG),让emacs显示图像? - tonicebrian
缓冲区中有大量图像数据会对您的标准编辑功能产生性能影响,即使只是为了跳过图像数据区域。当您删除一个笔记时,可能更容易创建一个钩子,询问您是否希望同时删除磁盘上的图像文件。 - assem
1
当我尝试运行call-proccess的“import”时,出现错误:“正在寻找程序:没有这样的文件或目录”。它在我的工作机器上可以正常运行,但在家里不行。有任何想法吗? - Leo Ufimtsev
这里有一个问题:尽管OP说“截图”,但标题却说要从剪贴板粘贴。因此,如果想要截取整个屏幕或不包括emacs窗口的大块屏幕,我们必须先隐藏emacs窗口。因此,如果真的是从剪贴板中粘贴,那可能会是更清洁的解决方案。例如,在使用GUI gnome-screenshot --interactive时,它会在让您选择剪辑区域之前隐藏GUI。希望我表达得清楚。 - Jason

21

对于OS X用户。以下是Assem的答案的补充说明。

import命令对我无效,所以我将其替换为screencapture并加上了-i参数。我还发现使用相对路径比绝对路径更方便链接。

(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))

更新:改进

我最近稍微改进了脚本。现在它会将截屏保存在子目录中,如果需要的话会创建该目录,并且只有当图片实际被创建时(即如果按ESC则不执行任何操作),才会将链接插入到emacs中。它还可以在Ubuntu和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-org-screenshot”时打开了emacs,则必须截取打开的内容,即emacs本身。或者,我必须首先记住将emacs移开。如果我想要截取另一个工具的屏幕截图,我必须先将emacs置于焦点,将其移开,然后才能调用“my-org-screenshot”。感觉很繁琐。 - Ricardo
这个问题已经得到解决:https://dev59.com/aWQm5IYBdhLWcg3wqwfn#46268816 - Ricardo

9
以下是您需要翻译的内容:

这里的答案集中在emacs内发起该过程。在macos上,另一种方法是使用cmd-ctl-shift-4将屏幕截图图像放置在系统剪贴板上,然后返回到Emacs并使用emacs lisp调用pngpaste将图像从剪贴板写入文件,并对图像文件进行必要的操作(插入到org、LaTeX或作为本地Emacs内联图像等)。

例如,插入到org:

(defun org-insert-clipboard-image (&optional file)
  (interactive "F")
  (shell-command (concat "pngpaste " file))
  (insert (concat "[[" file "]]"))
  (org-display-inline-images))

1
对于Linux用户而言,你可以使用xclip代替pngpaste:xclip -selection clipboard -t image/png -o > file.png 参考链接:https://unix.stackexchange.com/a/145134/118575 - fikovnik

7
(如果有用),我使用了@assem的代码(非常感谢)并创建了一个版本,它将创建一个子文件夹(如果不存在),例如:${filename}IMG/,然后将像img_date.png这样的图像放入该文件夹中,然后插入一个相对路径到缓冲区中,例如:
[[ ./${filename}IMG/img_2015...png ]] 以下是代码:
(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)
)

[编辑 2015.04.09]

同时,我发现上面的方法效果不佳。从技术上讲,它是可行的,但它会生成很多与文件名相同的文件夹名称,使得选择文件变得繁琐,因为有些文件夹具有类似的名称。

相反,我采用了一种解决方案,将所有我的 org 文件保存在同一个文件夹中,并创建一个名为“img”的子文件夹来存储所有的图片。

我使用以下代码来捕获图像:

(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)
)

在我的org/img文件夹中有一个小的bash脚本来归档未被引用的图片:
#!/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

6
我喜欢将与org文件相关的图像和其他材料放在一个明确关联的目录中。我使用org-attachment机制实现这一点,并编写了一个包,即org-attachment-screenshot,该包也可从MELPA获取。它允许提供一个函数来定义附件的目录名称(例如文档名称+一些后缀),并且还支持标准的附件功能,例如如果您想基于条目定义附件,并在条目内进行继承。 此外,它还提供了自定义变量以执行快照命令。 请看一下。

6

1
有关使用 org-download 的更详细的写作,请参阅 Diego Zamboni 的博客文章(存档在这里)。 - TomRoche

4

我的解决方案适用于Windows平台。非常感谢Assem提供的解决方案,我的解决方案是基于他的。

我编写了一个C#控制台应用程序CbImage2File,将剪贴板上的图像(如果有)写入到作为命令行参数给出的路径中。您需要引用System.Windows.Forms程序集。

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);
        }
    }
}

我使用 Greenshot 来截屏,通过其配置,自动将截屏复制到剪贴板。

然后我使用快捷键 C-S-v,使用函数 org-insert-image-from-clipboard 将图像粘贴到 org 模式缓冲区中:

(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)

这个函数会计算出文件路径,然后调用C#应用程序创建png文件,接着添加图片标签并调用org-display-inline-images来显示图片。如果剪贴板中没有图像,则会粘贴C#应用程序的响应。


4

对于Windows 10用户,我修改了@assem提供的答案,以便使用开箱即用的工具:

(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))

整篇文章可以在此处找到:这里

非常好,谢谢。我不得不在调用snippingtool和powershell脚本之间添加(sleep-for 0.5),否则ContainsImage()调用将返回false。但这很棒。 - Dave
很好。一个不错的补充是添加一个参数来控制是否首先调用截图工具。有时我已经在剪贴板上有一张图片,不想调用截图工具。 - Kevin Wright

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接