Image loader for .scr (ZX Spectrum) files
authorDusan Gallo  <>
Sun, 7 Nov 2021 10:14:54 +0000 (10:14 +0000)
committerColin Clark <colin.clark@cclark.uk>
Sun, 7 Nov 2021 10:14:54 +0000 (10:14 +0000)
README.md
doc/docbook/GuideReferenceSupportedFormats.xml
src/Makefile.am
src/filefilter.c
src/image-load.c
src/image_load_zxscr.c [new file with mode: 0644]
src/image_load_zxscr.h [new file with mode: 0644]

index b5b23f0..6c09b12 100644 (file)
--- a/README.md
+++ b/README.md
@@ -71,7 +71,7 @@ Geeqie is a graphics file viewer. Basic features:
     * output: single image, anaglyph, SBS, mirror, SBS half size (3DTV)
 
 *   Viewing raster and vector images, in the following formats:
-3FR, ANI, APM, ARW, AVIF, BMP, CR2, CR3, CRW, CUR, DDS, DjVu, DNG, ERF, GIF, HEIC, HEIF, ICNS, ICO, JP2. JPE/JPEG/JPG, JPEG XL, JPS, KDC, MEF, MOS, MPO, MRW, NEF, ORF, PBM/PGM/PNM/PPM, PEF, PNG, PSD, PTX, QIF/QTIF (QuickTime Image Format), RAF, RAW, RW2, SR2, SRF, SVG/SVGZ, TGA/TARGA, TIF/TIFF, WEBP, WMF, XBM, XPM.
+3FR, ANI, APM, ARW, AVIF, BMP, CR2, CR3, CRW, CUR, DDS, DjVu, DNG, ERF, GIF, HEIC, HEIF, ICNS, ICO, JP2. JPE/JPEG/JPG, JPEG XL, JPS, KDC, MEF, MOS, MPO, MRW, NEF, ORF, PBM/PGM/PNM/PPM, PEF, PNG, PSD, PTX, QIF/QTIF (QuickTime Image Format), RAF, RAW, RW2, SCR (ZX Spectrum), SR2, SRF, SVG/SVGZ, TGA/TARGA, TIF/TIFF, WEBP, WMF, XBM, XPM.
     * Display images in archive files (.ZIP, .RAR etc.).
     * Animated GIFs are supported.
 
index 25d4fcd..d2367fe 100644 (file)
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <section id="GuideReferenceSupportedFormats">
   <title id="titleGuideReferenceSupportedFormats">Supported File Formats</title>
-  <para>3FR, ANI, APM, ARW, BMP, CR2, CRW, CUR, DDS, DNG, ERF, GIF, ICNS, ICO, JPE/JPEG/JPG, JPS, KDC, MEF, MPO, MOS, MRW, NEF, ORF, PEF, PTX, PBM/PGM/PNM/PPM, PNG, QIF/QTIF (QuickTime Image Format), RAF, RAW, RW2, SR2, SRF, SVG/SVGZ, TGA/TARGA, TIF/TIFF, WMF, XBM, XPM, HEIF (primary image only), WebP, DjVu.</para>
+  <para>3FR, ANI, APM, ARW, AVIF, BMP, CR2, CR3, CRW, CUR, DDS, DjVu, DNG, ERF, GIF, HEIC, HEIF, ICNS, ICO, JP2. JPE/JPEG/JPG, JPEG XL, JPS, KDC, MEF, MOS, MPO, MRW, NEF, ORF, PBM/PGM/PNM/PPM, PEF, PNG, PSD, PTX, QIF/QTIF (QuickTime Image Format), RAF, RAW, RW2, SCR (ZX Spectrum), SR2, SRF, SVG/SVGZ, TGA/TARGA, TIF/TIFF, WEBP, WMF, XBM, XPM.</para>
   <para>Images in archive files (.zip, .tar etc) can be displayed.
   </para>
   <para>Animated GIFs are supported.
index 5fbc303..9a47bb6 100644 (file)
@@ -203,6 +203,8 @@ geeqie_SOURCES = \
        image_load_heif.h\
        image_load_webp.c\
        image_load_webp.h\
+       image_load_zxscr.c\
+       image_load_zxscr.h\
        image_load_djvu.c\
        image_load_djvu.h\
        image_load_psd.c\
index e79ae30..478831f 100644 (file)
@@ -310,6 +310,7 @@ void filter_add_defaults(void)
 #ifdef HAVE_ARCHIVE
        filter_add_if_missing("zip", "Archive files", ".zip;.rar;.tar;.tar.gz;.tar.bz2;.tar.xz;.tgz;.tbz;.txz;.cbr;.cbz;.gz;.bz2;.xz;.lzh;.lza;.7z", FORMAT_CLASS_ARCHIVE, FALSE, FALSE, TRUE);
 #endif
+       filter_add_if_missing("scr", "ZX Spectrum screen Format", ".scr", FORMAT_CLASS_IMAGE, FALSE, FALSE, TRUE);
        filter_add_if_missing("psd", "Adobe Photoshop Document", ".psd", FORMAT_CLASS_IMAGE, FALSE, FALSE, TRUE);
        filter_add_if_missing("apng", "Animated Portable Network Graphic", ".apng", FORMAT_CLASS_IMAGE, FALSE, FALSE, TRUE);
 }
index e899ada..ff5bf1c 100644 (file)
@@ -34,6 +34,7 @@
 #include "image_load_ffmpegthumbnailer.h"
 #include "image_load_collection.h"
 #include "image_load_webp.h"
+#include "image_load_zxscr.h"
 #include "image_load_j2k.h"
 #include "image_load_jpegxl.h"
 #include "image_load_libraw.h"
@@ -835,6 +836,13 @@ static void image_loader_setup_loader(ImageLoader *il)
                }
        else
 #endif
+       if ((il->bytes_total == 6144 || il->bytes_total == 6912) &&
+               (file_extension_match(il->fd->path, ".scr")))
+               {
+               DEBUG_1("Using custom zxscr loader");
+               image_loader_backend_set_zxscr(&il->backend);
+               }
+       else
        if (il->fd->format_class == FORMAT_CLASS_COLLECTION)
                {
                DEBUG_1("Using custom collection loader");
diff --git a/src/image_load_zxscr.c b/src/image_load_zxscr.c
new file mode 100644 (file)
index 0000000..59e6e77
--- /dev/null
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2021 - The Geeqie Team
+ *
+ * Author: Dusan Gallo
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "main.h"
+
+#include "image-load.h"
+#include "image_load_zxscr.h"
+
+typedef struct _ImageLoaderZXSCR ImageLoaderZXSCR;
+struct _ImageLoaderZXSCR {
+       ImageLoaderBackendCbAreaUpdated area_updated_cb;
+       ImageLoaderBackendCbSize size_cb;
+       ImageLoaderBackendCbAreaPrepared area_prepared_cb;
+       gpointer data;
+       GdkPixbuf *pixbuf;
+       guint requested_width;
+       guint requested_height;
+       gboolean abort;
+};
+
+const guchar palette[2][8][3] = {
+       {
+               {0x00, 0x00, 0x00},
+               {0x00, 0x00, 0xbf},
+               {0xbf, 0x00, 0x00},
+               {0xbf, 0x00, 0xbf},
+               {0x00, 0xbf, 0x00},
+               {0x00, 0xbf, 0xbf},
+               {0xbf, 0xbf, 0x00},
+               {0xbf, 0xbf, 0xbf}
+       }, {
+               {0x00, 0x00, 0x00},
+               {0x00, 0x00, 0xff},
+               {0xff, 0x00, 0x00},
+               {0xff, 0x00, 0xff},
+               {0x00, 0xff, 0x00},
+               {0x00, 0xff, 0xff},
+               {0xff, 0xff, 0x00},
+               {0xff, 0xff, 0xff}
+       }
+};
+
+static void free_buffer(guchar *pixels, gpointer data)
+{
+       g_free(pixels);
+}
+
+static gboolean image_loader_zxscr_load(gpointer loader, const guchar *buf, gsize count, GError **error)
+{
+       ImageLoaderZXSCR *ld = (ImageLoaderZXSCR *) loader;
+       guint8 *pixels;
+       gint width, height;
+       gint row, col, mrow, pxs, i;
+       guint8 attr, bright, ink, paper;
+       guint8 *ptr;
+
+       if (count != 6144 && count != 6912)
+               {
+               log_printf("Error: zxscr reader error\n");
+               return FALSE;
+               }
+
+       width = 256;
+       height = 192;
+
+       pixels = g_try_malloc(width * height * 3);
+
+       if (!pixels)
+               {
+               log_printf("Error: zxscr reader error\n");
+               return FALSE;
+               }
+
+       ld->pixbuf = gdk_pixbuf_new_from_data(pixels, GDK_COLORSPACE_RGB, FALSE, 8, width, height, width * 3, free_buffer, NULL);
+
+       if (!ld->pixbuf)
+               {
+               g_free(pixels);
+               DEBUG_1("Insufficient memory to open ZXSCR file");
+               return FALSE;
+               }
+       //let's decode screen
+       for (row = 0; row < 24; row++)
+               for (col = 0; col < 32; col++)
+                       {
+                       if (count == 6144)
+                               {
+                               //if we have pixels only, make default white ink on black paper
+                               bright = 0x01;
+                               ink = 0x07;
+                               paper = 0x00;
+                               }
+                       else
+                               {
+                               attr = buf[6144 + row * 32 + col];
+                               bright = (attr >> 6) & 0x01;
+                               ink = attr & 0x07;
+                               paper = ((attr >> 3) & 0x07);
+                               }
+                       ptr = pixels + (row * 256 + col) * 8 * 3;
+
+                       for (mrow = 0; mrow < 8; mrow ++)
+                               {
+                               pxs = buf[(row / 8) * 2048 + mrow * 256 + (row % 8) * 32 + col];
+                               for (i = 0; i < 8; i++)
+                                       {
+                                       if (pxs & 0x80)
+                                               {
+                                               *ptr++ = palette[bright][ink][0];       //r
+                                               *ptr++ = palette[bright][ink][1];       //g
+                                               *ptr++ = palette[bright][ink][2];       //b
+                                               }
+                                       else
+                                               {
+                                               *ptr++ = palette[bright][paper][0];
+                                               *ptr++ = palette[bright][paper][1];
+                                               *ptr++ = palette[bright][paper][2];
+                                               }
+                                       pxs <<= 1;
+                                       }
+                               ptr += (31 * 8 * 3);
+                               }
+                       }
+
+       ld->area_updated_cb(loader, 0, 0, width, height, ld->data);
+
+       return TRUE;
+}
+
+static gpointer image_loader_zxscr_new(ImageLoaderBackendCbAreaUpdated area_updated_cb, ImageLoaderBackendCbSize size_cb, ImageLoaderBackendCbAreaPrepared area_prepared_cb, gpointer data)
+{
+       ImageLoaderZXSCR *loader = g_new0(ImageLoaderZXSCR, 1);
+       loader->area_updated_cb = area_updated_cb;
+       loader->size_cb = size_cb;
+       loader->area_prepared_cb = area_prepared_cb;
+       loader->data = data;
+       return (gpointer) loader;
+}
+
+static void image_loader_zxscr_set_size(gpointer loader, int width, int height)
+{
+       ImageLoaderZXSCR *ld = (ImageLoaderZXSCR *) loader;
+       ld->requested_width = width;
+       ld->requested_height = height;
+}
+
+static GdkPixbuf *image_loader_zxscr_get_pixbuf(gpointer loader)
+{
+       ImageLoaderZXSCR *ld = (ImageLoaderZXSCR *) loader;
+       return ld->pixbuf;
+}
+
+static gchar *image_loader_zxscr_get_format_name(gpointer loader)
+{
+       return g_strdup("zxscr");
+}
+
+static gchar **image_loader_zxscr_get_format_mime_types(gpointer loader)
+{
+       static gchar *mime[] = {"application/octet-stream", NULL};
+       return g_strdupv(mime);
+}
+
+static gboolean image_loader_zxscr_close(gpointer loader, GError **error)
+{
+       return TRUE;
+}
+
+static void image_loader_zxscr_abort(gpointer loader)
+{
+       ImageLoaderZXSCR *ld = (ImageLoaderZXSCR *) loader;
+       ld->abort = TRUE;
+}
+
+static void image_loader_zxscr_free(gpointer loader)
+{
+       ImageLoaderZXSCR *ld = (ImageLoaderZXSCR *) loader;
+       if (ld->pixbuf) g_object_unref(ld->pixbuf);
+       g_free(ld);
+}
+
+void image_loader_backend_set_zxscr(ImageLoaderBackend *funcs)
+{
+       funcs->loader_new = image_loader_zxscr_new;
+       funcs->set_size = image_loader_zxscr_set_size;
+       funcs->load = image_loader_zxscr_load;
+       funcs->write = NULL;
+       funcs->get_pixbuf = image_loader_zxscr_get_pixbuf;
+       funcs->close = image_loader_zxscr_close;
+       funcs->abort = image_loader_zxscr_abort;
+       funcs->free = image_loader_zxscr_free;
+       funcs->get_format_name = image_loader_zxscr_get_format_name;
+       funcs->get_format_mime_types = image_loader_zxscr_get_format_mime_types;
+}
+/* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */
diff --git a/src/image_load_zxscr.h b/src/image_load_zxscr.h
new file mode 100644 (file)
index 0000000..5de9b3a
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2021 - The Geeqie Team
+ *
+ * Author: Dusan Gallo
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef IMAGE_LOAD_ZXSCR_H
+#define IMAGE_LOAD_ZXSCR_H
+
+void image_loader_backend_set_zxscr(ImageLoaderBackend *funcs);
+
+#endif
+/* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */