Lección 6: Imagenes | Curso de UI Emacs Lisp

Lección 6: Imagenes

No disponemos de un widget para mostrar imágenes, aunque Emacs posee la capacidad intrínseca para ello. En esta lección vamos a repasar algunos recursos para trabajar con elementos gráficos.

A la hora de insertar una imagen en un buffer, podemos usar la función insert-image. No obstante, en nuestro caso usaremos put-image. Es un equivalente pero con capacidad para insertar imágenes en las líneas que deseemos (y no donde estemos invocando la función) y por su facilidad a la hora de eliminar las imágenes presentes. Ambas son características fundamentales cuando trabajamos con una interfaz de usuario que irá cambiando a medida que el usuario interactúe con ella.

Para todos los ejemplos usaremos la imagen https://www.gnu.org/software/emacs/images/emacs.png. Puedes descargarla y guardarla en tu directorio de trabajo o donde esté tu archivo .el.

Insertar una imagen

Primero debemos crear la imagen a partir de un archivo. Para ello usaremos la función create-image. Esta función recibe como argumentos:

(create-image FILENAME TYPE DATA &rest PROPS)
  • FILENAME: ruta al archivo de la imagen.
  • TYPE: Tipo del archivo (png, jpeg, gif, etc).
  • DATA: Datos de la imagen. Por ejemplo si es un archivo svg, los datos serán el código svg. En caso contrario será nil.
  • PROPS: Propiedades de la imagen, como el ancho o el alto.

Para cargar la imagen del ejemplo, usaremos:

(setq imagen-emacs (create-image "emacs.png" 'png nil :width 100))

Aún no la estamos mostrando.

Ahora que tenemos la imagen, podemos insertarla en el buffer. Para ello usaremos la función put-image:

(put-image IMAGE &optional POSITION)
  • IMAGE: Imagen a insertar. Debe ser una imagen creada con create-image. Nosotros disponemos de la variable imagen-emacs que hemos creado anteriormente.
  • POSITION: Posición en la que insertar la imagen (la línea del buffer). Si no se especifica, se insertará en la posición actual del cursor.
(put-image imagen-emacs 2)

Con todo ello ya podremos visualizar la imagen.

(setq imagen-emacs (create-image "emacs.png" 'png nil :width 100))
(put-image imagen-emacs 2)

Eliminar una imagen

Para ello disponemos de una función llamada remove-images. Es una función que borra todas las imágenes que encuentre en un rango de líneas.

(remove-images &optional START END)
  • START: Línea inicial.
  • END: Línea final.

En el siguiente ejemplo se eliminan las imágenes de las líneas 2 y 3:

(remove-images 2 3)

Para borrar todas las imágenes del buffer, podemos usar:

(remove-images (point-min) (point-max))

Insertar una imagen desde una URL

Emacs no dispone de una función para descargar una imagen desde una URL. No obstante, podemos crearla nosotros mismos.

(require 'url)

(defun put-image-from-url (url pos &optional width)
  "Put an image from an URL in the buffer at position."
  (unless url (setq url (url-get-url-at-point)))
  (unless url
    (error "Couldn't find URL."))
  (let ((buffer (url-retrieve-synchronously url)))
    (unwind-protect
        (let ((data (with-current-buffer buffer
                      (goto-char (point-min))
                      (search-forward "\n\n")
                      (buffer-substring (point) (point-max)))))
	  (save-excursion
            (goto-char (point-min))
            (forward-line (1- pos)) ; Go to the beginning of the specified line
            (setq pos (line-beginning-position)))
	  (put-image (create-image data nil t :width width) pos))
      (kill-buffer buffer))))

Sería equivalente a usar put-image, pero nos ahorramos el paso de crear la imagen con create-image.

(put-image-from-url "https://www.gnu.org/software/emacs/images/emacs.png" 3)

Si la línea no existe, no se insertará la imagen en el lugar adecuado. Tenlo en cuenta.

Opcionalmente podemos especificar el ancho de la imagen. Por ejemplo 100 píxeles.

(put-image-from-url "https://www.gnu.org/software/emacs/images/emacs.png" 3 100)

Debes ser consciente de que esta función bloquea el hilo principal de Emacs hasta que se descarga la imagen. Si la imagen es muy grande, puede tardar un tiempo considerable. Te recomiendo que lo lances en un hilo secundario.

(make-thread (lambda ()
               (put-image-from-url "https://www.gnu.org/software/emacs/images/emacs.png" 3 100)))

svg-lib

De las pocas bibliotecas que podemos encontrar para crear gráficos, svg-lib es una de las más interesantes. Nos permite crear gráficos vectoriales, como su nombre indica, en formato svg.

Si quieres visualizar algunos ejemplos, puedes visitar su repositorio.

Puedes instalarla directamente desde MELPA.

M-x package-install RET svg-lib RET

O en tu archivo init.el:

(use-package svg-lib
  :ensure t)

Para crear un gráfico svg, usaremos la función svg-lib*.

(require 'svg-lib)

;; Etiqueta
(put-image (svg-lib-tag "TODO" nil) 1)
;; Icono
(put-image (svg-lib-icon "star" nil) 2)
;; Icono con etiqueta
(put-image (svg-lib-icon+tag "star" "Emacs" nil) 3)
;; Barra de progreso
(put-image (svg-lib-progress-bar 0.5 nil :width 5) 4)
;; Gráfico circular
(put-image (svg-lib-progress-pie 0.75 nil) 5)
;; Fecha
(put-image (svg-lib-date nil nil) 6)

Habrás visto que he usado :width para modificar la anchura de la barra de progreso. Si quieres personalizar las propiedades, puedes hacerlo a través de los argumentos.

  • :foreground: Color del texto.
  • :background: Color de fondo.
  • :padding: Espacio interno. Solo para tag e icon.
  • :margin: Espacio externo. Para char.
  • :stroke-width: Grosor del borde. En píxeles.
  • :corner-radius: Radio de las esquinas. En píxeles.
  • :align: Alineación horizontal. De 0 a 1.
  • :width: Ancho. En caracteres.
  • :height: Alto. Como escala de la altura de la línea.
  • :scale: Escala. Solo para iconos.
  • :ascent: Ascenso. Solo para texto.
  • :crop-left: Recortar a la izquierda.
  • :crop-right: Recortar a la derecha.
  • :collection: Colección de iconos.
  • :font-family: Familia de la fuente.
  • :font-size: Tamaño de la fuente.
  • :font-weight: Grosor de la fuente.

Dejo en tu mano las posibilidades de esta biblioteca.

Ejemplo

A continuación te voy a mostrar una aplicación sencilla para buscar y mostrar imágenes libres de Wikimedia.

Lee con atención el código y trata de entenderlo. Se ha utilizado los conceptos que hemos visto en las la lección.

;; -*- coding: utf-8 -*-
;; Imports
(require 'widget)
(require 'cl-lib)
(require 'url)
(require 'svg-lib)


(eval-when-compile
  (require 'wid-edit))

;; Variables
(defvar buffer-name "*Buscador de imágenes en Wikimedia*")
(defvar endpoint "https://commons.wikimedia.org/w/api.php")
(defvar results-width 300)
(defvar limit-results 10)
(defvar start-line-results 8)
(defvar list-urls '())
(defvar input-text nil)
(defvar button-search nil)
(defvar button-search-value "Buscar")
(defvar loading-value "⏳ Buscando... Por favor espere")

;; Functions

(defun process-response (pages)
  "Convert the response to a list of urls."
  ;; Add loading message
  (widget-value-set button-search loading-value)
  ;; Get the urls
  (setq list-urls '())
  (dolist (page pages)
    (setq list-urls (cons (assoc-default 'url (aref (assoc-default 'imageinfo page) 0)) list-urls)))
  (make-thread #'render-results "Render results"))

(defun search (widget &rest ignore)
  "Action for the search button. It will search for the text in the input field."
  (request endpoint
    :params `(("titles" . ,(widget-value input-text))
	      ("gimlimit" . ,limit-results)
              ("action" . "query")
	      ("format" . "json")
	      ("generator" . "images")
	      ("prop" . "imageinfo")
	      ("redirects" . 1)
	      ("iiprop" . "url"))
    :parser 'json-read
    :sync nil
    :success (cl-function
              (lambda (&key data &allow-other-keys)
		(process-response (assoc-default 'pages (assoc-default 'query data)))))
    :error (lambda (&rest _) (message "Error fetching data"))))


(defun put-image-from-url (url pos &optional width)
  "Put an image from an URL in the buffer at position."
  (unless url (setq url (url-get-url-at-point)))
  (unless url
    (error "Couldn't find URL."))
  (let ((buffer (url-retrieve-synchronously url)))
    (unwind-protect
        (let ((data (with-current-buffer buffer
                      (goto-char (point-min))
                      (search-forward "\n\n")
                      (buffer-substring (point) (point-max)))))
	  (save-excursion
            (goto-char (point-min))
            (forward-line (1- pos)) ; Go to the beginning of the specified line
            (setq pos (line-beginning-position)))
	  (put-image (create-image data nil t :width nil) pos))
      (kill-buffer buffer))))

(defun render-results ()
  "Render the results of the search."
  ;; Clear the previous results
  (remove-images (point-min) (point-max))
  ;; Add images
  (dotimes (i (length list-urls))
    (put-image-from-url (nth i list-urls) (+ start-line-results i) results-width))
  ;; Remove loading message
  (widget-value-set button-search button-search-value))

(defun main-layout ()
  "Make widgets for the main layout."
  (setq list-products '())
  ;; Create the buffer
  (switch-to-buffer buffer-name)
  ;; Clear the buffer
  (kill-all-local-variables)
  (let ((inhibit-read-only t))
    (erase-buffer))
  (remove-overlays)
  ;; Create the widgets
  ;; Title
  (widget-insert "\nBuscador de imágenes en Wikimedia\n\n")
  ;; Form
  (setq input-text (widget-create 'editable-field
				  :format "%v"
				  :size 20
				  :help-echo "¿Qué quieres buscar?"
				  ""))
  (widget-insert "\n\n")
  (setq button-search (widget-create 'push-button
				     :notify #'search
				     :help-echo "Busca"
				     "Buscar"))
  ;; Empty lines for the results
  (dotimes (i (1+ limit-results))
    (widget-insert "\n"))

  ;; End widgets
  ;; Display the buffer
  (use-local-map widget-keymap)
  (widget-setup)
  ;; Go to the first input field
  (widget-forward 1))

;; Initialization
(main-layout)
Actividad 1

Añade un botón que cuando sea pulsado muestre una imagen de tu equipo.

Actividad 2

Realiza un carousel de imágenes en un buffer. Cada 5 segundos debe cambiar por la siguiente, con 3 será suficiente. Recuerda eliminar la imagen anterior antes de insertar la siguiente para que solo sea visible una.

Actividad 3

Crea un botón que cuando sea pulsado muestre una imagen aleatorio de Unsplash. Puedes usar la función put-image-from-url. Necesitarás investigar la API de Unsplash.

Esta obra está bajo una Licencia Creative Commons Atribución-NoComercial-SinDerivadas 4.0 Internacional.

Atribución/Reconocimiento-NoComercial-SinDerivados 4.0 Internacional

¿Me ayudas?

Comprame un café
Pulsa sobre la imagen

No te sientas obligado a realizar una donación, pero cada aportación mantiene el sitio en activo logrando que continúe existiendo y sea accesible para otras personas. Además me motiva a crear nuevo contenido.

Comentarios

{{ comments.length }} comentarios

Nuevo comentario

Nueva replica  {{ formatEllipsisAuthor(replyComment.author) }}

Acepto la política de Protección de Datos.

Escribe el primer comentario