Emacs: Extract part of an image to another file
| emacs
It turns out that image-mode allows you to open an
image and then crop it with i c
(image-crop
),
all within Emacs. I want to select a region and
then write it to a different file. I think the
ability to select a portion of an image by
drawing/moving a rectangle is generally useful, so
let's start by defining a function for that. The
heavy lifting is done by
image-crop--crop-image-1
, which tracks the mouse
and listens for events.
;; Based on image-crop. (defun my-image-select-rect (op) "Select a region of the current buffer's image. `q': Exit without changing anything. `RET': Select this region. `m': Make mouse movements move the rectangle instead of altering the rectangle shape. `s': Same as `m', but make the rectangle into a square first." (unless (image-type-available-p 'svg) (error "SVG support is needed to crop and cut images")) (let ((image (image--get-image))) (unless (imagep image) (user-error "No image under point")) (when (overlays-at (point)) (user-error "Can't edit images that have overlays")) ;; We replace the image under point with an SVG image that looks ;; just like that image. That allows us to draw lines over it. ;; At the end, we replace that SVG with a cropped version of the ;; original image. (let* ((data (cl-getf (cdr image) :data)) (type (cond ((cl-getf (cdr image) :format) (format "%s" (cl-getf (cdr image) :format))) (data (image-crop--content-type data)))) (image-scaling-factor 1) (orig-point (point)) (size (image-size image t)) (svg (svg-create (car size) (cdr size) :xmlns:xlink "http://www.w3.org/1999/xlink" :stroke-width 5)) ;; We want to get the original text that's covered by the ;; image so that we can restore it. (image-start (save-excursion (let ((match (text-property-search-backward 'display image))) (if match (prop-match-end match) (point-min))))) (image-end (save-excursion (let ((match (text-property-search-forward 'display image))) (if match (prop-match-beginning match) (point-max))))) (text (buffer-substring image-start image-end)) (inhibit-read-only t) orig-data svg-end) (with-temp-buffer (set-buffer-multibyte nil) (if (null data) (insert-file-contents-literally (cl-getf (cdr image) :file)) (insert data)) (let ((image-crop-exif-rotate nil)) (image-crop--possibly-rotate-buffer image)) (setq orig-data (buffer-string)) (setq type (image-crop--content-type orig-data)) (image-crop--process image-crop-resize-command `((?w . 600) (?f . ,(cadr (split-string type "/"))))) (setq data (buffer-string))) (svg-embed svg data type t :width (car size) :height (cdr size)) (with-temp-buffer (svg-insert-image svg) (switch-to-buffer (current-buffer)) (setq svg-end (point)) ;; Area (let ((area (condition-case _ (save-excursion (forward-line 1) (image-crop--crop-image-1 svg op)) (quit nil)))) (when area ;; scale to original (let* ((image-scaling-factor 1) (osize (image-size (create-image orig-data nil t) t)) (factor (/ (float (car osize)) (car size))) ;; width x height + left + top (width (abs (truncate (* factor (- (cl-getf area :right) (cl-getf area :left)))))) (height (abs (truncate (* factor (- (cl-getf area :bottom) (cl-getf area :top)))))) (left (truncate (* factor (min (cl-getf area :left) (cl-getf area :right))))) (top (truncate (* factor (min (cl-getf area :top) (cl-getf area :bottom)))))) (list :left left :top top :width width :height height :right (+ left width) :bottom (+ top height)))))))))
Then we can use it to select part of an image, and then use ImageMagick to extract that part of the image:
(defun my-image-write-region () "Copy a section of the image under point to a different file. This command presents the image with a rectangular area superimposed on it, and allows moving and resizing the area to define which part of it to crop. While moving/resizing the cropping area, the following key bindings are available: `q': Exit without changing anything. `RET': Save the image. `m': Make mouse movements move the rectangle instead of altering the rectangle shape. `s': Same as `m', but make the rectangle into a square first." (interactive) (when-let* ((orig-data (buffer-string)) (area (my-image-select-rect "write")) (inhibit-read-only t) (type (image-crop--content-type orig-data)) (left (plist-get area :left)) (top (plist-get area :top)) (width (plist-get area :width)) (height (plist-get area :height))) (with-temp-file (read-file-name "File: ") (set-buffer-multibyte nil) (insert orig-data) (image-crop--process image-crop-crop-command `((?l . ,left) (?t . ,top) (?w . ,width) (?h . ,height) (?f . ,(cadr (split-string type "/"))))))))
i w
seems like a sensible shortcut for writing a region of an image.
(with-eval-after-load 'image (keymap-set image-mode-map "i w" #'my-image-write-region))
This is part of my Emacs configuration.
You can comment with Disqus or you can e-mail me at sacha@sachachua.com.