/* * DDS PIKA plugin * * Copyright (C) 2004-2012 Shawn Kirst , * with parts (C) 2003 Arne Reuter 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 #include #include #include #include #include #include #include #include #include "ddswrite.h" #include "dds.h" #include "dxt.h" #include "mipmap.h" #include "endian_rw.h" #include "imath.h" #include "color.h" static gboolean write_image (FILE *fp, PikaImage *image, PikaDrawable *drawable, PikaProcedureConfig *config); static gboolean save_dialog (PikaImage *image, PikaDrawable *drawable, PikaProcedure *procedure, PikaProcedureConfig *config); static const char *cubemap_face_names[4][6] = { { "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" } }; static PikaImage *global_image = NULL; 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 GtkWidget *transparent_check; static struct { gint format; DXGI_FORMAT dxgi_format; gint bpp; gint alpha; guint rmask; guint gmask; guint bmask; guint amask; } format_info[] = { { DDS_FORMAT_RGB8, DXGI_FORMAT_UNKNOWN, 3, 0, 0x00ff0000, 0x0000ff00, 0x000000ff, 0x00000000}, { DDS_FORMAT_RGBA8, DXGI_FORMAT_B8G8R8A8_UNORM, 4, 1, 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000}, { DDS_FORMAT_BGR8, DXGI_FORMAT_UNKNOWN, 3, 0, 0x000000ff, 0x0000ff00, 0x00ff0000, 0x00000000}, { DDS_FORMAT_ABGR8, DXGI_FORMAT_R8G8B8A8_UNORM, 4, 1, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000}, { DDS_FORMAT_R5G6B5, DXGI_FORMAT_B5G6R5_UNORM, 2, 0, 0x0000f800, 0x000007e0, 0x0000001f, 0x00000000}, { DDS_FORMAT_RGBA4, DXGI_FORMAT_B4G4R4A4_UNORM, 2, 1, 0x00000f00, 0x000000f0, 0x0000000f, 0x0000f000}, { DDS_FORMAT_RGB5A1, DXGI_FORMAT_B5G5R5A1_UNORM, 2, 1, 0x00007c00, 0x000003e0, 0x0000001f, 0x00008000}, { DDS_FORMAT_RGB10A2, DXGI_FORMAT_R10G10B10A2_UNORM, 4, 1, 0x000003ff, 0x000ffc00, 0x3ff00000, 0xc0000000}, { DDS_FORMAT_R3G3B2, DXGI_FORMAT_UNKNOWN, 1, 0, 0x000000e0, 0x0000001c, 0x00000003, 0x00000000}, { DDS_FORMAT_A8, DXGI_FORMAT_A8_UNORM, 1, 0, 0x00000000, 0x00000000, 0x00000000, 0x000000ff}, { DDS_FORMAT_L8, DXGI_FORMAT_R8_UNORM, 1, 0, 0x000000ff, 0x000000ff, 0x000000ff, 0x00000000}, { DDS_FORMAT_L8A8, DXGI_FORMAT_UNKNOWN, 2, 1, 0x000000ff, 0x000000ff, 0x000000ff, 0x0000ff00}, { DDS_FORMAT_AEXP, DXGI_FORMAT_B8G8R8A8_UNORM, 4, 1, 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000}, { DDS_FORMAT_YCOCG, DXGI_FORMAT_B8G8R8A8_UNORM, 4, 1, 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000} }; static gboolean check_mipmaps (gint savetype) { 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; max_surfaces = INT_MAX; } layers = pika_image_list_layers (global_image); num_layers = g_list_length (layers); w = pika_image_get_width (global_image); h = pika_image_get_height (global_image); 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; /* check for a valid cubemap with mipmap layers */ if (num_layers > 6) { /* check that mipmap layers are in order for a cubemap */ if (! check_mipmaps (DDS_SAVE_CUBEMAP)) return FALSE; /* invalidate cubemap faces */ for (i = 0; i < 6; ++i) cubemap_faces[i] = NULL; /* find the mipmap level 0 layers */ 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; layer_name = (char *) pika_item_get_name (PIKA_ITEM (drawable)); 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; } } } } } /* check for 6 valid faces */ for (i = 0; i < 6; ++i) { if (cubemap_faces[i] == NULL) { cubemap = FALSE; break; } } /* make sure they are all the same type */ 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) { /* invalidate cubemap faces */ 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; } } } } } /* check for 6 valid faces */ for (i = 0; i < 6; ++i) { if (cubemap_faces[i] == NULL) { cubemap = FALSE; break; } } /* make sure they are all the same size */ 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; } } /* make sure they are all the same type */ 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; /* make sure all layers are the same size */ 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) { /* make sure all layers are the same type */ 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; if (check_mipmaps (DDS_SAVE_ARRAY)) return 1; layers = pika_image_list_layers (image); num_layers = g_list_length (layers); if (num_layers > 1) { GList *list; array = TRUE; /* make sure all layers are the same size */ 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) { /* make sure all layers are the same type */ 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); 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)) { if ((pika_drawable_get_width (list->data) == w) && (pika_drawable_get_height (list->data) == h)) { elements++; } } g_list_free (layers); return elements; } PikaPDBStatusType write_dds (GFile *file, PikaImage *image, PikaDrawable *drawable, gboolean interactive, PikaProcedure *procedure, PikaProcedureConfig *config, gboolean is_duplicate_image) { FILE *fp; gint rc = 0; gint compression; gint mipmaps; gint savetype; compression = pika_procedure_config_get_choice_id (config, "compression-format"); g_object_get (config, "save-type", &savetype, "mipmaps", &mipmaps, NULL); global_image = image; is_mipmap_chain_valid = check_mipmaps (savetype); is_cubemap = check_cubemap (image); is_volume = check_volume (image); is_array = check_array (image); 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 { if (savetype == DDS_SAVE_CUBEMAP && ! is_cubemap) { g_message ("DDS: Cannot save image as cube map"); return PIKA_PDB_EXECUTION_ERROR; } if (savetype == DDS_SAVE_VOLUMEMAP && ! is_volume) { g_message ("DDS: Cannot save image as volume map"); return PIKA_PDB_EXECUTION_ERROR; } if (savetype == DDS_SAVE_VOLUMEMAP && compression != DDS_COMPRESS_NONE) { g_message ("DDS: Cannot save volume map with compression"); return PIKA_PDB_EXECUTION_ERROR; } if (mipmaps == DDS_MIPMAP_EXISTING && ! is_mipmap_chain_valid) { g_message ("DDS: Cannot save with existing mipmaps as the mipmap chain is incomplete"); return PIKA_PDB_EXECUTION_ERROR; } } 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; } pika_progress_init_printf ("Saving %s:", pika_file_get_utf8_name (file)); /* 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 swap_rb (unsigned char *pixels, unsigned int n, int bpp) { unsigned int i; unsigned char t; 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 alpha_exp (unsigned char *dst, int r, int g, int b, int a) { float ar, ag, ab, aa; ar = (float)r / 255.0f; ag = (float)g / 255.0f; ab = (float)b / 255.0f; aa = MAX (ar, MAX (ag, ab)); if (aa < 1e-04f) { dst[0] = b; dst[1] = g; dst[2] = r; dst[3] = 255; return; } ar /= aa; ag /= aa; ab /= aa; r = (int)floorf (255.0f * ar + 0.5f); g = (int)floorf (255.0f * ag + 0.5f); b = (int)floorf (255.0f * ab + 0.5f); a = (int)floorf (255.0f * aa + 0.5f); dst[0] = MAX (0, MIN (255, b)); dst[1] = MAX (0, MIN (255, g)); dst[2] = MAX (0, MIN (255, r)); dst[3] = MAX (0, MIN (255, a)); } static void convert_pixels (unsigned char *dst, unsigned char *src, int format, int w, int h, int d, int bpp, unsigned char *palette, int mipmaps) { unsigned int i, num_pixels; unsigned char r, g, b, a; 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: PUTL16(&dst[2 * i], pack_r5g6b5(r, g, b)); break; case DDS_FORMAT_RGBA4: PUTL16(&dst[2 * i], pack_rgba4(r, g, b, a)); break; case DDS_FORMAT_RGB5A1: PUTL16(&dst[2 * i], pack_rgb5a1(r, g, b, a)); break; case DDS_FORMAT_RGB10A2: PUTL32(&dst[4 * i], pack_rgb10a2(r, g, b, a)); break; case DDS_FORMAT_R3G3B2: dst[i] = pack_r3g3b2(r, g, b); break; case DDS_FORMAT_A8: dst[i] = a; break; case DDS_FORMAT_L8: dst[i] = rgb_to_luminance (r, g, b); break; case DDS_FORMAT_L8A8: dst[2 * i + 0] = rgb_to_luminance (r, g, b); dst[2 * i + 1] = a; break; case DDS_FORMAT_YCOCG: dst[4 * i] = a; RGB_to_YCoCg (&dst[4 * i], r, g, b); break; case DDS_FORMAT_AEXP: alpha_exp (&dst[4 * i], r, g, b, a); break; default: break; } } } static void get_mipmap_chain (unsigned char *dst, int w, int h, int bpp, PikaImage *image, PikaDrawable *drawable) { 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); /* we need BGRX or BGRA */ if (bpp >= 3) swap_rb (dst + offset, mipw * miph, bpp); offset += (mipw * miph * bpp); w = mipw; h = miph; } } static void write_layer (FILE *fp, PikaImage *image, PikaDrawable *drawable, PikaProcedureConfig *config, int w, int h, int bpp, int fmtbpp, int num_mipmaps) { 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; gint offset; gint colors; gint compression; gint mipmaps; gint pixel_format; gboolean perceptual_metric; gint flags = 0; g_object_get (config, "perceptual-metric", &perceptual_metric, "mipmaps", &mipmaps, NULL); compression = pika_procedure_config_get_choice_id (config, "compression-format"); pixel_format = pika_procedure_config_get_choice_id (config, "format"); 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; } } /* we want and assume BGRA ordered pixels for bpp >= 3 from here and onwards */ 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) { /* pre-convert indexed images to RGB for better quality mipmaps if a pixel format conversion is requested */ 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); mipmap_filter = pika_procedure_config_get_choice_id (config, "mipmap-filter"); mipmap_wrap = pika_procedure_config_get_choice_id (config, "mipmap-wrap"); 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; } offset = 0; 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); mipmap_filter = pika_procedure_config_get_choice_id (config, "mipmap-filter"); mipmap_wrap = pika_procedure_config_get_choice_id (config, "mipmap-wrap"); 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 write_volume_mipmaps (FILE *fp, PikaImage *image, PikaProcedureConfig *config, GList *layers, int w, int h, int d, int bpp, int fmtbpp, int num_mipmaps) { 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); 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"); 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; } /* we want and assume BGRA ordered pixels for bpp >= 3 from here and onwards */ if (bpp >= 3) swap_rb (src, w * h * d, bpp); /* pre-convert indexed images to RGB for better mipmaps if a pixel format conversion is requested */ 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 write_image (FILE *fp, PikaImage *image, PikaDrawable *drawable, PikaProcedureConfig *config) { PikaImageType drawable_type; PikaImageBaseType basetype; gint i, w, h; gint bpp = 0; gint fmtbpp = 0; gint has_alpha = 0; 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; guint fourcc = 0; DXGI_FORMAT dxgi_format = DXGI_FORMAT_UNKNOWN; gint32 num_layers; GList *layers; GList *list; guchar *cmap; gint colors; guchar zero[4] = {0, 0, 0, 0}; gint is_dx10 = 0; gint array_size = 1; gint compression; gint mipmaps; gint savetype; gint pixel_format; gint transindex; gboolean flip_export; g_object_get (config, "transparent-index", &transindex, "flip-image", &flip_export, "mipmaps", &mipmaps, "save-type", &savetype, NULL); compression = pika_procedure_config_get_choice_id (config, "compression-format"); pixel_format = pika_procedure_config_get_choice_id (config, "format"); 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) { w = pika_drawable_get_width (drawable); h = pika_drawable_get_height (drawable); } else { w = pika_image_get_width (image); 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; default: break; } 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; has_alpha = 0; rmask = bmask = gmask = amask = 0; } else { fmtbpp = 1; has_alpha = 0; rmask = 0x000000ff; gmask = bmask = amask = 0; dxgi_format = DXGI_FORMAT_R8_UNORM; } } else if (bpp == 2) { if (basetype == PIKA_INDEXED) { fmtbpp = 1; has_alpha = 0; rmask = gmask = bmask = amask = 0; } else { fmtbpp = 2; has_alpha = 1; 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; has_alpha = 1; rmask = 0x00ff0000; gmask = 0x0000ff00; bmask = 0x000000ff; amask = 0xff000000; dxgi_format = DXGI_FORMAT_B8G8R8A8_UNORM; } memset (hdr, 0, DDS_HEADERSIZE); PUTL32(hdr, FOURCC ('D','D','S',' ')); PUTL32(hdr + 4, 124); PUTL32(hdr + 12, h); PUTL32(hdr + 16, w); PUTL32(hdr + 76, 32); if (compression == DDS_COMPRESS_NONE) { PUTL32(hdr + 88, fmtbpp << 3); PUTL32(hdr + 92, rmask); PUTL32(hdr + 96, gmask); PUTL32(hdr + 100, bmask); PUTL32(hdr + 104, amask); } /* put some information 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); flags = DDSD_CAPS | DDSD_PIXELFORMAT | DDSD_WIDTH | DDSD_HEIGHT; caps = DDSCAPS_TEXTURE; if (mipmaps) { flags |= DDSD_MIPMAPCOUNT; caps |= (DDSCAPS_COMPLEX | DDSCAPS_MIPMAP); num_mipmaps = get_num_mipmaps (w, h); } else { num_mipmaps = 1; } if ((savetype == DDS_SAVE_CUBEMAP) && is_cubemap) { caps |= DDSCAPS_COMPLEX; caps2 |= (DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_ALL_FACES); } else if ((savetype == DDS_SAVE_VOLUMEMAP) && is_volume) { PUTL32(hdr + 24, num_layers); /* depth */ flags |= DDSD_DEPTH; caps |= DDSCAPS_COMPLEX; caps2 |= DDSCAPS2_VOLUME; } PUTL32(hdr + 28, num_mipmaps); PUTL32(hdr + 108, caps); PUTL32(hdr + 112, caps2); if (compression == DDS_COMPRESS_NONE) { 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); PUTL32 (hdr + 20, w * fmtbpp); /* pitch */ PUTL32 (hdr + 80, pflags); /* * 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 (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')); } } else { 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; //is_dx10 = 1; break; case DDS_COMPRESS_BC5: fourcc = FOURCC ('A','T','I','2'); dxgi_format = DXGI_FORMAT_BC5_UNORM; //is_dx10 = 1; 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); size = ((w + 3) >> 2) * ((h + 3) >> 2); if ((compression == DDS_COMPRESS_BC1) || (compression == DDS_COMPRESS_BC4)) size *= 8; else size *= 16; PUTL32 (hdr + 20, size); /* linear size */ /* * 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')); } } /* texture arrays require a DX10 header */ if (savetype == DDS_SAVE_ARRAY) is_dx10 = 1; if (is_dx10) { array_size = ((savetype == DDS_SAVE_SELECTED_LAYER || savetype == DDS_SAVE_VISIBLE_LAYERS) ? 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); /* update main header accordingly */ 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); /* write palette for indexed images */ if ((basetype == PIKA_INDEXED) && (pixel_format == DDS_FORMAT_DEFAULT) && (compression == DDS_COMPRESS_NONE)) { 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); } for (; i < 256; ++i) fwrite (zero, 1, 4, fp); } if (savetype == DDS_SAVE_CUBEMAP) { 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); } } else if (savetype == DDS_SAVE_VOLUMEMAP) { 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); } else if (savetype == DDS_SAVE_ARRAY) { 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 gboolean combo_sensitivity_func (gint value, gpointer data) { GtkTreeModel *model; GtkTreeIter iter; model = gtk_combo_box_get_model (GTK_COMBO_BOX (data)); if (pika_int_store_lookup_by_value (model, value, &iter)) { gpointer insensitive; gtk_tree_model_get (model, &iter, PIKA_INT_STORE_USER_DATA, &insensitive, -1); return ! GPOINTER_TO_INT (insensitive); } return TRUE; } static void combo_set_item_sensitive (GtkWidget *widget, gint value, gboolean sensitive) { GtkTreeModel *model; GtkTreeIter iter; model = gtk_combo_box_get_model (GTK_COMBO_BOX (widget)); if (pika_int_store_lookup_by_value (model, value, &iter)) { gtk_list_store_set (GTK_LIST_STORE (model), &iter, PIKA_INT_STORE_USER_DATA, ! GINT_TO_POINTER (sensitive), -1); } } static void config_notify (PikaProcedureConfig *config, const GParamSpec *pspec, PikaProcedureDialog *dialog) { if (! strcmp (pspec->name, "compression-format")) { gint compression; compression = pika_procedure_config_get_choice_id (config, "compression-format"); pika_procedure_dialog_set_sensitive (PIKA_PROCEDURE_DIALOG (dialog), "format", compression == DDS_COMPRESS_NONE, NULL, NULL, FALSE); pika_procedure_dialog_set_sensitive (PIKA_PROCEDURE_DIALOG (dialog), "perceptual-metric", compression != DDS_COMPRESS_NONE, NULL, NULL, FALSE); } else if (! strcmp (pspec->name, "save-type")) { gint savetype; GtkWidget *widget; GtkWidget *combo; g_object_get (config, "save-type", &savetype, NULL); switch (savetype) { case DDS_SAVE_SELECTED_LAYER: case DDS_SAVE_VISIBLE_LAYERS: case DDS_SAVE_CUBEMAP: case DDS_SAVE_ARRAY: pika_procedure_dialog_set_sensitive (PIKA_PROCEDURE_DIALOG (dialog), "compression-format", TRUE, NULL, NULL, FALSE); break; case DDS_SAVE_VOLUMEMAP: g_object_set (config, "compression-format", "none", NULL); pika_procedure_dialog_set_sensitive (PIKA_PROCEDURE_DIALOG (dialog), "compression-format", FALSE, NULL, NULL, FALSE); break; } widget = pika_procedure_dialog_get_widget (PIKA_PROCEDURE_DIALOG (dialog), "mipmaps", G_TYPE_NONE); combo = pika_label_int_widget_get_widget (PIKA_LABEL_INT_WIDGET (widget)); combo_set_item_sensitive (combo, DDS_MIPMAP_EXISTING, check_mipmaps (savetype)); } 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, "mipmaps", &mipmaps, NULL); pika_procedure_dialog_set_sensitive (PIKA_PROCEDURE_DIALOG (dialog), "mipmap-filter", mipmaps == DDS_MIPMAP_GENERATE, NULL, NULL, FALSE); pika_procedure_dialog_set_sensitive (PIKA_PROCEDURE_DIALOG (dialog), "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); } else if (! strcmp (pspec->name, "transparent-color")) { gboolean transparent_color; g_object_get (config, "transparent-color", &transparent_color, NULL); if (transparent_check && gtk_widget_get_sensitive (transparent_check)) pika_procedure_dialog_set_sensitive (PIKA_PROCEDURE_DIALOG (dialog), "transparent-index", transparent_color, NULL, NULL, FALSE); } else if (! strcmp (pspec->name, "gamma-correct")) { gboolean gamma_correct; gboolean srgb; g_object_get (config, "gamma-correct", &gamma_correct, "srgb", &srgb, NULL); pika_procedure_dialog_set_sensitive (PIKA_PROCEDURE_DIALOG (dialog), "srgb", gamma_correct, NULL, NULL, FALSE); pika_procedure_dialog_set_sensitive (PIKA_PROCEDURE_DIALOG (dialog), "gamma", (gamma_correct && ! srgb), NULL, NULL, FALSE); } else if (! strcmp (pspec->name, "srgb")) { gboolean gamma_correct; gboolean srgb; g_object_get (config, "gamma-correct", &gamma_correct, "srgb", &srgb, NULL); pika_procedure_dialog_set_sensitive (PIKA_PROCEDURE_DIALOG (dialog), "gamma", (gamma_correct && ! srgb), NULL, NULL, FALSE); } else if (! strcmp (pspec->name, "preserve-alpha-coverage")) { gboolean preserve_alpha_coverage; g_object_get (config, "preserve-alpha-coverage", &preserve_alpha_coverage, NULL); pika_procedure_dialog_set_sensitive (PIKA_PROCEDURE_DIALOG (dialog), "alpha-test-threshold", preserve_alpha_coverage, NULL, NULL, FALSE); } } static gboolean save_dialog (PikaImage *image, PikaDrawable *drawable, PikaProcedure *procedure, PikaProcedureConfig *config) { GtkWidget *dialog; GtkWidget *widget; GtkWidget *combo; GtkListStore *store; PikaImageBaseType base_type; gboolean run; base_type = pika_image_get_base_type (image); if (is_cubemap || is_volume || is_array) g_object_set (config, "save-type", DDS_SAVE_SELECTED_LAYER, NULL); dialog = pika_save_procedure_dialog_new (PIKA_SAVE_PROCEDURE (procedure), PIKA_PROCEDURE_CONFIG (config), image); gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE); transparent_check = pika_procedure_dialog_get_widget (PIKA_PROCEDURE_DIALOG (dialog), "transparent-color", G_TYPE_NONE); 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"); store = pika_int_store_new (_("Selected layer"), DDS_SAVE_SELECTED_LAYER, _("All visible layers"), DDS_SAVE_VISIBLE_LAYERS, _("As cube map"), DDS_SAVE_CUBEMAP, _("As volume map"), DDS_SAVE_VOLUMEMAP, _("As texture array"), DDS_SAVE_ARRAY, NULL); widget = pika_procedure_dialog_get_int_combo (PIKA_PROCEDURE_DIALOG (dialog), "save-type", PIKA_INT_STORE (store)); combo = pika_label_int_widget_get_widget (PIKA_LABEL_INT_WIDGET (widget)); pika_int_combo_box_set_sensitivity (PIKA_INT_COMBO_BOX (combo), combo_sensitivity_func, combo, NULL); combo_set_item_sensitive (combo, DDS_SAVE_CUBEMAP, is_cubemap); combo_set_item_sensitive (combo, DDS_SAVE_VOLUMEMAP, is_volume); combo_set_item_sensitive (combo, DDS_SAVE_ARRAY, is_array); store = pika_int_store_new (_("No mipmaps"), DDS_MIPMAP_NONE, _("Generate mipmaps"), DDS_MIPMAP_GENERATE, _("Use existing mipmaps"), DDS_MIPMAP_EXISTING, NULL); widget = pika_procedure_dialog_get_int_combo (PIKA_PROCEDURE_DIALOG (dialog), "mipmaps", PIKA_INT_STORE (store)); combo = pika_label_int_widget_get_widget (PIKA_LABEL_INT_WIDGET (widget)); pika_int_combo_box_set_sensitivity (PIKA_INT_COMBO_BOX (combo), combo_sensitivity_func, combo, NULL); combo_set_item_sensitive (combo, DDS_MIPMAP_EXISTING, ! (is_volume || is_cubemap) && is_mipmap_chain_valid); pika_procedure_dialog_fill (PIKA_PROCEDURE_DIALOG (dialog), "compression-format", "perceptual-metric", "format", "save-type", "flip-image", "mipmaps", "transparency-frame", "mipmap-options-frame", NULL); config_notify (PIKA_PROCEDURE_CONFIG (config), g_object_class_find_property (G_OBJECT_GET_CLASS (config), "compression-format"), PIKA_PROCEDURE_DIALOG (dialog)); config_notify (PIKA_PROCEDURE_CONFIG (config), g_object_class_find_property (G_OBJECT_GET_CLASS (config), "mipmaps"), PIKA_PROCEDURE_DIALOG (dialog)); config_notify (PIKA_PROCEDURE_CONFIG (config), g_object_class_find_property (G_OBJECT_GET_CLASS (config), "save-type"), PIKA_PROCEDURE_DIALOG (dialog)); config_notify (PIKA_PROCEDURE_CONFIG (config), g_object_class_find_property (G_OBJECT_GET_CLASS (config), "transparent-color"), PIKA_PROCEDURE_DIALOG (dialog)); g_signal_connect (PIKA_PROCEDURE_CONFIG (config), "notify", G_CALLBACK (config_notify), PIKA_PROCEDURE_DIALOG (dialog)); gtk_widget_show (dialog); run = pika_procedure_dialog_run (PIKA_PROCEDURE_DIALOG (dialog)); g_signal_handlers_disconnect_by_func (PIKA_PROCEDURE_CONFIG (config), config_notify, PIKA_PROCEDURE_DIALOG (dialog)); gtk_widget_destroy (dialog); return run; }