plug-ins: Add import support for Jeff's Image Format

JIF is a variation of the GIF format that compresses
indexed images using zlib rather than LZW. This patch
add import support for JIF images.
This commit is contained in:
Alx Sa 2025-06-06 14:15:06 +00:00
parent 992b7c40c6
commit 6395c37425

View file

@ -67,6 +67,7 @@
#include <errno.h> #include <errno.h>
#include <string.h> #include <string.h>
#include <zlib.h>
#include <glib/gstdio.h> #include <glib/gstdio.h>
@ -77,6 +78,8 @@
#define LOAD_PROC "file-gif-load" #define LOAD_PROC "file-gif-load"
#define LOAD_THUMB_PROC "file-gif-load-thumb" #define LOAD_THUMB_PROC "file-gif-load-thumb"
#define LOAD_JIF_PROC "file-jif-load"
#define LOAD_JIF_THUMB_PROC "file-jif-load-thumb"
/* uncomment the line below for a little debugging info */ /* uncomment the line below for a little debugging info */
@ -121,6 +124,7 @@ static GimpValueArray * gif_load_thumb (GimpProcedure *procedure,
static GimpImage * load_image (GFile *file, static GimpImage * load_image (GFile *file,
gboolean thumbnail, gboolean thumbnail,
gboolean is_jeff_image,
GError **error); GError **error);
@ -159,6 +163,8 @@ gif_query_procedures (GimpPlugIn *plug_in)
list = g_list_append (list, g_strdup (LOAD_THUMB_PROC)); list = g_list_append (list, g_strdup (LOAD_THUMB_PROC));
list = g_list_append (list, g_strdup (LOAD_PROC)); list = g_list_append (list, g_strdup (LOAD_PROC));
list = g_list_append (list, g_strdup (LOAD_JIF_THUMB_PROC));
list = g_list_append (list, g_strdup (LOAD_JIF_PROC));
return list; return list;
} }
@ -215,6 +221,46 @@ gif_create_procedure (GimpPlugIn *plug_in,
"Sven Neumann", "Sven Neumann",
"2006"); "2006");
} }
else if (! strcmp (name, LOAD_JIF_THUMB_PROC))
{
procedure = gimp_thumbnail_procedure_new (plug_in, name,
GIMP_PDB_PROC_TYPE_PLUGIN,
gif_load_thumb, NULL, NULL);
gimp_procedure_set_documentation (procedure,
"Loads only the first frame of a "
"Jeff's Image Format image, to be "
"used as a thumbnail",
"",
name);
gimp_procedure_set_attribution (procedure,
"Alx Sa",
"Alx Sa",
"2025");
}
else if (! strcmp (name, LOAD_JIF_PROC))
{
procedure = gimp_load_procedure_new (plug_in, name,
GIMP_PDB_PROC_TYPE_PLUGIN,
gif_load, NULL, NULL);
gimp_procedure_set_menu_label (procedure, _("Jeff's Image Format"));
gimp_procedure_set_documentation (procedure,
"Loads files of Jeff's Image Format "
"file format", NULL,
name);
gimp_procedure_set_attribution (procedure,
"Alx Sa",
"Alx Sa",
"2025");
gimp_file_procedure_set_magics (GIMP_FILE_PROCEDURE (procedure),
"0,string,JIF99a");
gimp_load_procedure_set_thumbnail_loader (GIMP_LOAD_PROCEDURE (procedure),
LOAD_JIF_THUMB_PROC);
}
return procedure; return procedure;
} }
@ -231,11 +277,15 @@ gif_load (GimpProcedure *procedure,
{ {
GimpValueArray *return_vals; GimpValueArray *return_vals;
GimpImage *image; GimpImage *image;
gboolean is_jeff = FALSE;
GError *error = NULL; GError *error = NULL;
gegl_init (NULL, NULL); gegl_init (NULL, NULL);
image = load_image (file, FALSE, &error); /* Unlikely, but check if we're loading a Jeff Image Format image */
is_jeff = (! strcmp (gimp_procedure_get_name (procedure), LOAD_JIF_PROC));
image = load_image (file, FALSE, is_jeff, &error);
if (! image) if (! image)
return gimp_procedure_new_return_values (procedure, return gimp_procedure_new_return_values (procedure,
@ -279,11 +329,15 @@ gif_load_thumb (GimpProcedure *procedure,
{ {
GimpValueArray *return_vals; GimpValueArray *return_vals;
GimpImage *image; GimpImage *image;
gboolean is_jeff = FALSE;
GError *error = NULL; GError *error = NULL;
gegl_init (NULL, NULL); gegl_init (NULL, NULL);
image = load_image (file, TRUE, &error); /* Unlikely, but check if we're loading a Jeff Image Format image */
is_jeff = (! strcmp (gimp_procedure_get_name (procedure), LOAD_JIF_THUMB_PROC));
image = load_image (file, TRUE, is_jeff, &error);
if (! image) if (! image)
return gimp_procedure_new_return_values (procedure, return gimp_procedure_new_return_values (procedure,
@ -385,12 +439,23 @@ static gboolean ReadImage (FILE *fd,
guint toppos, guint toppos,
guint screenwidth, guint screenwidth,
guint screenheight, guint screenheight,
gboolean is_jeff_image,
GimpImage **image,
GError **error);
static gboolean ReadJeffsImage (FILE *fd,
guchar *dest,
guchar bpp,
gint len,
gint height,
gboolean alpha_frame,
GimpImage **image, GimpImage **image,
GError **error); GError **error);
static GimpImage * static GimpImage *
load_image (GFile *file, load_image (GFile *file,
gboolean thumbnail, gboolean thumbnail,
gboolean is_jeff_image,
GError **error) GError **error)
{ {
FILE *fd; FILE *fd;
@ -425,6 +490,8 @@ load_image (GFile *file,
return NULL; return NULL;
} }
if (! is_jeff_image)
{
if (strncmp ((gchar *) buf, "GIF", 3) != 0) if (strncmp ((gchar *) buf, "GIF", 3) != 0)
{ {
g_set_error (error, GIMP_PLUG_IN_ERROR, 0, g_set_error (error, GIMP_PLUG_IN_ERROR, 0,
@ -433,13 +500,26 @@ load_image (GFile *file,
return NULL; return NULL;
} }
if ((strncmp ((gchar *) buf + 3, "87a", 3) != 0) && (strncmp ((gchar *) buf + 3, "89a", 3) != 0)) if ((strncmp ((gchar *) buf + 3, "87a", 3) != 0) &&
(strncmp ((gchar *) buf + 3, "89a", 3) != 0))
{ {
g_set_error (error, GIMP_PLUG_IN_ERROR, 0, g_set_error (error, GIMP_PLUG_IN_ERROR, 0,
_("Incorrect GIF version: not '87a' or '89a'")); _("Incorrect GIF version: not '87a' or '89a'"));
fclose (fd); fclose (fd);
return NULL; return NULL;
} }
}
else
{
if (strncmp ((gchar *) buf, "JIF99a", 6) != 0)
{
g_set_error (error, GIMP_PLUG_IN_ERROR, 0,
_("This is not a Jeff's Image Format file: "
"incorrect magic code"));
fclose (fd);
return NULL;
}
}
if (! ReadOK (fd, buf, 7)) if (! ReadOK (fd, buf, 7))
{ {
@ -538,7 +618,7 @@ load_image (GFile *file,
(guint) LM_to_uint (buf[2], buf[3]), (guint) LM_to_uint (buf[2], buf[3]),
GifScreen.Width, GifScreen.Width,
GifScreen.Height, GifScreen.Height,
&image, error); is_jeff_image, &image, error);
} }
else else
{ {
@ -551,7 +631,7 @@ load_image (GFile *file,
(guint) LM_to_uint (buf[2], buf[3]), (guint) LM_to_uint (buf[2], buf[3]),
GifScreen.Width, GifScreen.Width,
GifScreen.Height, GifScreen.Height,
&image, error); is_jeff_image, &image, error);
} }
if (!status) if (!status)
@ -1067,6 +1147,7 @@ ReadImage (FILE *fd,
guint toppos, guint toppos,
guint screenwidth, guint screenwidth,
guint screenheight, guint screenheight,
gboolean is_jeff_image,
GimpImage **image, GimpImage **image,
GError **error) GError **error)
{ {
@ -1111,7 +1192,8 @@ ReadImage (FILE *fd,
return FALSE; return FALSE;
} }
if (LZWReadByte (fd, TRUE, c) < 0) /* Jeff's Image Format uses zlib compression instead of LZW */
if (! is_jeff_image && LZWReadByte (fd, TRUE, c) < 0)
{ {
read_error (_("compressed image data"), *image, error); read_error (_("compressed image data"), *image, error);
return FALSE; return FALSE;
@ -1297,11 +1379,12 @@ ReadImage (FILE *fd,
else else
dest = (guchar *) g_malloc ((gsize)len * (gsize)height); dest = (guchar *) g_malloc ((gsize)len * (gsize)height);
if (! is_jeff_image)
{
while ((v = LZWReadByte (fd, FALSE, c)) >= 0) while ((v = LZWReadByte (fd, FALSE, c)) >= 0)
{ {
if (alpha_frame) if (alpha_frame)
{ {
if (promote_to_rgb) if (promote_to_rgb)
{ {
temp = dest + ( (ypos * len) + xpos ) * 4; temp = dest + ( (ypos * len) + xpos ) * 4;
@ -1319,7 +1402,6 @@ ReadImage (FILE *fd,
} }
else else
{ {
temp = dest + (ypos * len) + xpos; temp = dest + (ypos * len) + xpos;
*temp = (guchar) v; *temp = (guchar) v;
} }
@ -1380,6 +1462,11 @@ ReadImage (FILE *fd,
if (ypos >= height) if (ypos >= height)
break; break;
} }
}
else
{
ReadJeffsImage (fd, dest, c, len, height, alpha_frame, image, error);
}
/* v can be < 0 here, indicating an error or encountering an end_code (-2). /* v can be < 0 here, indicating an error or encountering an end_code (-2).
* At this point we have already safely handled any corruption, so we might * At this point we have already safely handled any corruption, so we might
@ -1402,7 +1489,8 @@ ReadImage (FILE *fd,
gboolean first = TRUE; gboolean first = TRUE;
/* Skip remaining compressed data if any. */ /* Skip remaining compressed data if any. */
if (! is_jeff_image)
{
while (LZWReadByte (fd, FALSE, c) >= 0) while (LZWReadByte (fd, FALSE, c) >= 0)
{ {
if (first) if (first)
@ -1412,6 +1500,107 @@ ReadImage (FILE *fd,
} }
} }
} }
}
return TRUE;
}
static gboolean
ReadJeffsImage (FILE *fd,
guchar *dest,
guchar bpp,
gint len,
gint height,
gboolean alpha_frame,
GimpImage **image,
GError **error)
{
guchar block_size = 255;
z_stream zs;
guchar block[255];
guchar *compressed;
guchar *indexes;
guint count = 0;
guint pos = 0;
guint mask = 0;
/* Indexes are stored as 1, 2, 4, or 8 bits per pixel
* in the uncompressed image. */
for (gint i = 7; i > 7 - bpp; i--)
{
if (i >= 0)
mask |= 1 << i;
}
compressed = g_malloc (len * height);
indexes = g_malloc (len * height);
/* Image data is stored as a zlib stream, arbitrarily broken
* in chunks of 255 bytes or less. We read in the chunk size,
* then that many bytes of compressed data to recreate the
* full zlib stream. */
while (block_size != 0)
{
if (! ReadOK (fd, &block_size, 1))
{
read_error (_("image data"), *image, error);
g_free (compressed);
g_free (indexes);
return FALSE;
}
if (block_size == 0)
break;
if (! ReadOK (fd, block, block_size))
{
read_error (_("image data"), *image, error);
g_free (compressed);
g_free (indexes);
return FALSE;
}
for (gint i = 0; i < block_size; i++)
compressed[i + count] = block[i];
count += block_size;
}
count = 0;
zs.next_in = (guchar *) compressed;
zs.avail_in = count;
zs.next_out = (guchar *) indexes;
zs.avail_in = len * height;
zs.zalloc = Z_NULL;
zs.zfree = Z_NULL;
zs.opaque = Z_NULL;
if (inflateInit (&zs) != Z_OK)
{
read_error (_("image data"), *image, error);
g_free (compressed);
g_free (indexes);
return FALSE;
}
inflate (&zs, Z_NO_FLUSH);
/* We then extract the indexes based on the bits per pixel
* set in the header */
for (gint i = 0; i < zs.total_out; i++)
{
for (gint j = 0; j < 8; j += bpp)
{
dest[pos++] = (indexes[i] & (mask >> j)) >> (8 - bpp - j);
if (alpha_frame)
{
dest[pos] = (dest[pos - 1] == Gif89.transparent) ? 0 : 255;
pos++;
}
}
}
g_free (indexes);
g_free (compressed);
inflateEnd (&zs);
return TRUE; return TRUE;
} }