changeset 2745:86dad5529aed

Merge with upstream changes.
author Matti Hamalainen <ccr@tnsp.org>
date Fri, 13 Apr 2018 19:06:23 +0300
parents 28978fd4c263 (current diff) 0b612372e82c (diff)
children 194f2e0719ba
files src/Makefile.am src/editors.c src/filefilter.c src/image-load.c
diffstat 26 files changed, 934 insertions(+), 52 deletions(-) [+]
line wrap: on
line diff
--- a/doc/docbook/GuideOptionsWindow.xml	Sun Mar 04 19:01:45 2018 +0200
+++ b/doc/docbook/GuideOptionsWindow.xml	Fri Apr 13 19:06:23 2018 +0300
@@ -69,6 +69,21 @@
         </listitem>
       </varlistentry>
     </variablelist>
+    <variablelist>
+      <varlistentry>
+        <term>
+          <guilabel>Show window IDs</guilabel>
+        </term>
+        <listitem>
+          <para>
+            Show the window ID in the titlebar of each window. When multiple Geeqie windows are opened, this option shows a unique identifier for each window. It may be used in conjunction with the command line option:
+            <link linkend="Remotecommands">
+              <programlisting>--remote --id:&lt;ID&gt;</programlisting>
+            </link>
+          </para>
+        </listitem>
+      </varlistentry>
+    </variablelist>
   </section>
   <section id="Size">
     <title>Size</title>
--- a/doc/docbook/GuideReferenceCommandLine.xml	Sun Mar 04 19:01:45 2018 +0200
+++ b/doc/docbook/GuideReferenceCommandLine.xml	Fri Apr 13 19:06:23 2018 +0300
@@ -247,6 +247,26 @@
             <entry>Bring the geeqie window to the top</entry>
           </row>
           <row>
+            <entry />
+            <entry>--id:&lt;ID&gt;</entry>
+            <entry>
+              Window ID for following commands
+              <footnote id='ref3'>
+                <para>The ID is shown in the titlebar of the window. If multiple windows are open, it can be used to direct commands to a particular window e.g. --remote --id:main --tell</para>
+              </footnote>
+            </entry>
+          </row>
+          <row>
+            <entry />
+            <entry>--new-window</entry>
+            <entry>Open new window</entry>
+          </row>
+          <row>
+            <entry />
+            <entry>--close-window</entry>
+            <entry>Close window</entry>
+          </row>
+          <row>
             <entry>-ct:clear|clean</entry>
             <entry>--cache-thumbs:clear|clean</entry>
             <entry>clear or clean thumbnail cache</entry>
--- a/doc/docbook/GuideReferenceSupportedFormats.xml	Sun Mar 04 19:01:45 2018 +0200
+++ b/doc/docbook/GuideReferenceSupportedFormats.xml	Fri Apr 13 19:06:23 2018 +0300
@@ -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, 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. Animated GIFs are supported.</para>
+  <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. Animated GIFs are supported.</para>
   <para>
     Refer to
     <link linkend="GuideReferencePixbufLoaders" endterm="titleGuideReferencePixbufLoaders" />
--- a/geeqie.1	Sun Mar 04 19:01:45 2018 +0200
+++ b/geeqie.1	Fri Apr 13 19:06:23 2018 +0300
@@ -188,6 +188,18 @@
 Bring the Geeqie window to the top.
 .br
 .B
+.IP \-\-id:<ID>
+Window ID for following commands.
+.br
+.B
+.IP \-\-new-window
+Open new window.
+.br
+.B
+.IP \-\-close-window
+Close window.
+.br
+.B
 .IP \-ct:clear|clean,\-\-cache-thumbs:clear|clean
 Clear or clean thumbnail cache.
 .br
--- a/src/Makefile.am	Sun Mar 04 19:01:45 2018 +0200
+++ b/src/Makefile.am	Fri Apr 13 19:06:23 2018 +0300
@@ -181,6 +181,8 @@
 	image_load_tiff.h\
 	image_load_bpg.c\
 	image_load_bpg.h\
+	image_load_dds.c\
+	image_load_dds.h\
 	image_load_ffmpegthumbnailer.c\
 	image_load_ffmpegthumbnailer.h\
 	image-overlay.c	\
--- a/src/collect-table.c	Sun Mar 04 19:01:45 2018 +0200
+++ b/src/collect-table.c	Fri Apr 13 19:06:23 2018 +0300
@@ -401,7 +401,7 @@
 	collection_table_update_status(ct);
 }
 
-static void collection_table_select(CollectTable *ct, CollectInfo *info)
+void collection_table_select(CollectTable *ct, CollectInfo *info)
 {
 	ct->prev_selection = info;
 
@@ -857,6 +857,26 @@
 	collection_dialog_append(NULL, ct->cd);
 }
 
+static void collection_table_popup_goto_original_cb(GtkWidget *widget, gpointer data)
+{
+	CollectTable *ct = data;
+	GList *list;
+	LayoutWindow *lw = NULL;
+	FileData *fd;
+
+	if (!layout_valid(&lw)) return;
+	list = collection_table_selection_get_list(ct);
+	if (list)
+		{
+		fd = list->data;
+		if (fd)
+			{
+			layout_set_fd(lw, fd);
+			}
+		}
+	g_list_free(list);
+}
+
 static void collection_table_popup_find_dupes_cb(GtkWidget *widget, gpointer data)
 {
 	CollectTable *ct = data;
@@ -914,6 +934,8 @@
 			G_CALLBACK(collection_table_popup_view_cb), ct);
 	menu_item_add_stock_sensitive(menu, _("View in _new window"), GTK_STOCK_NEW, over_icon,
 			G_CALLBACK(collection_table_popup_view_new_cb), ct);
+	menu_item_add_stock(menu, _("Go to original"), GTK_STOCK_FIND,
+			G_CALLBACK(collection_table_popup_goto_original_cb), ct);
 	menu_item_add_divider(menu);
 	menu_item_add_stock_sensitive(menu, _("Rem_ove"), GTK_STOCK_REMOVE, over_icon,
 			G_CALLBACK(collection_table_popup_remove_cb), ct);
@@ -986,7 +1008,7 @@
  *-------------------------------------------------------------------
  */
 
-static void collection_table_set_focus(CollectTable *ct, CollectInfo *info)
+void collection_table_set_focus(CollectTable *ct, CollectInfo *info)
 {
 	GtkTreeIter iter;
 	gint row, col;
--- a/src/collect-table.h	Sun Mar 04 19:01:45 2018 +0200
+++ b/src/collect-table.h	Fri Apr 13 19:06:23 2018 +0300
@@ -41,6 +41,7 @@
 
 CollectInfo *collection_table_get_focus_info(CollectTable *ct);
 GList *collection_table_selection_get_list(CollectTable *ct);
-
+void collection_table_set_focus(CollectTable *ct, CollectInfo *info);
+void collection_table_select(CollectTable *ct, CollectInfo *info);
 #endif
 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */
--- a/src/editors.c	Sun Mar 04 19:01:45 2018 +0200
+++ b/src/editors.c	Fri Apr 13 19:06:23 2018 +0300
@@ -111,7 +111,7 @@
 		{"image/*",		"*"},
 		{"image/bmp",		".bmp"},
 		{"image/gif",		".gif"},
-		{"image/jpeg",		".jpeg;.jpg"},
+		{"image/jpeg",		".jpeg;.jpg;.mpo"},
 		{"image/jpg",		".jpg;.jpeg"},
 		{"image/bpg",		".bpg"},
 		{"image/pcx",		".pcx"},
@@ -119,19 +119,25 @@
 		{"image/svg",		".svg"},
 		{"image/svg+xml",	".svg"},
 		{"image/svg+xml-compressed", 	".svg"},
-		{"image/tiff",		".tiff;.tif"},
+		{"image/tiff",		".tiff;.tif;.mef"},
+		{"image/vnd-ms.dds",	".dds"},
+		{"image/x-adobe-dng",	".dng"},
 		{"image/x-bmp",		".bmp"},
 		{"image/x-canon-crw",	".crw"},
 		{"image/x-canon-cr2",	".cr2"},
 		{"image/x-cr2",		".cr2"},
-		{"image/x-dcraw",	"%raw"},
+		{"image/x-dcraw",	"%raw;.mos"},
 		{"image/x-epson-erf",	"%erf"},
 		{"image/x-ico",		".ico"},
+		{"image/x-kodak-kdc",	".kdc"},
 		{"image/x-mrw",		".mrw"},
 		{"image/x-minolta-mrw",	".mrw"},
 		{"image/x-MS-bmp",	".bmp"},
 		{"image/x-nef",		".nef"},
 		{"image/x-nikon-nef",	".nef"},
+		{"image/x-panasonic-raw",	".raw"},
+		{"image/x-panasonic-rw2",	".rw2"},
+		{"image/x-pentax-pef",	".pef"},
 		{"image/x-orf",		".orf"},
 		{"image/x-olympus-orf",	".orf"},
 		{"image/x-pcx",		".pcx"},
@@ -145,11 +151,15 @@
 		{"image/x-raf",		".raf"},
 		{"image/x-fuji-raf",	".raf"},
 		{"image/x-sgi",		".sgi"},
+		{"image/x-sony-arw",	".arw"},
+		{"image/x-sony-sr2",	".sr2"},
+		{"image/x-sony-srf",	".srf"},
 		{"image/x-tga",		".tga"},
 		{"image/x-xbitmap",	".xbm"},
 		{"image/x-xcf",		".xcf"},
 		{"image/x-xpixmap",	".xpm"},
 		{"image/x-x3f",		".x3f"},
+		{"application/x-navi-animation",		".ani"},
 		{"application/x-ptoptimizer-script",	".pto"},
 		{NULL, NULL}};
 
@@ -544,7 +554,7 @@
 				   NULL, FALSE,
 				   NULL, ed);
 	buf = g_strdup_printf(_("Output of %s"), text);
-	generic_dialog_add_message(vd->gd, NULL, buf, NULL, TRUE);
+	generic_dialog_add_message(vd->gd, NULL, buf, NULL, FALSE);
 	g_free(buf);
 	vd->button_stop = generic_dialog_add_button(vd->gd, GTK_STOCK_STOP, NULL,
 						   editor_verbose_window_stop, FALSE);
--- a/src/filefilter.c	Sun Mar 04 19:01:45 2018 +0200
+++ b/src/filefilter.c	Fri Apr 13 19:06:23 2018 +0300
@@ -291,6 +291,9 @@
 	filter_add_if_missing("mkv", "Matroska video file", ".mkv;.webm", FORMAT_CLASS_VIDEO, FALSE, FALSE, FALSE);
 	filter_add_if_missing("wmv", "Windows Media Video file", ".wmv;.asf", FORMAT_CLASS_VIDEO, FALSE, FALSE, FALSE);
 	filter_add_if_missing("flv", "Flash Video file", ".flv", FORMAT_CLASS_VIDEO, FALSE, FALSE, FALSE);
+
+	/* other supported formats */
+	filter_add_if_missing("dds", "DirectDraw Surface", ".dds", FORMAT_CLASS_IMAGE, FALSE, FALSE, TRUE);
 }
 
 GList *filter_to_list(const gchar *extensions)
--- a/src/image-load.c	Sun Mar 04 19:01:45 2018 +0200
+++ b/src/image-load.c	Fri Apr 13 19:06:23 2018 +0300
@@ -25,6 +25,7 @@
 #include "image_load_jpeg.h"
 #include "image_load_tiff.h"
 #include "image_load_bpg.h"
+#include "image_load_dds.h"
 #include "image_load_ffmpegthumbnailer.h"
 
 #include "exif.h"
@@ -647,6 +648,12 @@
 		}
 	else
 #endif
+	if (il->bytes_total >= 3 && il->mapped_file[0] == 0x44 && il->mapped_file[1] == 0x44 && il->mapped_file[2] == 0x53)
+		{
+		DEBUG_1("Using dds loader");
+		image_loader_backend_set_dds(&il->backend);
+		}
+	else
 		image_loader_backend_set_default(&il->backend);
 
 	il->loader = il->backend.loader_new(image_loader_area_updated_cb, image_loader_size_cb, image_loader_area_prepared_cb, il);
--- a/src/image.c	Sun Mar 04 19:01:45 2018 +0200
+++ b/src/image.c	Fri Apr 13 19:06:23 2018 +0300
@@ -24,6 +24,7 @@
 
 
 #include "collect.h"
+#include "collect-table.h"
 #include "color-man.h"
 #include "exif.h"
 #include "metadata.h"
@@ -177,6 +178,8 @@
 	gchar *title = NULL;
 	gchar *zoom = NULL;
 	gchar *collection = NULL;
+	LayoutWindow *lw;
+	gchar *lw_ident = NULL;
 
 	if (!imd->top_window) return;
 
@@ -194,13 +197,25 @@
 		g_free(buf);
 		}
 
-	title = g_strdup_printf("%s%s%s%s%s%s",
+	lw = layout_find_by_image(imd);
+	if (lw)
+		{
+		lw_ident = g_strconcat(" (", lw->options.id, ")", NULL);
+		}
+
+	title = g_strdup_printf("%s%s%s%s%s%s%s",
 		imd->title ? imd->title : "",
 		imd->image_fd ? imd->image_fd->name : "",
 		zoom ? zoom : "",
 		collection ? collection : "",
 		imd->image_fd ? " - " : "",
-		imd->title_right ? imd->title_right : "");
+		imd->title_right ? imd->title_right : "",
+		options->show_window_ids ? (lw_ident ? lw_ident : "") : ""
+		);
+	if (lw_ident)
+		{
+		g_free(lw_ident);
+		}
 
 	gtk_window_set_title(GTK_WINDOW(imd->top_window), title);
 
@@ -1174,9 +1189,18 @@
 
 void image_change_from_collection(ImageWindow *imd, CollectionData *cd, CollectInfo *info, gdouble zoom)
 {
+	CollectWindow *cw;
+
 	if (!cd || !info || !g_list_find(cd->list, info)) return;
 
 	image_change_real(imd, info->fd, cd, info, zoom);
+	cw = collection_window_find(cd);
+	if (cw)
+		{
+		collection_table_set_focus(cw->table, info);
+		collection_table_unselect_all(cw->table);
+		collection_table_select(cw->table,info);
+		}
 }
 
 CollectionData *image_get_collection(ImageWindow *imd, CollectInfo **info)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/image_load_dds.c	Fri Apr 13 19:06:23 2018 +0300
@@ -0,0 +1,613 @@
+/*
+ * Copyright (C) 2018 The Geeqie Team
+ *
+ * Author: Wolfgang Lieff
+ *
+ * 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.
+
+ * Derived from dds_reader written by:
+ * Copyright (c) 2015 Kenji Sasaki
+ * Released under the MIT license.
+ * https://github.com/npedotnet/DDSReader/blob/master/LICENSE
+
+ */
+
+#include "main.h"
+
+#include "image-load.h"
+#include "image_load_dds.h"
+
+typedef struct _ImageLoaderDDS ImageLoaderDDS;
+struct _ImageLoaderDDS {
+	ImageLoaderBackendCbAreaUpdated area_updated_cb;
+	ImageLoaderBackendCbSize size_cb;
+	ImageLoaderBackendCbAreaPrepared area_prepared_cb;
+	gpointer data;
+	GdkPixbuf *pixbuf;
+	guint requested_width;
+	guint requested_height;
+	gboolean abort;
+};
+
+static void free_buffer(guchar *pixels, gpointer data)
+{
+	g_free(pixels);
+}
+
+int ddsGetHeight(unsigned const char * buffer) {
+	return (buffer[12] & 0xFF) | (buffer[13] & 0xFF) << 8 | (buffer[14] & 0xFF) << 16 | (buffer[15] & 0xFF) << 24;
+}
+
+int ddsGetWidth(unsigned const char * buffer) {
+	return (buffer[16] & 0xFF) | (buffer[17] & 0xFF) << 8 | (buffer[18] & 0xFF) << 16 | (buffer[19] & 0xFF) << 24;
+}
+
+int ddsGetMipmap(unsigned const char * buffer) {
+	return (buffer[28] & 0xFF) | (buffer[29] & 0xFF) << 8 | (buffer[30] & 0xFF) << 16 | (buffer[31] & 0xFF) << 24;
+}
+
+int ddsGetPixelFormatFlags(unsigned const char * buffer) {
+	return (buffer[80] & 0xFF) | (buffer[81] & 0xFF) << 8 | (buffer[82] & 0xFF) << 16 | (buffer[83] & 0xFF) << 24;
+}
+
+int ddsGetFourCC(unsigned const char * buffer) {
+	return (buffer[84] & 0xFF) << 24 | (buffer[85] & 0xFF) << 16 | (buffer[86] & 0xFF) << 8 | (buffer[87] & 0xFF);
+}
+
+int ddsGetBitCount(unsigned const char * buffer) {
+	return (buffer[88] & 0xFF) | (buffer[89] & 0xFF) << 8 | (buffer[90] & 0xFF) << 16 | (buffer[91] & 0xFF) << 24;
+}
+
+int ddsGetRedMask(unsigned const char * buffer) {
+	return (buffer[92] & 0xFF) | (buffer[93] & 0xFF) << 8 | (buffer[94] & 0xFF) << 16 | (buffer[95] & 0xFF) << 24;
+}
+
+int ddsGetGreenMask(unsigned const char * buffer) {
+	return (buffer[96] & 0xFF) | (buffer[97] & 0xFF) << 8 | (buffer[98] & 0xFF) << 16 | (buffer[99] & 0xFF) << 24;
+}
+
+int ddsGetBlueMask(unsigned const char * buffer) {
+	return (buffer[100] & 0xFF) | (buffer[101] & 0xFF) << 8 | (buffer[102] & 0xFF) << 16 | (buffer[103] & 0xFF) << 24;
+}
+
+int ddsGetAlphaMask(unsigned const char * buffer) {
+	return (buffer[104] & 0xFF) | (buffer[105] & 0xFF) << 8 | (buffer[106] & 0xFF) << 16 | (buffer[107] & 0xFF) << 24;
+}
+
+// Image Type
+#define DXT1 (0x44585431)
+#define DXT2 (0x44585432)
+#define DXT3 (0x44585433)
+#define DXT4 (0x44585434)
+#define DXT5 (0x44585435)
+#define A1R5G5B5 ((1 << 16) | 2)
+#define X1R5G5B5 ((2 << 16) | 2)
+#define A4R4G4B4 ((3 << 16) | 2)
+#define X4R4G4B4 ((4 << 16) | 2)
+#define R5G6B5 ((5 << 16) | 2)
+#define R8G8B8 ((1 << 16) | 3)
+#define A8B8G8R8 ((1 << 16) | 4)
+#define X8B8G8R8 ((2 << 16) | 4)
+#define A8R8G8B8 ((3 << 16) | 4)
+#define X8R8G8B8 ((4 << 16) | 4)
+
+// RGBA Masks
+static const int A1R5G5B5_MASKS[] = { 0x7C00, 0x03E0, 0x001F, 0x8000 };
+static const int X1R5G5B5_MASKS[] = { 0x7C00, 0x03E0, 0x001F, 0x0000 };
+static const int A4R4G4B4_MASKS[] = { 0x0F00, 0x00F0, 0x000F, 0xF000 };
+static const int X4R4G4B4_MASKS[] = { 0x0F00, 0x00F0, 0x000F, 0x0000 };
+static const int R5G6B5_MASKS[] = { 0xF800, 0x07E0, 0x001F, 0x0000 };
+static const int R8G8B8_MASKS[] = { 0xFF0000, 0x00FF00, 0x0000FF, 0x000000 };
+static const int A8B8G8R8_MASKS[] = { 0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000 };
+static const int X8B8G8R8_MASKS[] = { 0x000000FF, 0x0000FF00, 0x00FF0000, 0x00000000 };
+static const int A8R8G8B8_MASKS[] = { 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000 };
+static const int X8R8G8B8_MASKS[] = { 0x00FF0000, 0x0000FF00, 0x000000FF, 0x00000000 };
+
+// BIT4 = 17 * index;
+static const int BIT5[] = { 0, 8, 16, 25, 33, 41, 49, 58, 66, 74, 82, 90, 99, 107, 115, 123, 132, 140, 148, 156, 165, 173, 181, 189, 197, 206, 214, 222, 230, 239, 247, 255 };
+static const int BIT6[] = { 0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 45, 49, 53, 57, 61, 65, 69, 73, 77, 81, 85, 89, 93, 97, 101, 105, 109, 113, 117, 121, 125, 130, 134, 138, 142, 146, 150, 154, 158, 162, 166, 170, 174, 178, 182, 186, 190, 194, 198, 202, 206, 210, 215, 219, 223, 227, 231, 235, 239, 243, 247, 251, 255 };
+
+static int ddsGetType(const unsigned char *buffer) {
+	int type = 0;
+	int flags = ddsGetPixelFormatFlags(buffer);
+	if ((flags & 0x04) != 0) {
+		// DXT
+		type = ddsGetFourCC(buffer);
+	}
+	else if ((flags & 0x40) != 0) {
+		// RGB
+		int bitCount = ddsGetBitCount(buffer);
+		int redMask = ddsGetRedMask(buffer);
+		int greenMask = ddsGetGreenMask(buffer);
+		int blueMask = ddsGetBlueMask(buffer);
+		int alphaMask = ((flags & 0x01) != 0) ? ddsGetAlphaMask(buffer) : 0; // 0x01 alpha
+		if (bitCount == 16) {
+			if (redMask == A1R5G5B5_MASKS[0] && greenMask == A1R5G5B5_MASKS[1] && blueMask == A1R5G5B5_MASKS[2] && alphaMask == A1R5G5B5_MASKS[3]) {
+				// A1R5G5B5
+				type = A1R5G5B5;
+			}
+			else if (redMask == X1R5G5B5_MASKS[0] && greenMask == X1R5G5B5_MASKS[1] && blueMask == X1R5G5B5_MASKS[2] && alphaMask == X1R5G5B5_MASKS[3]) {
+				// X1R5G5B5
+				type = X1R5G5B5;
+			}
+			else if (redMask == A4R4G4B4_MASKS[0] && greenMask == A4R4G4B4_MASKS[1] && blueMask == A4R4G4B4_MASKS[2] && alphaMask == A4R4G4B4_MASKS[3]) {
+				// A4R4G4B4
+				type = A4R4G4B4;
+			}
+			else if (redMask == X4R4G4B4_MASKS[0] && greenMask == X4R4G4B4_MASKS[1] && blueMask == X4R4G4B4_MASKS[2] && alphaMask == X4R4G4B4_MASKS[3]) {
+				// X4R4G4B4
+				type = X4R4G4B4;
+			}
+			else if (redMask == R5G6B5_MASKS[0] && greenMask == R5G6B5_MASKS[1] && blueMask == R5G6B5_MASKS[2] && alphaMask == R5G6B5_MASKS[3]) {
+				// R5G6B5
+				type = R5G6B5;
+			}
+			else {
+				// Unsupported 16bit RGB image
+			}
+		}
+		else if (bitCount == 24) {
+			if (redMask == R8G8B8_MASKS[0] && greenMask == R8G8B8_MASKS[1] && blueMask == R8G8B8_MASKS[2] && alphaMask == R8G8B8_MASKS[3]) {
+				// R8G8B8
+				type = R8G8B8;
+			}
+			else {
+				// Unsupported 24bit RGB image
+			}
+		}
+		else if (bitCount == 32) {
+			if (redMask == A8B8G8R8_MASKS[0] && greenMask == A8B8G8R8_MASKS[1] && blueMask == A8B8G8R8_MASKS[2] && alphaMask == A8B8G8R8_MASKS[3]) {
+				// A8B8G8R8
+				type = A8B8G8R8;
+			}
+			else if (redMask == X8B8G8R8_MASKS[0] && greenMask == X8B8G8R8_MASKS[1] && blueMask == X8B8G8R8_MASKS[2] && alphaMask == X8B8G8R8_MASKS[3]) {
+				// X8B8G8R8
+				type = X8B8G8R8;
+			}
+			else if (redMask == A8R8G8B8_MASKS[0] && greenMask == A8R8G8B8_MASKS[1] && blueMask == A8R8G8B8_MASKS[2] && alphaMask == A8R8G8B8_MASKS[3]) {
+				// A8R8G8B8
+				type = A8R8G8B8;
+			}
+			else if (redMask == X8R8G8B8_MASKS[0] && greenMask == X8R8G8B8_MASKS[1] && blueMask == X8R8G8B8_MASKS[2] && alphaMask == X8R8G8B8_MASKS[3]) {
+				// X8R8G8B8
+				type = X8R8G8B8;
+			}
+			else {
+				// Unsupported 32bit RGB image
+			}
+		}
+	}
+	else {
+		// YUV or LUMINANCE image
+	}
+	return type;
+}
+
+int ddsGetDXTColor2_1(int c0, int c1, int a) {
+	// 2*c0/3 + c1/3
+	int r = (2 * BIT5[(c0 & 0xFC00) >> 11] + BIT5[(c1 & 0xFC00) >> 11]) / 3;
+	int g = (2 * BIT6[(c0 & 0x07E0) >> 5] + BIT6[(c1 & 0x07E0) >> 5]) / 3;
+	int b = (2 * BIT5[c0 & 0x001F] + BIT5[c1 & 0x001F]) / 3;
+	return (a << 24) | (r << 0) | (g << 8) | (b << 16);
+}
+
+int ddsGetDXTColor1_1(int c0, int c1, int a) {
+	// (c0+c1) / 2
+	int r = (BIT5[(c0 & 0xFC00) >> 11] + BIT5[(c1 & 0xFC00) >> 11]) / 2;
+	int g = (BIT6[(c0 & 0x07E0) >> 5] + BIT6[(c1 & 0x07E0) >> 5]) / 2;
+	int b = (BIT5[c0 & 0x001F] + BIT5[c1 & 0x001F]) / 2;
+	return (a << 24) | (r << 0) | (g << 8) | (b << 16);
+}
+
+int ddsGetDXTColor1(int c, int a) {
+	int r = BIT5[(c & 0xFC00) >> 11];
+	int g = BIT6[(c & 0x07E0) >> 5];
+	int b = BIT5[(c & 0x001F)];
+	return (a << 24) | (r << 0) | (g << 8) | (b << 16);
+}
+
+int ddsGetDXTColor(int c0, int c1, int a, int t) {
+	switch (t) {
+	case 0: return ddsGetDXTColor1(c0, a);
+	case 1: return ddsGetDXTColor1(c1, a);
+	case 2: return (c0 > c1) ? ddsGetDXTColor2_1(c0, c1, a) : ddsGetDXTColor1_1(c0, c1, a);
+	case 3: return (c0 > c1) ? ddsGetDXTColor2_1(c1, c0, a) : 0;
+	}
+	return 0;
+}
+
+guchar *ddsDecodeDXT1(int width, int height, const unsigned char *buffer) {
+	int *pixels = g_try_malloc(4 * width*height);
+	int index = 128;
+	int w = (width + 3) / 4;
+	int h = (height + 3) / 4;
+	for (int i = 0; i<h; i++) {
+		for (int j = 0; j<w; j++) {
+			int c0 = (buffer[index] & 0xFF) | (buffer[index + 1] & 0xFF) << 8; index += 2;
+			int c1 = (buffer[index] & 0xFF) | (buffer[index + 1] & 0xFF) << 8; index += 2;
+			for (int k = 0; k<4; k++) {
+				if (4 * i + k >= height) break;
+				int t0 = (buffer[index] & 0x03);
+				int t1 = (buffer[index] & 0x0C) >> 2;
+				int t2 = (buffer[index] & 0x30) >> 4;
+				int t3 = (buffer[index++] & 0xC0) >> 6;
+				pixels[4 * width*i + 4 * j + width*k + 0] = ddsGetDXTColor(c0, c1, 0xFF, t0);
+				if (4 * j + 1 >= width) continue;
+				pixels[4 * width*i + 4 * j + width*k + 1] = ddsGetDXTColor(c0, c1, 0xFF, t1);
+				if (4 * j + 2 >= width) continue;
+				pixels[4 * width*i + 4 * j + width*k + 2] = ddsGetDXTColor(c0, c1, 0xFF, t2);
+				if (4 * j + 3 >= width) continue;
+				pixels[4 * width*i + 4 * j + width*k + 3] = ddsGetDXTColor(c0, c1, 0xFF, t3);
+			}
+		}
+	}
+	return (guchar *) pixels;
+}
+
+guchar *ddsDecodeDXT3(int width, int height, const unsigned char *buffer) {
+	int *pixels = g_try_malloc(4 * width*height);
+	int index = 128;
+	int w = (width + 3) / 4;
+	int h = (height + 3) / 4;
+	int alphaTable[16];
+	for (int i = 0; i<h; i++) {
+		for (int j = 0; j<w; j++) {
+			// create alpha table(4bit to 8bit)
+			for (int k = 0; k<4; k++) {
+				int a0 = (buffer[index++] & 0xFF);
+				int a1 = (buffer[index++] & 0xFF);
+				// 4bit alpha to 8bit alpha
+				alphaTable[4 * k + 0] = 17 * ((a0 & 0xF0) >> 4);
+				alphaTable[4 * k + 1] = 17 * (a0 & 0x0F);
+				alphaTable[4 * k + 2] = 17 * ((a1 & 0xF0) >> 4);
+				alphaTable[4 * k + 3] = 17 * (a1 & 0x0F);
+			}
+			int c0 = (buffer[index] & 0xFF) | (buffer[index + 1] & 0xFF) << 8; index += 2;
+			int c1 = (buffer[index] & 0xFF) | (buffer[index + 1] & 0xFF) << 8; index += 2;
+			for (int k = 0; k<4; k++) {
+				if (4 * i + k >= height) break;
+				int t0 = (buffer[index] & 0x03);
+				int t1 = (buffer[index] & 0x0C) >> 2;
+				int t2 = (buffer[index] & 0x30) >> 4;
+				int t3 = (buffer[index++] & 0xC0) >> 6;
+				pixels[4 * width*i + 4 * j + width*k + 0] = ddsGetDXTColor(c0, c1, alphaTable[4 * k + 0], t0);
+				if (4 * j + 1 >= width) continue;
+				pixels[4 * width*i + 4 * j + width*k + 1] = ddsGetDXTColor(c0, c1, alphaTable[4 * k + 1], t1);
+				if (4 * j + 2 >= width) continue;
+				pixels[4 * width*i + 4 * j + width*k + 2] = ddsGetDXTColor(c0, c1, alphaTable[4 * k + 2], t2);
+				if (4 * j + 3 >= width) continue;
+				pixels[4 * width*i + 4 * j + width*k + 3] = ddsGetDXTColor(c0, c1, alphaTable[4 * k + 3], t3);
+			}
+		}
+	}
+	return (guchar *) pixels;
+}
+
+guchar *ddsDecodeDXT2(int width, int height, const unsigned char *buffer) {
+	return ddsDecodeDXT3(width, height, buffer);
+}
+
+int ddsGetDXT5Alpha(int a0, int a1, int t) {
+	if (a0 > a1) switch (t) {
+	case 0: return a0;
+	case 1: return a1;
+	case 2: return (6 * a0 + a1) / 7;
+	case 3: return (5 * a0 + 2 * a1) / 7;
+	case 4: return (4 * a0 + 3 * a1) / 7;
+	case 5: return (3 * a0 + 4 * a1) / 7;
+	case 6: return (2 * a0 + 5 * a1) / 7;
+	case 7: return (a0 + 6 * a1) / 7;
+	}
+	else switch (t) {
+	case 0: return a0;
+	case 1: return a1;
+	case 2: return (4 * a0 + a1) / 5;
+	case 3: return (3 * a0 + 2 * a1) / 5;
+	case 4: return (2 * a0 + 3 * a1) / 5;
+	case 5: return (a0 + 4 * a1) / 5;
+	case 6: return 0;
+	case 7: return 255;
+	}
+	return 0;
+}
+
+guchar *ddsDecodeDXT5(int width, int height, const unsigned char *buffer) {
+	int *pixels = g_try_malloc(4 * width*height);
+	int index = 128;
+	int w = (width + 3) / 4;
+	int h = (height + 3) / 4;
+	int alphaTable[16];
+	for (int i = 0; i<h; i++) {
+		for (int j = 0; j<w; j++) {
+			// create alpha table
+			int a0 = (buffer[index++] & 0xFF);
+			int a1 = (buffer[index++] & 0xFF);
+			int b0 = (buffer[index] & 0xFF) | (buffer[index + 1] & 0xFF) << 8 | (buffer[index + 2] & 0xFF) << 16; index += 3;
+			int b1 = (buffer[index] & 0xFF) | (buffer[index + 1] & 0xFF) << 8 | (buffer[index + 2] & 0xFF) << 16; index += 3;
+			alphaTable[0] = b0 & 0x07;
+			alphaTable[1] = (b0 >> 3) & 0x07;
+			alphaTable[2] = (b0 >> 6) & 0x07;
+			alphaTable[3] = (b0 >> 9) & 0x07;
+			alphaTable[4] = (b0 >> 12) & 0x07;
+			alphaTable[5] = (b0 >> 15) & 0x07;
+			alphaTable[6] = (b0 >> 18) & 0x07;
+			alphaTable[7] = (b0 >> 21) & 0x07;
+			alphaTable[8] = b1 & 0x07;
+			alphaTable[9] = (b1 >> 3) & 0x07;
+			alphaTable[10] = (b1 >> 6) & 0x07;
+			alphaTable[11] = (b1 >> 9) & 0x07;
+			alphaTable[12] = (b1 >> 12) & 0x07;
+			alphaTable[13] = (b1 >> 15) & 0x07;
+			alphaTable[14] = (b1 >> 18) & 0x07;
+			alphaTable[15] = (b1 >> 21) & 0x07;
+			int c0 = (buffer[index] & 0xFF) | (buffer[index + 1] & 0xFF) << 8; index += 2;
+			int c1 = (buffer[index] & 0xFF) | (buffer[index + 1] & 0xFF) << 8; index += 2;
+			for (int k = 0; k<4; k++) {
+				if (4 * i + k >= height) break;
+				int t0 = (buffer[index] & 0x03);
+				int t1 = (buffer[index] & 0x0C) >> 2;
+				int t2 = (buffer[index] & 0x30) >> 4;
+				int t3 = (buffer[index++] & 0xC0) >> 6;
+				pixels[4 * width*i + 4 * j + width*k + 0] = ddsGetDXTColor(c0, c1, ddsGetDXT5Alpha(a0, a1, alphaTable[4 * k + 0]), t0);
+				if (4 * j + 1 >= width) continue;
+				pixels[4 * width*i + 4 * j + width*k + 1] = ddsGetDXTColor(c0, c1, ddsGetDXT5Alpha(a0, a1, alphaTable[4 * k + 1]), t1);
+				if (4 * j + 2 >= width) continue;
+				pixels[4 * width*i + 4 * j + width*k + 2] = ddsGetDXTColor(c0, c1, ddsGetDXT5Alpha(a0, a1, alphaTable[4 * k + 2]), t2);
+				if (4 * j + 3 >= width) continue;
+				pixels[4 * width*i + 4 * j + width*k + 3] = ddsGetDXTColor(c0, c1, ddsGetDXT5Alpha(a0, a1, alphaTable[4 * k + 3]), t3);
+			}
+		}
+	}
+	return (guchar *) pixels;
+}
+
+guchar *ddsDecodeDXT4(int width, int height, const unsigned char *buffer) {
+	return ddsDecodeDXT5(width, height, buffer);
+}
+
+guchar *ddsReadA1R5G5B5(int width, int height, const unsigned char *buffer) {
+	int index = 128;
+	int *pixels = g_try_malloc(4*width*height);
+	for (int i = 0; i<height*width; i++) {
+		int rgba = (buffer[index] & 0xFF) | (buffer[index + 1] & 0xFF) << 8; index += 2;
+		int r = BIT5[(rgba & A1R5G5B5_MASKS[0]) >> 10];
+		int g = BIT5[(rgba & A1R5G5B5_MASKS[1]) >> 5];
+		int b = BIT5[(rgba & A1R5G5B5_MASKS[2])];
+		int a = 255 * ((rgba & A1R5G5B5_MASKS[3]) >> 15);
+		pixels[i] = (a << 24) | (r << 0) | (g << 8) | (b << 16);
+	}
+	return (guchar *) pixels;
+}
+
+guchar *ddsReadX1R5G5B5(int width, int height, const unsigned char *buffer) {
+	int index = 128;
+	int *pixels = g_try_malloc(4 * width*height);
+	for (int i = 0; i<height*width; i++) {
+		int rgba = (buffer[index] & 0xFF) | (buffer[index + 1] & 0xFF) << 8; index += 2;
+		int r = BIT5[(rgba & X1R5G5B5_MASKS[0]) >> 10];
+		int g = BIT5[(rgba & X1R5G5B5_MASKS[1]) >> 5];
+		int b = BIT5[(rgba & X1R5G5B5_MASKS[2])];
+		int a = 255;
+		pixels[i] = (a << 24) | (r << 0) | (g << 8) | (b << 16);
+	}
+	return (guchar *) pixels;
+}
+
+guchar *ddsReadA4R4G4B4(int width, int height, const unsigned char *buffer) {
+	int index = 128;
+	int *pixels = g_try_malloc(4 * width*height);
+	for (int i = 0; i<height*width; i++) {
+		int rgba = (buffer[index] & 0xFF) | (buffer[index + 1] & 0xFF) << 8; index += 2;
+		int r = 17 * ((rgba & A4R4G4B4_MASKS[0]) >> 8);
+		int g = 17 * ((rgba & A4R4G4B4_MASKS[1]) >> 4);
+		int b = 17 * ((rgba & A4R4G4B4_MASKS[2]));
+		int a = 17 * ((rgba & A4R4G4B4_MASKS[3]) >> 12);
+		pixels[i] = (a << 24) | (r << 0) | (g << 8) | (b << 16);
+	}
+	return (guchar *) pixels;
+}
+
+guchar *ddsReadX4R4G4B4(int width, int height, const unsigned char *buffer) {
+	int index = 128;
+	int *pixels = g_try_malloc(4 * width*height);
+	for (int i = 0; i<height*width; i++) {
+		int rgba = (buffer[index] & 0xFF) | (buffer[index + 1] & 0xFF) << 8; index += 2;
+		int r = 17 * ((rgba & A4R4G4B4_MASKS[0]) >> 8);
+		int g = 17 * ((rgba & A4R4G4B4_MASKS[1]) >> 4);
+		int b = 17 * ((rgba & A4R4G4B4_MASKS[2]));
+		int a = 255;
+		pixels[i] = (a << 24) | (r << 0) | (g << 8) | (b << 16);
+	}
+	return (guchar *) pixels;
+}
+
+guchar *ddsReadR5G6B5(int width, int height, const unsigned char *buffer) {
+	int index = 128;
+	int *pixels = g_try_malloc(4 * width*height);
+	for (int i = 0; i<height*width; i++) {
+		int rgba = (buffer[index] & 0xFF) | (buffer[index + 1] & 0xFF) << 8; index += 2;
+		int r = BIT5[((rgba & R5G6B5_MASKS[0]) >> 11)];
+		int g = BIT6[((rgba & R5G6B5_MASKS[1]) >> 5)];
+		int b = BIT5[((rgba & R5G6B5_MASKS[2]))];
+		int a = 255;
+		pixels[i] = (a << 24) | (r << 0) | (g << 8) | (b << 16);
+	}
+	return (guchar *) pixels;
+}
+
+guchar *ddsReadR8G8B8(int width, int height, const unsigned char *buffer) {
+	int index = 128;
+	int *pixels = g_try_malloc(4 * width*height);
+	for (int i = 0; i<height*width; i++) {
+		int b = buffer[index++] & 0xFF;
+		int g = buffer[index++] & 0xFF;
+		int r = buffer[index++] & 0xFF;
+		int a = 255;
+		pixels[i] = (a << 24) | (r << 0) | (g << 8) | (b << 16);
+	}
+	return (guchar *) pixels;
+}
+
+guchar *ddsReadA8B8G8R8(int width, int height, const unsigned char *buffer) {
+	int index = 128;
+	int *pixels = g_try_malloc(4 * width*height);
+	for (int i = 0; i<height*width; i++) {
+		int r = buffer[index++] & 0xFF;
+		int g = buffer[index++] & 0xFF;
+		int b = buffer[index++] & 0xFF;
+		int a = buffer[index++] & 0xFF;
+		pixels[i] = (a << 24) | (r << 0) | (g << 8) | (b << 16);
+	}
+	return (guchar *) pixels;
+}
+
+guchar *ddsReadX8B8G8R8(int width, int height, const unsigned char *buffer) {
+	int index = 128;
+	int *pixels = g_try_malloc(4 * width*height);
+	for (int i = 0; i<height*width; i++) {
+		int r = buffer[index++] & 0xFF;
+		int g = buffer[index++] & 0xFF;
+		int b = buffer[index++] & 0xFF;
+		int a = 255; index++;
+		pixels[i] = (a << 24) | (r << 0) | (g << 8) | (b << 16);
+	}
+	return (guchar *) pixels;
+}
+
+guchar *ddsReadA8R8G8B8(int width, int height, const unsigned char *buffer) {
+	int index = 128;
+	int *pixels = g_try_malloc(4 * width*height);
+	for (int i = 0; i<height*width; i++) {
+		int b = buffer[index++] & 0xFF;
+		int g = buffer[index++] & 0xFF;
+		int r = buffer[index++] & 0xFF;
+		int a = buffer[index++] & 0xFF;
+		pixels[i] = (a << 24) | (r << 0) | (g << 8) | (b << 16);
+	}
+	return (guchar *) pixels;
+}
+
+guchar *ddsReadX8R8G8B8(int width, int height, const unsigned char *buffer) {
+	int index = 128;
+	int *pixels = g_try_malloc(4 * width*height);
+	for (int i = 0; i<height*width; i++) {
+		int b = buffer[index++] & 0xFF;
+		int g = buffer[index++] & 0xFF;
+		int r = buffer[index++] & 0xFF;
+		int a = 255; index++;
+		pixels[i] = (a << 24) | (r << 0) | (g << 8) | (b << 16);
+	}
+	return (guchar *) pixels;
+}
+
+static gboolean image_loader_dds_load (gpointer loader, const guchar *buf, gsize count, GError **error)
+{
+	ImageLoaderDDS *ld = (ImageLoaderDDS *) loader;
+	int width = ddsGetWidth(buf);
+	int height = ddsGetHeight(buf);
+	int type = ddsGetType(buf);
+	if (type == 0) return FALSE;
+	{
+		guchar *pixels = NULL;
+		gint rowstride = width * 4;
+		switch (type) {
+		case DXT1: pixels = ddsDecodeDXT1(width, height, buf); break;
+		case DXT2: pixels = ddsDecodeDXT2(width, height, buf); break;
+		case DXT3: pixels = ddsDecodeDXT3(width, height, buf); break;
+		case DXT4: pixels = ddsDecodeDXT4(width, height, buf); break;
+		case DXT5: pixels = ddsDecodeDXT5(width, height, buf); break;
+		case A1R5G5B5: pixels = ddsReadA1R5G5B5(width, height, buf); break;
+		case X1R5G5B5: pixels = ddsReadX1R5G5B5(width, height, buf); break;
+		case A4R4G4B4: pixels = ddsReadA4R4G4B4(width, height, buf); break;
+		case X4R4G4B4: pixels = ddsReadX4R4G4B4(width, height, buf); break;
+		case R5G6B5: pixels = ddsReadR5G6B5(width, height, buf); break;
+		case R8G8B8: pixels = ddsReadR8G8B8(width, height, buf); break;
+		case A8B8G8R8: pixels = ddsReadA8B8G8R8(width, height, buf); break;
+		case X8B8G8R8: pixels = ddsReadX8B8G8R8(width, height, buf); break;
+		case A8R8G8B8: pixels = ddsReadA8R8G8B8(width, height, buf); break;
+		case X8R8G8B8: pixels = ddsReadX8R8G8B8(width, height, buf); break;
+		}
+		ld->pixbuf = gdk_pixbuf_new_from_data (pixels, GDK_COLORSPACE_RGB, TRUE, 8, width, height, rowstride, free_buffer, NULL);
+		ld->area_updated_cb(loader, 0, 0, width, height, ld->data);
+		return TRUE;
+	}
+}
+
+static gpointer image_loader_dds_new(ImageLoaderBackendCbAreaUpdated area_updated_cb, ImageLoaderBackendCbSize size_cb, ImageLoaderBackendCbAreaPrepared area_prepared_cb, gpointer data)
+{
+	ImageLoaderDDS *loader = g_new0(ImageLoaderDDS, 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_dds_set_size(gpointer loader, int width, int height)
+{
+	ImageLoaderDDS *ld = (ImageLoaderDDS *) loader;
+	ld->requested_width = width;
+	ld->requested_height = height;
+}
+
+static GdkPixbuf* image_loader_dds_get_pixbuf(gpointer loader)
+{
+	ImageLoaderDDS *ld = (ImageLoaderDDS *) loader;
+	return ld->pixbuf;
+}
+
+static gchar* image_loader_dds_get_format_name(gpointer loader)
+{
+	return g_strdup("dds");
+}
+static gchar** image_loader_dds_get_format_mime_types(gpointer loader)
+{
+	static gchar *mime[] = {"image/vnd-ms.dds", NULL};
+	return g_strdupv(mime);
+}
+
+static gboolean image_loader_dds_close(gpointer loader, GError **error)
+{
+	return TRUE;
+}
+
+static void image_loader_dds_abort(gpointer loader)
+{
+	ImageLoaderDDS *ld = (ImageLoaderDDS *) loader;
+	ld->abort = TRUE;
+}
+
+static void image_loader_dds_free(gpointer loader)
+{
+	ImageLoaderDDS *ld = (ImageLoaderDDS *) loader;
+	if (ld->pixbuf) g_object_unref(ld->pixbuf);
+	g_free(ld);
+}
+
+void image_loader_backend_set_dds(ImageLoaderBackend *funcs)
+{
+	funcs->loader_new = image_loader_dds_new;
+	funcs->set_size = image_loader_dds_set_size;
+	funcs->load = image_loader_dds_load;
+	funcs->write = NULL;
+	funcs->get_pixbuf = image_loader_dds_get_pixbuf;
+	funcs->close = image_loader_dds_close;
+	funcs->abort = image_loader_dds_abort;
+	funcs->free = image_loader_dds_free;
+	funcs->get_format_name = image_loader_dds_get_format_name;
+	funcs->get_format_mime_types = image_loader_dds_get_format_mime_types;
+}
+
+/* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/image_load_dds.h	Fri Apr 13 19:06:23 2018 +0300
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2018 The Geeqie Team
+ *
+ * Author: Wolfgang Lieff
+ *
+ * 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_DDS_H
+#define IMAGE_LOAD_DDS_H
+
+void image_loader_backend_set_dds(ImageLoaderBackend *funcs);
+
+#endif
+
+/* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */
--- a/src/layout_util.c	Sun Mar 04 19:01:45 2018 +0200
+++ b/src/layout_util.c	Fri Apr 13 19:06:23 2018 +0300
@@ -171,6 +171,11 @@
 
 	if (x != 0 || y!= 0)
 		{
+		if (event->state & GDK_SHIFT_MASK)
+			{
+			x *= 3;
+			y *= 3;
+			}
 		keyboard_scroll_calc(&x, &y, event);
 		layout_image_scroll(lw, x, y, (event->state & GDK_SHIFT_MASK));
 		}
@@ -202,7 +207,7 @@
 	layout_image_full_screen_stop(lw);
 }
 
-static void layout_menu_new_window_cb(GtkAction *action, gpointer data)
+LayoutWindow *layout_menu_new_window(GtkAction *action, gpointer data)
 {
 	LayoutWindow *lw = data;
 	LayoutWindow *nw;
@@ -222,6 +227,13 @@
 	layout_sort_set(nw, options->file_sort.method, options->file_sort.ascending);
 	layout_set_fd(nw, lw->dir_fd);
 	options->save_window_positions = tmp;
+
+	return nw;
+}
+
+static void layout_menu_new_window_cb(GtkAction *action, gpointer data)
+{
+	layout_menu_new_window(action, data);
 }
 
 static void layout_menu_new_cb(GtkAction *action, gpointer data)
@@ -344,7 +356,7 @@
 	file_data_disable_grouping_list(layout_selection_list(lw), FALSE);
 }
 
-static void layout_menu_close_cb(GtkAction *action, gpointer data)
+void layout_menu_close_cb(GtkAction *action, gpointer data)
 {
 	LayoutWindow *lw = data;
 
--- a/src/layout_util.h	Sun Mar 04 19:01:45 2018 +0200
+++ b/src/layout_util.h	Fri Apr 13 19:06:23 2018 +0300
@@ -72,6 +72,7 @@
 void layout_exif_window_new(LayoutWindow *lw);
 
 gboolean is_help_key(GdkEventKey *event);
-
+LayoutWindow *layout_menu_new_window(GtkAction *action, gpointer data);
+void layout_menu_close_cb(GtkAction *action, gpointer data);
 #endif
 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */
--- a/src/main.c	Sun Mar 04 19:01:45 2018 +0200
+++ b/src/main.c	Fri Apr 13 19:06:23 2018 +0300
@@ -221,6 +221,7 @@
 	GList *remote_errors = NULL;
 	gboolean remote_do = FALSE;
 	gchar *first_dir = NULL;
+	gchar *app_lock;
 
 	command_line = g_new0(CommandLine, 1);
 
@@ -412,6 +413,17 @@
 		}
 	g_free(first_dir);
 
+	/* If Geeqie is already running, prevent a second instance
+	 * from being started. Open a new window instead.
+	 */
+	app_lock = g_build_filename(get_rc_dir(), ".command", NULL);
+	if (remote_server_exists(app_lock) && !remote_do)
+		{
+		remote_do = TRUE;
+		remote_list = g_list_append(remote_list, "--new-window");
+	}
+	g_free(app_lock);
+
 	if (remote_do)
 		{
 		if (remote_errors)
@@ -782,6 +794,7 @@
 	CollectionData *first_collection = NULL;
 	gchar *buf;
 	CollectionData *cd = NULL;
+	gchar *app_lock;
 
 #ifdef HAVE_GTHREAD
 #if !GLIB_CHECK_VERSION(2,32,0)
--- a/src/options.c	Sun Mar 04 19:01:45 2018 +0200
+++ b/src/options.c	Fri Apr 13 19:06:23 2018 +0300
@@ -60,6 +60,7 @@
 	options->use_saved_window_positions_for_new_windows = FALSE;
 	options->tools_restore_state = TRUE;
 	options->save_dialog_window_positions = FALSE;
+	options->show_window_ids = FALSE;
 
 	options->file_ops.confirm_delete = TRUE;
 	options->file_ops.enable_delete_key = TRUE;
--- a/src/options.h	Sun Mar 04 19:01:45 2018 +0200
+++ b/src/options.h	Fri Apr 13 19:06:23 2018 +0300
@@ -57,6 +57,7 @@
 	gboolean use_saved_window_positions_for_new_windows;
 	gboolean tools_restore_state;
 	gboolean save_dialog_window_positions;
+	gboolean show_window_ids;
 
 	gint log_window_lines;
 
--- a/src/preferences.c	Sun Mar 04 19:01:45 2018 +0200
+++ b/src/preferences.c	Fri Apr 13 19:06:23 2018 +0300
@@ -256,6 +256,7 @@
 	options->save_window_positions = c_options->save_window_positions;
 	options->use_saved_window_positions_for_new_windows = c_options->use_saved_window_positions_for_new_windows;
 	options->save_dialog_window_positions = c_options->save_dialog_window_positions;
+	options->show_window_ids = c_options->show_window_ids;
 	options->image.scroll_reset_method = c_options->image.scroll_reset_method;
 	options->image.zoom_2pass = c_options->image.zoom_2pass;
 	options->image.fit_window_to_image = c_options->image.fit_window_to_image;
@@ -1806,6 +1807,9 @@
 	pref_checkbox_new_int(group, _("Remember dialog window positions"),
 			      options->save_dialog_window_positions, &c_options->save_dialog_window_positions);
 
+	pref_checkbox_new_int(group, _("Show window IDs"),
+			      options->show_window_ids, &c_options->show_window_ids);
+
 	group = pref_group_new(vbox, FALSE, _("Size"), GTK_ORIENTATION_VERTICAL);
 
 	pref_checkbox_new_int(group, _("Fit window to image when tools are hidden/floating"),
--- a/src/rcfile.c	Sun Mar 04 19:01:45 2018 +0200
+++ b/src/rcfile.c	Fri Apr 13 19:06:23 2018 +0300
@@ -338,6 +338,7 @@
 	WRITE_NL(); WRITE_BOOL(*options, use_saved_window_positions_for_new_windows);
 	WRITE_NL(); WRITE_BOOL(*options, tools_restore_state);
 	WRITE_NL(); WRITE_BOOL(*options, save_dialog_window_positions);
+	WRITE_NL(); WRITE_BOOL(*options, show_window_ids);
 
 	WRITE_NL(); WRITE_UINT(*options, log_window_lines);
 	WRITE_NL(); WRITE_BOOL(*options, log_window.timer_data);
@@ -641,6 +642,7 @@
 		if (READ_BOOL(*options, use_saved_window_positions_for_new_windows)) continue;
 		if (READ_BOOL(*options, tools_restore_state)) continue;
 		if (READ_BOOL(*options, save_dialog_window_positions)) continue;
+		if (READ_BOOL(*options, show_window_ids)) continue;
 
 		if (READ_INT(*options, log_window_lines)) continue;
 		if (READ_BOOL(*options, log_window.timer_data)) continue;
--- a/src/remote.c	Sun Mar 04 19:01:45 2018 +0200
+++ b/src/remote.c	Fri Apr 13 19:06:23 2018 +0300
@@ -29,6 +29,7 @@
 #include "img-view.h"
 #include "layout.h"
 #include "layout_image.h"
+#include "layout_util.h"
 #include "misc.h"
 #include "pixbuf-renderer.h"
 #include "slideshow.h"
@@ -56,6 +57,7 @@
 static gint remote_client_send(RemoteConnection *rc, const gchar *text);
 static void gr_raise(const gchar *text, GIOChannel *channel, gpointer data);
 
+static LayoutWindow *lw_id = NULL; /* points to the window set by the --id option */
 
 typedef struct _RemoteClient RemoteClient;
 struct _RemoteClient {
@@ -76,6 +78,7 @@
 	RemoteConnection *rc;
 	GIOStatus status = G_IO_STATUS_NORMAL;
 
+	lw_id = NULL;
 	rc = client->rc;
 
 	if (condition & G_IO_IN)
@@ -181,7 +184,7 @@
 	return TRUE;
 }
 
-static gboolean remote_server_exists(const gchar *path)
+gboolean remote_server_exists(const gchar *path)
 {
 	RemoteConnection *rc;
 
@@ -388,37 +391,70 @@
 
 static void gr_image_next(const gchar *text, GIOChannel *channel, gpointer data)
 {
-	layout_image_next(NULL);
+	layout_image_next(lw_id);
+}
+
+static void gr_new_window(const gchar *text, GIOChannel *channel, gpointer data)
+{
+	LayoutWindow *lw = NULL;
+
+	if (!layout_valid(&lw)) return;
+
+	lw_id = layout_menu_new_window(NULL, lw);
+}
+
+static gboolean gr_close_window_cb()
+{
+	if (!layout_valid(&lw_id)) return FALSE;
+
+	layout_menu_close_cb(NULL, lw_id);
+
+	return FALSE;
+}
+
+static void gr_close_window(const gchar *text, GIOChannel *channel, gpointer data)
+{
+	g_idle_add(gr_close_window_cb, NULL);
 }
 
 static void gr_image_prev(const gchar *text, GIOChannel *channel, gpointer data)
 {
-	layout_image_prev(NULL);
+	layout_image_prev(lw_id);
 }
 
 static void gr_image_first(const gchar *text, GIOChannel *channel, gpointer data)
 {
-	layout_image_first(NULL);
+	layout_image_first(lw_id);
 }
 
 static void gr_image_last(const gchar *text, GIOChannel *channel, gpointer data)
 {
-	layout_image_last(NULL);
+	layout_image_last(lw_id);
 }
 
 static void gr_fullscreen_toggle(const gchar *text, GIOChannel *channel, gpointer data)
 {
-	layout_image_full_screen_toggle(NULL);
+	layout_image_full_screen_toggle(lw_id);
 }
 
 static void gr_fullscreen_start(const gchar *text, GIOChannel *channel, gpointer data)
 {
-	layout_image_full_screen_start(NULL);
+	layout_image_full_screen_start(lw_id);
 }
 
 static void gr_fullscreen_stop(const gchar *text, GIOChannel *channel, gpointer data)
 {
-	layout_image_full_screen_stop(NULL);
+	layout_image_full_screen_stop(lw_id);
+}
+
+static void gr_lw_id(const gchar *text, GIOChannel *channel, gpointer data)
+{
+	lw_id = layout_find_by_layout_id(text);
+	if (!lw_id)
+		{
+		log_printf("remote sent window ID that does not exist:\"%s\"\n",text);
+		}
+	layout_valid(&lw_id);
 }
 
 static void gr_slideshow_start_rec(const gchar *text, GIOChannel *channel, gpointer data)
@@ -429,8 +465,8 @@
 	file_data_unref(dir_fd);
 	if (!list) return;
 //printf("length: %d\n", g_list_length(list));
-	layout_image_slideshow_stop(NULL);
-	layout_image_slideshow_start_from_list(NULL, list);
+	layout_image_slideshow_stop(lw_id);
+	layout_image_slideshow_start_from_list(lw_id, list);
 }
 
 static void gr_cache_thumb(const gchar *text, GIOChannel *channel, gpointer data)
@@ -478,17 +514,17 @@
 
 static void gr_slideshow_toggle(const gchar *text, GIOChannel *channel, gpointer data)
 {
-	layout_image_slideshow_toggle(NULL);
+	layout_image_slideshow_toggle(lw_id);
 }
 
 static void gr_slideshow_start(const gchar *text, GIOChannel *channel, gpointer data)
 {
-	layout_image_slideshow_start(NULL);
+	layout_image_slideshow_start(lw_id);
 }
 
 static void gr_slideshow_stop(const gchar *text, GIOChannel *channel, gpointer data)
 {
-	layout_image_slideshow_stop(NULL);
+	layout_image_slideshow_stop(lw_id);
 }
 
 static void gr_slideshow_delay(const gchar *text, GIOChannel *channel, gpointer data)
@@ -542,9 +578,9 @@
 	gboolean popped;
 	gboolean hidden;
 
-	if (layout_tools_float_get(NULL, &popped, &hidden) && hidden)
+	if (layout_tools_float_get(lw_id, &popped, &hidden) && hidden)
 		{
-		layout_tools_float_set(NULL, popped, FALSE);
+		layout_tools_float_set(lw_id, popped, FALSE);
 		}
 }
 
@@ -553,9 +589,9 @@
 	gboolean popped;
 	gboolean hidden;
 
-	if (layout_tools_float_get(NULL, &popped, &hidden) && !hidden)
+	if (layout_tools_float_get(lw_id, &popped, &hidden) && !hidden)
 		{
-		layout_tools_float_set(NULL, popped, TRUE);
+		layout_tools_float_set(lw_id, popped, TRUE);
 		}
 }
 
@@ -586,17 +622,17 @@
 			}
 		else
 			{
-			layout_set_path(NULL, filename);
+			layout_set_path(lw_id, filename);
 			}
 		}
 	else if (isdir(filename))
 		{
-		layout_set_path(NULL, filename);
+		layout_set_path(lw_id, filename);
 		}
 	else
 		{
 		log_printf("remote sent filename that does not exist:\"%s\"\n", filename);
-		layout_set_path(NULL, homedir());
+		layout_set_path(lw_id, homedir());
 		}
 
 	g_free(filename);
@@ -618,9 +654,9 @@
 	PixbufRenderer *pr;
 	LayoutWindow *lw = NULL;
 
-	if (!layout_valid(&lw)) return;
+	if (!layout_valid(&lw_id)) return;
 
-	pr = (PixbufRenderer*)lw->image->pr;
+	pr = (PixbufRenderer*)lw_id->image->pr;
 
 	if (pr)
 		{
@@ -656,11 +692,11 @@
 
 static void gr_file_tell(const gchar *text, GIOChannel *channel, gpointer data)
 {
-	LayoutWindow *lw = NULL; /* NULL to force layout_valid() to do some magic */
-	if (!layout_valid(&lw)) return;
-	if (image_get_path(lw->image))
+	if (!layout_valid(&lw_id)) return;
+
+	if (image_get_path(lw_id->image))
 		{
-		g_io_channel_write_chars(channel, image_get_path(lw->image), -1, NULL, NULL);
+		g_io_channel_write_chars(channel, image_get_path(lw_id->image), -1, NULL, NULL);
 		g_io_channel_write_chars(channel, "\n", -1, NULL, NULL);
 		}
 }
@@ -771,9 +807,9 @@
 {
 	LayoutWindow *lw = NULL;
 
-	if (layout_valid(&lw))
+	if (layout_valid(&lw_id))
 		{
-		gtk_window_present(GTK_WINDOW(lw->window));
+		gtk_window_present(GTK_WINDOW(lw_id->window));
 		}
 }
 
@@ -849,6 +885,9 @@
 	{ NULL, "--list-clear",         gr_list_clear,          FALSE, FALSE, NULL, N_("clear command line collection list") },
 	{ NULL, "--list-add:",          gr_list_add,            TRUE,  FALSE, N_("<FILE>"), N_("add FILE to command line collection list") },
 	{ NULL, "raise",                gr_raise,               FALSE, FALSE, NULL, N_("bring the Geeqie window to the top") },
+	{ NULL, "--id:",                gr_lw_id,               TRUE, FALSE, N_("<ID>"), N_("window id for following commands") },
+	{ NULL, "--new-window",         gr_new_window,          FALSE, FALSE, NULL, N_("new window") },
+	{ NULL, "--close-window",       gr_close_window,        FALSE, FALSE, NULL, N_("close window") },
 	{ "-ct:", "--cache-thumbs:",    gr_cache_thumb,         TRUE, FALSE, N_("clear|clean"), N_("clear or clean thumbnail cache") },
 	{ "-cs:", "--cache-shared:",    gr_cache_shared,        TRUE, FALSE, N_("clear|clean"), N_("clear or clean shared thumbnail cache") },
 	{ "-cm","--cache-metadata",      gr_cache_metadata,               FALSE, FALSE, NULL, N_("    clean the metadata cache") },
--- a/src/remote.h	Sun Mar 04 19:01:45 2018 +0200
+++ b/src/remote.h	Fri Apr 13 19:06:23 2018 +0300
@@ -47,6 +47,7 @@
 		    GList *cmd_list, GList *collection_list);
 
 RemoteConnection *remote_server_init(gchar *path, CollectionData *command_collection);
+gboolean remote_server_exists(const gchar *path);
 
 
 #endif
--- a/src/utilops.c	Sun Mar 04 19:01:45 2018 +0200
+++ b/src/utilops.c	Fri Apr 13 19:06:23 2018 +0300
@@ -1132,7 +1132,7 @@
 		}
 }
 
-static void file_util_fdlg_ok_cb(FileDialog *fdlg, gpointer data)
+static void file_util_fdlg_rename_cb(FileDialog *fdlg, gpointer data)
 {
 	UtilityData *ud = data;
 	gchar *desc = NULL;
@@ -1168,6 +1168,21 @@
 		}
 }
 
+static void file_util_fdlg_ok_cb(FileDialog *fdlg, gpointer data)
+{
+	UtilityData *ud = data;
+
+	file_util_dest_folder_update_path(ud);
+	if (isdir(ud->dest_path)) file_dialog_sync_history(fdlg, TRUE);
+	file_dialog_close(fdlg);
+
+	ud->fdlg = NULL;
+	ud->phase = UTILITY_PHASE_ENTERING;
+
+	file_util_dialog_run(ud);
+
+	return;
+}
 
 static void file_util_dest_folder_entry_cb(GtkWidget *entry, gpointer data)
 {
@@ -1569,6 +1584,7 @@
 	gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
 	pref_spacer(GENERIC_DIALOG(fdlg)->vbox, 0);
 
+	file_dialog_add_button(fdlg, GTK_STOCK_EDIT, "With Rename", file_util_fdlg_rename_cb, TRUE);
 	file_dialog_add_button(fdlg, stock_id, ud->messages.title, file_util_fdlg_ok_cb, TRUE);
 
 	file_dialog_add_path_widgets(fdlg, NULL, ud->dest_path, "move_copy", NULL, NULL);
--- a/web/help/GuideOptionsWindow.html	Sun Mar 04 19:01:45 2018 +0200
+++ b/web/help/GuideOptionsWindow.html	Fri Apr 13 19:06:23 2018 +0300
@@ -516,6 +516,19 @@
           <p class="para block block-first">This will maintain dialog windows size and position between Geeqie sessions.</p>
         </dd>
 </dl></div>
+<div class="block list variablelist"><dl class="variablelist">
+<dt class="term dt-first">
+          <span class="guilabel">Show window IDs</span>
+        </dt>
+<dd>
+          <p class="para block block-first">
+            Show the window ID in the titlebar of each window. When multiple Geeqie windows are opened, this option shows a unique identifier for each window. It may be used in conjunction with the command line option:
+            <a class="link" href="GuideReferenceCommandLine.html#Remotecommands" title="Remote commands">
+              <div dir="ltr" class=" block programlisting block-indent block-first"><pre class="programlisting">--remote --id:&lt;ID&gt;</pre></div>
+            </a>
+          </p>
+        </dd>
+</dl></div>
 </div>
 <div class="division section">
 <a name="Size"></a><div class="header"><h2 class="section title"><span class="title"><span class="label">11.3.2. </span>Size</span></h2></div>
--- a/web/help/GuideReferenceCommandLine.html	Sun Mar 04 19:01:45 2018 +0200
+++ b/web/help/GuideReferenceCommandLine.html	Fri Apr 13 19:06:23 2018 +0300
@@ -699,44 +699,62 @@
 <td class="td-rowsep">Bring the geeqie window to the top</td>
 </tr>
 <tr>
+<td class="td-colsep td-rowsep"></td>
+<td class="td-colsep td-rowsep">--id:&lt;ID&gt;</td>
+<td class="td-rowsep">
+              Window ID for following commands
+              <a name="-noteref-ref3"></a><sup><a class="footnote" href="#ref3">2</a></sup>
+            </td>
+</tr>
+<tr class="tr-shade">
+<td class="td-colsep td-rowsep"></td>
+<td class="td-colsep td-rowsep">--new-window</td>
+<td class="td-rowsep">Open new window</td>
+</tr>
+<tr>
+<td class="td-colsep td-rowsep"></td>
+<td class="td-colsep td-rowsep">--close-window</td>
+<td class="td-rowsep">Close window</td>
+</tr>
+<tr class="tr-shade">
 <td class="td-colsep td-rowsep">-ct:clear|clean</td>
 <td class="td-colsep td-rowsep">--cache-thumbs:clear|clean</td>
 <td class="td-rowsep">clear or clean thumbnail cache</td>
 </tr>
-<tr class="tr-shade">
+<tr>
 <td class="td-colsep td-rowsep">-cs:clear|clean</td>
 <td class="td-colsep td-rowsep">--cache-shared:clear|clean</td>
 <td class="td-rowsep">clear or clean shared thumbnail cache</td>
 </tr>
-<tr>
+<tr class="tr-shade">
 <td class="td-colsep td-rowsep">-cm</td>
 <td class="td-colsep td-rowsep">--cache-metadata</td>
 <td class="td-rowsep">clean the metadata cache</td>
 </tr>
-<tr class="tr-shade">
+<tr>
 <td class="td-colsep td-rowsep">-cr:&lt;folder&gt;</td>
 <td class="td-colsep td-rowsep">--cache-render:&lt;folder&gt;</td>
 <td class="td-rowsep">render thumbnails</td>
 </tr>
-<tr>
+<tr class="tr-shade">
 <td class="td-colsep td-rowsep">-crr:&lt;folder&gt;</td>
 <td class="td-colsep td-rowsep">--cache-render-recurse:&lt;folder&gt;</td>
 <td class="td-rowsep">render thumbnails recursively</td>
 </tr>
-<tr class="tr-shade">
+<tr>
 <td class="td-colsep td-rowsep">-crs:&lt;folder&gt;</td>
 <td class="td-colsep td-rowsep">--cache-render-shared:&lt;folder&gt;</td>
 <td class="td-rowsep">
               render thumbnails
-              <a name="-noteref-ref2"></a><sup><a class="footnote" href="#ref2">2</a></sup>
+              <a name="-noteref-ref2"></a><sup><a class="footnote" href="#ref2">3</a></sup>
             </td>
 </tr>
-<tr>
+<tr class="tr-shade">
 <td class="td-colsep td-rowsep">-crsr:&lt;folder&gt;</td>
 <td class="td-colsep td-rowsep">--cache-render-shared-recurse:&lt;folder&gt;</td>
 <td class="td-rowsep">render thumbnails recursively</td>
 </tr>
-<tr class="tr-shade">
+<tr>
 <td class="td-colsep"></td>
 <td class="td-colsep">--lua:&lt;file&gt;,&lt;lua script&gt;</td>
 <td>run lua script on file</td>
@@ -749,7 +767,11 @@
 <div class="footnote">
 <a name="ref1"></a><span class="footnote-number"><a class="footnote-ref" href="#-noteref-ref1">1</a></span>The name of a collection, with or without either path or extension (.gqv) may be used. If a path is not used and there is a name conflict with a file or folder, that will take precedence.</div>
 <div class="footnote">
-<a name="ref2"></a><span class="footnote-number"><a class="footnote-ref" href="#-noteref-ref2">2</a></span>
+<a name="ref3"></a><span class="footnote-number"><a class="footnote-ref" href="#-noteref-ref3">2</a></span>
+                <p class="para block block-first">The ID is shown in the titlebar of the window. If multiple windows are open, it can be used to direct commands to a particular window e.g. --remote --id:main --tell</p>
+              </div>
+<div class="footnote">
+<a name="ref2"></a><span class="footnote-number"><a class="footnote-ref" href="#-noteref-ref2">3</a></span>
                 <p class="para block block-first">If standard thumbnail cache is not enabled, this command will be ignored.</p>
               </div>
 </div>
--- a/web/help/GuideReferenceSupportedFormats.html	Sun Mar 04 19:01:45 2018 +0200
+++ b/web/help/GuideReferenceSupportedFormats.html	Fri Apr 13 19:06:23 2018 +0300
@@ -458,7 +458,7 @@
 <li class="linktrail linktrail-first"><a class="linktrail" href="GuideIndex.html" title="The Geeqie User Manual">The Geeqie User Manual</a></li>
 <li class="linktrail linktrail-last"><a class="linktrail" href="GuideReference.html" title="Reference">Reference</a></li>
 </ul>
-<p class="para block block-first">3FR, ANI, APM, ARW, BMP, CR2, CRW, CUR, 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. Animated GIFs are supported.</p>
+<p class="para block block-first">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. Animated GIFs are supported.</p>
 <p class="para block">
     Refer to
     <a class="link" href="GuideReferencePixbufLoaders.html" title="Additional pixbuf loaders">Additional pixbuf loaders</a>