gimp/plug-ins/file-dds/ddsread.c
Jacob Boerema c17b324910 plug-ins/dds: fix #12790 for 32-bit
On 32-bit systems the computed linear size can overflow, causing a
crash.
Use a function that checks for overflow when multiplying and return
an error if that fails.
As extra security also update the loop to compute the base offset after
each line of data, and convert to gsize first when computing the
size for g_malloc and memset.
2025-06-20 10:29:43 -04:00

1632 lines
56 KiB
C

/*
* DDS GIMP 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.
*/
/*
** !!! COPYRIGHT NOTICE !!!
**
** The following is based on code (C) 2003 Arne Reuter <homepage@arnereuter.de>
** URL: http://www.dr-reuter.de/arne/dds.html
**
*/
#include "config.h"
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <gtk/gtk.h>
#include <glib/gstdio.h>
#include <libgimp/gimp.h>
#include <libgimp/gimpui.h>
#include <libgimp/stdplugins-intl.h>
#include "dds.h"
#include "ddsread.h"
#include "dxt.h"
#include "endian_rw.h"
#include "formats.h"
#include "imath.h"
#include "misc.h"
/*
* Struct containing all info needed to parse the file.
* This can be thought of as a version-agnostic header,
* holding all relevant data from the two headers
* plus some GIMP-specific information.
*/
typedef struct
{
fmt_read_info_t read_info;
gchar fourcc[4];
gchar gimp_fourcc[4];
guint flags;
guint fmt_flags;
guint bpp;
guint gimp_bpp;
DXGI_FORMAT dxgi_format;
D3DFORMAT d3d9_format;
DDS_COMPRESSION_TYPE comp_format;
guint width;
guint height;
gint tile_height;
gsize linear_size;
gsize pitch;
guint mipmaps;
guint volume_slices;
guint array_items;
guint cubemap_faces;
guint gimp_version;
guchar *palette;
} dds_load_info_t;
static gboolean read_header (dds_header_t *hdr,
FILE *fp);
static gboolean read_header_dx10 (dds_header_dx10_t *hdr,
FILE *fp);
static gboolean validate_header (dds_header_t *hdr,
GError **error);
static gboolean validate_dx10_header (dds_header_dx10_t *dx10hdr,
dds_load_info_t *load_info,
GError **error);
static gboolean load_layer (FILE *fp,
dds_load_info_t *load_info,
GimpImage *image,
guint level,
gchar *prefix,
guint *layer_index,
guchar *pixels,
guchar *buf,
GError **error);
static gboolean load_mipmaps (FILE *fp,
dds_load_info_t *load_info,
GimpImage *image,
gchar *prefix,
guint *layer_index,
guchar *pixels,
guchar *buf,
gboolean read_mipmaps,
GError **error);
static gboolean load_face (FILE *fp,
dds_load_info_t *load_info,
GimpImage *image,
gchar *prefix,
guint *layer_index,
guchar *pixels,
guchar *buf,
gboolean read_mipmaps,
GError **error);
static gboolean load_dialog (GimpProcedure *procedure,
GimpProcedureConfig *config);
/* Read DDS file */
GimpPDBStatusType
read_dds (GFile *file,
GimpImage **ret_image,
gboolean interactive,
GimpProcedure *procedure,
GimpProcedureConfig *config,
GError **error)
{
GimpImage *image = NULL;
guint layer_index = 0;
guchar *buf, *pixels;
FILE *fp;
gsize file_size;
dds_header_t hdr;
dds_header_dx10_t dx10hdr;
dds_load_info_t load_info;
GList *layers;
GimpImageBaseType type;
GimpPrecision precision = GIMP_PRECISION_U8_NON_LINEAR;
gboolean read_mipmaps;
gboolean flip_import;
gint i;
if (interactive)
{
gimp_ui_init ("dds");
if (! load_dialog (procedure, config))
return GIMP_PDB_CANCEL;
}
g_object_get (config,
"load-mipmaps", &read_mipmaps,
"flip-image", &flip_import,
NULL);
fp = g_fopen (g_file_peek_path (file), "rb");
if (! fp)
{
g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
_("Could not open '%s' for reading: %s"),
gimp_file_get_utf8_name (file), g_strerror (errno));
return GIMP_PDB_EXECUTION_ERROR;
}
/* Get total file size to compare against header info later */
fseek (fp, 0L, SEEK_END);
file_size = ftell (fp);
fseek (fp, 0L, SEEK_SET);
gimp_progress_init_printf (_("Loading: %s"), gimp_file_get_utf8_name (file));
/* Read standard header */
memset (&hdr, 0, sizeof (dds_header_t));
read_header (&hdr, fp);
/* Check that header is actually valid */
if (! validate_header (&hdr, error))
{
fclose (fp);
return GIMP_PDB_EXECUTION_ERROR;
}
/* Initialize load_info with data from header */
memset (&load_info, 0, sizeof (dds_load_info_t));
PUTL32 (load_info.fourcc, GETL32 (hdr.pixelfmt.fourcc));
load_info.flags = hdr.flags;
load_info.fmt_flags = hdr.pixelfmt.flags;
load_info.width = hdr.width;
load_info.height = hdr.height;
load_info.gimp_version = hdr.reserved.gimp_dds_special.version;
PUTL32 (load_info.gimp_fourcc, hdr.reserved.gimp_dds_special.extra_fourcc);
/* Get D3DFORMAT directly from FourCC if present there,
* otherwise find it based on provided bpp, masks, and flags */
if ((load_info.fmt_flags & DDPF_FOURCC) && (load_info.fourcc[1] == 0))
load_info.d3d9_format = GETL32 (load_info.fourcc);
else
load_info.d3d9_format = get_d3d9format (hdr.pixelfmt.bpp,
hdr.pixelfmt.rmask,
hdr.pixelfmt.gmask,
hdr.pixelfmt.bmask,
hdr.pixelfmt.amask,
hdr.pixelfmt.flags);
/* Read DX10 header if present */
memset (&dx10hdr, 0, sizeof (dds_header_dx10_t));
if (GETL32 (load_info.fourcc) == FOURCC ('D','X','1','0'))
{
read_header_dx10 (&dx10hdr, fp);
/* Check that DX10 header is actually valid */
if (! validate_dx10_header (&dx10hdr, &load_info, error))
{
fclose (fp);
return GIMP_PDB_EXECUTION_ERROR;
}
load_info.array_items = dx10hdr.arraySize;
}
/* If format search was successful, get info needed to parse the file */
if (load_info.d3d9_format || load_info.dxgi_format)
{
load_info.read_info = get_format_read_info (load_info.d3d9_format,
load_info.dxgi_format);
if ((! hdr.pixelfmt.bpp) && load_info.d3d9_format)
hdr.pixelfmt.bpp = get_bpp_d3d9 (load_info.d3d9_format);
else if (load_info.dxgi_format)
hdr.pixelfmt.bpp = get_bpp_dxgi (load_info.dxgi_format);
/* Unset the FourCC flag as D3D formats will be handled as uncompressed */
if ((load_info.fmt_flags & DDPF_FOURCC) && load_info.d3d9_format)
load_info.fmt_flags &= ~DDPF_FOURCC;
}
/* Exit if uncompressed format could not be determined by any method */
if ((! (load_info.fmt_flags & DDPF_FOURCC)) &&
(! (load_info.d3d9_format || load_info.dxgi_format)))
{
fclose (fp);
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
_("Unsupported DDS pixel format:\n"
"bpp: %d, Rmask: %x, Gmask: %x, Bmask: %x, Amask: %x, flags: %u"),
hdr.pixelfmt.bpp,
hdr.pixelfmt.rmask, hdr.pixelfmt.gmask,
hdr.pixelfmt.bmask, hdr.pixelfmt.amask,
hdr.pixelfmt.flags);
return GIMP_PDB_EXECUTION_ERROR;
}
/* If compressed, determine the format used */
if (load_info.fmt_flags & DDPF_FOURCC)
{
if (GETL32 (load_info.fourcc) == FOURCC ('D','X','1','0'))
{
/* Compression type from DXGI format */
switch (dx10hdr.dxgiFormat)
{
case DXGI_FORMAT_BC1_TYPELESS:
case DXGI_FORMAT_BC1_UNORM:
case DXGI_FORMAT_BC1_UNORM_SRGB:
load_info.comp_format = DDS_COMPRESS_BC1;
break;
case DXGI_FORMAT_BC2_TYPELESS:
case DXGI_FORMAT_BC2_UNORM:
case DXGI_FORMAT_BC2_UNORM_SRGB:
load_info.comp_format = DDS_COMPRESS_BC2;
break;
case DXGI_FORMAT_BC3_TYPELESS:
case DXGI_FORMAT_BC3_UNORM:
case DXGI_FORMAT_BC3_UNORM_SRGB:
load_info.comp_format = DDS_COMPRESS_BC3;
break;
case DXGI_FORMAT_BC4_TYPELESS:
case DXGI_FORMAT_BC4_UNORM:
case DXGI_FORMAT_BC4_SNORM:
load_info.comp_format = DDS_COMPRESS_BC4;
break;
case DXGI_FORMAT_BC5_TYPELESS:
case DXGI_FORMAT_BC5_UNORM:
case DXGI_FORMAT_BC5_SNORM:
load_info.comp_format = DDS_COMPRESS_BC5;
break;
/* TODO: Implement BC6 format */
case DXGI_FORMAT_BC7_TYPELESS:
case DXGI_FORMAT_BC7_UNORM:
case DXGI_FORMAT_BC7_UNORM_SRGB:
load_info.comp_format = DDS_COMPRESS_BC7;
break;
default:
load_info.comp_format = DDS_COMPRESS_MAX;
break;
}
}
else
{
/* Compression type from FourCC */
switch (GETL32 (load_info.fourcc))
{
case FOURCC ('D','X','T','1'):
load_info.comp_format = DDS_COMPRESS_BC1;
break;
case FOURCC ('D','X','T','2'):
case FOURCC ('D','X','T','3'):
load_info.comp_format = DDS_COMPRESS_BC2;
break;
case FOURCC ('D','X','T','4'):
case FOURCC ('D','X','T','5'):
case FOURCC ('R','X','G','B'):
load_info.comp_format = DDS_COMPRESS_BC3;
break;
case FOURCC ('A','T','I','1'):
case FOURCC ('B','C','4','U'):
case FOURCC ('B','C','4','S'):
load_info.comp_format = DDS_COMPRESS_BC4;
break;
case FOURCC ('A','T','I','2'):
case FOURCC ('B','C','5','U'):
case FOURCC ('B','C','5','S'):
load_info.comp_format = DDS_COMPRESS_BC5;
break;
default:
load_info.comp_format = DDS_COMPRESS_MAX;
break;
}
}
}
/* Determine resource type (cubemap, volume, array) and number of mipmaps.
* Filling in these variables conditionally here simplifies some checks later */
if (load_info.dxgi_format)
{
if (dx10hdr.resourceDimension == D3D10_RESOURCE_DIMENSION_TEXTURE3D)
load_info.volume_slices = hdr.depth;
if ((dx10hdr.resourceDimension == D3D10_RESOURCE_DIMENSION_TEXTURE2D) &&
(dx10hdr.miscFlag & D3D10_RESOURCE_MISC_TEXTURECUBE))
load_info.cubemap_faces = DDSCAPS2_CUBEMAP_ALL_FACES;
}
else
{
/* This and the mipmap check below were originally AND, not OR,
* but some images out there only have one of these two flags,
* so for compatibility's sake we take the more lenient route */
if ((hdr.caps.caps2 & DDSCAPS2_VOLUME) ||
(load_info.flags & DDSD_DEPTH))
load_info.volume_slices = hdr.depth;
load_info.cubemap_faces = hdr.caps.caps2 & DDSCAPS2_CUBEMAP_ALL_FACES;
}
if ((hdr.caps.caps1 & DDSCAPS_MIPMAP) ||
(load_info.flags & DDSD_MIPMAPCOUNT))
load_info.mipmaps = hdr.num_mipmaps;
/* Historically many DDS exporters haven't set pitch/linearsize and the corresponding flags,
* or set them incorrectly, so it's more reliable to always compute these manually.
* See: https://learn.microsoft.com/en-us/windows/win32/direct3ddds/dx-graphics-dds-pguide
*/
if (load_info.fmt_flags & DDPF_FOURCC)
{
if (hdr.flags & DDSD_PITCH)
{
g_printerr ("Warning: DDSD_PITCH is incorrectly set for DDPF_FOURCC! (recovered)\n");
load_info.flags &= ~DDSD_PITCH;
}
if (! (hdr.flags & DDSD_LINEARSIZE))
{
g_printerr ("Warning: DDSD_LINEARSIZE is incorrectly not set for DDPF_FOURCC! (recovered)\n");
load_info.flags |= DDSD_LINEARSIZE;
}
load_info.pitch = MAX (1, (hdr.width + 3) >> 2);
if (load_info.comp_format == DDS_COMPRESS_BC1 ||
load_info.comp_format == DDS_COMPRESS_BC4)
{
load_info.pitch *= 8;
}
else
{
load_info.pitch *= 16;
}
if (! g_size_checked_mul (&load_info.linear_size,
MAX (1, (hdr.height + 3) >> 2),
load_info.pitch))
{
fclose (fp);
g_set_error (error, GIMP_PLUG_IN_ERROR, 0,
_("Image size is too big to handle."));
return GIMP_PDB_EXECUTION_ERROR;
}
if (load_info.linear_size != hdr.pitch_or_linsize)
{
g_printerr ("Unexpected linear size (%u) set to %u\n",
hdr.pitch_or_linsize, (guint32) load_info.linear_size);
}
}
else
{
if (! (hdr.flags & DDSD_PITCH))
{
g_printerr ("Warning: DDSD_PITCH is incorrectly not set for an uncompressed texture! (recovered)\n");
load_info.flags |= DDSD_PITCH;
}
if ((hdr.flags & DDSD_LINEARSIZE))
{
g_printerr ("Warning: DDSD_LINEARSIZE is incorrectly set for an uncompressed texture! (recovered)\n");
load_info.flags &= ~DDSD_LINEARSIZE;
}
load_info.pitch = (hdr.width * hdr.pixelfmt.bpp + 7) >> 3;
if (load_info.pitch != hdr.pitch_or_linsize)
{
g_printerr ("Unexpected pitch (%u) set to %u\n",
hdr.pitch_or_linsize, (guint32) load_info.pitch);
}
load_info.linear_size = load_info.pitch * hdr.height;
}
/* Determine bytes-per-pixel and GIMP type needed */
if (load_info.fmt_flags & DDPF_FOURCC)
{
/* Compressed */
switch (load_info.comp_format)
{
case DDS_COMPRESS_BC4:
load_info.bpp = load_info.gimp_bpp = 1; /* Gray */
type = GIMP_GRAY;
break;
case DDS_COMPRESS_BC5:
load_info.bpp = load_info.gimp_bpp = 3; /* RGB */
type = GIMP_RGB;
break;
default:
load_info.bpp = load_info.gimp_bpp = 4; /* RGBA */
type = GIMP_RGB;
break;
}
precision = GIMP_PRECISION_U8_NON_LINEAR;
}
else
{
/* Uncompressed */
load_info.bpp = hdr.pixelfmt.bpp >> 3;
type = load_info.read_info.gimp_type;
/* Set up GIMP bytes-per-pixel */
if (load_info.read_info.gimp_type == GIMP_INDEXED)
{
load_info.gimp_bpp = 1;
if (load_info.read_info.use_alpha)
load_info.gimp_bpp += 1;
}
else
{
if (load_info.read_info.gimp_type == GIMP_RGB)
load_info.gimp_bpp = 3;
else /* load_info.read_info.gimp_type == GIMP_GRAY */
load_info.gimp_bpp = 1;
if (load_info.read_info.use_alpha)
load_info.gimp_bpp += 1;
if (load_info.read_info.output_bit_depth == 16)
load_info.gimp_bpp *= 2;
else if (load_info.read_info.output_bit_depth == 32)
load_info.gimp_bpp *= 4;
}
/* Set up canvas precision */
if (load_info.read_info.output_bit_depth == 8)
{
precision = GIMP_PRECISION_U8_NON_LINEAR;
}
else if (load_info.read_info.output_bit_depth == 16)
{
if (load_info.read_info.is_float)
precision = GIMP_PRECISION_HALF_LINEAR;
else
precision = GIMP_PRECISION_U16_NON_LINEAR;
}
else if (load_info.read_info.output_bit_depth == 32)
{
if (load_info.read_info.is_float)
precision = GIMP_PRECISION_FLOAT_LINEAR;
else
precision = GIMP_PRECISION_U32_NON_LINEAR;
}
}
/* Verify header information is accurate to avoid allocating more memory than is actually needed */
if (load_info.bpp < 1 ||
(load_info.linear_size > (file_size - sizeof (hdr))))
{
fclose (fp);
g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno),
_("Invalid or corrupted DDS header."));
return GIMP_PDB_EXECUTION_ERROR;
}
/* Generate GIMP image with set precision */
image = gimp_image_new_with_precision (load_info.width,
load_info.height,
type, precision);
if (! image)
{
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_NOMEM,
_("Could not allocate a new image."));
fclose (fp);
return GIMP_PDB_EXECUTION_ERROR;
}
/* Read palette for indexed DDS */
if (load_info.fmt_flags & DDPF_PALETTEINDEXED8)
{
const Babl *format = babl_format ("R'G'B' u8");
GimpPalette *palette = gimp_image_get_palette (image);
GeglColor *color = gegl_color_new (NULL);
load_info.palette = g_malloc (256 * 4);
if (fread (load_info.palette, 1, 1024, fp) != 1024)
{
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
_("Error reading palette."));
fclose (fp);
gimp_image_delete (image);
return GIMP_PDB_EXECUTION_ERROR;
}
for (i = 0; i < 1024; i += 4)
{
gint entry_num;
/* Looks like DDS indexed images have an alpha channel (or
* what else is this fourth byte?) and we just ignore it since
* our own palette colors are opaque?
*/
gegl_color_set_pixel (color, format, &load_info.palette[i]);
gimp_palette_add_entry (palette, NULL, color, &entry_num);
}
g_object_unref (color);
}
load_info.tile_height = gimp_tile_height ();
pixels = g_new (guchar, load_info.tile_height * load_info.width * load_info.gimp_bpp);
buf = g_malloc (load_info.linear_size);
if (load_info.cubemap_faces) /* Cubemap texture */
{
if ((load_info.cubemap_faces & DDSCAPS2_CUBEMAP_POSITIVEX) &&
! load_face (fp, &load_info, image, "(positive x)",
&layer_index, pixels, buf, read_mipmaps, error))
{
fclose (fp);
gimp_image_delete (image);
return GIMP_PDB_EXECUTION_ERROR;
}
if ((load_info.cubemap_faces & DDSCAPS2_CUBEMAP_NEGATIVEX) &&
! load_face (fp, &load_info, image, "(negative x)",
&layer_index, pixels, buf, read_mipmaps, error))
{
fclose (fp);
gimp_image_delete (image);
return GIMP_PDB_EXECUTION_ERROR;
}
if ((load_info.cubemap_faces & DDSCAPS2_CUBEMAP_POSITIVEY) &&
! load_face (fp, &load_info, image, "(positive y)",
&layer_index, pixels, buf, read_mipmaps, error))
{
fclose (fp);
gimp_image_delete (image);
return GIMP_PDB_EXECUTION_ERROR;
}
if ((load_info.cubemap_faces & DDSCAPS2_CUBEMAP_NEGATIVEY) &&
! load_face (fp, &load_info, image, "(negative y)",
&layer_index, pixels, buf, read_mipmaps, error))
{
fclose (fp);
gimp_image_delete (image);
return GIMP_PDB_EXECUTION_ERROR;
}
if ((load_info.cubemap_faces & DDSCAPS2_CUBEMAP_POSITIVEZ) &&
! load_face (fp, &load_info, image, "(positive z)",
&layer_index, pixels, buf, read_mipmaps, error))
{
fclose (fp);
gimp_image_delete (image);
return GIMP_PDB_EXECUTION_ERROR;
}
if ((load_info.cubemap_faces & DDSCAPS2_CUBEMAP_NEGATIVEZ) &&
! load_face (fp, &load_info, image, "(negative z)",
&layer_index, pixels, buf, read_mipmaps, error))
{
fclose (fp);
gimp_image_delete (image);
return GIMP_PDB_EXECUTION_ERROR;
}
}
else if (load_info.volume_slices > 0) /* Volume texture */
{
guint i, level;
gchar *plane;
for (i = 0; i < load_info.volume_slices; ++i)
{
plane = g_strdup_printf ("(z = %d)", i);
if (! load_layer (fp, &load_info, image, 0, plane,
&layer_index, pixels, buf, error))
{
g_free (plane);
fclose (fp);
gimp_image_delete (image);
return GIMP_PDB_EXECUTION_ERROR;
}
g_free (plane);
}
if (read_mipmaps)
{
for (level = 1; level < load_info.mipmaps; ++level)
{
int n = load_info.volume_slices >> level;
if (n < 1)
n = 1;
for (i = 0; i < n; ++i)
{
plane = g_strdup_printf ("(z = %d)", i);
if (! load_layer (fp, &load_info, image, level, plane,
&layer_index, pixels, buf, error))
{
g_free (plane);
fclose (fp);
gimp_image_delete (image);
return GIMP_PDB_EXECUTION_ERROR;
}
g_free (plane);
}
}
}
}
else if (load_info.array_items > 1) /* Texture Array */
{
guint i;
gchar *elem;
for (i = 0; i < load_info.array_items; ++i)
{
elem = g_strdup_printf ("(array element %d)", i);
if (! load_layer (fp, &load_info, image, 0, elem, &layer_index,
pixels, buf, error))
{
fclose (fp);
gimp_image_delete (image);
return GIMP_PDB_EXECUTION_ERROR;
}
if (! load_mipmaps (fp, &load_info, image, elem, &layer_index,
pixels, buf, read_mipmaps, error))
{
fclose (fp);
gimp_image_delete (image);
return GIMP_PDB_EXECUTION_ERROR;
}
g_free (elem);
}
}
else /* Standard 2D texture */
{
if (! load_layer (fp, &load_info, image, 0, "", &layer_index,
pixels, buf, error))
{
fclose (fp);
gimp_image_delete (image);
return GIMP_PDB_EXECUTION_ERROR;
}
if (! load_mipmaps (fp, &load_info, image, "", &layer_index,
pixels, buf, read_mipmaps, error))
{
fclose (fp);
gimp_image_delete (image);
return GIMP_PDB_EXECUTION_ERROR;
}
}
gimp_progress_update (1.0);
if (load_info.fmt_flags & DDPF_PALETTEINDEXED8)
g_free (load_info.palette);
g_free (buf);
g_free (pixels);
fclose (fp);
layers = gimp_image_list_layers (image);
if (! layers)
{
/* XXX This error should never happen, and probably it should be a
* CRITICAL/g_return_if_fail(). Yet let's just set it to the
* GError until we better handle the debug dialog for plug-ins. A
* pop-up with this message will be easier to track. No need to
* localize it though.
*/
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
"Oops! NULL image read! Please report this!");
return GIMP_PDB_EXECUTION_ERROR;
}
gimp_image_take_selected_layers (image, layers);
if (flip_import)
gimp_image_flip (image, GIMP_ORIENTATION_VERTICAL);
*ret_image = image;
return GIMP_PDB_SUCCESS;
}
/*
* Read data from standard header
*/
static gboolean
read_header (dds_header_t *hdr,
FILE *fp)
{
guchar buf[DDS_HEADERSIZE];
if (fread (buf, 1, DDS_HEADERSIZE, fp) != DDS_HEADERSIZE)
return FALSE;
hdr->magic = GETL32 (buf);
hdr->size = GETL32 (buf + 4);
hdr->flags = GETL32 (buf + 8);
hdr->height = GETL32 (buf + 12);
hdr->width = GETL32 (buf + 16);
hdr->pitch_or_linsize = GETL32 (buf + 20);
hdr->depth = GETL32 (buf + 24);
hdr->num_mipmaps = GETL32 (buf + 28);
hdr->pixelfmt.size = GETL32 (buf + 76);
hdr->pixelfmt.flags = GETL32 (buf + 80);
hdr->pixelfmt.fourcc[0] = buf[84];
hdr->pixelfmt.fourcc[1] = buf[85];
hdr->pixelfmt.fourcc[2] = buf[86];
hdr->pixelfmt.fourcc[3] = buf[87];
hdr->pixelfmt.bpp = GETL32 (buf + 88);
hdr->pixelfmt.rmask = GETL32 (buf + 92);
hdr->pixelfmt.gmask = GETL32 (buf + 96);
hdr->pixelfmt.bmask = GETL32 (buf + 100);
hdr->pixelfmt.amask = GETL32 (buf + 104);
hdr->caps.caps1 = GETL32 (buf + 108);
hdr->caps.caps2 = GETL32 (buf + 112);
/* GIMP-DDS special info */
if (GETL32 (buf + 32) == FOURCC ('G','I','M','P') &&
GETL32 (buf + 36) == FOURCC ('-','D','D','S'))
{
hdr->reserved.gimp_dds_special.magic1 = GETL32 (buf + 32);
hdr->reserved.gimp_dds_special.magic2 = GETL32 (buf + 36);
hdr->reserved.gimp_dds_special.version = GETL32 (buf + 40);
hdr->reserved.gimp_dds_special.extra_fourcc = GETL32 (buf + 44);
}
return TRUE;
}
/*
* Read data from DX10 header
*/
static gboolean
read_header_dx10 (dds_header_dx10_t *dx10hdr,
FILE *fp)
{
gchar buf[DDS_HEADERSIZE_DX10];
if (fread (buf, 1, DDS_HEADERSIZE_DX10, fp) != DDS_HEADERSIZE_DX10)
return FALSE;
dx10hdr->dxgiFormat = GETL32 (buf);
dx10hdr->resourceDimension = GETL32 (buf + 4);
dx10hdr->miscFlag = GETL32 (buf + 8);
dx10hdr->arraySize = GETL32 (buf + 12);
dx10hdr->reserved = GETL32 (buf + 16);
return TRUE;
}
/*
* Check data from standard header for validity
* Invalid header data is corrected where possible
*/
static gboolean
validate_header (dds_header_t *hdr,
GError **error)
{
guint fourcc;
/* Check ~ m a g i c ~ */
if (hdr->magic != FOURCC ('D','D','S',' '))
{
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL,
_("Invalid DDS format magic number."));
return FALSE;
}
/* Check pixel format flags
* If none are set, try to recover based on what information is available */
fourcc = GETL32 (hdr->pixelfmt.fourcc);
if (! (hdr->pixelfmt.flags & DDPF_RGB) &&
! (hdr->pixelfmt.flags & DDPF_ALPHA) &&
! (hdr->pixelfmt.flags & DDPF_BUMPDUDV) &&
! (hdr->pixelfmt.flags & DDPF_BUMPLUMINANCE) &&
! (hdr->pixelfmt.flags & DDPF_ZBUFFER) &&
! (hdr->pixelfmt.flags & DDPF_FOURCC) &&
! (hdr->pixelfmt.flags & DDPF_LUMINANCE) &&
! (hdr->pixelfmt.flags & DDPF_PALETTEINDEXED8))
{
g_message (_("File lacks expected pixel format flags! "
"Image may not be decoded correctly."));
switch (fourcc)
{
case FOURCC ('D','X','T','1'):
case FOURCC ('D','X','T','2'):
case FOURCC ('D','X','T','3'):
case FOURCC ('D','X','T','4'):
case FOURCC ('D','X','T','5'):
case FOURCC ('R','X','G','B'):
case FOURCC ('A','T','I','1'):
case FOURCC ('B','C','4','U'):
case FOURCC ('B','C','4','S'):
case FOURCC ('A','T','I','2'):
case FOURCC ('B','C','5','U'):
case FOURCC ('B','C','5','S'):
hdr->pixelfmt.flags |= DDPF_FOURCC;
break;
default:
switch (hdr->pixelfmt.bpp)
{
case 8:
if (hdr->pixelfmt.flags & DDPF_ALPHAPIXELS)
hdr->pixelfmt.flags |= DDPF_ALPHA;
else
hdr->pixelfmt.flags |= DDPF_LUMINANCE;
break;
case 16:
case 24:
case 32:
case 64:
hdr->pixelfmt.flags |= DDPF_RGB;
break;
default:
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
_("Invalid pixel format."));
return FALSE;
}
break;
}
}
/* Check all supported FourCC codes */
if ((hdr->pixelfmt.flags & DDPF_FOURCC) &&
fourcc != FOURCC ('D','X','T','1') &&
fourcc != FOURCC ('D','X','T','2') &&
fourcc != FOURCC ('D','X','T','3') &&
fourcc != FOURCC ('D','X','T','4') &&
fourcc != FOURCC ('D','X','T','5') &&
fourcc != FOURCC ('R','X','G','B') &&
fourcc != FOURCC ('A','T','I','1') &&
fourcc != FOURCC ('B','C','4','U') &&
fourcc != FOURCC ('B','C','4','S') &&
fourcc != FOURCC ('A','T','I','2') &&
fourcc != FOURCC ('B','C','5','U') &&
fourcc != FOURCC ('B','C','5','S') &&
fourcc != FOURCC ('D','X','1','0') &&
hdr->pixelfmt.fourcc[1] != 0)
{
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
_("Unsupported format (FourCC: %c%c%c%c, hex: %08x)"),
hdr->pixelfmt.fourcc[0],
hdr->pixelfmt.fourcc[1] != 0 ? hdr->pixelfmt.fourcc[1] : ' ',
hdr->pixelfmt.fourcc[2] != 0 ? hdr->pixelfmt.fourcc[2] : ' ',
hdr->pixelfmt.fourcc[3] != 0 ? hdr->pixelfmt.fourcc[3] : ' ',
GETL32 (hdr->pixelfmt.fourcc));
return FALSE;
}
/* Check bits-per-pixel */
if (hdr->pixelfmt.flags & DDPF_RGB)
{
if ((hdr->pixelfmt.bpp != 8) &&
(hdr->pixelfmt.bpp != 16) &&
(hdr->pixelfmt.bpp != 24) &&
(hdr->pixelfmt.bpp != 32) &&
(hdr->pixelfmt.bpp != 48) &&
(hdr->pixelfmt.bpp != 64) &&
(hdr->pixelfmt.bpp != 96) &&
(hdr->pixelfmt.bpp != 128))
{
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
_("Invalid bpp value for RGB data: %d"),
hdr->pixelfmt.bpp);
return FALSE;
}
}
else if (hdr->pixelfmt.flags & DDPF_LUMINANCE)
{
if ((hdr->pixelfmt.bpp != 8) &&
(hdr->pixelfmt.bpp != 16))
{
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
_("Invalid bpp value for luminance data: %d"),
hdr->pixelfmt.bpp);
return FALSE;
}
}
return TRUE;
}
/*
* Check data from DX10 header for validity
*/
static gboolean
validate_dx10_header (dds_header_dx10_t *dx10hdr,
dds_load_info_t *load_info,
GError **error)
{
if ((dx10hdr->resourceDimension != D3D10_RESOURCE_DIMENSION_TEXTURE1D) &&
(dx10hdr->resourceDimension != D3D10_RESOURCE_DIMENSION_TEXTURE2D) &&
(dx10hdr->resourceDimension != D3D10_RESOURCE_DIMENSION_TEXTURE3D))
{
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
_("Invalid DX10 header"));
return FALSE;
}
switch (dx10hdr->dxgiFormat)
{
case DXGI_FORMAT_BC1_TYPELESS:
case DXGI_FORMAT_BC1_UNORM:
case DXGI_FORMAT_BC1_UNORM_SRGB:
case DXGI_FORMAT_BC2_TYPELESS:
case DXGI_FORMAT_BC2_UNORM:
case DXGI_FORMAT_BC2_UNORM_SRGB:
case DXGI_FORMAT_BC3_TYPELESS:
case DXGI_FORMAT_BC3_UNORM:
case DXGI_FORMAT_BC3_UNORM_SRGB:
case DXGI_FORMAT_BC4_TYPELESS:
case DXGI_FORMAT_BC4_UNORM:
case DXGI_FORMAT_BC4_SNORM:
case DXGI_FORMAT_BC5_TYPELESS:
case DXGI_FORMAT_BC5_UNORM:
case DXGI_FORMAT_BC5_SNORM:
/* TODO: Implement BC6 format */
case DXGI_FORMAT_BC7_TYPELESS:
case DXGI_FORMAT_BC7_UNORM:
case DXGI_FORMAT_BC7_UNORM_SRGB:
/* Return early for supported compressed formats */
load_info->dxgi_format = dx10hdr->dxgiFormat & 0xFF;
return TRUE;
default:
/* Unset FourCC flag for uncompressed formats */
load_info->fmt_flags &= ~DDPF_FOURCC;
break;
}
if (! dxgiformat_supported (dx10hdr->dxgiFormat & 0xFF))
{
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
_("Unsupported DXGI Format: %u"),
dx10hdr->dxgiFormat & 0xFF);
return FALSE;
}
load_info->dxgi_format = dx10hdr->dxgiFormat & 0xFF;
return TRUE;
}
static const Babl *
premultiplied_variant (const Babl* format)
{
if (format == babl_format ("R'G'B'A u8"))
return babl_format ("R'aG'aB'aA u8");
else
g_printerr ("Add format %s to premultiplied_variant () %s: %d\n",
babl_get_name (format), __FILE__, __LINE__);
return format;
}
static gboolean
load_layer (FILE *fp,
dds_load_info_t *load_info,
GimpImage *image,
guint level,
gchar *prefix,
guint *layer_index,
guchar *pixels,
guchar *buf,
GError **error)
{
GeglBuffer *buffer;
const Babl *bablfmt = NULL;
gchar *babl_str = "";
GimpImageType type = GIMP_RGBA_IMAGE;
guint width = load_info->width >> level;
guint height = load_info->height >> level;
guint size = width * height * load_info->bpp;
gchar *layer_name;
GimpLayer *layer;
guint layerw;
gsize file_size;
gsize current_position;
gint x, y, n;
current_position = ftell (fp);
fseek (fp, 0L, SEEK_END);
file_size = ftell (fp);
fseek (fp, current_position, SEEK_SET);
if (width < 1) width = 1;
if (height < 1) height = 1;
/* Setup image type and Babl format */
if (load_info->fmt_flags & DDPF_FOURCC) /* Compressed */
{
/* Set Babl format */
switch (load_info->comp_format)
{
case DDS_COMPRESS_BC4:
type = GIMP_GRAY_IMAGE;
babl_str = "Y'";
break;
case DDS_COMPRESS_BC5:
type = GIMP_RGB_IMAGE;
babl_str = "R'G'B'";
break;
default:
type = GIMP_RGBA_IMAGE;
babl_str = "R'G'B'A";
break;
}
/* Set Babl precision */
if ((GETL32 (load_info->fourcc) == FOURCC ('D','X','1','0')) &&
(load_info->dxgi_format >= DXGI_FORMAT_BC6H_TYPELESS) &&
(load_info->dxgi_format <= DXGI_FORMAT_BC6H_SF16))
{
babl_str = g_strdup_printf ("%s %s", babl_str, "half");
}
else
{
babl_str = g_strdup_printf ("%s %s", babl_str, "u8");
}
}
else /* Uncompressed */
{
/* Set Babl format */
if (load_info->read_info.gimp_type == GIMP_INDEXED)
{
if (load_info->read_info.use_alpha)
type = GIMP_INDEXEDA_IMAGE;
else
type = GIMP_INDEXED_IMAGE;
}
else if (load_info->read_info.gimp_type == GIMP_RGB)
{
if (load_info->read_info.use_alpha)
{
type = GIMP_RGBA_IMAGE;
babl_str = "R'G'B'A";
}
else
{
type = GIMP_RGB_IMAGE;
babl_str = "R'G'B'";
}
}
else /* load_info->read_info.gimp_type == GIMP_GRAY */
{
if (load_info->read_info.use_alpha)
{
type = GIMP_GRAYA_IMAGE;
babl_str = "Y'A";
}
else
{
type = GIMP_GRAY_IMAGE;
babl_str = "Y'";
}
}
/* Set Babl precision */
if (load_info->read_info.is_float)
{
/* Floating-point */
if (load_info->read_info.output_bit_depth == 16)
babl_str = g_strdup_printf ("%s %s", babl_str, "half");
else /* load_info->read_info.output_bit_depth == 32 */
babl_str = g_strdup_printf ("%s %s", babl_str, "float");
}
else
{
/* Integer */
if (load_info->read_info.output_bit_depth == 32)
babl_str = g_strdup_printf ("%s %s", babl_str, "u32");
else if (load_info->read_info.output_bit_depth == 16)
babl_str = g_strdup_printf ("%s %s", babl_str, "u16");
else /* load_info->read_info.output_bit_depth == 8 */
babl_str = g_strdup_printf ("%s %s", babl_str, "u8");
}
}
if (! (load_info->read_info.gimp_type == GIMP_INDEXED))
bablfmt = babl_format (babl_str);
g_free (babl_str);
layer_name = (level) ? g_strdup_printf ("mipmap %d %s", level, prefix) :
g_strdup_printf ("main surface %s", prefix);
layer = gimp_layer_new (image, layer_name, width, height, type, 100,
gimp_image_get_default_new_layer_mode (image));
g_free (layer_name);
gimp_image_insert_layer (image, layer, NULL, *layer_index);
if (type == GIMP_INDEXED_IMAGE || type == GIMP_INDEXEDA_IMAGE)
bablfmt = gimp_drawable_get_format (GIMP_DRAWABLE (layer));
if ((*layer_index)++)
gimp_item_set_visible (GIMP_ITEM (layer), FALSE);
buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (layer));
layerw = gegl_buffer_get_width (buffer);
if (load_info->fmt_flags & DDPF_FOURCC)
{
size = ((width + 3) >> 2) * ((height + 3) >> 2);
/* Let Babl handle premultiplied format conversion */
if ((GETL32 (load_info->fourcc) == FOURCC ('D','X','T','2')) ||
(GETL32 (load_info->fourcc) == FOURCC ('D','X','T','4')))
bablfmt = premultiplied_variant (bablfmt);
if ((load_info->comp_format == DDS_COMPRESS_BC1) ||
(load_info->comp_format == DDS_COMPRESS_BC4))
size *= 8;
else
size *= 16;
}
if (size > (file_size - current_position) ||
size > load_info->linear_size)
{
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
_("Requested data exceeds size of file.\n"));
return FALSE;
}
if ((load_info->flags & DDSD_LINEARSIZE) &&
! fread (buf, size, 1, fp))
{
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
_("Unexpected EOF.\n"));
return FALSE;
}
if (! (load_info->fmt_flags & DDPF_FOURCC)) /* Read uncompressed pixel data */
{
guint rowstride = width * load_info->bpp;
guint32 sign_add[4] = { 0, 0, 0, 0 };
guint idx_r = 0, idx_b = 2;
/* Prior plug-in versions (3.9.91 and earlier) wrote the R and G channels reversed for RGB10A2. */
if ((load_info->gimp_version > 0) &&
(load_info->gimp_version <= 199003) &&
(load_info->d3d9_format == D3DFMT_A2R10G10B10))
{
g_printerr ("Switching incorrect red and green channels in RGB10A2 DDS "
"written by an older version of GIMP's DDS plug-in.\n");
idx_r = 2;
idx_b = 0;
}
/* Set up offset to apply to signed integer formats
* Per-channel to accommodate for mixed formats */
if (load_info->read_info.is_signed &&
(! load_info->read_info.is_float))
{
if (load_info->read_info.output_bit_depth == 8)
{
sign_add[0] = 128;
sign_add[1] = 128;
if (! (load_info->d3d9_format == D3DFMT_L6V5U5 ||
load_info->d3d9_format == D3DFMT_X8L8V8U8))
sign_add[2] = 128;
sign_add[3] = 128;
}
else if (load_info->read_info.output_bit_depth == 16)
{
sign_add[0] = 32768;
sign_add[1] = 32768;
sign_add[2] = 32768;
if (! (load_info->d3d9_format == D3DFMT_A2W10V10U10 ||
load_info->dxgi_format == DXGI_FORMAT_R10G10B10_SNORM_A2_UNORM))
sign_add[3] = 32768;
}
else /* load_info->read_info.output_bit_depth == 32 */
{
sign_add[0] = 2147483648;
sign_add[1] = 2147483648;
sign_add[2] = 2147483648;
sign_add[3] = 2147483648;
}
}
if ((load_info->flags & DDSD_PITCH) && (rowstride > load_info->pitch))
{
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
_("Requested data exceeds size of file.\n"));
return FALSE;
}
for (y = 0, n = 0; y < height; ++y, ++n)
{
if (n >= load_info->tile_height)
{
gegl_buffer_set (buffer, GEGL_RECTANGLE (0, y - n, layerw, n), 0,
bablfmt, pixels, GEGL_AUTO_ROWSTRIDE);
n = 0;
gimp_progress_update ((gdouble) y / (gdouble) load_info->height);
}
if (load_info->flags & DDSD_PITCH)
{
current_position = ftell (fp);
if (rowstride > (file_size - current_position))
{
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
_("Requested data exceeds size of file.\n"));
return FALSE;
}
if (! fread (buf, rowstride, 1, fp))
{
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
_("Unexpected EOF.\n"));
return FALSE;
}
}
for (x = 0; x < layerw; ++x)
{
guint pos = (n * layerw + x) * load_info->gimp_bpp;
guint buf_reads = 0;
guchar read_buf;
guint32 ch_registers[4];
memset (ch_registers, 0, sizeof (ch_registers));
/* Format-agnostic bit-reader, driven by the 'format_read_info' table.
* Reads one bit at a time from source bytes into per-channel registers.
* While somewhat simplistic, reading bit-by-bit allows us to handle channels
* that cross byte boundaries trivially, and without the need for look-ahead.
*/
read_buf = *buf;
for (gint reg = 0; reg < 4; reg++)
{
const guchar ch = load_info->read_info.channel_order[reg];
const guchar ch_bits = load_info->read_info.channel_bits[reg];
const guint32 write_bit = 1 << (ch_bits - 1);
if (! ch_bits) continue;
/* Note: bits are written to the registers in the opposite order they're read in */
for (gint bit = 0; bit < ch_bits; bit++)
{
ch_registers[ch] >>= 1;
ch_registers[ch] |= read_buf & 1 ? write_bit : 0;
read_buf >>= 1;
buf_reads++;
if (buf_reads == 8)
{
/* Roll-over to next byte */
buf++;
read_buf = *buf;
buf_reads = 0;
}
}
/* Most DXGI small-float formats have 5 exponent bits, so can be interpreted as 16-bit floats with a simple shift.
* Integers meanwhile must be properly requantized to the output range */
if (load_info->read_info.is_float)
{
guint shift = load_info->read_info.output_bit_depth - ch_bits;
if (load_info->dxgi_format == DXGI_FORMAT_R9G9B9E5_SHAREDEXP ||
load_info->dxgi_format == DXGI_FORMAT_R10G10B10_7E3_A2_FLOAT ||
load_info->dxgi_format == DXGI_FORMAT_R10G10B10_6E4_A2_FLOAT)
/* Skip shifting for float formats that require special handling */
shift = 0;
else if (! load_info->read_info.is_signed)
/* Don't shift into sign bit for unsigned floats, eg. R11G11B10 */
shift -= 1;
ch_registers[ch] = ch_registers[ch] << shift;
}
else
{
ch_registers[ch] = requantize_component (ch_registers[ch], ch_bits,
load_info->read_info.output_bit_depth);
}
}
/* Special cases for formats requiring extra decoding */
if (load_info->dxgi_format == DXGI_FORMAT_R9G9B9E5_SHAREDEXP)
float_from_9e5 (ch_registers);
else if (load_info->dxgi_format == DXGI_FORMAT_R10G10B10_7E3_A2_FLOAT)
float_from_7e3a2 (ch_registers);
else if (load_info->dxgi_format == DXGI_FORMAT_R10G10B10_6E4_A2_FLOAT)
float_from_6e4a2 (ch_registers);
else if (load_info->d3d9_format == D3DFMT_CxV8U8)
reconstruct_z (ch_registers);
/* Clear alpha to all 1s instead of all 0s */
if (! load_info->read_info.use_alpha)
ch_registers[3] = G_MAXUINT32;
/* Output converted values to canvas pixels */
if (load_info->read_info.gimp_type == GIMP_RGB)
{
if (load_info->read_info.output_bit_depth == 8)
{
guchar *pixel8 = (guchar *) &pixels[pos];
pixel8[0] = ch_registers[0] + sign_add[0];
pixel8[1] = ch_registers[1] + sign_add[1];
pixel8[2] = ch_registers[2] + sign_add[2];
if (load_info->read_info.use_alpha)
pixel8[3] = ch_registers[3] + sign_add[3];
}
else if (load_info->read_info.output_bit_depth == 16)
{
/* Variable indices for R and B to accommodate RGB10A2 fixup */
guint16 *pixel16 = (guint16 *) &pixels[pos];
pixel16[0] = ch_registers[idx_r] + sign_add[0];
pixel16[1] = ch_registers[1] + sign_add[1];
pixel16[2] = ch_registers[idx_b] + sign_add[2];
if (load_info->read_info.use_alpha)
pixel16[3] = ch_registers[3] + sign_add[3];
}
else /* load_info->read_info.output_bit_depth == 32 */
{
guint32 *pixel32 = (guint32 *) &pixels[pos];
pixel32[0] = (guint64) ch_registers[0] + sign_add[0];
pixel32[1] = (guint64) ch_registers[1] + sign_add[1];
pixel32[2] = (guint64) ch_registers[2] + sign_add[2];
if (load_info->read_info.use_alpha)
pixel32[3] = (guint64) ch_registers[3] + sign_add[3];
}
}
else if (load_info->read_info.gimp_type == GIMP_GRAY)
{
if (load_info->read_info.output_bit_depth == 8)
{
guchar *pixel8 = (guchar *) &pixels[pos];
pixel8[0] = ch_registers[0] + sign_add[0];
if (load_info->read_info.use_alpha)
pixel8[1] = ch_registers[3] + sign_add[3];
}
else if (load_info->read_info.output_bit_depth == 16)
{
guint16 *pixel16 = (guint16 *) &pixels[pos];
pixel16[0] = ch_registers[0] + sign_add[0];
if (load_info->read_info.use_alpha)
pixel16[1] = ch_registers[3] + sign_add[3];
}
else /* load_info->read_info.output_bit_depth == 32 */
{
guint32 *pixel32 = (guint32 *) &pixels[pos];
pixel32[0] = (guint64) ch_registers[0] + sign_add[0];
if (load_info->read_info.use_alpha)
pixel32[1] = (guint64) ch_registers[3] + sign_add[3];
}
}
else /* load_info->read_info.gimp_type == GIMP_INDEXED */
{
pixels[pos] = ch_registers[0] & 0xFF;
if (load_info->read_info.use_alpha)
pixels[pos + 1] = ch_registers[3] & 0xFF;
}
}
}
gegl_buffer_set (buffer, GEGL_RECTANGLE (0, y - n, layerw, n), 0,
bablfmt, pixels, GEGL_AUTO_ROWSTRIDE);
}
else /* Read compressed pixel data */
{
guchar *dst;
dst = g_malloc ((gsize) width * height * load_info->gimp_bpp);
memset (dst, 0, (gsize) width * height * load_info->gimp_bpp);
/* Initialize alpha to all 1s instead of all 0s */
if (load_info->gimp_bpp == 4)
{
guchar *dst_line;
dst_line = dst;
for (y = 0; y < height; ++y)
{
for (x = 0; x < width; ++x)
{
dst_line[(x * 4) + 3] = 255;
}
dst_line += width * 4;
}
}
dxt_decompress (dst, buf, load_info->comp_format, size, width, height,
load_info->gimp_bpp, load_info->fmt_flags & DDPF_NORMAL);
/* Prior plug-in versions (before 3.9.90) wrote the R and G channels reversed for BC5. */
if ((load_info->gimp_version > 0) &&
(load_info->gimp_version <= 199002) &&
(load_info->comp_format == DDS_COMPRESS_BC5))
{
g_printerr ("Switching incorrect red and green channels in BC5 DDS "
"written by an older version of GIMP's DDS plug-in.\n");
for (y = 0; y < height; ++y)
{
for (x = 0; x < width; ++x)
{
guchar tmpG;
guint pix_width = width * load_info->gimp_bpp;
guint x_width = x * load_info->gimp_bpp;
tmpG = dst[y * pix_width + x_width];
dst[y * pix_width + x_width] = dst[y * pix_width + x_width + 1];
dst[y * pix_width + x_width + 1] = tmpG;
}
}
}
for (y = 0, n = 0; y < height; ++y, ++n)
{
if (n >= load_info->tile_height)
{
gegl_buffer_set (buffer, GEGL_RECTANGLE (0, y - n, layerw, n), 0,
bablfmt, pixels, GEGL_AUTO_ROWSTRIDE);
n = 0;
gimp_progress_update ((gdouble) y / (gdouble) load_info->height);
}
memcpy (pixels + n * layerw * load_info->gimp_bpp,
dst + y * layerw * load_info->gimp_bpp,
width * load_info->gimp_bpp);
}
gegl_buffer_set (buffer, GEGL_RECTANGLE (0, y - n, layerw, n), 0,
bablfmt, pixels, GEGL_AUTO_ROWSTRIDE);
g_free (dst);
}
gegl_buffer_flush (buffer);
g_object_unref (buffer);
/* Decode files with GIMP-specific encodings */
if (load_info->gimp_version > 0)
{
switch (GETL32 (load_info->gimp_fourcc))
{
case FOURCC ('A','E','X','P'):
decode_alpha_exponent (GIMP_DRAWABLE (layer));
break;
case FOURCC ('Y','C','G','1'):
decode_ycocg (GIMP_DRAWABLE (layer));
break;
case FOURCC ('Y','C','G','2'):
decode_ycocg_scaled (GIMP_DRAWABLE (layer));
break;
default:
break;
}
}
return TRUE;
}
static gboolean
load_mipmaps (FILE *fp,
dds_load_info_t *load_info,
GimpImage *image,
gchar *prefix,
guint *layer_index,
guchar *pixels,
guchar *buf,
gboolean read_mipmaps,
GError **error)
{
guint level;
if (read_mipmaps)
{
for (level = 1; level < load_info->mipmaps; ++level)
{
if (! load_layer (fp, load_info, image, level, prefix, layer_index,
pixels, buf, error))
return FALSE;
}
}
else
{
/* Skip past mipmaps, as simply not reading them leaves us in the wrong pos for subsequent layers */
for (level = 1; level < load_info->mipmaps; ++level)
{
guint width = MAX (1, load_info->width >> level);
guint height = MAX (1, load_info->height >> level);
guint size = load_info->linear_size >> (2 * level);
if (load_info->fmt_flags & DDPF_FOURCC)
{
size = ((width + 3) >> 2) * ((height + 3) >> 2);
if ((load_info->comp_format == DDS_COMPRESS_BC1) ||
(load_info->comp_format == DDS_COMPRESS_BC4))
size *= 8;
else
size *= 16;
}
fseek (fp, size, SEEK_CUR);
}
}
return TRUE;
}
static gboolean
load_face (FILE *fp,
dds_load_info_t *load_info,
GimpImage *image,
gchar *prefix,
guint *layer_index,
guchar *pixels,
guchar *buf,
gboolean read_mipmaps,
GError **error)
{
if (! load_layer (fp, load_info, image, 0, prefix,
layer_index, pixels, buf, error))
return FALSE;
return load_mipmaps (fp, load_info, image, prefix, layer_index,
pixels, buf, read_mipmaps, error);
}
static gboolean
load_dialog (GimpProcedure *procedure,
GimpProcedureConfig *config)
{
GtkWidget *dialog;
GtkWidget *vbox;
gboolean run;
dialog = gimp_procedure_dialog_new (procedure,
config,
_("Open DDS"));
vbox = gimp_procedure_dialog_fill_box (GIMP_PROCEDURE_DIALOG (dialog),
"dds-read-box",
"load-mipmaps",
"flip-image",
NULL);
gtk_box_set_spacing (GTK_BOX (vbox), 8);
gtk_container_set_border_width (GTK_CONTAINER (vbox), 8);
gimp_procedure_dialog_fill (GIMP_PROCEDURE_DIALOG (dialog),
"dds-read-box", NULL);
gtk_widget_show (dialog);
run = gimp_procedure_dialog_run (GIMP_PROCEDURE_DIALOG (dialog));
gtk_widget_destroy (dialog);
return run;
}