bug#80281: 31.0.50; FR: Canvas object as display property

Eli Zaretskii <[email protected]>
Newsgroups gmane.emacs.bugs
Message-ID <[email protected]>
> From: Divya Ranjan Pattanaik <[email protected]>
> Cc: [email protected], Daniel Mendler <[email protected]>,
>  [email protected], [email protected], [email protected],
>  [email protected]
> Date: Sat, 01 Aug 2026 14:17:15 +0000
> 
> Hello Eli and others,
> 
> Daniel and I have been working on a revision of the canvas patch over the last few months, which we submit here. Several of Eli's and others' suggestions have been incorporated, such as the following:
> 
> - Creating a canvas image from Elisp via create-image or directly building the image spec
> - Canvas can be loaded from :file and :data via create-image
> - Supported and tested on multiple GNU/Linux ports (PGTK, X11, Lucid), MacOS and Windows.
> - The redrawing/refreshing logic of canvases has been made more robust. Specifically, if the frame is garbaged we wait for next full redisplay.
> 
> Apart from the above, the patch also contains a test suite, and more documentation. We have tried to keep the changes minimal and as non-invasive as possible.
> 
> Looking forward to hear your thoughts.

Thanks, my comments are below.  I'd appreciate review by Alan as well
(and anyone else who might have comments).

> +Once a canvas image object has been created and displayed, it can be
> +``refreshed'' via @code{canvas-refresh}. If an optional non-@code{nil}
> +argument is passed to @code{canvas-refresh} then Emacs will reload the
> +@code{:data} or @code{:file} before redrawing the canvas.

Please document canvas-refresh using @defun, as we do with every other
function in the manual.

> +@deftypefn Function uint32_t *canvas_data (emacs_env *@var{env}, emacs_value @var{canvas})
> +The function gives access to the pixel buffer of @var{canvas}.  The
> +pixel buffer is in row-major order with a size @var{width} * @var{height}.
> +The pixel format is ARGB32 on all platforms.  Return @code{NULL} in case
   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Is this factually correct?  What about Windows, where the native
format is BGR AFAIU?  Or is the above correct only because Windows is
not currently supported?

> +Note that the pixel buffer is only valid as the canvas object is alive
                                            ^^
"as long as"

> +and its dimensions have not been changed. If it has changed, Emacs will

Please always leave two spaces between sentences, here and elsewhere
in the patch, including manuals, doc strings, comments, commit log
messages, etc.

> +@example
> +/* Access the pixel buffer */
> +uint32_t *pixel = env->canvas_data (env, canvas);
> +/* Write to the pixel buffer */
> +...
> +/* Redraw the canvas */
> +env->funcall(env, env->intern(env, "canvas-refresh"), 1, &canvas);
> +@end example
> +
> +Note that @code{canvas-refresh} has an optional boolean argument to
> +reload the @code{:data} vector or string from the canvas image
> +specification.  When called from dynamic modules, the argument should
> +usually be @code{nil}, thus not provided.

I find it a better convention to always provider optional arguments,
as Qnil.

> +Thus, one can simply define a module function that takes as an argument
> +a canvas image object, following the specification as laid down in
> +@xref{Canvas Images}, and then use the above code to manipulate the pixel
   ^^^^^
@ref, nor @xref.

> +extern uint32_t* canvas_data (Lisp_Object);

Our style is to put the '*' at the beginning of the identifier, like
this:

  extern uint32_t *canvas_data (Lisp_Object);

There are numerous places where you place the '*' as above, so please
make sure to fix all of them.  For example, this:

> +static uint32_t*
> +module_canvas_data (emacs_env *env, emacs_value canvas)

should be written like this:

  +static uint32_t *
  +module_canvas_data (emacs_env *env, emacs_value canvas)

> +struct canvas
> +{
> +  /* Linked list of canvases, see `canvas_list'.  */
> +  struct canvas *next;
> +  /* Pixel memory buffer in ARGB32 format across all platforms.  */
                               ^^^^^^
Same comment about Windows here.

>  	  /* Some animation-related data doesn't affect display, but
> -	     breaks the image cache.  Filter those out.  */
> -	  if (!(EQ (key, QCanimate_buffer)
> -		|| EQ (key, QCanimate_tardiness)
> -		|| EQ (key, QCanimate_position)))
> +	     breaks the image cache.  Furthermore for canvases do not
> +	     check the data for equality, such that data can be changed
> +	     via mutation to refresh the canvas. Filter these keys out.
                                               ^^
Two spaces here (and elsewhere in comments), please.

> +`redisplay' must be called after `canvas-refresh' such that the updated
> +canvas is displayed even if double buffering is enabled.  It can be
> +updated in a loop.
> +
> +Example:
> +
> +  (setq canvas (create-image (make-vector (* 100 100) 0) 'canvas t
> +                             :data-width 100 :data-height 100))
> +  (insert (propertize "#" 'display canvas))
> +  (dotimes (i (* 100 100))
> +    (aset (plist-get (cdr canvas) :data) i #xFF)
> +    (canvas-refresh canvas 'reload-data)
> +    (redisplay))
> +
> +When `canvas-refresh' is called from a timer or a command, `redisplay'
> +will be called implicitly after the timer or command.
> +
> +Example:
> +
> +  (let ((i 0))
> +    (run-at-time nil 0.03 (lambda ()
> +      (when (< i (* 100 100))
> +        (aset (plist-get (cdr canvas) :data) i #xFF)
> +        (incf i)
> +        (canvas-refresh canvas 'reload-data)))))  */)

Please move these examples and notes to the ELisp manual.

> +  for (int y = 0; y < w->current_matrix->nrows; ++y)
> +    {
> +      struct glyph_row *row = w->current_matrix->rows + y;
> +      if (row->enabled_p)
> +	{
> +	  int start = row->x;
> +	  for (int x = 0; x < row->used[TEXT_AREA]; ++x)
> +	    {
> +	      struct glyph *glyph = row->glyphs[TEXT_AREA] + x;
> +	      if (glyph->type == IMAGE_GLYPH)
> +		{
> +		  struct image* img = IMAGE_OPT_FROM_ID (f, glyph->u.img_id);
> +		  if (img && EQ (img->spec, spec))
> +		    {
> +		      prepare_image_for_display (f, img);
> +		      draw_glyphs (w, start, row, TEXT_AREA, x, x + 1, DRAW_NORMAL_TEXT, 0);
> +		    }
> +		}
> +	      start += glyph->pixel_width;

Can canvas images be displayed in the marginal area?  If so, why
doesn't the above redraw them as well?

> +   if (env->non_local_exit_check (env) != emacs_funcall_exit_return)
> +     env->non_local_exit_clear (env); // don't propagate the error to ERT

We don't use C++-style comments in C source files.

> +(defun test-canvas-gen-file (width height pixel)
> +  (let* ((bytes (unibyte-string (logand pixel #xff)
> +                                (logand (ash pixel -8) #xff)
> +                                (logand (ash pixel -16) #xff)
> +                                (logand (ash pixel -24) #xff)))
> +         (coding-system-for-write 'binary))
                                     ^^^^^^^
It is better to use 'no-conversion here.
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.