PIKApp/plug-ins/file-dds/ddswrite.c

2014 lines
61 KiB
C
Raw Normal View History

2023-09-26 00:35:21 +02:00
/*
* DDS PIKA plugin
*
* Copyright (C) 2004-2012 Shawn Kirst <skirst@gmail.com>,
* with parts (C) 2003 Arne Reuter <homepage@arnereuter.de> where specified.
*
* 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; see the file COPYING. If not, write to
* the Free Software Foundation, 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301, USA.
*/
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <gtk/gtk.h>
#include <glib/gstdio.h>
#include <libpika/pika.h>
#include <libpika/pikaui.h>
#include <libpika/stdplugins-intl.h>
#include "dds.h"
2023-12-02 20:03:24 +01:00
#include "ddswrite.h"
2023-09-26 00:35:21 +02:00
#include "dxt.h"
#include "endian_rw.h"
#include "imath.h"
2023-12-02 20:03:24 +01:00
#include "mipmap.h"
#include "misc.h"
2023-09-26 00:35:21 +02:00
2023-10-30 23:55:30 +01:00
static gboolean write_image (FILE *fp,
PikaImage *image,
PikaDrawable *drawable,
PikaProcedureConfig *config);
static gboolean save_dialog (PikaImage *image,
PikaDrawable *drawable,
PikaProcedure *procedure,
PikaProcedureConfig *config);
2023-09-26 00:35:21 +02:00
2023-12-02 20:03:24 +01:00
static const gchar *cubemap_face_names[4][6] =
2023-09-26 00:35:21 +02:00
{
{
"positive x", "negative x",
"positive y", "negative y",
"positive z", "negative z"
},
{
"pos x", "neg x",
"pos y", "neg y",
"pos z", "neg z",
},
{
"+x", "-x",
"+y", "-y",
"+z", "-z"
},
{
"right", "left",
"top", "bottom",
"back", "front"
}
};
2023-10-30 23:55:30 +01:00
static PikaImage *global_image = NULL;
2023-09-26 00:35:21 +02:00
static PikaLayer *cubemap_faces[6];
static gboolean is_cubemap = FALSE;
static gboolean is_volume = FALSE;
static gboolean is_array = FALSE;
static gboolean is_mipmap_chain_valid = FALSE;
static struct
{
gint format;
DXGI_FORMAT dxgi_format;
gint bpp;
2023-12-02 20:03:24 +01:00
gboolean alpha;
2023-09-26 00:35:21 +02:00
guint rmask;
guint gmask;
guint bmask;
guint amask;
} format_info[] =
{
2023-12-02 20:03:24 +01:00
{ DDS_FORMAT_RGB8, DXGI_FORMAT_UNKNOWN, 3, FALSE, 0x00ff0000, 0x0000ff00, 0x000000ff, 0x00000000},
{ DDS_FORMAT_RGBA8, DXGI_FORMAT_B8G8R8A8_UNORM, 4, TRUE, 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000},
{ DDS_FORMAT_BGR8, DXGI_FORMAT_UNKNOWN, 3, FALSE, 0x000000ff, 0x0000ff00, 0x00ff0000, 0x00000000},
{ DDS_FORMAT_ABGR8, DXGI_FORMAT_R8G8B8A8_UNORM, 4, TRUE, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000},
{ DDS_FORMAT_R5G6B5, DXGI_FORMAT_B5G6R5_UNORM, 2, FALSE, 0x0000f800, 0x000007e0, 0x0000001f, 0x00000000},
{ DDS_FORMAT_RGBA4, DXGI_FORMAT_B4G4R4A4_UNORM, 2, TRUE, 0x00000f00, 0x000000f0, 0x0000000f, 0x0000f000},
{ DDS_FORMAT_RGB5A1, DXGI_FORMAT_B5G5R5A1_UNORM, 2, TRUE, 0x00007c00, 0x000003e0, 0x0000001f, 0x00008000},
{ DDS_FORMAT_RGB10A2, DXGI_FORMAT_R10G10B10A2_UNORM, 4, TRUE, 0x000003ff, 0x000ffc00, 0x3ff00000, 0xc0000000},
{ DDS_FORMAT_R3G3B2, DXGI_FORMAT_UNKNOWN, 1, FALSE, 0x000000e0, 0x0000001c, 0x00000003, 0x00000000},
{ DDS_FORMAT_A8, DXGI_FORMAT_A8_UNORM, 1, FALSE, 0x00000000, 0x00000000, 0x00000000, 0x000000ff},
{ DDS_FORMAT_L8, DXGI_FORMAT_R8_UNORM, 1, FALSE, 0x000000ff, 0x000000ff, 0x000000ff, 0x00000000},
{ DDS_FORMAT_L8A8, DXGI_FORMAT_UNKNOWN, 2, TRUE, 0x000000ff, 0x000000ff, 0x000000ff, 0x0000ff00},
{ DDS_FORMAT_AEXP, DXGI_FORMAT_B8G8R8A8_UNORM, 4, TRUE, 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000},
{ DDS_FORMAT_YCOCG, DXGI_FORMAT_B8G8R8A8_UNORM, 4, TRUE, 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000}
2023-09-26 00:35:21 +02:00
};
static gboolean
2023-10-30 23:55:30 +01:00
check_mipmaps (gint savetype)
2023-09-26 00:35:21 +02:00
{
GList *layers;
GList *list;
gint num_layers;
gint i, j;
gint w, h;
gint mipw, miph;
gint num_mipmaps;
gint num_surfaces = 0;
gint min_surfaces = 1;
gint max_surfaces = 1;
gboolean valid = TRUE;
PikaImageType type;
/* not handling volume maps for the moment... */
if (savetype == DDS_SAVE_VOLUMEMAP)
return 0;
if (savetype == DDS_SAVE_CUBEMAP)
{
min_surfaces = 6;
max_surfaces = 6;
}
else if (savetype == DDS_SAVE_ARRAY)
{
min_surfaces = 2;
2023-12-02 20:03:24 +01:00
max_surfaces = G_MAXINT;
2023-09-26 00:35:21 +02:00
}
2023-10-30 23:55:30 +01:00
layers = pika_image_list_layers (global_image);
2023-09-26 00:35:21 +02:00
num_layers = g_list_length (layers);
2023-10-30 23:55:30 +01:00
w = pika_image_get_width (global_image);
h = pika_image_get_height (global_image);
2023-09-26 00:35:21 +02:00
num_mipmaps = get_num_mipmaps (w, h);
type = pika_drawable_type (layers->data);
for (list = layers; list; list = g_list_next (list))
{
if (type != pika_drawable_type (list->data))
return 0;
if ((pika_drawable_get_width (list->data) == w) &&
(pika_drawable_get_height (list->data) == h))
++num_surfaces;
}
if ((num_surfaces < min_surfaces) ||
(num_surfaces > max_surfaces) ||
(num_layers != (num_surfaces * num_mipmaps)))
return 0;
for (i = 0; valid && i < num_layers; i += num_mipmaps)
{
PikaDrawable *drawable = g_list_nth_data (layers, i);
if ((pika_drawable_get_width (drawable) != w) ||
(pika_drawable_get_height (drawable) != h))
{
valid = FALSE;
break;
}
for (j = 1; j < num_mipmaps; ++j)
{
drawable = g_list_nth_data (layers, i + j);
mipw = w >> j;
miph = h >> j;
if (mipw < 1) mipw = 1;
if (miph < 1) miph = 1;
if ((pika_drawable_get_width (drawable) != mipw) ||
(pika_drawable_get_height (drawable) != miph))
{
valid = FALSE;
break;
}
}
}
return valid;
}
static gboolean
check_cubemap (PikaImage *image)
{
GList *layers;
GList *list;
gint num_layers;
gboolean cubemap = TRUE;
gint i, j, k;
gint w, h;
gchar *layer_name;
PikaImageType type;
layers = pika_image_list_layers (image);
num_layers = g_list_length (layers);
if (num_layers < 6)
return FALSE;
2023-12-02 20:03:24 +01:00
/* Check for a valid cubemap with mipmap layers */
2023-09-26 00:35:21 +02:00
if (num_layers > 6)
{
2023-12-02 20:03:24 +01:00
/* Check that mipmap layers are in order for a cubemap */
2023-10-30 23:55:30 +01:00
if (! check_mipmaps (DDS_SAVE_CUBEMAP))
2023-09-26 00:35:21 +02:00
return FALSE;
2023-12-02 20:03:24 +01:00
/* Invalidate cubemap faces */
2023-09-26 00:35:21 +02:00
for (i = 0; i < 6; ++i)
cubemap_faces[i] = NULL;
2023-12-02 20:03:24 +01:00
/* Find the mipmap level 0 layers */
2023-09-26 00:35:21 +02:00
w = pika_image_get_width (image);
h = pika_image_get_height (image);
for (i = 0, list = layers;
i < num_layers;
++i, list = g_list_next (list))
{
PikaDrawable *drawable = list->data;
if ((pika_drawable_get_width (drawable) != w) ||
(pika_drawable_get_height (drawable) != h))
continue;
2023-12-02 20:03:24 +01:00
layer_name = (gchar *) pika_item_get_name (PIKA_ITEM (drawable));
2023-09-26 00:35:21 +02:00
for (j = 0; j < 6; ++j)
{
for (k = 0; k < 4; ++k)
{
if (strstr (layer_name, cubemap_face_names[k][j]))
{
if (cubemap_faces[j] == NULL)
{
cubemap_faces[j] = PIKA_LAYER (drawable);
break;
}
}
}
}
}
2023-12-02 20:03:24 +01:00
/* Check for 6 valid faces */
2023-09-26 00:35:21 +02:00
for (i = 0; i < 6; ++i)
{
if (cubemap_faces[i] == NULL)
{
cubemap = FALSE;
break;
}
}
2023-12-02 20:03:24 +01:00
/* Make sure all faces are of the same type */
2023-09-26 00:35:21 +02:00
if (cubemap)
{
type = pika_drawable_type (PIKA_DRAWABLE (cubemap_faces[0]));
for (i = 1; i < 6 && cubemap; ++i)
{
if (pika_drawable_type (PIKA_DRAWABLE (cubemap_faces[i])) != type)
cubemap = FALSE;
}
}
}
if (num_layers == 6)
{
2023-12-02 20:03:24 +01:00
/* Invalidate cubemap faces */
2023-09-26 00:35:21 +02:00
for (i = 0; i < 6; ++i)
cubemap_faces[i] = NULL;
for (i = 0, list = layers;
i < 6;
++i, list = g_list_next (layers))
{
PikaLayer *layer = list->data;
layer_name = (gchar *) pika_item_get_name (PIKA_ITEM (layer));
for (j = 0; j < 6; ++j)
{
for (k = 0; k < 4; ++k)
{
if (strstr (layer_name, cubemap_face_names[k][j]))
{
if (cubemap_faces[j] == NULL)
{
cubemap_faces[j] = layer;
break;
}
}
}
}
}
2023-12-02 20:03:24 +01:00
/* Check for 6 valid faces */
2023-09-26 00:35:21 +02:00
for (i = 0; i < 6; ++i)
{
if (cubemap_faces[i] == NULL)
{
cubemap = FALSE;
break;
}
}
2023-12-02 20:03:24 +01:00
/* Make sure all faces are of the same size */
2023-09-26 00:35:21 +02:00
if (cubemap)
{
w = pika_drawable_get_width (PIKA_DRAWABLE (cubemap_faces[0]));
h = pika_drawable_get_height (PIKA_DRAWABLE (cubemap_faces[0]));
for (i = 1; i < 6 && cubemap; ++i)
{
if ((pika_drawable_get_width (PIKA_DRAWABLE (cubemap_faces[i])) != w) ||
(pika_drawable_get_height (PIKA_DRAWABLE (cubemap_faces[i])) != h))
cubemap = FALSE;
}
}
2023-12-02 20:03:24 +01:00
/* Make sure all faces are of the same type */
2023-09-26 00:35:21 +02:00
if (cubemap)
{
type = pika_drawable_type (PIKA_DRAWABLE (cubemap_faces[0]));
for (i = 1; i < 6 && cubemap; ++i)
{
if (pika_drawable_type (PIKA_DRAWABLE (cubemap_faces[i])) != type)
cubemap = FALSE;
}
}
}
return cubemap;
}
static gboolean
check_volume (PikaImage *image)
{
GList *layers;
GList *list;
gint num_layers;
gboolean volume = FALSE;
gint i;
gint w, h;
PikaImageType type;
layers = pika_image_list_layers (image);
num_layers = g_list_length (layers);
if (num_layers > 1)
{
volume = TRUE;
2023-12-02 20:03:24 +01:00
/* Make sure all layers are of the same size */
2023-09-26 00:35:21 +02:00
w = pika_drawable_get_width (layers->data);
h = pika_drawable_get_height (layers->data);
for (i = 1, list = layers->next;
i < num_layers && volume;
++i, list = g_list_next (list))
{
if ((pika_drawable_get_width (list->data) != w) ||
(pika_drawable_get_height (list->data) != h))
volume = FALSE;
}
if (volume)
{
2023-12-02 20:03:24 +01:00
/* Make sure all layers are of the same type */
2023-09-26 00:35:21 +02:00
type = pika_drawable_type (layers->data);
for (i = 1, list = layers->next;
i < num_layers && volume;
++i, list = g_list_next (list))
{
if (pika_drawable_type (list->data) != type)
volume = FALSE;
}
}
}
return volume;
}
static gboolean
check_array (PikaImage *image)
{
GList *layers;
gint num_layers;
gboolean array = FALSE;
gint i;
gint w, h;
PikaImageType type;
2023-10-30 23:55:30 +01:00
if (check_mipmaps (DDS_SAVE_ARRAY))
2023-09-26 00:35:21 +02:00
return 1;
layers = pika_image_list_layers (image);
num_layers = g_list_length (layers);
if (num_layers > 1)
{
GList *list;
array = TRUE;
2023-12-02 20:03:24 +01:00
/* Make sure all layers are of the same size */
2023-09-26 00:35:21 +02:00
w = pika_drawable_get_width (layers->data);
h = pika_drawable_get_height (layers->data);
for (i = 1, list = g_list_next (layers);
i < num_layers && array;
++i, list = g_list_next (list))
{
if ((pika_drawable_get_width (list->data) != w) ||
(pika_drawable_get_height (list->data) != h))
array = FALSE;
}
if (array)
{
2023-12-02 20:03:24 +01:00
/* Make sure all layers are of the same type */
2023-09-26 00:35:21 +02:00
type = pika_drawable_type (layers->data);
for (i = 1, list = g_list_next (layers);
i < num_layers;
++i, list = g_list_next (list))
{
if (pika_drawable_type (list->data) != type)
{
array = FALSE;
break;
}
}
}
}
g_list_free (layers);
return array;
}
static int
get_array_size (PikaImage *image)
{
GList *layers;
GList *list;
gint num_layers;
gint i;
gint w, h;
gint elements = 0;
layers = pika_image_list_layers (image);
num_layers = g_list_length (layers);
2023-12-02 20:03:24 +01:00
w = pika_image_get_width (image);
2023-09-26 00:35:21 +02:00
h = pika_image_get_height (image);
for (i = 0, list = layers;
i < num_layers;
++i, list = g_list_next (list))
{
if ((pika_drawable_get_width (list->data) == w) &&
(pika_drawable_get_height (list->data) == h))
{
elements++;
}
}
g_list_free (layers);
return elements;
}
PikaPDBStatusType
2023-10-30 23:55:30 +01:00
write_dds (GFile *file,
PikaImage *image,
PikaDrawable *drawable,
gboolean interactive,
PikaProcedure *procedure,
PikaProcedureConfig *config,
gboolean is_duplicate_image)
2023-09-26 00:35:21 +02:00
{
FILE *fp;
gint rc = 0;
gint compression;
gint mipmaps;
gint savetype;
2023-12-02 20:03:24 +01:00
savetype = pika_procedure_config_get_choice_id (config, "save-type");
compression = pika_procedure_config_get_choice_id (config, "compression-format");
mipmaps = pika_procedure_config_get_choice_id (config, "mipmaps");
2023-09-26 00:35:21 +02:00
2023-10-30 23:55:30 +01:00
global_image = image;
is_mipmap_chain_valid = check_mipmaps (savetype);
2023-09-26 00:35:21 +02:00
is_cubemap = check_cubemap (image);
2023-12-02 20:03:24 +01:00
is_volume = check_volume (image);
is_array = check_array (image);
2023-09-26 00:35:21 +02:00
if (interactive)
{
if (! is_mipmap_chain_valid &&
mipmaps == DDS_MIPMAP_EXISTING)
mipmaps = DDS_MIPMAP_NONE;
if (! save_dialog (image, drawable, procedure, config))
return PIKA_PDB_CANCEL;
}
else
{
2023-12-02 20:03:24 +01:00
if ((savetype == DDS_SAVE_CUBEMAP) && (! is_cubemap))
2023-09-26 00:35:21 +02:00
{
g_message ("DDS: Cannot save image as cube map");
return PIKA_PDB_EXECUTION_ERROR;
}
2023-12-02 20:03:24 +01:00
if ((savetype == DDS_SAVE_VOLUMEMAP) && (! is_volume))
2023-09-26 00:35:21 +02:00
{
g_message ("DDS: Cannot save image as volume map");
return PIKA_PDB_EXECUTION_ERROR;
}
2023-12-02 20:03:24 +01:00
if ((savetype == DDS_SAVE_VOLUMEMAP) && (compression != DDS_COMPRESS_NONE))
2023-09-26 00:35:21 +02:00
{
g_message ("DDS: Cannot save volume map with compression");
return PIKA_PDB_EXECUTION_ERROR;
}
2023-12-02 20:03:24 +01:00
if ((mipmaps == DDS_MIPMAP_EXISTING) && (! is_mipmap_chain_valid))
2023-09-26 00:35:21 +02:00
{
g_message ("DDS: Cannot save with existing mipmaps as the mipmap chain is incomplete");
return PIKA_PDB_EXECUTION_ERROR;
}
}
2023-12-02 20:03:24 +01:00
/* Open up the file to write */
2023-09-26 00:35:21 +02:00
fp = g_fopen (g_file_peek_path (file), "wb");
if (! fp)
{
g_message ("Error opening %s", g_file_peek_path (file));
return PIKA_PDB_EXECUTION_ERROR;
}
2023-12-02 20:03:24 +01:00
pika_progress_init_printf (_("Saving: %s"), pika_file_get_utf8_name (file));
2023-09-26 00:35:21 +02:00
/* If destructive changes are going to happen to the image,
* make sure we send a duplicate of it to write_image()
*/
if (! is_duplicate_image)
{
PikaImage *duplicate_image = pika_image_duplicate (image);
PikaItem **drawables;
gint n_drawables;
drawables = pika_image_get_selected_drawables (duplicate_image, &n_drawables);
rc = write_image (fp, duplicate_image, PIKA_DRAWABLE (drawables[0]), config);
pika_image_delete (duplicate_image);
g_free (drawables);
}
else
{
rc = write_image (fp, image, drawable, config);
}
fclose (fp);
return rc ? PIKA_PDB_SUCCESS : PIKA_PDB_EXECUTION_ERROR;
}
static void
2023-12-02 20:03:24 +01:00
swap_rb (guchar *pixels,
guint n,
gint bpp)
2023-09-26 00:35:21 +02:00
{
2023-12-02 20:03:24 +01:00
guint i;
guchar t;
2023-09-26 00:35:21 +02:00
for (i = 0; i < n; ++i)
{
t = pixels[bpp * i + 0];
pixels[bpp * i + 0] = pixels[bpp * i + 2];
pixels[bpp * i + 2] = t;
}
}
static void
2023-12-02 20:03:24 +01:00
convert_pixels (guchar *dst,
guchar *src,
gint format,
gint w,
gint h,
gint d,
gint bpp,
guchar *palette,
gint mipmaps)
2023-09-26 00:35:21 +02:00
{
2023-12-02 20:03:24 +01:00
guint i, num_pixels;
guchar r, g, b, a;
2023-09-26 00:35:21 +02:00
if (d > 0)
num_pixels = get_volume_mipmapped_size (w, h, d, 1, 0, mipmaps, DDS_COMPRESS_NONE);
else
num_pixels = get_mipmapped_size (w, h, 1, 0, mipmaps, DDS_COMPRESS_NONE);
for (i = 0; i < num_pixels; ++i)
{
if (bpp == 1)
{
if (palette)
{
r = palette[3 * src[i] + 0];
g = palette[3 * src[i] + 1];
b = palette[3 * src[i] + 2];
}
else
r = g = b = src[i];
if (format == DDS_FORMAT_A8)
a = src[i];
else
a = 255;
}
else if (bpp == 2)
{
r = g = b = src[2 * i];
a = src[2 * i + 1];
}
else if (bpp == 3)
{
b = src[3 * i + 0];
g = src[3 * i + 1];
r = src[3 * i + 2];
a = 255;
}
else
{
b = src[4 * i + 0];
g = src[4 * i + 1];
r = src[4 * i + 2];
a = src[4 * i + 3];
}
switch (format)
{
case DDS_FORMAT_RGB8:
dst[3 * i + 0] = b;
dst[3 * i + 1] = g;
dst[3 * i + 2] = r;
break;
case DDS_FORMAT_RGBA8:
dst[4 * i + 0] = b;
dst[4 * i + 1] = g;
dst[4 * i + 2] = r;
dst[4 * i + 3] = a;
break;
case DDS_FORMAT_BGR8:
dst[3 * i + 0] = r;
dst[3 * i + 1] = g;
dst[3 * i + 2] = b;
break;
case DDS_FORMAT_ABGR8:
dst[4 * i + 0] = r;
dst[4 * i + 1] = g;
dst[4 * i + 2] = b;
dst[4 * i + 3] = a;
break;
case DDS_FORMAT_R5G6B5:
2023-12-02 20:03:24 +01:00
PUTL16 (&dst[2 * i],
(mul8bit (r, 31) << 11) |
(mul8bit (g, 63) << 5) |
(mul8bit (b, 31) ));
2023-09-26 00:35:21 +02:00
break;
case DDS_FORMAT_RGBA4:
2023-12-02 20:03:24 +01:00
PUTL16 (&dst[2 * i],
(mul8bit (a, 15) << 12) |
(mul8bit (r, 15) << 8) |
(mul8bit (g, 15) << 4) |
(mul8bit (b, 15) ));
2023-09-26 00:35:21 +02:00
break;
case DDS_FORMAT_RGB5A1:
2023-12-02 20:03:24 +01:00
PUTL16 (&dst[2 * i],
(((a >> 7) & 0x01) << 15) |
(mul8bit (r, 31) << 10) |
(mul8bit (g, 31) << 5) |
(mul8bit (b, 31) ));
2023-09-26 00:35:21 +02:00
break;
case DDS_FORMAT_RGB10A2:
2023-12-02 20:03:24 +01:00
PUTL32 (&dst[4 * i],
((guint) ((a >> 6) & 0x003) << 30) |
((guint) ((b << 2) & 0x3ff) << 20) |
((guint) ((g << 2) & 0x3ff) << 10) |
((guint) ((r << 2) & 0x3ff) ));
2023-09-26 00:35:21 +02:00
break;
case DDS_FORMAT_R3G3B2:
2023-12-02 20:03:24 +01:00
dst[i] =
(mul8bit (r, 7) << 5) |
(mul8bit (g, 7) << 2) |
(mul8bit (b, 3) );
2023-09-26 00:35:21 +02:00
break;
case DDS_FORMAT_A8:
dst[i] = a;
break;
case DDS_FORMAT_L8:
2023-12-02 20:03:24 +01:00
dst[i] =
((r * 54 + g * 182 + b * 20) + 128) >> 8;
2023-09-26 00:35:21 +02:00
break;
case DDS_FORMAT_L8A8:
2023-12-02 20:03:24 +01:00
dst[2 * i + 0] =
((r * 54 + g * 182 + b * 20) + 128) >> 8;
2023-09-26 00:35:21 +02:00
dst[2 * i + 1] = a;
break;
case DDS_FORMAT_YCOCG:
dst[4 * i] = a;
2023-12-02 20:03:24 +01:00
encode_ycocg (&dst[4 * i], r, g, b);
2023-09-26 00:35:21 +02:00
break;
case DDS_FORMAT_AEXP:
2023-12-02 20:03:24 +01:00
encode_alpha_exponent (&dst[4 * i], r, g, b, a);
2023-09-26 00:35:21 +02:00
break;
default:
break;
}
}
}
static void
2023-12-02 20:03:24 +01:00
get_mipmap_chain (guchar *dst,
gint w,
gint h,
gint bpp,
PikaImage *image,
PikaDrawable *drawable)
2023-09-26 00:35:21 +02:00
{
GList *layers;
GList *list;
gint num_layers;
GeglBuffer *buffer;
const Babl *format;
gint i;
gint idx = 0;
gint offset;
gint mipw, miph;
if (bpp == 1)
format = babl_format ("Y' u8");
else if (bpp == 2)
format = babl_format ("Y'A u8");
else if (bpp == 3)
format = babl_format ("R'G'B' u8");
else
format = babl_format ("R'G'B'A u8");
layers = pika_image_list_layers (image);
num_layers = g_list_length (layers);
for (i = 0, list = layers;
i < num_layers;
++i, list = g_list_next (list))
{
if (list->data == drawable)
{
idx = i;
break;
}
}
if (i == num_layers)
return;
offset = 0;
while (get_next_mipmap_dimensions (&mipw, &miph, w, h))
{
buffer = pika_drawable_get_buffer (g_list_nth_data (layers, ++idx));
if ((gegl_buffer_get_width (buffer) != mipw) ||
(gegl_buffer_get_height (buffer) != miph))
{
g_object_unref (buffer);
return;
}
gegl_buffer_get (buffer, GEGL_RECTANGLE (0, 0, mipw, miph), 1.0, format,
dst + offset, GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
g_object_unref (buffer);
2023-12-02 20:03:24 +01:00
/* BGRX or BGRA needed */
2023-09-26 00:35:21 +02:00
if (bpp >= 3)
swap_rb (dst + offset, mipw * miph, bpp);
offset += (mipw * miph * bpp);
w = mipw;
h = miph;
}
}
static void
2023-10-30 23:55:30 +01:00
write_layer (FILE *fp,
PikaImage *image,
PikaDrawable *drawable,
PikaProcedureConfig *config,
2023-12-02 20:03:24 +01:00
gint w,
gint h,
gint bpp,
gint fmtbpp,
gint num_mipmaps)
2023-09-26 00:35:21 +02:00
{
GeglBuffer *buffer;
const Babl *format;
PikaImageBaseType basetype;
PikaImageType type;
guchar *src;
guchar *dst;
guchar *fmtdst;
guchar *tmp;
guchar *palette = NULL;
gint i, c;
gint x, y;
gint size;
gint fmtsize;
2023-12-02 20:03:24 +01:00
gint offset = 0;
2023-09-26 00:35:21 +02:00
gint colors;
gint compression;
gint mipmaps;
gint pixel_format;
gboolean perceptual_metric;
2023-12-02 20:03:24 +01:00
gint flags = 0;
2023-09-26 00:35:21 +02:00
g_object_get (config,
"perceptual-metric", &perceptual_metric,
NULL);
2023-12-02 20:03:24 +01:00
compression = pika_procedure_config_get_choice_id (config, "compression-format");
2023-10-30 23:55:30 +01:00
pixel_format = pika_procedure_config_get_choice_id (config, "format");
2023-12-02 20:03:24 +01:00
mipmaps = pika_procedure_config_get_choice_id (config, "mipmaps");
2023-09-26 00:35:21 +02:00
basetype = pika_image_get_base_type (image);
type = pika_drawable_type (drawable);
buffer = pika_drawable_get_buffer (drawable);
src = g_malloc (w * h * bpp);
if (basetype == PIKA_INDEXED)
format = pika_drawable_get_format (drawable);
else if (bpp == 1)
format = babl_format ("Y' u8");
else if (bpp == 2)
format = babl_format ("Y'A u8");
else if (bpp == 3)
format = babl_format ("R'G'B' u8");
else
format = babl_format ("R'G'B'A u8");
gegl_buffer_get (buffer, GEGL_RECTANGLE (0, 0, w, h), 1.0, format, src,
GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
if (basetype == PIKA_INDEXED)
{
palette = pika_image_get_colormap (image, NULL, &colors);
if (type == PIKA_INDEXEDA_IMAGE)
{
tmp = g_malloc (w * h);
for (i = 0; i < w * h; ++i)
tmp[i] = src[2 * i];
g_free (src);
src = tmp;
bpp = 1;
}
}
2023-12-02 20:03:24 +01:00
/* We want and assume BGRA ordered pixels for bpp >= 3 from here on */
2023-09-26 00:35:21 +02:00
if (bpp >= 3)
swap_rb (src, w * h, bpp);
if (compression == DDS_COMPRESS_BC3N)
{
if (bpp != 4)
{
fmtsize = w * h * 4;
fmtdst = g_malloc (fmtsize);
convert_pixels (fmtdst, src, DDS_FORMAT_RGBA8, w, h, 0, bpp,
palette, 1);
g_free (src);
src = fmtdst;
bpp = 4;
}
for (y = 0; y < h; ++y)
{
for (x = 0; x < w; ++x)
{
/* set alpha to red (x) */
src[y * (w * 4) + (x * 4) + 3] =
src[y * (w * 4) + (x * 4) + 2];
/* set red to 1 */
src[y * (w * 4) + (x * 4) + 2] = 255;
}
}
}
/* RXGB (Doom3) */
if (compression == DDS_COMPRESS_RXGB)
{
if (bpp != 4)
{
fmtsize = w * h * 4;
fmtdst = g_malloc (fmtsize);
convert_pixels (fmtdst, src, DDS_FORMAT_RGBA8, w, h, 0, bpp,
palette, 1);
g_free (src);
src = fmtdst;
bpp = 4;
}
for (y = 0; y < h; ++y)
{
for (x = 0; x < w; ++x)
{
/* swap red and alpha */
c = src[y * (w * 4) + (x * 4) + 3];
src[y * (w * 4) + (x * 4) + 3] =
src[y * (w * 4) + (x * 4) + 2];
src[y * (w * 4) + (x * 4) + 2] = c;
}
}
}
if (compression == DDS_COMPRESS_YCOCG ||
compression == DDS_COMPRESS_YCOCGS) /* convert to YCoCG */
{
fmtsize = w * h * 4;
fmtdst = g_malloc (fmtsize);
convert_pixels (fmtdst, src, DDS_FORMAT_YCOCG, w, h, 0, bpp,
palette, 1);
g_free (src);
src = fmtdst;
bpp = 4;
}
if (compression == DDS_COMPRESS_AEXP)
{
fmtsize = w * h * 4;
fmtdst = g_malloc (fmtsize);
convert_pixels (fmtdst, src, DDS_FORMAT_AEXP, w, h, 0, bpp,
palette, 1);
g_free (src);
src = fmtdst;
bpp = 4;
}
if (compression == DDS_COMPRESS_NONE)
{
if (num_mipmaps > 1)
{
2023-12-02 20:03:24 +01:00
/* Pre-convert indexed images to RGB if not exporting as indexed */
2023-09-26 00:35:21 +02:00
if (pixel_format > DDS_FORMAT_DEFAULT && basetype == PIKA_INDEXED)
{
fmtsize = get_mipmapped_size (w, h, 3, 0, num_mipmaps, DDS_COMPRESS_NONE);
fmtdst = g_malloc (fmtsize);
convert_pixels (fmtdst, src, DDS_FORMAT_RGB8, w, h, 0, bpp,
palette, 1);
g_free (src);
src = fmtdst;
bpp = 3;
palette = NULL;
}
size = get_mipmapped_size (w, h, bpp, 0, num_mipmaps,
DDS_COMPRESS_NONE);
dst = g_malloc (size);
if (mipmaps == DDS_MIPMAP_GENERATE)
{
gint mipmap_filter;
gint mipmap_wrap;
gboolean gamma_correct;
gboolean srgb;
gdouble gamma;
gboolean preserve_alpha_coverage;
gdouble alpha_test_threshold;
g_object_get (config,
"gamma-correct", &gamma_correct,
"srgb", &srgb,
"gamma", &gamma,
"preserve-alpha-coverage", &preserve_alpha_coverage,
"alpha-test-threshold", &alpha_test_threshold,
NULL);
2023-10-30 23:55:30 +01:00
mipmap_filter = pika_procedure_config_get_choice_id (config,
"mipmap-filter");
mipmap_wrap = pika_procedure_config_get_choice_id (config,
"mipmap-wrap");
2023-09-26 00:35:21 +02:00
generate_mipmaps (dst, src, w, h, bpp, palette != NULL,
num_mipmaps,
mipmap_filter,
mipmap_wrap,
gamma_correct + srgb,
gamma,
preserve_alpha_coverage,
alpha_test_threshold);
}
else
{
memcpy (dst, src, w * h * bpp);
get_mipmap_chain (dst + (w * h * bpp), w, h, bpp, image, drawable);
}
if (pixel_format > DDS_FORMAT_DEFAULT)
{
fmtsize = get_mipmapped_size (w, h, fmtbpp, 0, num_mipmaps,
DDS_COMPRESS_NONE);
fmtdst = g_malloc (fmtsize);
convert_pixels (fmtdst, dst, pixel_format, w, h, 0, bpp,
palette, num_mipmaps);
g_free (dst);
dst = fmtdst;
bpp = fmtbpp;
}
for (i = 0; i < num_mipmaps; ++i)
{
size = get_mipmapped_size (w, h, bpp, i, 1, DDS_COMPRESS_NONE);
fwrite (dst + offset, 1, size, fp);
offset += size;
}
g_free (dst);
}
else
{
if (pixel_format > DDS_FORMAT_DEFAULT)
{
fmtdst = g_malloc (h * w * fmtbpp);
convert_pixels (fmtdst, src, pixel_format, w, h, 0, bpp,
palette, 1);
g_free (src);
src = fmtdst;
bpp = fmtbpp;
}
fwrite (src, 1, h * w * bpp, fp);
}
}
else
{
size = get_mipmapped_size (w, h, bpp, 0, num_mipmaps, compression);
dst = g_malloc (size);
if (basetype == PIKA_INDEXED)
{
fmtsize = get_mipmapped_size (w, h, 3, 0, num_mipmaps,
DDS_COMPRESS_NONE);
fmtdst = g_malloc (fmtsize);
convert_pixels (fmtdst, src, DDS_FORMAT_RGB8, w, h, 0, bpp,
palette, num_mipmaps);
g_free (src);
src = fmtdst;
bpp = 3;
}
if (num_mipmaps > 1)
{
fmtsize = get_mipmapped_size (w, h, bpp, 0, num_mipmaps,
DDS_COMPRESS_NONE);
fmtdst = g_malloc (fmtsize);
if (mipmaps == DDS_MIPMAP_GENERATE)
{
gint mipmap_filter;
gint mipmap_wrap;
gboolean gamma_correct;
gboolean srgb;
gdouble gamma;
gboolean preserve_alpha_coverage;
gdouble alpha_test_threshold;
g_object_get (config,
"gamma-correct", &gamma_correct,
"srgb", &srgb,
"gamma", &gamma,
"preserve-alpha-coverage", &preserve_alpha_coverage,
"alpha-test-threshold", &alpha_test_threshold,
NULL);
2023-10-30 23:55:30 +01:00
mipmap_filter = pika_procedure_config_get_choice_id (config,
"mipmap-filter");
mipmap_wrap = pika_procedure_config_get_choice_id (config,
"mipmap-wrap");
2023-09-26 00:35:21 +02:00
generate_mipmaps (fmtdst, src, w, h, bpp, 0, num_mipmaps,
mipmap_filter,
mipmap_wrap,
gamma_correct + srgb,
gamma,
preserve_alpha_coverage,
alpha_test_threshold);
}
else
{
memcpy (fmtdst, src, w * h * bpp);
get_mipmap_chain (fmtdst + (w * h * bpp), w, h, bpp, image, drawable);
}
g_free (src);
src = fmtdst;
}
flags = 0;
if (perceptual_metric)
flags |= DXT_PERCEPTUAL;
dxt_compress (dst, src, compression, w, h, bpp, num_mipmaps, flags);
fwrite (dst, 1, size, fp);
g_free (dst);
}
g_free (src);
g_object_unref (buffer);
}
static void
2023-10-30 23:55:30 +01:00
write_volume_mipmaps (FILE *fp,
PikaImage *image,
PikaProcedureConfig *config,
GList *layers,
2023-12-02 20:03:24 +01:00
gint w,
gint h,
gint d,
gint bpp,
gint fmtbpp,
gint num_mipmaps)
2023-09-26 00:35:21 +02:00
{
GList *list;
gint i;
gint size;
gint offset;
gint colors;
guchar *src;
guchar *dst;
guchar *tmp;
guchar *fmtdst;
guchar *palette = 0;
GeglBuffer *buffer;
const Babl *format;
PikaImageBaseType type;
gint compression;
gint pixel_format;
gint mipmap_filter;
gint mipmap_wrap;
gboolean gamma_correct;
gboolean srgb;
gdouble gamma;
g_object_get (config,
"gamma-correct", &gamma_correct,
"srgb", &srgb,
"gamma", &gamma,
NULL);
2023-10-30 23:55:30 +01:00
compression = pika_procedure_config_get_choice_id (config,
"compression-format");
pixel_format = pika_procedure_config_get_choice_id (config, "format");
mipmap_filter = pika_procedure_config_get_choice_id (config,
"mipmap-filter");
mipmap_wrap = pika_procedure_config_get_choice_id (config, "mipmap-wrap");
2023-09-26 00:35:21 +02:00
type = pika_image_get_base_type (image);
if (compression != DDS_COMPRESS_NONE)
return;
src = g_malloc (w * h * bpp * d);
if (bpp == 1)
format = babl_format ("Y' u8");
else if (bpp == 2)
format = babl_format ("Y'A u8");
else if (bpp == 3)
format = babl_format ("R'G'B' u8");
else
format = babl_format ("R'G'B'A u8");
if (pika_image_get_base_type (image) == PIKA_INDEXED)
palette = pika_image_get_colormap (image, NULL, &colors);
offset = 0;
for (i = 0, list = layers;
i < d;
++i, list = g_list_next (list))
{
buffer = pika_drawable_get_buffer (list->data);
gegl_buffer_get (buffer, GEGL_RECTANGLE (0, 0, w, h), 1.0, format,
src + offset, GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
offset += (w * h * bpp);
g_object_unref (buffer);
}
if (pika_drawable_type (layers->data) == PIKA_INDEXEDA_IMAGE)
{
tmp = g_malloc (w * h * d);
for (i = 0; i < w * h * d; ++i)
tmp[i] = src[2 * i];
g_free (src);
src = tmp;
bpp = 1;
}
2023-12-02 20:03:24 +01:00
/* We want and assume BGRA ordered pixels for bpp >= 3 from here on */
2023-09-26 00:35:21 +02:00
if (bpp >= 3)
swap_rb (src, w * h * d, bpp);
2023-12-02 20:03:24 +01:00
/* Pre-convert indexed images to RGB if not exporting as indexed */
2023-09-26 00:35:21 +02:00
if (pixel_format > DDS_FORMAT_DEFAULT && type == PIKA_INDEXED)
{
size = get_volume_mipmapped_size (w, h, d, 3, 0, num_mipmaps,
DDS_COMPRESS_NONE);
dst = g_malloc (size);
convert_pixels (dst, src, DDS_FORMAT_RGB8, w, h, d, bpp, palette, 1);
g_free (src);
src = dst;
bpp = 3;
palette = NULL;
}
size = get_volume_mipmapped_size (w, h, d, bpp, 0, num_mipmaps,
compression);
dst = g_malloc (size);
offset = get_volume_mipmapped_size (w, h, d, bpp, 0, 1,
compression);
generate_volume_mipmaps (dst, src, w, h, d, bpp,
palette != NULL, num_mipmaps,
mipmap_filter,
mipmap_wrap,
gamma_correct + srgb,
gamma);
if (pixel_format > DDS_FORMAT_DEFAULT)
{
size = get_volume_mipmapped_size (w, h, d, fmtbpp, 0, num_mipmaps,
compression);
offset = get_volume_mipmapped_size (w, h, d, fmtbpp, 0, 1,
compression);
fmtdst = g_malloc (size);
convert_pixels (fmtdst, dst, pixel_format, w, h, d, bpp,
palette, num_mipmaps);
g_free (dst);
dst = fmtdst;
}
fwrite (dst + offset, 1, size, fp);
g_free (src);
g_free (dst);
}
static gboolean
2023-10-30 23:55:30 +01:00
write_image (FILE *fp,
PikaImage *image,
PikaDrawable *drawable,
PikaProcedureConfig *config)
2023-09-26 00:35:21 +02:00
{
PikaImageType drawable_type;
PikaImageBaseType basetype;
gint i, w, h;
2023-12-02 20:03:24 +01:00
gint bpp = 0;
gint fmtbpp = 0;
gboolean has_alpha = FALSE;
gboolean is_dx10 = FALSE;
guint fourcc = 0;
gint array_size = 1;
2023-09-26 00:35:21 +02:00
gint num_mipmaps;
guchar hdr[DDS_HEADERSIZE];
guchar hdr10[DDS_HEADERSIZE_DX10];
guint flags = 0, pflags = 0, caps = 0, caps2 = 0, size = 0;
guint rmask = 0, gmask = 0, bmask = 0, amask = 0;
DXGI_FORMAT dxgi_format = DXGI_FORMAT_UNKNOWN;
gint32 num_layers;
2023-12-02 20:03:24 +01:00
GList *layers, *list;
2023-09-26 00:35:21 +02:00
guchar *cmap;
gint colors;
2023-12-02 20:03:24 +01:00
gint compression, mipmaps;
gint savetype, pixel_format;
2023-09-26 00:35:21 +02:00
gint transindex;
gboolean flip_export;
g_object_get (config,
"transparent-index", &transindex,
"flip-image", &flip_export,
NULL);
2023-12-02 20:03:24 +01:00
savetype = pika_procedure_config_get_choice_id (config, "save-type");
compression = pika_procedure_config_get_choice_id (config, "compression-format");
2023-10-30 23:55:30 +01:00
pixel_format = pika_procedure_config_get_choice_id (config, "format");
2023-12-02 20:03:24 +01:00
mipmaps = pika_procedure_config_get_choice_id (config, "mipmaps");
2023-09-26 00:35:21 +02:00
if (flip_export)
pika_image_flip (image, PIKA_ORIENTATION_VERTICAL);
layers = pika_image_list_layers (image);
num_layers = g_list_length (layers);
if (mipmaps == DDS_MIPMAP_EXISTING)
drawable = layers->data;
if (savetype == DDS_SAVE_SELECTED_LAYER)
{
2023-12-02 20:03:24 +01:00
w = pika_drawable_get_width (drawable);
2023-09-26 00:35:21 +02:00
h = pika_drawable_get_height (drawable);
}
else
{
2023-12-02 20:03:24 +01:00
w = pika_image_get_width (image);
2023-09-26 00:35:21 +02:00
h = pika_image_get_height (image);
}
basetype = pika_image_get_base_type (image);
drawable_type = pika_drawable_type (drawable);
switch (drawable_type)
{
case PIKA_RGB_IMAGE: bpp = 3; break;
case PIKA_RGBA_IMAGE: bpp = 4; break;
case PIKA_GRAY_IMAGE: bpp = 1; break;
case PIKA_GRAYA_IMAGE: bpp = 2; break;
case PIKA_INDEXED_IMAGE: bpp = 1; break;
case PIKA_INDEXEDA_IMAGE: bpp = 2; break;
2023-12-02 20:03:24 +01:00
default: break;
2023-09-26 00:35:21 +02:00
}
2023-12-02 20:03:24 +01:00
/* Get uncompressed format data */
2023-09-26 00:35:21 +02:00
if (pixel_format > DDS_FORMAT_DEFAULT)
{
for (i = 0; ; ++i)
{
if (format_info[i].format == pixel_format)
{
fmtbpp = format_info[i].bpp;
has_alpha = format_info[i].alpha;
rmask = format_info[i].rmask;
gmask = format_info[i].gmask;
bmask = format_info[i].bmask;
amask = format_info[i].amask;
dxgi_format = format_info[i].dxgi_format;
break;
}
}
}
else if (bpp == 1)
{
if (basetype == PIKA_INDEXED)
{
fmtbpp = 1;
2023-12-02 20:03:24 +01:00
has_alpha = FALSE;
2023-09-26 00:35:21 +02:00
rmask = bmask = gmask = amask = 0;
}
else
{
fmtbpp = 1;
2023-12-02 20:03:24 +01:00
has_alpha = FALSE;
2023-09-26 00:35:21 +02:00
rmask = 0x000000ff;
gmask = bmask = amask = 0;
dxgi_format = DXGI_FORMAT_R8_UNORM;
}
}
else if (bpp == 2)
{
if (basetype == PIKA_INDEXED)
{
fmtbpp = 1;
2023-12-02 20:03:24 +01:00
has_alpha = FALSE;
2023-09-26 00:35:21 +02:00
rmask = gmask = bmask = amask = 0;
}
else
{
fmtbpp = 2;
2023-12-02 20:03:24 +01:00
has_alpha = TRUE;
2023-09-26 00:35:21 +02:00
rmask = 0x000000ff;
gmask = 0x000000ff;
bmask = 0x000000ff;
amask = 0x0000ff00;
}
}
else if (bpp == 3)
{
fmtbpp = 3;
rmask = 0x00ff0000;
gmask = 0x0000ff00;
bmask = 0x000000ff;
amask = 0x00000000;
}
else
{
fmtbpp = 4;
2023-12-02 20:03:24 +01:00
has_alpha = TRUE;
2023-09-26 00:35:21 +02:00
rmask = 0x00ff0000;
gmask = 0x0000ff00;
bmask = 0x000000ff;
amask = 0xff000000;
dxgi_format = DXGI_FORMAT_B8G8R8A8_UNORM;
}
2023-12-02 20:03:24 +01:00
/* Write header */
2023-09-26 00:35:21 +02:00
memset (hdr, 0, DDS_HEADERSIZE);
2023-12-02 20:03:24 +01:00
PUTL32 (hdr, FOURCC ('D','D','S',' '));
PUTL32 (hdr + 4, 124); /* Header size */
PUTL32 (hdr + 12, h);
PUTL32 (hdr + 16, w);
PUTL32 (hdr + 76, 32); /* Pixel Format size */
2023-09-26 00:35:21 +02:00
if (compression == DDS_COMPRESS_NONE)
{
2023-12-02 20:03:24 +01:00
PUTL32 (hdr + 88, fmtbpp << 3);
PUTL32 (hdr + 92, rmask);
PUTL32 (hdr + 96, gmask);
PUTL32 (hdr + 100, bmask);
PUTL32 (hdr + 104, amask);
2023-09-26 00:35:21 +02:00
}
2023-12-02 20:03:24 +01:00
/* Put some custom info in the reserved area to identify the origin of the image */
PUTL32 (hdr + 32, FOURCC ('G','I','M','P'));
PUTL32 (hdr + 36, FOURCC ('-','D','D','S'));
PUTL32 (hdr + 40, DDS_PLUGIN_VERSION);
2023-09-26 00:35:21 +02:00
flags = DDSD_CAPS | DDSD_PIXELFORMAT | DDSD_WIDTH | DDSD_HEIGHT;
caps = DDSCAPS_TEXTURE;
if (mipmaps)
{
flags |= DDSD_MIPMAPCOUNT;
2023-12-02 20:03:24 +01:00
caps |= (DDSCAPS_COMPLEX | DDSCAPS_MIPMAP);
2023-09-26 00:35:21 +02:00
num_mipmaps = get_num_mipmaps (w, h);
}
else
{
num_mipmaps = 1;
}
if ((savetype == DDS_SAVE_CUBEMAP) && is_cubemap)
{
2023-12-02 20:03:24 +01:00
caps |= DDSCAPS_COMPLEX;
2023-09-26 00:35:21 +02:00
caps2 |= (DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_ALL_FACES);
}
else if ((savetype == DDS_SAVE_VOLUMEMAP) && is_volume)
{
2023-12-02 20:03:24 +01:00
PUTL32 (hdr + 24, num_layers); /* Depth */
2023-09-26 00:35:21 +02:00
flags |= DDSD_DEPTH;
2023-12-02 20:03:24 +01:00
caps |= DDSCAPS_COMPLEX;
2023-09-26 00:35:21 +02:00
caps2 |= DDSCAPS2_VOLUME;
}
2023-12-02 20:03:24 +01:00
PUTL32 (hdr + 28, num_mipmaps);
PUTL32 (hdr + 108, caps);
PUTL32 (hdr + 112, caps2);
2023-09-26 00:35:21 +02:00
2023-12-02 20:03:24 +01:00
if (compression == DDS_COMPRESS_NONE) /* Write uncompressed data */
2023-09-26 00:35:21 +02:00
{
flags |= DDSD_PITCH;
if (pixel_format > DDS_FORMAT_DEFAULT)
{
if (pixel_format == DDS_FORMAT_A8)
pflags |= DDPF_ALPHA;
else
{
if (((fmtbpp == 1) || (pixel_format == DDS_FORMAT_L8A8)) &&
(pixel_format != DDS_FORMAT_R3G3B2))
pflags |= DDPF_LUMINANCE;
else
pflags |= DDPF_RGB;
}
}
else
{
if (bpp == 1)
{
if (basetype == PIKA_INDEXED)
pflags |= DDPF_PALETTEINDEXED8;
else
pflags |= DDPF_LUMINANCE;
}
else if ((bpp == 2) && (basetype == PIKA_INDEXED))
{
pflags |= DDPF_PALETTEINDEXED8;
}
else
{
pflags |= DDPF_RGB;
}
}
if (has_alpha)
pflags |= DDPF_ALPHAPIXELS;
PUTL32 (hdr + 8, flags);
2023-12-02 20:03:24 +01:00
PUTL32 (hdr + 20, w * fmtbpp); /* Pitch */
2023-09-26 00:35:21 +02:00
PUTL32 (hdr + 80, pflags);
2023-12-02 20:03:24 +01:00
/* Write extra FourCC info specific to PIKA DDS. When the image
* is read again we use this information to decode the pixels.
2023-09-26 00:35:21 +02:00
*/
if (pixel_format == DDS_FORMAT_AEXP)
{
PUTL32 (hdr + 44, FOURCC ('A','E','X','P'));
}
else if (pixel_format == DDS_FORMAT_YCOCG)
{
PUTL32 (hdr + 44, FOURCC ('Y','C','G','1'));
}
}
2023-12-02 20:03:24 +01:00
else /* Write compressed data */
2023-09-26 00:35:21 +02:00
{
flags |= DDSD_LINEARSIZE;
pflags = DDPF_FOURCC;
switch (compression)
{
case DDS_COMPRESS_BC1:
fourcc = FOURCC ('D','X','T','1');
dxgi_format = DXGI_FORMAT_BC1_UNORM;
break;
case DDS_COMPRESS_BC2:
fourcc = FOURCC ('D','X','T','3');
dxgi_format = DXGI_FORMAT_BC2_UNORM;
break;
case DDS_COMPRESS_BC3:
case DDS_COMPRESS_BC3N:
case DDS_COMPRESS_YCOCG:
case DDS_COMPRESS_YCOCGS:
case DDS_COMPRESS_AEXP:
fourcc = FOURCC ('D','X','T','5');
dxgi_format = DXGI_FORMAT_BC3_UNORM;
break;
case DDS_COMPRESS_RXGB:
fourcc = FOURCC ('R','X','G','B');
dxgi_format = DXGI_FORMAT_BC3_UNORM;
break;
case DDS_COMPRESS_BC4:
fourcc = FOURCC ('A','T','I','1');
dxgi_format = DXGI_FORMAT_BC4_UNORM;
2023-12-02 20:03:24 +01:00
/*is_dx10 = TRUE;*/
2023-09-26 00:35:21 +02:00
break;
case DDS_COMPRESS_BC5:
fourcc = FOURCC ('A','T','I','2');
dxgi_format = DXGI_FORMAT_BC5_UNORM;
2023-12-02 20:03:24 +01:00
/*is_dx10 = TRUE;*/
2023-09-26 00:35:21 +02:00
break;
}
if ((compression == DDS_COMPRESS_BC3N) ||
(compression == DDS_COMPRESS_RXGB))
{
pflags |= DDPF_NORMAL;
}
PUTL32 (hdr + 8, flags);
PUTL32 (hdr + 80, pflags);
PUTL32 (hdr + 84, fourcc);
2023-12-02 20:03:24 +01:00
/* Linear size */
2023-09-26 00:35:21 +02:00
size = ((w + 3) >> 2) * ((h + 3) >> 2);
if ((compression == DDS_COMPRESS_BC1) ||
(compression == DDS_COMPRESS_BC4))
size *= 8;
else
size *= 16;
2023-12-02 20:03:24 +01:00
PUTL32 (hdr + 20, size);
2023-09-26 00:35:21 +02:00
/*
* write extra fourcc info - this is special to PIKA DDS. When the image
* is read by the plugin, we can detect the added information to decode
* the pixels
*/
if (compression == DDS_COMPRESS_AEXP)
{
PUTL32 (hdr + 44, FOURCC ('A','E','X','P'));
}
else if (compression == DDS_COMPRESS_YCOCG)
{
PUTL32 (hdr + 44, FOURCC ('Y','C','G','1'));
}
else if (compression == DDS_COMPRESS_YCOCGS)
{
PUTL32 (hdr + 44, FOURCC ('Y','C','G','2'));
}
}
2023-12-02 20:03:24 +01:00
/* Texture arrays always require a DX10 header */
2023-09-26 00:35:21 +02:00
if (savetype == DDS_SAVE_ARRAY)
2023-12-02 20:03:24 +01:00
is_dx10 = TRUE;
2023-09-26 00:35:21 +02:00
2023-12-02 20:03:24 +01:00
/* Upgrade to DX10 header when desired */
2023-09-26 00:35:21 +02:00
if (is_dx10)
{
array_size = ((savetype == DDS_SAVE_SELECTED_LAYER ||
2023-12-02 20:03:24 +01:00
savetype == DDS_SAVE_VISIBLE_LAYERS) ?
2023-09-26 00:35:21 +02:00
1 : get_array_size (image));
PUTL32 (hdr10 + 0, dxgi_format);
PUTL32 (hdr10 + 4, D3D10_RESOURCE_DIMENSION_TEXTURE2D);
PUTL32 (hdr10 + 8, 0);
PUTL32 (hdr10 + 12, array_size);
PUTL32 (hdr10 + 16, 0);
2023-12-02 20:03:24 +01:00
/* Update main header accordingly */
2023-09-26 00:35:21 +02:00
PUTL32 (hdr + 80, pflags | DDPF_FOURCC);
PUTL32 (hdr + 84, FOURCC ('D','X','1','0'));
}
fwrite (hdr, DDS_HEADERSIZE, 1, fp);
if (is_dx10)
fwrite (hdr10, DDS_HEADERSIZE_DX10, 1, fp);
2023-12-02 20:03:24 +01:00
/* Write palette for indexed images */
2023-09-26 00:35:21 +02:00
if ((basetype == PIKA_INDEXED) &&
(pixel_format == DDS_FORMAT_DEFAULT) &&
(compression == DDS_COMPRESS_NONE))
{
2023-12-02 20:03:24 +01:00
const guchar zero[4] = {0, 0, 0, 0};
2023-09-26 00:35:21 +02:00
cmap = pika_image_get_colormap (image, NULL, &colors);
for (i = 0; i < colors; ++i)
{
fwrite (&cmap[3 * i], 1, 3, fp);
if (i == transindex)
fputc (0, fp);
else
fputc (255, fp);
}
2023-12-02 20:03:24 +01:00
/* Pad unused palette space with zeroes */
2023-09-26 00:35:21 +02:00
for (; i < 256; ++i)
fwrite (zero, 1, 4, fp);
}
2023-12-02 20:03:24 +01:00
if (savetype == DDS_SAVE_CUBEMAP) /* Write cubemap layers */
2023-09-26 00:35:21 +02:00
{
for (i = 0; i < 6; ++i)
{
write_layer (fp, image, PIKA_DRAWABLE (cubemap_faces[i]), config,
w, h, bpp, fmtbpp,
num_mipmaps);
pika_progress_update ((float)(i + 1) / 6.0);
}
}
2023-12-02 20:03:24 +01:00
else if (savetype == DDS_SAVE_VOLUMEMAP) /* Write volume slices */
2023-09-26 00:35:21 +02:00
{
for (i = 0, list = layers;
i < num_layers;
++i, list = g_list_next (layers))
{
write_layer (fp, image, list->data, config,
w, h, bpp, fmtbpp, 1);
pika_progress_update ((float)i / (float)num_layers);
}
if (num_mipmaps > 1)
write_volume_mipmaps (fp, image, config, layers, w, h, num_layers,
bpp, fmtbpp, num_mipmaps);
}
2023-12-02 20:03:24 +01:00
else if (savetype == DDS_SAVE_ARRAY) /* Write array entries */
2023-09-26 00:35:21 +02:00
{
for (i = 0, list = layers;
i < num_layers;
++i, list = g_list_next (layers))
{
if ((pika_drawable_get_width (list->data) == w) &&
(pika_drawable_get_height (list->data) == h))
{
write_layer (fp, image, list->data, config,
w, h, bpp, fmtbpp, num_mipmaps);
}
pika_progress_update ((float)i / (float)num_layers);
}
}
else
{
if (savetype == DDS_SAVE_VISIBLE_LAYERS)
drawable = PIKA_DRAWABLE (pika_image_merge_visible_layers (image,
PIKA_CLIP_TO_IMAGE));
write_layer (fp, image, drawable, config,
w, h, bpp, fmtbpp, num_mipmaps);
}
pika_progress_update (1.0);
return TRUE;
}
static void
2023-10-30 23:55:30 +01:00
config_notify (PikaProcedureConfig *config,
const GParamSpec *pspec,
PikaProcedureDialog *dialog)
2023-09-26 00:35:21 +02:00
{
if (! strcmp (pspec->name, "compression-format"))
{
gint compression;
2023-10-30 23:55:30 +01:00
compression = pika_procedure_config_get_choice_id (config,
"compression-format");
2023-09-26 00:35:21 +02:00
2023-10-30 23:55:30 +01:00
pika_procedure_dialog_set_sensitive (PIKA_PROCEDURE_DIALOG (dialog),
"format",
compression == DDS_COMPRESS_NONE,
NULL, NULL, FALSE);
2023-09-26 00:35:21 +02:00
2023-10-30 23:55:30 +01:00
pika_procedure_dialog_set_sensitive (PIKA_PROCEDURE_DIALOG (dialog),
"perceptual-metric",
compression != DDS_COMPRESS_NONE,
NULL, NULL, FALSE);
2023-09-26 00:35:21 +02:00
}
else if (! strcmp (pspec->name, "save-type"))
{
2023-12-02 20:03:24 +01:00
gint savetype;
PikaParamSpecChoice *pspec;
2023-09-26 00:35:21 +02:00
2023-12-02 20:03:24 +01:00
savetype = pika_procedure_config_get_choice_id (config, "save-type");
2023-09-26 00:35:21 +02:00
switch (savetype)
{
case DDS_SAVE_SELECTED_LAYER:
case DDS_SAVE_VISIBLE_LAYERS:
case DDS_SAVE_CUBEMAP:
case DDS_SAVE_ARRAY:
2023-10-30 23:55:30 +01:00
pika_procedure_dialog_set_sensitive (PIKA_PROCEDURE_DIALOG (dialog),
"compression-format", TRUE,
NULL, NULL, FALSE);
2023-09-26 00:35:21 +02:00
break;
case DDS_SAVE_VOLUMEMAP:
g_object_set (config,
2023-10-30 23:55:30 +01:00
"compression-format", "none",
2023-09-26 00:35:21 +02:00
NULL);
2023-10-30 23:55:30 +01:00
pika_procedure_dialog_set_sensitive (PIKA_PROCEDURE_DIALOG (dialog),
"compression-format", FALSE,
NULL, NULL, FALSE);
2023-09-26 00:35:21 +02:00
break;
}
2023-12-02 20:03:24 +01:00
pspec = PIKA_PARAM_SPEC_CHOICE (g_object_class_find_property (G_OBJECT_GET_CLASS (config),
"mipmaps"));
pika_choice_set_sensitive (pspec->choice, "existing", check_mipmaps (savetype));
2023-09-26 00:35:21 +02:00
}
else if (! strcmp (pspec->name, "mipmaps"))
{
gint mipmaps;
gboolean gamma_correct;
gboolean srgb;
gboolean preserve_alpha_coverage;
g_object_get (config,
"gamma-correct", &gamma_correct,
"srgb", &srgb,
"preserve-alpha-coverage", &preserve_alpha_coverage,
NULL);
2023-12-02 20:03:24 +01:00
mipmaps = pika_procedure_config_get_choice_id (config, "mipmaps");
2023-09-26 00:35:21 +02:00
2023-12-02 20:03:24 +01:00
pika_procedure_dialog_set_sensitive (PIKA_PROCEDURE_DIALOG (dialog),
2023-10-30 23:55:30 +01:00
"mipmap-filter",
mipmaps == DDS_MIPMAP_GENERATE,
NULL, NULL, FALSE);
2023-12-02 20:03:24 +01:00
pika_procedure_dialog_set_sensitive (PIKA_PROCEDURE_DIALOG (dialog),
2023-10-30 23:55:30 +01:00
"mipmap-wrap",
mipmaps == DDS_MIPMAP_GENERATE,
NULL, NULL, FALSE);
pika_procedure_dialog_set_sensitive (PIKA_PROCEDURE_DIALOG (dialog),
"gamma-correct",
mipmaps == DDS_MIPMAP_GENERATE,
NULL, NULL, FALSE);
pika_procedure_dialog_set_sensitive (PIKA_PROCEDURE_DIALOG (dialog),
"srgb",
((mipmaps == DDS_MIPMAP_GENERATE) &&
gamma_correct),
NULL, NULL, FALSE);
pika_procedure_dialog_set_sensitive (PIKA_PROCEDURE_DIALOG (dialog),
"gamma",
((mipmaps == DDS_MIPMAP_GENERATE) &&
gamma_correct && ! srgb),
NULL, NULL, FALSE);
pika_procedure_dialog_set_sensitive (PIKA_PROCEDURE_DIALOG (dialog),
"preserve-alpha-coverage",
mipmaps == DDS_MIPMAP_GENERATE,
NULL, NULL, FALSE);
pika_procedure_dialog_set_sensitive (PIKA_PROCEDURE_DIALOG (dialog),
"alpha-test-threshold",
((mipmaps == DDS_MIPMAP_GENERATE) &&
preserve_alpha_coverage),
NULL, NULL, FALSE);
2023-09-26 00:35:21 +02:00
}
else if (! strcmp (pspec->name, "transparent-color"))
{
2023-12-02 20:03:24 +01:00
GtkWidget *transparent_check;
gboolean transparent_color;
2023-09-26 00:35:21 +02:00
g_object_get (config,
"transparent-color", &transparent_color,
NULL);
2023-12-02 20:03:24 +01:00
transparent_check = pika_procedure_dialog_get_widget (PIKA_PROCEDURE_DIALOG (dialog),
"transparent-color",
G_TYPE_NONE);
if ((transparent_check != NULL) &&
2023-10-30 23:55:30 +01:00
gtk_widget_get_sensitive (transparent_check))
pika_procedure_dialog_set_sensitive (PIKA_PROCEDURE_DIALOG (dialog),
"transparent-index",
transparent_color,
NULL, NULL, FALSE);
2023-09-26 00:35:21 +02:00
}
else if (! strcmp (pspec->name, "gamma-correct"))
{
gboolean gamma_correct;
gboolean srgb;
g_object_get (config,
"gamma-correct", &gamma_correct,
"srgb", &srgb,
NULL);
2023-10-30 23:55:30 +01:00
pika_procedure_dialog_set_sensitive (PIKA_PROCEDURE_DIALOG (dialog),
"srgb", gamma_correct,
NULL, NULL, FALSE);
2023-09-26 00:35:21 +02:00
2023-10-30 23:55:30 +01:00
pika_procedure_dialog_set_sensitive (PIKA_PROCEDURE_DIALOG (dialog),
"gamma",
(gamma_correct && ! srgb),
NULL, NULL, FALSE);
2023-09-26 00:35:21 +02:00
}
else if (! strcmp (pspec->name, "srgb"))
{
gboolean gamma_correct;
gboolean srgb;
g_object_get (config,
"gamma-correct", &gamma_correct,
"srgb", &srgb,
NULL);
2023-10-30 23:55:30 +01:00
pika_procedure_dialog_set_sensitive (PIKA_PROCEDURE_DIALOG (dialog),
"gamma",
(gamma_correct && ! srgb),
NULL, NULL, FALSE);
2023-09-26 00:35:21 +02:00
}
else if (! strcmp (pspec->name, "preserve-alpha-coverage"))
{
gboolean preserve_alpha_coverage;
g_object_get (config,
"preserve-alpha-coverage", &preserve_alpha_coverage,
NULL);
2023-10-30 23:55:30 +01:00
pika_procedure_dialog_set_sensitive (PIKA_PROCEDURE_DIALOG (dialog),
"alpha-test-threshold",
preserve_alpha_coverage,
NULL, NULL, FALSE);
2023-09-26 00:35:21 +02:00
}
}
2023-12-02 20:03:24 +01:00
2023-09-26 00:35:21 +02:00
static gboolean
2023-10-30 23:55:30 +01:00
save_dialog (PikaImage *image,
PikaDrawable *drawable,
PikaProcedure *procedure,
PikaProcedureConfig *config)
2023-09-26 00:35:21 +02:00
{
2023-12-02 20:03:24 +01:00
GtkWidget *dialog;
PikaParamSpecChoice *cspec;
PikaImageBaseType base_type;
gboolean run;
2023-10-30 23:55:30 +01:00
base_type = pika_image_get_base_type (image);
2023-09-26 00:35:21 +02:00
if (is_cubemap || is_volume || is_array)
g_object_set (config,
2023-12-02 20:03:24 +01:00
"save-type", "layer",
2023-09-26 00:35:21 +02:00
NULL);
2023-10-30 23:55:30 +01:00
dialog = pika_save_procedure_dialog_new (PIKA_SAVE_PROCEDURE (procedure),
PIKA_PROCEDURE_CONFIG (config),
image);
2023-09-26 00:35:21 +02:00
gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
2023-12-02 20:03:24 +01:00
pika_procedure_dialog_get_widget (PIKA_PROCEDURE_DIALOG (dialog),
"transparent-color",
G_TYPE_NONE);
2023-10-30 23:55:30 +01:00
pika_procedure_dialog_set_sensitive (PIKA_PROCEDURE_DIALOG (dialog),
"transparent-color",
base_type == PIKA_INDEXED,
NULL, NULL, FALSE);
pika_procedure_dialog_fill_frame (PIKA_PROCEDURE_DIALOG (dialog),
"transparency-frame",
"transparent-color", FALSE,
"transparent-index");
pika_procedure_dialog_get_label (PIKA_PROCEDURE_DIALOG (dialog),
"mipmap-options-label",
_("Mipmap Options"),
FALSE, FALSE);
pika_procedure_dialog_fill_box (PIKA_PROCEDURE_DIALOG (dialog),
"mipmap-options-box",
"mipmap-filter", "mipmap-wrap",
"gamma-correct", "srgb", "gamma",
"preserve-alpha-coverage",
"alpha-test-threshold", NULL);
pika_procedure_dialog_fill_frame (PIKA_PROCEDURE_DIALOG (dialog),
"mipmap-options-frame",
"mipmap-options-label", FALSE,
"mipmap-options-box");
2023-12-02 20:03:24 +01:00
pika_procedure_dialog_get_widget (PIKA_PROCEDURE_DIALOG (dialog),
"save-type", G_TYPE_NONE);
cspec = PIKA_PARAM_SPEC_CHOICE (g_object_class_find_property (G_OBJECT_GET_CLASS (config),
"save-type"));
pika_choice_set_sensitive (cspec->choice, "cube", is_cubemap);
pika_choice_set_sensitive (cspec->choice, "volume", is_volume);
pika_choice_set_sensitive (cspec->choice, "array", is_array);
pika_procedure_dialog_get_widget (PIKA_PROCEDURE_DIALOG (dialog),
"mipmaps", G_TYPE_NONE);
cspec = PIKA_PARAM_SPEC_CHOICE (g_object_class_find_property (G_OBJECT_GET_CLASS (config),
"mipmaps"));
pika_choice_set_sensitive (cspec->choice, "existing",
! (is_volume || is_cubemap) && is_mipmap_chain_valid);
2023-09-26 00:35:21 +02:00
2023-10-30 23:55:30 +01:00
pika_procedure_dialog_fill (PIKA_PROCEDURE_DIALOG (dialog),
"compression-format", "perceptual-metric",
"format", "save-type", "flip-image",
"mipmaps", "transparency-frame",
"mipmap-options-frame", NULL);
2023-09-26 00:35:21 +02:00
2023-10-30 23:55:30 +01:00
config_notify (PIKA_PROCEDURE_CONFIG (config),
2023-09-26 00:35:21 +02:00
g_object_class_find_property (G_OBJECT_GET_CLASS (config),
"compression-format"),
2023-10-30 23:55:30 +01:00
PIKA_PROCEDURE_DIALOG (dialog));
2023-09-26 00:35:21 +02:00
2023-10-30 23:55:30 +01:00
config_notify (PIKA_PROCEDURE_CONFIG (config),
2023-09-26 00:35:21 +02:00
g_object_class_find_property (G_OBJECT_GET_CLASS (config),
"mipmaps"),
2023-10-30 23:55:30 +01:00
PIKA_PROCEDURE_DIALOG (dialog));
2023-09-26 00:35:21 +02:00
2023-10-30 23:55:30 +01:00
config_notify (PIKA_PROCEDURE_CONFIG (config),
2023-09-26 00:35:21 +02:00
g_object_class_find_property (G_OBJECT_GET_CLASS (config),
"save-type"),
2023-10-30 23:55:30 +01:00
PIKA_PROCEDURE_DIALOG (dialog));
2023-09-26 00:35:21 +02:00
2023-10-30 23:55:30 +01:00
config_notify (PIKA_PROCEDURE_CONFIG (config),
2023-09-26 00:35:21 +02:00
g_object_class_find_property (G_OBJECT_GET_CLASS (config),
"transparent-color"),
2023-10-30 23:55:30 +01:00
PIKA_PROCEDURE_DIALOG (dialog));
2023-09-26 00:35:21 +02:00
2023-10-30 23:55:30 +01:00
g_signal_connect (PIKA_PROCEDURE_CONFIG (config), "notify",
2023-09-26 00:35:21 +02:00
G_CALLBACK (config_notify),
2023-10-30 23:55:30 +01:00
PIKA_PROCEDURE_DIALOG (dialog));
2023-09-26 00:35:21 +02:00
gtk_widget_show (dialog);
run = pika_procedure_dialog_run (PIKA_PROCEDURE_DIALOG (dialog));
2023-10-30 23:55:30 +01:00
g_signal_handlers_disconnect_by_func (PIKA_PROCEDURE_CONFIG (config),
2023-09-26 00:35:21 +02:00
config_notify,
2023-10-30 23:55:30 +01:00
PIKA_PROCEDURE_DIALOG (dialog));
2023-09-26 00:35:21 +02:00
gtk_widget_destroy (dialog);
return run;
}