#1142941 libgraphicsmagick1-dev: can't generate GIF animations using demo API code

Package:
libgraphicsmagick1-dev
Source:
libgraphicsmagick1-dev
Description:
format-independent image processing - C development files
Submitter:
Christophe Beauregard
Date:
2026-07-29 20:47:02 UTC
Severity:
normal
#1142941#5
Date:
2026-07-28 12:20:53 UTC
From:
To:
Using the demo.c example code from
/usr/share/doc/libgraphicsmagick1-dev/www/api/api.html fails to generate a gif
animation in the trixie version of libgraphicsmagick1-dev. It worked
correctly under bullseye.

I'm not sure if this is a failure of the documentation to track the API
or an actual bug in the library.

demo.c:

	#include <stdio.h>
	#include <string.h>
	#include <time.h>
	#include <sys/types.h>
	#include <magick/api.h>

	int main(int argc,char **argv)
	{
	  ExceptionInfo
		 exception;

	  Image
		 *image,
		 *images,
		 *resize_image,
		 *thumbnails;

	  ImageInfo
		 *image_info;

	  int
		 i;

	  /*
		 Initialize the image info structure and read the list of files
		 provided by the user as a image sequence
	  */
	  InitializeMagick(*argv);
	  GetExceptionInfo(&exception);
	  image_info=CloneImageInfo((ImageInfo *) NULL);
	  images=NewImageList();
	  for (i=1; i< argc-1; i++)
		 {
			(void) strcpy(image_info->filename,argv[i]);
			printf("Reading %s ...", image_info->filename);
			image=ReadImage(image_info,&exception);
			printf(" %lu frames\n", GetImageListLength(image));
			if (exception.severity != UndefinedException)
			  CatchException(&exception);
			if (image)
			  (void) AppendImageToList(&images,image);
		 }

	  if (!images)
		 {
			printf("Failed to read any images!\n");
			exit(1);
		 }
	  /*
		 Create a thumbnail image sequence
	  */
	  thumbnails=NewImageList();
	  while ((image=RemoveFirstImageFromList(&images)) != (Image *) NULL)
		 {
			resize_image=ResizeImage(image,106,80,LanczosFilter,1.0,&exception);
			DestroyImage(image);
			if (resize_image == (Image *) NULL)
			  {
				 CatchException(&exception);
				 continue;
			  }
			(void) AppendImageToList(&thumbnails,resize_image);
		 }
	  /*
		 Write the thumbnail image sequence to file
	  */
	  if (thumbnails)
		 {
			(void) strcpy(thumbnails->filename,argv[argc-1]);
			image_info->adjoin=MagickTrue;
			printf("Writing %s ... %lu frames\n", thumbnails->filename,
					 GetImageListLength(thumbnails));
			WriteImage(image_info,thumbnails);
		 }

	  /*
		 Release resources
	  */
	  DestroyImageList(thumbnails);
	  DestroyImageInfo(image_info);
	  DestroyExceptionInfo(&exception);
	  DestroyMagick();
	  return(0);
	}

test script test.sh:

	#!/bin/sh

	dpkg-query -W libgraphicsmagick1-dev

	for i in 1 2 3 4 5 6
	do
	gm convert -size 300x300 -background white -fill black -pointsize 200 \
		label:$i $i.png
	done

	gcc -o demo demo.c -O `GraphicsMagick-config --cppflags --ldflags --libs`

	./demo *.png out.gif

	gm identify out.gif

output under trixie:

	libgraphicsmagick1-dev	1.4+really1.3.45+hg17696-1
	Reading 1.png ... 1 frames
	Reading 2.png ... 1 frames
	Reading 3.png ... 1 frames
	Reading 4.png ... 1 frames
	Reading 5.png ... 1 frames
	Reading 6.png ... 1 frames
	Writing out.gif ... 6 frames
	out.gif GIF 106x80+0+0 PseudoClass 256c 8-bit 1.7Ki 0.000u 0m:0.000000s

Only 6.png ends up in the final image. However the following works fine:

	$ gm convert *.png out2.gif
	$ gm identify out2.gif
	out2.gif[0] GIF 300x300+0+0 PseudoClass 128c 8-bit 17.2Ki 0.000u 0m:0.000000s
	out2.gif[1] GIF 300x300+0+0 PseudoClass 256c 8-bit 17.2Ki 0.000u 0m:0.000203s
	out2.gif[2] GIF 300x300+0+0 PseudoClass 256c 8-bit 17.2Ki 0.000u 0m:0.000205s
	out2.gif[3] GIF 300x300+0+0 PseudoClass 256c 8-bit 17.2Ki 0.000u 0m:0.000200s
	out2.gif[4] GIF 300x300+0+0 PseudoClass 256c 8-bit 17.2Ki 0.000u 0m:0.000203s
	out2.gif[5] GIF 300x300+0+0 PseudoClass 256c 8-bit 17.2Ki 0.000u 0m:0.000210s

Test script output undef bullseye:

	libgraphicsmagick1-dev	1.4+really1.3.36+hg16481-2+deb11u1
	Reading 1.png ... 1 frames
	Reading 2.png ... 1 frames
	Reading 3.png ... 1 frames
	Reading 4.png ... 1 frames
	Reading 5.png ... 1 frames
	Reading 6.png ... 1 frames
	Writing out.gif ... 6 frames
	out.gif[0] GIF 106x80+0+0 PseudoClass 256c 8-bit 8.6Ki 0.000u 0m:0.000002s
	out.gif[1] GIF 106x80+0+0 PseudoClass 256c 8-bit 8.6Ki 0.000u 0m:0.000096s
	out.gif[2] GIF 106x80+0+0 PseudoClass 256c 8-bit 8.6Ki 0.000u 0m:0.000097s
	out.gif[3] GIF 106x80+0+0 PseudoClass 128c 8-bit 8.6Ki 0.000u 0m:0.000090s
	out.gif[4] GIF 106x80+0+0 PseudoClass 256c 8-bit 8.6Ki 0.000u 0m:0.000096s
	out.gif[5] GIF 106x80+0+0 PseudoClass 256c 8-bit 8.6Ki 0.000u 0m:0.000101s

#1142941#10
Date:
2026-07-28 13:34:06 UTC
From:
To:
The following change to the demo code fixes the problem (which is what
ConvertImageCommand() does):

$ diff demo.c.bak demo.c
68a69
 > 	   thumbnails=GetFirstImageInList(thumbnails);

Presumably some change internally stopped the GIF coder from finding the
head of the list, or the Append call changed the ordering, or something?

$ sh test.sh
libgraphicsmagick1-dev	1.4+really1.3.45+hg17696-1
Reading 1.png ... 1 frames
Reading 2.png ... 1 frames
Reading 3.png ... 1 frames
Reading 4.png ... 1 frames
Reading 5.png ... 1 frames
Reading 6.png ... 1 frames
Writing out.gif ... 6 frames
out.gif[0] GIF 106x80+0+0 PseudoClass 256c 8-bit 9.1Ki 0.000u 0m:0.000000s
out.gif[1] GIF 106x80+0+0 PseudoClass 256c 8-bit 9.1Ki 0.000u 0m:0.000034s
out.gif[2] GIF 106x80+0+0 PseudoClass 256c 8-bit 9.1Ki 0.000u 0m:0.000036s
out.gif[3] GIF 106x80+0+0 PseudoClass 128c 8-bit 9.1Ki 0.000u 0m:0.000034s
out.gif[4] GIF 106x80+0+0 PseudoClass 256c 8-bit 9.1Ki 0.000u 0m:0.000034s
out.gif[5] GIF 106x80+0+0 PseudoClass 256c 8-bit 9.1Ki 0.000u 0m:0.000035s

#1142941#15
Date:
2026-07-28 13:46:41 UTC
From:
To:
It seems that the API demo stopped working due to a documented behavior
change for the GraphicsMagick 1.3.40 release:

    API Updates:

    * AppendImageToList() now updates the image list pointer to be the
       image which was just added.  Use GetFirstImageInList() when the
       pointer to the first image in the list is needed.

GraphicsMagick rarely changes its API behavior.  This change was made
due to observing performance issues with many (e.g. 10k) images in a
list. It is much more efficient to remember the end of the list while
appending additional frames to the list rather than re-starting from the
beginning.

Bob

#1142941#20
Date:
2026-07-28 14:34:15 UTC
From:
To:
Agreed, that's 100% why we migrated to it.

The problem I think I'm having with this change is it's unclear that the first image in the list is needed for WriteImage(), because you have an API that's inconsistent about when any list element can be a shorthand for the entire list.

For example the call:

     GetImageListLength(thumbnails)

Always returns 6 whether it's before or after the:

      thumbnails=GetFirstImageInList(thumbnails);

Since the list functions (only the list functions?) internally rewind to the beginning of the list before doing what they do.

Clearly we're passing a 6 image list to GetImages(), and the list code knows it's a 6 image list, but... WriteImage() (and presumably most other API calls) ignore those list API semantics. This is misleading.

I'm not saying the behaviour is right or wrong, but at minimum the API documentation really should highlight the distinction between functions which operate on image lists (per list API semantics) and functions which only operate on the rest of the image list.

c.


Presumably some change internally stopped the GIF coder from finding the head of the list, or the Append call changed the ordering, or something?

$ sh test.sh
libgraphicsmagick1-dev    1.4+really1.3.45+hg17696-1
Reading 1.png ... 1 frames
Reading 2.png ... 1 frames
Reading 3.png ... 1 frames
Reading 4.png ... 1 frames
Reading 5.png ... 1 frames
Reading 6.png ... 1 frames
Writing out.gif ... 6 frames
out.gif[0] GIF 106x80+0+0 PseudoClass 256c 8-bit 9.1Ki 0.000u 0m:0.000000s
out.gif[1] GIF 106x80+0+0 PseudoClass 256c 8-bit 9.1Ki 0.000u 0m:0.000034s
out.gif[2] GIF 106x80+0+0 PseudoClass 256c 8-bit 9.1Ki 0.000u 0m:0.000036s
out.gif[3] GIF 106x80+0+0 PseudoClass 128c 8-bit 9.1Ki 0.000u 0m:0.000034s
out.gif[4] GIF 106x80+0+0 PseudoClass 256c 8-bit 9.1Ki 0.000u 0m:0.000034s
out.gif[5] GIF 106x80+0+0 PseudoClass 256c 8-bit 9.1Ki 0.000u 0m:0.000035s

#1142941#25
Date:
2026-07-28 19:33:34 UTC
From:
To:
In response to Christophe's concerns, I have updated the API example to
show use of GetFirstImageInList() after using AppendImageToList(). I
also improved the documentation for each of the image list APIs.  If
something is missing, or not clear, please let me know.


The image list APIs have existed roughly as is since at least January
2002 and many were originally written by John Cristy (ImageMagick
author).  In fact, the functions which appeared in 2002 were already
elsewhere in the code and gathered/renamed to create list.c.


There is good reason to be confused regarding an image list given that
although each entry in an image list is one image frame, some file
formats (e.g. GIF) may contain many frames.  It is possible (and often
reasonable) to conjoin frames from many files into one list.  So frames
from formats which only support one frame per file could be mixed with
some/all of the frames from GIF files. When dealing with GIF files, it
makes most sense that all frames correspond to the same GIF file.


Appending to a list becomes slower and slower as the list gets longer
and longer if the list reference is not the tail of the list. It is
convenient if the code which recently appended to the list
uses GetFirstImageInList() to return the head of the list to higher
level consumers.  It appears to me that ReadGIFImage() does return the
head of the list.


Bob

#1142941#30
Date:
2026-07-29 12:01:50 UTC
From:
To:
That should address my most immediate concern, which was essentially rooted in code based on the original demo.c from about 15 years ago suddenly not working in spite of still appearing to follow the same design, and demo.c not having been updated with the differences.

There may be a Debian policy issue from an API-breaking change in a minor point release of a library. That's a whole other can of worms that's maybe way too late to do anything about other than to ensure is documented.

c.



Agreed, that's 100% why we migrated to it.

The problem I think I'm having with this change is it's unclear that the first image in the list is needed for WriteImage(), because you have an API that's inconsistent about when any list element can be a shorthand for the entire list.

For example the call:

     GetImageListLength(thumbnails)

Always returns 6 whether it's before or after the:

      thumbnails=GetFirstImageInList(thumbnails);

Since the list functions (only the list functions?) internally rewind to the beginning of the list before doing what they do.

Clearly we're passing a 6 image list to GetImages(), and the list code knows it's a 6 image list, but... WriteImage() (and presumably most other API calls) ignore those list API semantics. This is misleading.

I'm not saying the behaviour is right or wrong, but at minimum the API documentation really should highlight the distinction between functions which operate on image lists (per list API semantics) and functions which only operate on the rest of the image list.

c.


Presumably some change internally stopped the GIF coder from finding the head of the list, or the Append call changed the ordering, or something?

$ sh test.sh
libgraphicsmagick1-dev    1.4+really1.3.45+hg17696-1
Reading 1.png ... 1 frames
Reading 2.png ... 1 frames
Reading 3.png ... 1 frames
Reading 4.png ... 1 frames
Reading 5.png ... 1 frames
Reading 6.png ... 1 frames
Writing out.gif ... 6 frames
out.gif[0] GIF 106x80+0+0 PseudoClass 256c 8-bit 9.1Ki 0.000u 0m:0.000000s
out.gif[1] GIF 106x80+0+0 PseudoClass 256c 8-bit 9.1Ki 0.000u 0m:0.000034s
out.gif[2] GIF 106x80+0+0 PseudoClass 256c 8-bit 9.1Ki 0.000u 0m:0.000036s
out.gif[3] GIF 106x80+0+0 PseudoClass 128c 8-bit 9.1Ki 0.000u 0m:0.000034s
out.gif[4] GIF 106x80+0+0 PseudoClass 256c 8-bit 9.1Ki 0.000u 0m:0.000034s
out.gif[5] GIF 106x80+0+0 PseudoClass 256c 8-bit 9.1Ki 0.000u 0m:0.000035s

#1142941#35
Date:
2026-07-29 15:09:07 UTC
From:
To:
I should point out that this change probably wasn't necessary.

With the old AppendImageToList() implementation, callers who needed faster appends could have already achieved faster performance on a call like:

    (void)AppendImageToList(&thumbnails,resize_image);

By noting that resize_image becomes the list tail, and simply changing the code to:

    (void)AppendImageToList(&thumbnails,resize_image);
      thumbnails = resize_image;

then adding:

    thumbnails = GetFirstImageInList(thumbnails);

Just outside the loop (like they need to now).

c.

#1142941#40
Date:
2026-07-29 20:44:20 UTC
From:
To:
You are correct that in 2022 I could have inspected the 26 locations in
GraphicsMagick and used your smart approach for the locations which
needed it.  Instead I fixed the function to be efficient by default, and
documented it.  There is always quite a lot to do compared to the time
available to do it.


I am a (now) retired person (as is the leader of the ImageMagick
project) and have been actively soliciting for assistance/mentoring for
several/many years, but assistance has not appeared.


I beg your forgiveness,


Bob