[gimp/gimp-2-10] file-dds: add original source code of the DDS plug-in



commit 369884cf9c55761124038f7b4076a6df8ca23fa1
Author: Alexandre Prokoudine <alexandre prokoudine gmail com>
Date:   Tue Nov 13 02:35:07 2018 +0300

    file-dds: add original source code of the DDS plug-in
    
    This is the code from the 'gimp-2.9' branch of the plug-in,
    integrated into GIMP's build system.
    
    (cherry picked from commit 79bc2dc1effa820396e15e79f8ed56ffb0a85933)

 configure.ac                   |    1 +
 plug-ins/Makefile.am           |    1 +
 plug-ins/file-dds/COPYING      |  339 +++++++
 plug-ins/file-dds/LICENSE      |   21 +
 plug-ins/file-dds/LICENSE.nvtt |   23 +
 plug-ins/file-dds/Makefile.am  |   65 ++
 plug-ins/file-dds/README       |   29 +
 plug-ins/file-dds/README.dxt   |  175 ++++
 plug-ins/file-dds/TODO         |    7 +
 plug-ins/file-dds/color.c      |   55 ++
 plug-ins/file-dds/color.h      |   91 ++
 plug-ins/file-dds/dds.c        |  373 +++++++
 plug-ins/file-dds/dds.h        |  327 +++++++
 plug-ins/file-dds/ddsplugin.h  |   78 ++
 plug-ins/file-dds/ddsread.c    | 1235 +++++++++++++++++++++++
 plug-ins/file-dds/ddswrite.c   | 2122 ++++++++++++++++++++++++++++++++++++++++
 plug-ins/file-dds/dxt.c        | 1412 ++++++++++++++++++++++++++
 plug-ins/file-dds/dxt.h        |   41 +
 plug-ins/file-dds/dxt_tables.h |  216 ++++
 plug-ins/file-dds/endian.h     |   71 ++
 plug-ins/file-dds/imath.h      |   67 ++
 plug-ins/file-dds/mipmap.c     | 1013 +++++++++++++++++++
 plug-ins/file-dds/mipmap.h     |   47 +
 plug-ins/file-dds/misc.c       |  252 +++++
 plug-ins/file-dds/misc.h       |   30 +
 plug-ins/file-dds/mktables.c   |  128 +++
 plug-ins/file-dds/vec.h        |  226 +++++
 27 files changed, 8445 insertions(+)
---
diff --git a/configure.ac b/configure.ac
index ed07556119..c61b633bfd 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2856,6 +2856,7 @@ build/windows/installer/Makefile
 build/windows/installer/lang/Makefile
 plug-ins/Makefile
 plug-ins/file-bmp/Makefile
+plug-ins/file-dds/Makefile
 plug-ins/file-exr/Makefile
 plug-ins/file-faxg3/Makefile
 plug-ins/file-fits/Makefile
diff --git a/plug-ins/Makefile.am b/plug-ins/Makefile.am
index 76b92b13cb..5fe438479f 100644
--- a/plug-ins/Makefile.am
+++ b/plug-ins/Makefile.am
@@ -29,6 +29,7 @@ SUBDIRS = \
        $(pygimp)               \
        file-bmp                \
        $(file_darktable)       \
+       file-dds                \
        $(file_exr)             \
        file-faxg3              \
        file-fits               \
diff --git a/plug-ins/file-dds/COPYING b/plug-ins/file-dds/COPYING
new file mode 100644
index 0000000000..6f17fd7034
--- /dev/null
+++ b/plug-ins/file-dds/COPYING
@@ -0,0 +1,339 @@
+                   GNU GENERAL PUBLIC LICENSE
+                      Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+                      51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                           Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                   GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                           NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                    END OF TERMS AND CONDITIONS
+
+       Appendix: How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) 19yy  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) 19yy name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/plug-ins/file-dds/LICENSE b/plug-ins/file-dds/LICENSE
new file mode 100644
index 0000000000..74522cc711
--- /dev/null
+++ b/plug-ins/file-dds/LICENSE
@@ -0,0 +1,21 @@
+/*
+       DDS GIMP plugin
+
+       Copyright (C) 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.
+*/
diff --git a/plug-ins/file-dds/LICENSE.nvtt b/plug-ins/file-dds/LICENSE.nvtt
new file mode 100644
index 0000000000..a42204cc9e
--- /dev/null
+++ b/plug-ins/file-dds/LICENSE.nvtt
@@ -0,0 +1,23 @@
+// Copyright (c) 2009-2011 Ignacio Castano <castano gmail com>
+// Copyright (c) 2007-2009 NVIDIA Corporation -- Ignacio Castano <icastano nvidia com>
+// 
+// Permission is hereby granted, free of charge, to any person
+// obtaining a copy of this software and associated documentation
+// files (the "Software"), to deal in the Software without
+// restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following
+// conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+// OTHER DEALINGS IN THE SOFTWARE.
diff --git a/plug-ins/file-dds/Makefile.am b/plug-ins/file-dds/Makefile.am
new file mode 100644
index 0000000000..9c4aa8fb3a
--- /dev/null
+++ b/plug-ins/file-dds/Makefile.am
@@ -0,0 +1,65 @@
+## Process this file with automake to produce Makefile.in
+
+libgimpui = $(top_builddir)/libgimp/libgimpui-$(GIMP_API_VERSION).la
+libgimpconfig = $(top_builddir)/libgimpconfig/libgimpconfig-$(GIMP_API_VERSION).la
+libgimpwidgets = $(top_builddir)/libgimpwidgets/libgimpwidgets-$(GIMP_API_VERSION).la
+libgimp = $(top_builddir)/libgimp/libgimp-$(GIMP_API_VERSION).la
+libgimpcolor = $(top_builddir)/libgimpcolor/libgimpcolor-$(GIMP_API_VERSION).la
+libgimpbase = $(top_builddir)/libgimpbase/libgimpbase-$(GIMP_API_VERSION).la
+libgimpmath = $(top_builddir)/libgimpmath/libgimpmath-$(GIMP_API_VERSION).la
+
+if OS_WIN32
+mwindows = -mwindows
+endif
+
+if HAVE_WINDRES
+include $(top_srcdir)/build/windows/gimprc-plug-ins.rule
+file_dds_RC = file-dds.rc.o
+endif
+
+AM_LDFLAGS = $(mwindows)
+AM_CFLAGS = -fno-strict-aliasing
+
+libexecdir = $(gimpplugindir)/plug-ins/file-dds
+
+libexec_PROGRAMS = file-dds
+
+file_dds_SOURCES = \
+       dds.c                   \
+       dds.h                   \
+       color.c                 \
+       color.h                 \
+       ddsplugin.h             \
+       ddsread.c               \
+       ddswrite.c              \
+       dxt.c                   \
+       dxt.h                   \
+       dxt_tables.h    \
+       endian.h                \
+       imath.h                 \
+       mipmap.c                \
+       mipmap.h                \
+       misc.c                  \
+       misc.h                  \
+       mktables.c              \
+       vec.h
+
+AM_CPPFLAGS = \
+       -I$(top_srcdir) \
+       $(GTK_CFLAGS)   \
+       $(GEGL_CFLAGS) \
+       -I$(includedir)
+
+LDADD = \
+       $(libgimpui)            \
+       $(libgimpwidgets)       \
+       $(libgimpconfig)        \
+       $(libgimp)              \
+       $(libgimpcolor)         \
+       $(libgimpmath)          \
+       $(libgimpbase)          \
+       $(GTK_LIBS)             \
+       $(GEGL_LIBS)            \
+       $(RT_LIBS)              \
+       $(INTLLIBS)             \
+       $(file_dds_RC)
diff --git a/plug-ins/file-dds/README b/plug-ins/file-dds/README
new file mode 100644
index 0000000000..40903432b8
--- /dev/null
+++ b/plug-ins/file-dds/README
@@ -0,0 +1,29 @@
+DDS plugin for The GIMP
+(C) 2004-2012 Shawn Kirst <skirst gmail com>,
+with parts (C) 2003 Arne Reuter <homepage arnereuter de> where specified.
+==========================================
+
+This is a plugin for GIMP version 2.4.x. It allows you to load and save
+images in Direct Draw Surface (DDS) format.
+
+Features
+==========================================
+* Load/Save DDS files using DXT texture compression
+* Automatic mipmap generation on save
+* Load mipmaps into separate layers
+* Load cube map faces and volume map slices into separate layers
+* Cube and volume map saving
+* Pixel conversion selection for custom formats (RGBA4, R5G6B5, RGB10A2, etc.)
+* Load/save DDS files, optionally using DirectX texture compression (DXT)
+* Optional automatic mipmap generation when saving
+* Load mipmaps into separate layers
+* Load cube map faces and volume map slices into separate layers
+* Save cube maps and volume maps with automatic mipmap generation support
+* Save image with a custom pixel format
+* Non-power-of-two image loading and saving support with automatic mipmap generation support
+* Compliant with DirectX 10 compressed formats
+    
+
+Installation
+==========================================
+See the file INSTALL for installation instructions
diff --git a/plug-ins/file-dds/README.dxt b/plug-ins/file-dds/README.dxt
new file mode 100644
index 0000000000..9de2adf7b9
--- /dev/null
+++ b/plug-ins/file-dds/README.dxt
@@ -0,0 +1,175 @@
+The following notes are from an email I received from Fabian 'ryg' Giesen. They
+should help answer some questions regarding the code found in dxt.c
+
+---
+
+mul8bit: This formula is equivalent to (a*b)/255 for a fairly large set of
+values (didn't bother finding out the exact bounds). It's a fairly well-known
+trick also used in other parts of the GIMP codebase (among others) and was
+first documented by Jim Blinn, I think. A good reference is his book "Dirty
+Pixels".
+
+---
+
+lerp_rgb: The expression computed is exactly equivalent to mul8bit(a[i],255-f)
++ mul8bit(b[i],f) - I just verified that by brute force for -255 <= b[i]-a[i]
+<= 255 because I couldn't be bothered to find a derivation for this :) . You
+customarily use a factor between 0 and 256 incluse for LERPing if you can, but
+normal DXT blocks have colors placed at 1/3 and 2/3 between the two
+interpolated colors. 255 is divisible by 3, so lerp_rgb can later be used in
+eval_colors to determine the result of
+
+  a*(1/3) + b*(2/3)  and  a*(2/3) + b*(1/3)
+
+exactly, which is nice :)
+
+---
+
+dither_block: This is just Floyd-Steinberg dithering. Distributing the error
+terms to the adjacent pixels for each source pixel is the customary variant to
+write this, but since blocks are so small, it's nearly all boundary
+cases; "gathering" the error terms per source pixel turned out to be simpler.
+
+---
+
+match_colors_block: This includes a few tricks. We want to map each source
+color to its nearest representable color (index), using the euclidean distance
+as a metric.
+
+The obvious, brute-force way is to just compare squared distances to the 4
+representable colors for each source pixel (using, for example,
+color_distance); this requires a lot of arithmetic operations.
+
+Instead, the code uses the fact that the 4 colors lie on a line in RGB space
+(only approximately in truth, since we have discrete steps). It's a well-known
+fact in geometry that if P is the closest point to the line L in 3D space, and
+Q is the point closest to P on L, then (P-Q) is orthogonal to the direction of
+L. So (in R3 at least), we can simply determine Q by projecting P onto the
+direction vector of L, which is done by the 16 dot products in the first for
+loop. Since the RGB values have discrete steps in reality, this is just an
+approximation, but a quite good one.
+
+The loop after that determines where the 4 actually representable colors lie
+along the line. After that, you simply need to determine which of those 4
+values your current pixel's dot product is closest to. Instead of doing a
+bunch of comparisions per pixel, the code computes the points along the line
+at which the decision would change (that's c0pt, halfpt and c3pt). This would
+still require 3 comparisions; by testing the middle point - which is halfpt -
+first, one point is always excluded from consideration, which reduces the
+number of compares to two in all cases. No big deal, but hey, why not :)
+
+Similarly, instead of dithering full RGB values, I just dither the dot product
+values. Again, by my experiments this works just as well and reduces the
+amount of work significantly.
+
+---
+
+optimize_colors_block: This first determines min/max/mean for r,g,b and the
+covariance matrix for the color distribution. The latter is used to determine
+the principal component (=eigenvector with largest eigenvalue) of that color
+distribution, or the direction along which the colors in the block vary most
+(in layman's terms) - the eigenvector is determined using simple power
+iteration (a standard technique). That iteration needs a seed vector; I just
+use (max_r-min_r,max_g-min_g,max_b-min_b), which works well in practice. If
+the iteration converges to a vector with very small magnitude (or zero), which
+can happen sometimes, it just defaults to an approximation of the YCbCr Y
+vector (scaled appropriately to make sure no precision is lost with the dot
+products).
+
+This is then used as an initial approximation for the direction of the line
+through RGB color space that is used to select colors for that block. It
+simply uses the two most extreme points along that axis as the two colors
+stored in the block.
+
+---
+
+refine_block: This takes a block and a chosen set of color indices, and tries
+to determine the optimal endpoints for these indices (i.e. the full process
+is: take block, use color distribution to get rough estimate of optimal
+direction, assign color indices accordingly, use these to get better
+endpoints, assign indices again). The computation just solves a least-squares
+system to minimize the square error between the actual pixels and the
+interpolated colors (solving for the two extremal colors). The least-squares
+computation turns out to boil down to solving a 2x2 system of linear equations
+for each of the RGB color channels; the actual solution is computed using
+Cramer's rule.
+
+The code is somewhat weird (especially the "prods"/"akku" thing), but that's
+just to reduce the amount of computation done (this piece of code is a hot
+spot, so it's worth it).
+
+The (!yy || !xx || xx * yy == xy*xy) part checks whether the system of linear
+equations is degenerate. After pondering about this some months ago, I found
+out that the only case in which this can ever happen is when all pixels of the
+source block get mapped to the same color value. But that case can be handled
+better in any case, by just using the single-color lookup tables. I've
+attached the new version of refine_block using that observation - it both
+increases performance (a bit) and image quality, so it's pretty neat.
+
+---
+
+encode_alpha_block_DXT5: The only thing that shouldn't be obvious is the index
+computation. This just uses some two's complement arithmetic and bit shuffling
+to avoid computing
+
+  7 * (in_alpha - min_alpha) / (max_alpha - min_alpha)
+
+which would be more expensive. (The extra calc with idx is just because of the
+weird DXT color numbering).
+
+---
+
+Some more notes on the general flow:
+
+The computation without dithering is just as I explained in the part about
+refine_block:
+1. Calc initial endpoints directly from block colors
+2. Determine color indices for these endpoints
+3. Optimize endpoints given color indices
+4. Determine new color indices given optimized endpoints
+
+With dithering, there's a twist: The first two steps are done using a version
+of the block dithered to colors that are representable using 16-bit 565 RGB
+values. I've found that this significantly improves visual quality with
+dithering; if you don't do this, the colors inside a block typically vary too
+little for dithering to be useful. This process decreases objective quality
+but typically looks notably better.
+
+The single-color match (omatch5/omatch6) trick: If the block only contains a
+single color (or, for the improved version of refine_block, if the color
+values are sufficiently close to all map to the same index), an optimal
+solution can be used instead.
+
+You normally want solid-color blocks to map to solid-color block (because
+dithering patterns are very obvious otherwise). This means that all color
+indices for the block are going to be identical, i.e. all 0, 1, 2 or 3. All-0
+is symmetrical to all-1 (with the endpoints flipped), and all-2 is symmetrical
+to all-3 (again with the endpoints flipped). So you only need to consider
+all-0 or all-2 indices for the block. Furthermore, all-0 means that the first
+endpoint specified in the block gets used for all pixels; you can get the same
+result by setting both endpoints to the same value and using index 2 for
+everything.
+
+In short, you can always set all indices to 2 without sacrificing any quality
+whatsoever. For any of the color components R,G,B, you then want to determine
+5-bit or 6-bit values a and b such that
+
+  expand[a]*(2/3) + expand[b]*(1/3) is as close as possible to R/G/B
+
+and that's exactly what's in omatch5 (for 5-bit values) and omatch6 (for 6-bit
+values).
+
+If you use the 3-color+transparency mode of DXT1, you need separate versions
+for omatch5/omatch6 for this case, since the interpolated value is exactly
+halfway between the two endpoints instead of 1/3 of the way along. But I
+recommend against that mode, because even if your top-level mipmap has 1-bit
+transparency, mipmaps will have more than 2 distinct values, and the DXT mode
+is selected per texture. That's why my original code doesn't support the
+3-color mode of DXT1 at all: I don't think it's useful in practice.
+
+Not sure if all of this is useful to you or not, but I guess it might make
+sense to at least put this in a seperate text file or whatever, because
+otherwise it's really quite hard to see what's going on in some places.
+
+Cheers,
+-Fabian "ryg" Giesen 
diff --git a/plug-ins/file-dds/TODO b/plug-ins/file-dds/TODO
new file mode 100644
index 0000000000..94b0411105
--- /dev/null
+++ b/plug-ins/file-dds/TODO
@@ -0,0 +1,7 @@
+TODO list for future releases of gimp-dds:
+
+* Add support for DX10 DDS extensions
+* BC6H and BC7 compression support
+* Volume map compression support (VTC)
+* Add support for GIMP 2.6.x GEGL for reading and writing higher precision
+pixel formats
diff --git a/plug-ins/file-dds/color.c b/plug-ins/file-dds/color.c
new file mode 100644
index 0000000000..edc8f6c93e
--- /dev/null
+++ b/plug-ins/file-dds/color.c
@@ -0,0 +1,55 @@
+/*
+       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.
+*/
+
+#include <math.h>
+
+int linear_to_sRGB(int c)
+{
+   float v = (float)c / 255.0f;
+   
+   if(v < 0)
+      v = 0;
+   else if(v > 1)
+      v = 1;
+   else if(v <= 0.0031308f)
+      v = 12.92f * v;
+   else
+      v = 1.055f * powf(v, 0.41666f) - 0.055f;
+   
+   return((int)floorf(255.0f * v + 0.5f));
+}
+
+int sRGB_to_linear(int c)
+{
+   float v = (float)c / 255.0f;
+   
+   if(v < 0)
+      v = 0;
+   else if(v > 1)
+      v = 1;
+   else if(v <= 0.04045f)
+      v /= 12.92f;
+   else
+      v = powf((v + 0.055f) / 1.055f, 2.4f);
+   
+   return((int)floorf(255.0f * v + 0.5f));
+}
diff --git a/plug-ins/file-dds/color.h b/plug-ins/file-dds/color.h
new file mode 100644
index 0000000000..8cb1f544d9
--- /dev/null
+++ b/plug-ins/file-dds/color.h
@@ -0,0 +1,91 @@
+/*
+       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.
+*/
+
+#ifndef COLOR_H
+#define COLOR_H
+
+#include "imath.h"
+
+/* sRGB encoding/decoding */
+int linear_to_sRGB(int c);
+int sRGB_to_linear(int c);
+
+/* YCoCg encoding */
+static inline void RGB_to_YCoCg(unsigned char *dst, int r, int g, int b)
+{
+   int y  = ((r +     (g << 1) + b) + 2) >> 2;
+   int co = ((((r << 1) - (b << 1)) + 2) >> 2) + 128;
+   int cg = (((-r +   (g << 1) - b) + 2) >> 2) + 128;
+
+   dst[0] = 255;
+   dst[1] = (cg > 255 ? 255 : (cg < 0 ? 0 : cg));
+   dst[2] = (co > 255 ? 255 : (co < 0 ? 0 : co));
+   dst[3] = (y  > 255 ? 255 : (y  < 0 ? 0 :  y));
+}
+
+/* other color conversions */
+
+static inline int rgb_to_luminance(int r, int g, int b)
+{
+   /* ITU-R BT.709 luma coefficents, scaled by 256 */
+   return(((r * 54 + g * 182 + b * 20) + 128) >> 8);
+}
+
+static inline unsigned short pack_r5g6b5(int r, int g, int b)
+{
+   return((mul8bit(r, 31) << 11) |
+          (mul8bit(g, 63) <<  5) |
+          (mul8bit(b, 31)      ));
+}
+
+static inline unsigned short pack_rgba4(int r, int g, int b, int a)
+{
+   return((mul8bit(a, 15) << 12) |
+          (mul8bit(r, 15) <<  8) |
+          (mul8bit(g, 15) <<  4) |
+          (mul8bit(b, 15)      ));
+}
+
+static inline unsigned short pack_rgb5a1(int r, int g, int b, int a)
+{
+   return((((a >> 7) & 0x01) << 15) |
+          (mul8bit(r, 31)    << 10) |
+          (mul8bit(g, 31)    <<  5) |
+          (mul8bit(b, 31)         ));
+}
+
+static inline unsigned char pack_r3g3b2(int r, int g, int b)
+{
+   return((mul8bit(r, 7) << 5) |
+          (mul8bit(g, 7) << 2) |
+          (mul8bit(b, 3)     ));
+}
+
+static inline unsigned int pack_rgb10a2(int r, int g, int b, int a)
+{
+   return(((unsigned int)((a >> 6) & 0x003) << 30) |
+          ((unsigned int)((r << 2) & 0x3ff) << 20) |
+          ((unsigned int)((g << 2) & 0x3ff) << 10) |
+          ((unsigned int)((b << 2) & 0x3ff)      ));
+}
+
+#endif
diff --git a/plug-ins/file-dds/dds.c b/plug-ins/file-dds/dds.c
new file mode 100644
index 0000000000..2e1f2dfe12
--- /dev/null
+++ b/plug-ins/file-dds/dds.c
@@ -0,0 +1,373 @@
+/*
+       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.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <gtk/gtk.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#include "ddsplugin.h"
+#include "dds.h"
+#include "misc.h"
+
+FILE *errFile;
+gchar *prog_name = "dds";
+gchar *filename;
+gint interactive_dds;
+
+static void query(void);
+static void run(const gchar *name, gint nparams, const GimpParam *param,
+                                        gint *nreturn_vals, GimpParam **return_vals);
+
+GimpPlugInInfo PLUG_IN_INFO =
+{
+       0, 0, query, run
+};
+
+
+DDSWriteVals dds_write_vals =
+{
+       DDS_COMPRESS_NONE, DDS_MIPMAP_NONE, DDS_SAVE_SELECTED_LAYER,
+   DDS_FORMAT_DEFAULT, -1, DDS_MIPMAP_FILTER_DEFAULT, DDS_MIPMAP_WRAP_DEFAULT,
+   0, 0, 0.0, 0, 0,
+   0, 0.5
+};
+
+DDSReadVals dds_read_vals =
+{
+   1, 1, 1
+};
+
+static GimpParamDef load_args[] =
+{
+   {GIMP_PDB_INT32, "run_mode", "Interactive, non-interactive"},
+   {GIMP_PDB_STRING, "filename", "The name of the file to load"},
+   {GIMP_PDB_STRING, "raw_filename", "The name entered"},
+   {GIMP_PDB_INT32, "load_mipmaps", "Load mipmaps if present"},
+   {GIMP_PDB_INT32, "decode_images", "Decode YCoCg/AExp images when detected"}
+};
+static GimpParamDef load_return_vals[] =
+{
+   {GIMP_PDB_IMAGE, "image", "Output image"}
+};
+
+static GimpParamDef save_args[] =
+{
+   {GIMP_PDB_INT32, "run_mode", "Interactive, non-interactive"},
+   {GIMP_PDB_IMAGE, "image", "Input image"},
+   {GIMP_PDB_DRAWABLE, "drawable", "Drawable to save"},
+   {GIMP_PDB_STRING, "filename", "The name of the file to save the image as"},
+   {GIMP_PDB_STRING, "raw_filename", "The name entered"},
+   {GIMP_PDB_INT32, "compression_format", "Compression format (0 = None, 1 = BC1/DXT1, 2 = BC2/DXT3, 3 = 
BC3/DXT5, 4 = BC3n/DXT5nm, 5 = BC4/ATI1N, 6 = BC5/ATI2N, 7 = RXGB (DXT5), 8 = Alpha Exponent (DXT5), 9 = 
YCoCg (DXT5), 10 = YCoCg scaled (DXT5))"},
+   {GIMP_PDB_INT32, "mipmaps", "How to handle mipmaps (0 = No mipmaps, 1 = Generate mipmaps, 2 = Use 
existing mipmaps (layers)"},
+   {GIMP_PDB_INT32, "savetype", "How to save the image (0 = selected layer, 1 = cube map, 2 = volume map, 3 
= texture array"},
+   {GIMP_PDB_INT32, "format", "Custom pixel format (0 = default, 1 = R5G6B5, 2 = RGBA4, 3 = RGB5A1, 4 = 
RGB10A2)"},
+   {GIMP_PDB_INT32, "transparent_index", "Index of transparent color or -1 to disable (for indexed images 
only)."},
+   {GIMP_PDB_INT32, "mipmap_filter", "Filtering to use when generating mipmaps (0 = default, 1 = nearest, 2 
= box, 3 = triangle, 4 = quadratic, 5 = bspline, 6 = mitchell, 7 = lanczos, 8 = kaiser)"},
+   {GIMP_PDB_INT32, "mipmap_wrap", "Wrap mode to use when generating mipmaps (0 = default, 1 = mirror, 2 = 
repeat, 3 = clamp)"},
+   {GIMP_PDB_INT32, "gamma_correct", "Use gamma correct mipmap filtering"},
+   {GIMP_PDB_INT32, "srgb", "Use sRGB colorspace for gamma correction"},
+   {GIMP_PDB_FLOAT, "gamma", "Gamma value to use for gamma correction (i.e. 2.2)"},
+   {GIMP_PDB_INT32, "perceptual_metric", "Use a perceptual error metric during compression"},
+   {GIMP_PDB_INT32, "preserve_alpha_coverage", "Preserve alpha test converage for alpha channel maps"},
+   {GIMP_PDB_FLOAT, "alpha_test_threshold", "Alpha test threshold value for which alpha test converage 
should be preserved"}
+};
+
+static GimpParamDef decode_args[] =
+{
+   {GIMP_PDB_INT32, "run_mode", "Interactive, non-interactive"},
+   {GIMP_PDB_IMAGE, "image", "Input image"},
+   {GIMP_PDB_DRAWABLE, "drawable", "Drawable to save"}
+};
+
+MAIN()
+
+static void query(void)
+{
+       gimp_install_procedure(LOAD_PROC,
+                                                                 "Loads files in DDS image format",
+                                                                 "Loads files in DDS image format",
+                                                                 "Shawn Kirst",
+                                                                 "Shawn Kirst",
+                                                                 "2008",
+                                                                 "<Load>/DDS image",
+                                                                 0,
+                                                                 GIMP_PLUGIN,
+                                                                 G_N_ELEMENTS(load_args),
+                          G_N_ELEMENTS(load_return_vals),
+                                                                 load_args, load_return_vals);
+
+   gimp_register_file_handler_mime(LOAD_PROC, "image/dds");
+       gimp_register_magic_load_handler(LOAD_PROC,
+                                                                                               "dds",
+                                                                                               "",
+                                                                                               
"0,string,DDS");
+
+       gimp_install_procedure(SAVE_PROC,
+                                                                 "Saves files in DDS image format",
+                                                                 "Saves files in DDS image format",
+                                                                 "Shawn Kirst",
+                                                                 "Shawn Kirst",
+                                                                 "2008",
+                                                                 "<Save>/DDS image",
+                                                                 "INDEXED, GRAY, RGB",
+                                                                 GIMP_PLUGIN,
+                                                                 G_N_ELEMENTS(save_args), 0,
+                                                                 save_args, 0);
+
+   gimp_register_file_handler_mime(SAVE_PROC, "image/dds");
+       gimp_register_save_handler(SAVE_PROC,
+                                                                               "dds",
+                                                                               "");
+
+   gimp_install_procedure(DECODE_YCOCG_PROC,
+                          "Converts YCoCg encoded pixels to RGB",
+                          "Converts YCoCg encoded pixels to RGB",
+                          "Shawn Kirst",
+                          "Shawn Kirst",
+                          "2008",
+                          "<Image>/Filters/Colors/Decode YCoCg",
+                          "RGBA",
+                          GIMP_PLUGIN,
+                          G_N_ELEMENTS(decode_args), 0,
+                          decode_args, 0);
+
+   gimp_install_procedure(DECODE_YCOCG_SCALED_PROC,
+                          "Converts YCoCg (scaled) encoded pixels to RGB",
+                          "Converts YCoCg (scaled) encoded pixels to RGB",
+                          "Shawn Kirst",
+                          "Shawn Kirst",
+                          "2008",
+                          "<Image>/Filters/Colors/Decode YCoCg (scaled)",
+                          "RGBA",
+                          GIMP_PLUGIN,
+                          G_N_ELEMENTS(decode_args), 0,
+                          decode_args, 0);
+
+   gimp_install_procedure(DECODE_ALPHA_EXP_PROC,
+                          "Converts alpha exponent encoded pixels to RGB",
+                          "Converts alpha exponent encoded pixels to RGB",
+                          "Shawn Kirst",
+                          "Shawn Kirst",
+                          "2008",
+                          "<Image>/Filters/Colors/Decode Alpha exponent",
+                          "RGBA",
+                          GIMP_PLUGIN,
+                          G_N_ELEMENTS(decode_args), 0,
+                          decode_args, 0);
+
+}
+
+static void run(const gchar *name, gint nparams, const GimpParam *param,
+                                        gint *nreturn_vals, GimpParam **return_vals)
+{
+       static GimpParam values[2];
+       GimpRunMode run_mode;
+       GimpPDBStatusType status = GIMP_PDB_SUCCESS;
+       gint32 imageID;
+       gint32 drawableID;
+       GimpExportReturn export = GIMP_EXPORT_CANCEL;
+   
+   gegl_init(NULL, NULL);
+
+       run_mode = param[0].data.d_int32;
+
+       *nreturn_vals = 1;
+       *return_vals = values;
+
+       values[0].type = GIMP_PDB_STATUS;
+       values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR;
+
+       if(!strcmp(name, LOAD_PROC))
+       {
+               switch(run_mode)
+               {
+                       case GIMP_RUN_INTERACTIVE:
+                          gimp_ui_init("dds", 0);
+                          interactive_dds = 1;
+            gimp_get_data(LOAD_PROC, &dds_read_vals);
+                          break;
+                       case GIMP_RUN_NONINTERACTIVE:
+                          interactive_dds = 0;
+            dds_read_vals.show_dialog = 0;
+            dds_read_vals.mipmaps = param[3].data.d_int32;
+            dds_read_vals.decode_images = param[4].data.d_int32;
+                          if(nparams != G_N_ELEMENTS(load_args))
+                                  status = GIMP_PDB_CALLING_ERROR;
+                          break;
+                       default:
+                          break;
+               }
+
+               if(status == GIMP_PDB_SUCCESS)
+               {
+                       status = read_dds(param[1].data.d_string, &imageID);
+                       if(status == GIMP_PDB_SUCCESS && imageID != -1)
+                       {
+                               *nreturn_vals = 2;
+                               values[1].type = GIMP_PDB_IMAGE;
+                               values[1].data.d_image = imageID;
+            if(interactive_dds)
+               gimp_set_data(LOAD_PROC, &dds_read_vals, sizeof(dds_read_vals));
+                       }
+                       else if(status != GIMP_PDB_CANCEL)
+                               status = GIMP_PDB_EXECUTION_ERROR;
+               }
+       }
+       else if(!strcmp(name, SAVE_PROC))
+       {
+               imageID = param[1].data.d_int32;
+               drawableID = param[2].data.d_int32;
+
+               switch(run_mode)
+               {
+                       case GIMP_RUN_INTERACTIVE:
+                       case GIMP_RUN_WITH_LAST_VALS:
+                          gimp_ui_init("dds", 0);
+                          export = gimp_export_image(&imageID, &drawableID, "DDS",
+                                       (GIMP_EXPORT_CAN_HANDLE_RGB |
+                                        GIMP_EXPORT_CAN_HANDLE_GRAY |
+                                        GIMP_EXPORT_CAN_HANDLE_INDEXED |
+                                        GIMP_EXPORT_CAN_HANDLE_ALPHA |
+                                        GIMP_EXPORT_CAN_HANDLE_LAYERS));
+                          if(export == GIMP_EXPORT_CANCEL)
+                          {
+                                       values[0].data.d_status = GIMP_PDB_CANCEL;
+                                       return;
+                               }
+                       default:
+                          break;
+               }
+
+               switch(run_mode)
+               {
+                       case GIMP_RUN_INTERACTIVE:
+                          gimp_get_data(SAVE_PROC, &dds_write_vals);
+                          interactive_dds = 1;
+                          break;
+                       case GIMP_RUN_NONINTERACTIVE:
+                          interactive_dds = 0;
+                          if(nparams != G_N_ELEMENTS(save_args))
+                                  status = GIMP_PDB_CALLING_ERROR;
+                          else
+                          {
+                                       dds_write_vals.compression = param[5].data.d_int32;
+                                       dds_write_vals.mipmaps = param[6].data.d_int32;
+               dds_write_vals.savetype = param[7].data.d_int32;
+               dds_write_vals.format = param[8].data.d_int32;
+               dds_write_vals.transindex = param[9].data.d_int32;
+               dds_write_vals.mipmap_filter = param[10].data.d_int32;
+               dds_write_vals.mipmap_wrap = param[11].data.d_int32;
+               dds_write_vals.gamma_correct = param[12].data.d_int32;
+               dds_write_vals.srgb = param[13].data.d_int32;
+               dds_write_vals.gamma = param[14].data.d_float;
+               dds_write_vals.perceptual_metric = param[15].data.d_int32;
+               dds_write_vals.preserve_alpha_coverage = param[16].data.d_int32;
+               dds_write_vals.alpha_test_threshold = param[17].data.d_float;
+
+                                       if((dds_write_vals.compression <  DDS_COMPRESS_NONE) ||
+                                               (dds_write_vals.compression >= DDS_COMPRESS_MAX))
+                                               status = GIMP_PDB_CALLING_ERROR;
+               if((dds_write_vals.mipmaps <  DDS_MIPMAP_NONE) ||
+                  (dds_write_vals.mipmaps >= DDS_MIPMAP_MAX))
+                  status = GIMP_PDB_CALLING_ERROR;
+               if((dds_write_vals.savetype <  DDS_SAVE_SELECTED_LAYER) ||
+                  (dds_write_vals.savetype >= DDS_SAVE_MAX))
+                  status = GIMP_PDB_CALLING_ERROR;
+               if((dds_write_vals.format <  DDS_FORMAT_DEFAULT) ||
+                  (dds_write_vals.format >= DDS_FORMAT_MAX))
+                  status = GIMP_PDB_CALLING_ERROR;
+               if((dds_write_vals.mipmap_filter <  DDS_MIPMAP_FILTER_DEFAULT) ||
+                  (dds_write_vals.mipmap_filter >= DDS_MIPMAP_FILTER_MAX))
+                  status = GIMP_PDB_CALLING_ERROR;
+               if((dds_write_vals.mipmap_wrap <  DDS_MIPMAP_WRAP_DEFAULT) ||
+                  (dds_write_vals.mipmap_wrap >= DDS_MIPMAP_WRAP_MAX))
+                  status = GIMP_PDB_CALLING_ERROR;
+                               }
+                          break;
+                       case GIMP_RUN_WITH_LAST_VALS:
+                          gimp_get_data(SAVE_PROC, &dds_write_vals);
+                          interactive_dds = 0;
+                          break;
+                       default:
+                          break;
+               }
+
+      if(dds_write_vals.gamma < 1e-04f)
+         dds_write_vals.gamma = gimp_gamma();
+
+               if(status == GIMP_PDB_SUCCESS)
+               {
+                       status = write_dds(param[3].data.d_string, imageID, drawableID);
+                       if(status == GIMP_PDB_SUCCESS)
+                               gimp_set_data(SAVE_PROC, &dds_write_vals, sizeof(dds_write_vals));
+               }
+
+               if(export == GIMP_EXPORT_EXPORT)
+                       gimp_image_delete(imageID);
+   }
+   else if(!strcmp(name, DECODE_YCOCG_PROC))
+   {
+               imageID = param[1].data.d_int32;
+               drawableID = param[2].data.d_int32;
+
+      decode_ycocg_image(drawableID, TRUE);
+
+      status = GIMP_PDB_SUCCESS;
+
+      if(run_mode != GIMP_RUN_NONINTERACTIVE)
+         gimp_displays_flush();
+   }
+   else if(!strcmp(name, DECODE_YCOCG_SCALED_PROC))
+   {
+               imageID = param[1].data.d_int32;
+               drawableID = param[2].data.d_int32;
+
+      decode_ycocg_scaled_image(drawableID, TRUE);
+
+      status = GIMP_PDB_SUCCESS;
+
+      if(run_mode != GIMP_RUN_NONINTERACTIVE)
+         gimp_displays_flush();
+   }
+   else if(!strcmp(name, DECODE_ALPHA_EXP_PROC))
+   {
+               imageID = param[1].data.d_int32;
+               drawableID = param[2].data.d_int32;
+
+      decode_alpha_exp_image(drawableID, TRUE);
+
+      status = GIMP_PDB_SUCCESS;
+
+      if(run_mode != GIMP_RUN_NONINTERACTIVE)
+         gimp_displays_flush();
+   }
+       else
+               status = GIMP_PDB_CALLING_ERROR;
+
+       values[0].data.d_status = status;
+}
+
diff --git a/plug-ins/file-dds/dds.h b/plug-ins/file-dds/dds.h
new file mode 100644
index 0000000000..6affc41a5c
--- /dev/null
+++ b/plug-ins/file-dds/dds.h
@@ -0,0 +1,327 @@
+/*
+       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.
+*/
+
+#ifndef DDS_H
+#define DDS_H
+
+#define FOURCC(a, b, c, d) \
+         ((unsigned int)((unsigned int)(a)      ) | \
+                        ((unsigned int)(b) <<  8) | \
+                        ((unsigned int)(c) << 16) | \
+                        ((unsigned int)(d) << 24))
+
+typedef enum
+{
+   DDS_COMPRESS_NONE = 0,
+   DDS_COMPRESS_BC1,        /* DXT1  */
+   DDS_COMPRESS_BC2,        /* DXT3  */
+   DDS_COMPRESS_BC3,        /* DXT5  */
+   DDS_COMPRESS_BC3N,       /* DXT5n */
+   DDS_COMPRESS_BC4,        /* ATI1  */
+   DDS_COMPRESS_BC5,        /* ATI2  */
+   DDS_COMPRESS_RXGB,       /* DXT5  */
+   DDS_COMPRESS_AEXP,       /* DXT5  */
+   DDS_COMPRESS_YCOCG,      /* DXT5  */
+   DDS_COMPRESS_YCOCGS,     /* DXT5  */
+   DDS_COMPRESS_MAX
+} DDS_COMPRESSION_TYPE;
+
+typedef enum
+{
+   DDS_SAVE_SELECTED_LAYER = 0,
+   DDS_SAVE_CUBEMAP,
+   DDS_SAVE_VOLUMEMAP,
+   DDS_SAVE_ARRAY,
+   DDS_SAVE_MAX
+} DDS_SAVE_TYPE;
+
+typedef enum
+{
+   DDS_FORMAT_DEFAULT = 0,
+   DDS_FORMAT_RGB8,
+   DDS_FORMAT_RGBA8,
+   DDS_FORMAT_BGR8,
+   DDS_FORMAT_ABGR8,
+   DDS_FORMAT_R5G6B5,
+   DDS_FORMAT_RGBA4,
+   DDS_FORMAT_RGB5A1,
+   DDS_FORMAT_RGB10A2,
+   DDS_FORMAT_R3G3B2,
+   DDS_FORMAT_A8,
+   DDS_FORMAT_L8,
+   DDS_FORMAT_L8A8,
+   DDS_FORMAT_AEXP,
+   DDS_FORMAT_YCOCG,
+   DDS_FORMAT_MAX
+} DDS_FORMAT_TYPE;
+
+typedef enum
+{
+   DDS_MIPMAP_NONE = 0,
+   DDS_MIPMAP_GENERATE,
+   DDS_MIPMAP_EXISTING,
+   DDS_MIPMAP_MAX
+} DDS_MIPMAP;
+
+typedef enum
+{
+   DDS_MIPMAP_FILTER_DEFAULT = 0,
+   DDS_MIPMAP_FILTER_NEAREST,
+   DDS_MIPMAP_FILTER_BOX,
+   DDS_MIPMAP_FILTER_TRIANGLE,
+   DDS_MIPMAP_FILTER_QUADRATIC,
+   DDS_MIPMAP_FILTER_BSPLINE,
+   DDS_MIPMAP_FILTER_MITCHELL,
+   DDS_MIPMAP_FILTER_LANCZOS,
+   DDS_MIPMAP_FILTER_KAISER,
+   DDS_MIPMAP_FILTER_MAX
+} DDS_MIPMAP_FILTER;
+
+typedef enum
+{
+   DDS_MIPMAP_WRAP_DEFAULT = 0,
+   DDS_MIPMAP_WRAP_MIRROR,
+   DDS_MIPMAP_WRAP_REPEAT,
+   DDS_MIPMAP_WRAP_CLAMP,
+   DDS_MIPMAP_WRAP_MAX
+} DDS_MIPMAP_WRAP;
+
+#define DDS_HEADERSIZE             128
+#define DDS_HEADERSIZE_DX10        20
+
+#define DDSD_CAPS                  0x00000001
+#define DDSD_HEIGHT                0x00000002
+#define DDSD_WIDTH                 0x00000004
+#define DDSD_PITCH                 0x00000008
+#define DDSD_PIXELFORMAT           0x00001000
+#define DDSD_MIPMAPCOUNT           0x00020000
+#define DDSD_LINEARSIZE            0x00080000
+#define DDSD_DEPTH                 0x00800000
+
+#define DDPF_ALPHAPIXELS           0x00000001
+#define DDPF_ALPHA                 0x00000002
+#define DDPF_FOURCC                0x00000004
+#define DDPF_PALETTEINDEXED8       0x00000020
+#define DDPF_RGB                   0x00000040
+#define DDPF_LUMINANCE             0x00020000
+#define DDPF_NORMAL                0x80000000  // nvidia specific
+
+#define DDSCAPS_COMPLEX            0x00000008
+#define DDSCAPS_TEXTURE            0x00001000
+#define DDSCAPS_MIPMAP             0x00400000
+
+#define DDSCAPS2_CUBEMAP           0x00000200
+#define DDSCAPS2_CUBEMAP_POSITIVEX 0x00000400
+#define DDSCAPS2_CUBEMAP_NEGATIVEX 0x00000800
+#define DDSCAPS2_CUBEMAP_POSITIVEY 0x00001000
+#define DDSCAPS2_CUBEMAP_NEGATIVEY 0x00002000
+#define DDSCAPS2_CUBEMAP_POSITIVEZ 0x00004000
+#define DDSCAPS2_CUBEMAP_NEGATIVEZ 0x00008000
+#define DDSCAPS2_CUBEMAP_ALL_FACES \
+   (DDSCAPS2_CUBEMAP_POSITIVEX | DDSCAPS2_CUBEMAP_NEGATIVEX | \
+    DDSCAPS2_CUBEMAP_POSITIVEY | DDSCAPS2_CUBEMAP_NEGATIVEY | \
+    DDSCAPS2_CUBEMAP_POSITIVEZ | DDSCAPS2_CUBEMAP_NEGATIVEZ)
+
+#define DDSCAPS2_VOLUME            0x00200000
+
+#define D3D10_RESOURCE_MISC_TEXTURECUBE    0x04
+#define D3D10_RESOURCE_DIMENSION_BUFFER    1
+#define D3D10_RESOURCE_DIMENSION_TEXTURE1D 2
+#define D3D10_RESOURCE_DIMENSION_TEXTURE2D 3
+#define D3D10_RESOURCE_DIMENSION_TEXTURE3D 4
+
+typedef struct
+{
+   unsigned int size;
+   unsigned int flags;
+   char fourcc[4];
+   unsigned int bpp;
+   unsigned int rmask;
+   unsigned int gmask;
+   unsigned int bmask;
+   unsigned int amask;
+} dds_pixel_format_t;
+
+typedef struct
+{
+   unsigned int caps1;
+   unsigned int caps2;
+   unsigned int reserved[2];
+} dds_caps_t;
+
+typedef struct
+{
+   unsigned int magic;
+   unsigned int size;
+   unsigned int flags;
+   unsigned int height;
+   unsigned int width;
+   unsigned int pitch_or_linsize;
+   unsigned int depth;
+   unsigned int num_mipmaps;
+   union
+   {
+      struct
+      {
+         unsigned int magic1;   // FOURCC "GIMP"
+         unsigned int magic2;   // FOURCC "-DDS"
+         unsigned int version;
+         unsigned int extra_fourcc;
+      } gimp_dds_special;
+      unsigned char pad[4 * 11];
+   } reserved;
+   dds_pixel_format_t pixelfmt;
+   dds_caps_t caps;
+   unsigned int reserved2;
+} dds_header_t;
+
+typedef enum
+{
+   DXGI_FORMAT_UNKNOWN                      = 0,
+   DXGI_FORMAT_R32G32B32A32_TYPELESS        = 1,
+   DXGI_FORMAT_R32G32B32A32_FLOAT           = 2,
+   DXGI_FORMAT_R32G32B32A32_UINT            = 3,
+   DXGI_FORMAT_R32G32B32A32_SINT            = 4,
+   DXGI_FORMAT_R32G32B32_TYPELESS           = 5,
+   DXGI_FORMAT_R32G32B32_FLOAT              = 6,
+   DXGI_FORMAT_R32G32B32_UINT               = 7,
+   DXGI_FORMAT_R32G32B32_SINT               = 8,
+   DXGI_FORMAT_R16G16B16A16_TYPELESS        = 9,
+   DXGI_FORMAT_R16G16B16A16_FLOAT           = 10,
+   DXGI_FORMAT_R16G16B16A16_UNORM           = 11,
+   DXGI_FORMAT_R16G16B16A16_UINT            = 12,
+   DXGI_FORMAT_R16G16B16A16_SNORM           = 13,
+   DXGI_FORMAT_R16G16B16A16_SINT            = 14,
+   DXGI_FORMAT_R32G32_TYPELESS              = 15,
+   DXGI_FORMAT_R32G32_FLOAT                 = 16,
+   DXGI_FORMAT_R32G32_UINT                  = 17,
+   DXGI_FORMAT_R32G32_SINT                  = 18,
+   DXGI_FORMAT_R32G8X24_TYPELESS            = 19,
+   DXGI_FORMAT_D32_FLOAT_S8X24_UINT         = 20,
+   DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS     = 21,
+   DXGI_FORMAT_X32_TYPELESS_G8X24_UINT      = 22,
+   DXGI_FORMAT_R10G10B10A2_TYPELESS         = 23,
+   DXGI_FORMAT_R10G10B10A2_UNORM            = 24,
+   DXGI_FORMAT_R10G10B10A2_UINT             = 25,
+   DXGI_FORMAT_R11G11B10_FLOAT              = 26,
+   DXGI_FORMAT_R8G8B8A8_TYPELESS            = 27,
+   DXGI_FORMAT_R8G8B8A8_UNORM               = 28,
+   DXGI_FORMAT_R8G8B8A8_UNORM_SRGB          = 29,
+   DXGI_FORMAT_R8G8B8A8_UINT                = 30,
+   DXGI_FORMAT_R8G8B8A8_SNORM               = 31,
+   DXGI_FORMAT_R8G8B8A8_SINT                = 32,
+   DXGI_FORMAT_R16G16_TYPELESS              = 33,
+   DXGI_FORMAT_R16G16_FLOAT                 = 34,
+   DXGI_FORMAT_R16G16_UNORM                 = 35,
+   DXGI_FORMAT_R16G16_UINT                  = 36,
+   DXGI_FORMAT_R16G16_SNORM                 = 37,
+   DXGI_FORMAT_R16G16_SINT                  = 38,
+   DXGI_FORMAT_R32_TYPELESS                 = 39,
+   DXGI_FORMAT_D32_FLOAT                    = 40,
+   DXGI_FORMAT_R32_FLOAT                    = 41,
+   DXGI_FORMAT_R32_UINT                     = 42,
+   DXGI_FORMAT_R32_SINT                     = 43,
+   DXGI_FORMAT_R24G8_TYPELESS               = 44,
+   DXGI_FORMAT_D24_UNORM_S8_UINT            = 45,
+   DXGI_FORMAT_R24_UNORM_X8_TYPELESS        = 46,
+   DXGI_FORMAT_X24_TYPELESS_G8_UINT         = 47,
+   DXGI_FORMAT_R8G8_TYPELESS                = 48,
+   DXGI_FORMAT_R8G8_UNORM                   = 49,
+   DXGI_FORMAT_R8G8_UINT                    = 50,
+   DXGI_FORMAT_R8G8_SNORM                   = 51,
+   DXGI_FORMAT_R8G8_SINT                    = 52,
+   DXGI_FORMAT_R16_TYPELESS                 = 53,
+   DXGI_FORMAT_R16_FLOAT                    = 54,
+   DXGI_FORMAT_D16_UNORM                    = 55,
+   DXGI_FORMAT_R16_UNORM                    = 56,
+   DXGI_FORMAT_R16_UINT                     = 57,
+   DXGI_FORMAT_R16_SNORM                    = 58,
+   DXGI_FORMAT_R16_SINT                     = 59,
+   DXGI_FORMAT_R8_TYPELESS                  = 60,
+   DXGI_FORMAT_R8_UNORM                     = 61,
+   DXGI_FORMAT_R8_UINT                      = 62,
+   DXGI_FORMAT_R8_SNORM                     = 63,
+   DXGI_FORMAT_R8_SINT                      = 64,
+   DXGI_FORMAT_A8_UNORM                     = 65,
+   DXGI_FORMAT_R1_UNORM                     = 66,
+   DXGI_FORMAT_R9G9B9E5_SHAREDEXP           = 67,
+   DXGI_FORMAT_R8G8_B8G8_UNORM              = 68,
+   DXGI_FORMAT_G8R8_G8B8_UNORM              = 69,
+   DXGI_FORMAT_BC1_TYPELESS                 = 70,
+   DXGI_FORMAT_BC1_UNORM                    = 71,
+   DXGI_FORMAT_BC1_UNORM_SRGB               = 72,
+   DXGI_FORMAT_BC2_TYPELESS                 = 73,
+   DXGI_FORMAT_BC2_UNORM                    = 74,
+   DXGI_FORMAT_BC2_UNORM_SRGB               = 75,
+   DXGI_FORMAT_BC3_TYPELESS                 = 76,
+   DXGI_FORMAT_BC3_UNORM                    = 77,
+   DXGI_FORMAT_BC3_UNORM_SRGB               = 78,
+   DXGI_FORMAT_BC4_TYPELESS                 = 79,
+   DXGI_FORMAT_BC4_UNORM                    = 80,
+   DXGI_FORMAT_BC4_SNORM                    = 81,
+   DXGI_FORMAT_BC5_TYPELESS                 = 82,
+   DXGI_FORMAT_BC5_UNORM                    = 83,
+   DXGI_FORMAT_BC5_SNORM                    = 84,
+   DXGI_FORMAT_B5G6R5_UNORM                 = 85,
+   DXGI_FORMAT_B5G5R5A1_UNORM               = 86,
+   DXGI_FORMAT_B8G8R8A8_UNORM               = 87,
+   DXGI_FORMAT_B8G8R8X8_UNORM               = 88,
+   DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM   = 89,
+   DXGI_FORMAT_B8G8R8A8_TYPELESS            = 90,
+   DXGI_FORMAT_B8G8R8A8_UNORM_SRGB          = 91,
+   DXGI_FORMAT_B8G8R8X8_TYPELESS            = 92,
+   DXGI_FORMAT_B8G8R8X8_UNORM_SRGB          = 93,
+   DXGI_FORMAT_BC6H_TYPELESS                = 94,
+   DXGI_FORMAT_BC6H_UF16                    = 95,
+   DXGI_FORMAT_BC6H_SF16                    = 96,
+   DXGI_FORMAT_BC7_TYPELESS                 = 97,
+   DXGI_FORMAT_BC7_UNORM                    = 98,
+   DXGI_FORMAT_BC7_UNORM_SRGB               = 99,
+   DXGI_FORMAT_AYUV                         = 100,
+   DXGI_FORMAT_Y410                         = 101,
+   DXGI_FORMAT_Y416                         = 102,
+   DXGI_FORMAT_NV12                         = 103,
+   DXGI_FORMAT_P010                         = 104,
+   DXGI_FORMAT_P016                         = 105,
+   DXGI_FORMAT_420_OPAQUE                   = 106,
+   DXGI_FORMAT_YUY2                         = 107,
+   DXGI_FORMAT_Y210                         = 108,
+   DXGI_FORMAT_Y216                         = 109,
+   DXGI_FORMAT_NV11                         = 110,
+   DXGI_FORMAT_AI44                         = 111,
+   DXGI_FORMAT_IA44                         = 112,
+   DXGI_FORMAT_P8                           = 113,
+   DXGI_FORMAT_A8P8                         = 114,
+   DXGI_FORMAT_B4G4R4A4_UNORM               = 115,
+   DXGI_FORMAT_FORCE_UINT                   = 0xffffffffUL
+} DXGI_FORMAT;
+
+typedef struct
+{
+   DXGI_FORMAT dxgiFormat;
+   unsigned int resourceDimension;
+   unsigned int miscFlag;
+   unsigned int arraySize;
+   unsigned int reserved;
+} dds_header_dx10_t;
+
+#endif
diff --git a/plug-ins/file-dds/ddsplugin.h b/plug-ins/file-dds/ddsplugin.h
new file mode 100644
index 0000000000..809ea34b0a
--- /dev/null
+++ b/plug-ins/file-dds/ddsplugin.h
@@ -0,0 +1,78 @@
+/*
+       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.
+*/
+
+#ifndef __DDSPLUGIN_H
+#define __DDSPLUGIN_H
+
+#define DDS_PLUGIN_VERSION_MAJOR     3
+#define DDS_PLUGIN_VERSION_MINOR     9
+#define DDS_PLUGIN_VERSION_REVISION  90
+
+#define DDS_PLUGIN_VERSION  \
+   ((unsigned int)(DDS_PLUGIN_VERSION_MAJOR << 16) | \
+    (unsigned int)(DDS_PLUGIN_VERSION_MINOR <<  8) | \
+    (unsigned int)(DDS_PLUGIN_VERSION_REVISION))
+
+typedef struct
+{
+   int compression;
+   int mipmaps;
+   int savetype;
+   int format;
+   int transindex;
+   int mipmap_filter;
+   int mipmap_wrap;
+   int gamma_correct;
+   int srgb;
+   float gamma;
+   int perceptual_metric;
+   int show_adv_opt;
+   int preserve_alpha_coverage;
+   float alpha_test_threshold;
+} DDSWriteVals;
+
+typedef struct
+{
+   int show_dialog;
+   int mipmaps;
+   int decode_images;
+} DDSReadVals;
+
+extern DDSWriteVals dds_write_vals;
+extern DDSReadVals dds_read_vals;
+
+extern GimpPDBStatusType read_dds(gchar *filename, gint32 *imageID);
+extern GimpPDBStatusType write_dds(gchar *, gint32, gint32);
+
+extern gint interactive_dds;
+extern gchar *prog_name;
+extern gchar *filename;
+extern FILE *errorFile;
+
+#define LOAD_PROC "file-dds-load"
+#define SAVE_PROC "file-dds-save"
+
+#define DECODE_YCOCG_PROC "color-decode-ycocg"
+#define DECODE_YCOCG_SCALED_PROC "color-decode-ycocg-scaled"
+#define DECODE_ALPHA_EXP_PROC "color-decode-alpha-exp"
+
+#endif
diff --git a/plug-ins/file-dds/ddsread.c b/plug-ins/file-dds/ddsread.c
new file mode 100644
index 0000000000..c2af3d50b2
--- /dev/null
+++ b/plug-ins/file-dds/ddsread.c
@@ -0,0 +1,1235 @@
+/*
+       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 <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 "ddsplugin.h"
+#include "dds.h"
+#include "dxt.h"
+#include "endian.h"
+#include "misc.h"
+#include "imath.h"
+
+typedef struct
+{
+   unsigned char rshift, gshift, bshift, ashift;
+   unsigned char rbits, gbits, bbits, abits;
+   unsigned int rmask, gmask, bmask, amask;
+   unsigned int bpp, gimp_bpp;
+   int tile_height;
+   unsigned char *palette;
+} dds_load_info_t;
+
+static int read_header(dds_header_t *hdr, FILE *fp);
+static int read_header_dx10(dds_header_dx10_t *hdr, FILE *fp);
+static int validate_header(dds_header_t *hdr);
+static int setup_dxgi_format(dds_header_t *hdr, dds_header_dx10_t *dx10hdr);
+static int load_layer(FILE *fp, dds_header_t *hdr, dds_load_info_t *d,
+                      gint32 image, unsigned int level, char *prefix,
+                      unsigned int *l, guchar *pixels, unsigned char *buf);
+static int load_mipmaps(FILE *fp, dds_header_t *hdr, dds_load_info_t *d,
+                        gint32 image, char *prefix, unsigned int *l,
+                        guchar *pixels, unsigned char *buf);
+static int load_face(FILE *fp, dds_header_t *hdr, dds_load_info_t *d,
+                     gint32 image, char *prefix, unsigned int *l,
+                     guchar *pixels, unsigned char *buf);
+static unsigned char color_bits(unsigned int mask);
+static unsigned char color_shift(unsigned int mask);
+static int load_dialog(void);
+
+static int runme = 0;
+
+GimpPDBStatusType read_dds(gchar *filename, gint32 *imageID)
+{
+   gint32 image = 0;
+   unsigned char *buf;
+   unsigned int l = 0;
+   guchar *pixels;
+   gchar *tmp;
+   FILE *fp;
+   dds_header_t hdr;
+   dds_header_dx10_t dx10hdr;
+   dds_load_info_t d;
+   gint *layers, layer_count;
+   GimpImageBaseType type;
+   int i, j;
+
+   if(interactive_dds && dds_read_vals.show_dialog)
+   {
+      if(!load_dialog())
+         return(GIMP_PDB_CANCEL);
+   }
+
+   fp = g_fopen(filename, "rb");
+   if(fp == 0)
+   {
+      g_message("Error opening file.\n");
+      return(GIMP_PDB_EXECUTION_ERROR);
+   }
+
+   if(strrchr(filename, '/'))
+      tmp = g_strdup_printf("Loading %s:", strrchr(filename, '/') + 1);
+   else
+      tmp = g_strdup_printf("Loading %s:", filename);
+   gimp_progress_init(tmp);
+   g_free(tmp);
+
+   /* read header */
+   read_header(&hdr, fp);
+
+   memset(&dx10hdr, 0, sizeof(dds_header_dx10_t));
+
+   /* read DX10 header if necessary */
+   if(GETL32(hdr.pixelfmt.fourcc) == FOURCC('D','X','1','0'))
+   {
+      read_header_dx10(&dx10hdr, fp);
+
+      if(!setup_dxgi_format(&hdr, &dx10hdr))
+      {
+         fclose(fp);
+         return(GIMP_PDB_EXECUTION_ERROR);
+      }
+   }
+
+   if(!validate_header(&hdr))
+   {
+      fclose(fp);
+      g_message("Invalid DDS header!\n");
+      return(GIMP_PDB_EXECUTION_ERROR);
+   }
+
+   /* a lot of DDS images out there don't have this for some reason -_- */
+   if(hdr.pitch_or_linsize == 0)
+   {
+      if(hdr.pixelfmt.flags & DDPF_FOURCC) /* assume linear size */
+      {
+         hdr.pitch_or_linsize = ((hdr.width + 3) >> 2) * ((hdr.height + 3) >> 2);
+         switch(GETL32(hdr.pixelfmt.fourcc))
+         {
+            case FOURCC('D','X','T','1'):
+            case FOURCC('A','T','I','1'):
+            case FOURCC('B','C','4','U'):
+            case FOURCC('B','C','4','S'):
+               hdr.pitch_or_linsize *= 8;
+               break;
+            default:
+               hdr.pitch_or_linsize *= 16;
+               break;
+         }
+      }
+      else /* assume pitch */
+      {
+         hdr.pitch_or_linsize = hdr.height * hdr.width * (hdr.pixelfmt.bpp >> 3);
+      }
+   }
+
+   if(hdr.pixelfmt.flags & DDPF_FOURCC)
+   {
+      /* fourcc is dXt* or rXgb */
+      if(hdr.pixelfmt.fourcc[1] == 'X')
+         hdr.pixelfmt.flags |= DDPF_ALPHAPIXELS;
+   }
+
+   if(hdr.pixelfmt.flags & DDPF_FOURCC)
+   {
+      switch(GETL32(hdr.pixelfmt.fourcc))
+      {
+         case FOURCC('A','T','I','1'):
+         case FOURCC('B','C','4','U'):
+         case FOURCC('B','C','4','S'):
+            d.bpp = d.gimp_bpp = 1;
+            type = GIMP_GRAY;
+            break;
+         case FOURCC('A','T','I','2'):
+         case FOURCC('B','C','5','U'):
+         case FOURCC('B','C','5','S'):
+            d.bpp = d.gimp_bpp = 3;
+            type = GIMP_RGB;
+            break;
+         default:
+            d.bpp = d.gimp_bpp = 4;
+            type = GIMP_RGB;
+            break;
+      }
+   }
+   else
+   {
+      d.bpp = hdr.pixelfmt.bpp >> 3;
+
+      if(d.bpp == 2)
+      {
+         if(hdr.pixelfmt.amask == 0xf000) // RGBA4
+         {
+            d.gimp_bpp = 4;
+            type = GIMP_RGB;
+         }
+         else if(hdr.pixelfmt.amask == 0xff00) //L8A8
+         {
+            d.gimp_bpp = 2;
+            type = GIMP_GRAY;
+         }
+         else if(hdr.pixelfmt.bmask == 0x1f) //R5G6B5 or RGB5A1
+         {
+            if(hdr.pixelfmt.amask == 0x8000) // RGB5A1
+               d.gimp_bpp = 4;
+            else
+               d.gimp_bpp = 3;
+
+            type = GIMP_RGB;
+         }
+         else //L16
+         {
+            d.gimp_bpp = 1;
+            type = GIMP_GRAY;
+         }
+      }
+      else
+      {
+         if(hdr.pixelfmt.flags & DDPF_PALETTEINDEXED8)
+         {
+            type = GIMP_INDEXED;
+            d.gimp_bpp = 1;
+         }
+         else if(hdr.pixelfmt.rmask == 0xe0) // R3G3B2
+         {
+            type = GIMP_RGB;
+            d.gimp_bpp = 3;
+         }
+         else
+         {
+            /* test alpha only image */
+            if(d.bpp == 1 && (hdr.pixelfmt.flags & DDPF_ALPHA))
+            {
+               d.gimp_bpp = 2;
+               type = GIMP_GRAY;
+            }
+            else
+            {
+               d.gimp_bpp = d.bpp;
+               type = (d.bpp == 1) ? GIMP_GRAY : GIMP_RGB;
+            }
+         }
+      }
+   }
+
+   image = gimp_image_new(hdr.width, hdr.height, type);
+
+   if(image == -1)
+   {
+      g_message("Can't allocate new image.\n");
+      fclose(fp);
+      return(GIMP_PDB_EXECUTION_ERROR);
+   }
+
+   gimp_image_set_filename(image, filename);
+
+   if(hdr.pixelfmt.flags & DDPF_PALETTEINDEXED8)
+   {
+      d.palette = g_malloc(256 * 4);
+      if(fread(d.palette, 1, 1024, fp) != 1024)
+      {
+         g_message("Error reading palette.\n");
+         fclose(fp);
+         gimp_image_delete(image);
+         return(GIMP_PDB_EXECUTION_ERROR);
+      }
+      for(i = j = 0; i < 768; i += 3, j += 4)
+      {
+         d.palette[i + 0] = d.palette[j + 0];
+         d.palette[i + 1] = d.palette[j + 1];
+         d.palette[i + 2] = d.palette[j + 2];
+      }
+      gimp_image_set_colormap(image, d.palette, 256);
+   }
+
+   d.tile_height = gimp_tile_height();
+
+   pixels = g_new(guchar, d.tile_height * hdr.width * d.gimp_bpp);
+   buf = g_malloc(hdr.pitch_or_linsize);
+
+   d.rshift = color_shift(hdr.pixelfmt.rmask);
+   d.gshift = color_shift(hdr.pixelfmt.gmask);
+   d.bshift = color_shift(hdr.pixelfmt.bmask);
+   d.ashift = color_shift(hdr.pixelfmt.amask);
+   d.rbits = color_bits(hdr.pixelfmt.rmask);
+   d.gbits = color_bits(hdr.pixelfmt.gmask);
+   d.bbits = color_bits(hdr.pixelfmt.bmask);
+   d.abits = color_bits(hdr.pixelfmt.amask);
+   d.rmask = (hdr.pixelfmt.rmask >> d.rshift) << (8 - d.rbits);
+   d.gmask = (hdr.pixelfmt.gmask >> d.gshift) << (8 - d.gbits);
+   d.bmask = (hdr.pixelfmt.bmask >> d.bshift) << (8 - d.bbits);
+   d.amask = (hdr.pixelfmt.amask >> d.ashift) << (8 - d.abits);
+
+   if(!(hdr.caps.caps2 & DDSCAPS2_CUBEMAP) &&
+      !(hdr.caps.caps2 & DDSCAPS2_VOLUME) &&
+      dx10hdr.arraySize == 0)
+   {
+      if(!load_layer(fp, &hdr, &d, image, 0, "", &l, pixels, buf))
+      {
+         fclose(fp);
+         gimp_image_delete(image);
+         return(GIMP_PDB_EXECUTION_ERROR);
+      }
+      if(!load_mipmaps(fp, &hdr, &d, image, "", &l, pixels, buf))
+      {
+         fclose(fp);
+         gimp_image_delete(image);
+         return(GIMP_PDB_EXECUTION_ERROR);
+      }
+   }
+   else if(hdr.caps.caps2 & DDSCAPS2_CUBEMAP)
+   {
+      if((hdr.caps.caps2 & DDSCAPS2_CUBEMAP_POSITIVEX) &&
+         !load_face(fp, &hdr, &d, image, "(positive x)", &l, pixels, buf))
+      {
+         fclose(fp);
+         gimp_image_delete(image);
+         return(GIMP_PDB_EXECUTION_ERROR);
+      }
+      if((hdr.caps.caps2 & DDSCAPS2_CUBEMAP_NEGATIVEX) &&
+         !load_face(fp, &hdr, &d, image, "(negative x)", &l, pixels, buf))
+      {
+         fclose(fp);
+         gimp_image_delete(image);
+         return(GIMP_PDB_EXECUTION_ERROR);
+      }
+      if((hdr.caps.caps2 & DDSCAPS2_CUBEMAP_POSITIVEY) &&
+         !load_face(fp, &hdr, &d, image, "(positive y)", &l, pixels, buf))
+      {
+         fclose(fp);
+         gimp_image_delete(image);
+         return(GIMP_PDB_EXECUTION_ERROR);
+      }
+      if((hdr.caps.caps2 & DDSCAPS2_CUBEMAP_NEGATIVEY) &&
+         !load_face(fp, &hdr, &d, image, "(negative y)", &l, pixels, buf))
+      {
+         fclose(fp);
+         gimp_image_delete(image);
+         return(GIMP_PDB_EXECUTION_ERROR);
+      }
+      if((hdr.caps.caps2 & DDSCAPS2_CUBEMAP_POSITIVEZ) &&
+         !load_face(fp, &hdr, &d, image, "(positive z)", &l, pixels, buf))
+      {
+         fclose(fp);
+         gimp_image_delete(image);
+         return(GIMP_PDB_EXECUTION_ERROR);
+      }
+      if((hdr.caps.caps2 & DDSCAPS2_CUBEMAP_NEGATIVEZ) &&
+         !load_face(fp, &hdr, &d, image, "(negative z)", &l, pixels, buf))
+      {
+         fclose(fp);
+         gimp_image_delete(image);
+         return(GIMP_PDB_EXECUTION_ERROR);
+      }
+   }
+   else if((hdr.caps.caps2 & DDSCAPS2_VOLUME) &&
+            (hdr.flags & DDSD_DEPTH))
+   {
+      unsigned int i, level;
+      char *plane;
+      for(i = 0; i < hdr.depth; ++i)
+      {
+         plane = g_strdup_printf("(z = %d)", i);
+         if(!load_layer(fp, &hdr, &d, image, 0, plane, &l, pixels, buf))
+         {
+            g_free(plane);
+            fclose(fp);
+            gimp_image_delete(image);
+            return(GIMP_PDB_EXECUTION_ERROR);
+         }
+         g_free(plane);
+      }
+
+      if((hdr.flags & DDSD_MIPMAPCOUNT) &&
+         (hdr.caps.caps1 & DDSCAPS_MIPMAP) &&
+         (dds_read_vals.mipmaps != 0))
+      {
+         for(level = 1; level < hdr.num_mipmaps; ++level)
+         {
+            int n = hdr.depth >> level;
+            if(n < 1) n = 1;
+            for(i = 0; i < n; ++i)
+            {
+               plane = g_strdup_printf("(z = %d)", i);
+               if(!load_layer(fp, &hdr, &d, image, level, plane, &l, pixels, buf))
+               {
+                  g_free(plane);
+                  fclose(fp);
+                  gimp_image_delete(image);
+                  return(GIMP_PDB_EXECUTION_ERROR);
+               }
+               g_free(plane);
+            }
+         }
+      }
+   }
+   else if(dx10hdr.arraySize > 0)
+   {
+      unsigned int i;
+      char *elem;
+
+      for(i = 0; i < dx10hdr.arraySize; ++i)
+      {
+         elem = g_strdup_printf("(array element %d)", i);
+         if(!load_layer(fp, &hdr, &d, image, 0, elem, &l, pixels, buf))
+         {
+            fclose(fp);
+            gimp_image_delete(image);
+            return(GIMP_PDB_EXECUTION_ERROR);
+         }
+         if(!load_mipmaps(fp, &hdr, &d, image, elem, &l, pixels, buf))
+         {
+            fclose(fp);
+            gimp_image_delete(image);
+            return(GIMP_PDB_EXECUTION_ERROR);
+         }
+         g_free(elem);
+      }
+   }
+
+   gimp_progress_update(1.0);
+
+   if(hdr.pixelfmt.flags & DDPF_PALETTEINDEXED8)
+      g_free(d.palette);
+
+   g_free(buf);
+   g_free(pixels);
+   fclose(fp);
+
+   layers = gimp_image_get_layers(image, &layer_count);
+
+   if(layers == NULL || layer_count == 0)
+   {
+      g_message("Oops!  NULL image read!  Please report this!");
+      return(GIMP_PDB_EXECUTION_ERROR);
+   }
+
+   gimp_image_set_active_layer(image, layers[0]);
+
+   *imageID = image;
+
+   return(GIMP_PDB_SUCCESS);
+}
+
+static int read_header(dds_header_t *hdr, FILE *fp)
+{
+   unsigned char buf[DDS_HEADERSIZE];
+
+   memset(hdr, 0, sizeof(dds_header_t));
+
+   if(fread(buf, 1, DDS_HEADERSIZE, fp) != DDS_HEADERSIZE)
+      return(0);
+
+   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(1);
+}
+
+static int read_header_dx10(dds_header_dx10_t *hdr, FILE *fp)
+{
+   char buf[DDS_HEADERSIZE_DX10];
+
+   memset(hdr, 0, sizeof(dds_header_dx10_t));
+
+   if(fread(buf, 1, DDS_HEADERSIZE_DX10, fp) != DDS_HEADERSIZE_DX10)
+      return(0);
+
+   hdr->dxgiFormat = GETL32(buf);
+   hdr->resourceDimension = GETL32(buf + 4);
+   hdr->miscFlag = GETL32(buf + 8);
+   hdr->arraySize = GETL32(buf + 12);
+   hdr->reserved = GETL32(buf + 16);
+
+   return(1);
+}
+
+static int validate_header(dds_header_t *hdr)
+{
+   unsigned int fourcc;
+
+   if(hdr->magic != FOURCC('D','D','S',' '))
+   {
+      g_message("Invalid DDS file.\n");
+      return(0);
+   }
+
+   if((hdr->flags & DDSD_PITCH) == (hdr->flags & DDSD_LINEARSIZE))
+   {
+      //g_message("Warning: DDSD_PITCH or DDSD_LINEARSIZE is not set.\n");
+      if(hdr->pixelfmt.flags & DDPF_FOURCC)
+         hdr->flags |= DDSD_LINEARSIZE;
+      else
+         hdr->flags |= DDSD_PITCH;
+   }
+/*
+   if((hdr->pixelfmt.flags & DDPF_FOURCC) ==
+      (hdr->pixelfmt.flags & DDPF_RGB))
+   {
+      g_message("Invalid pixel format.\n");
+      return(0);
+   }
+*/
+   fourcc = GETL32(hdr->pixelfmt.fourcc);
+
+   if((hdr->pixelfmt.flags & DDPF_FOURCC) &&
+      fourcc != FOURCC('D','X','T','1') &&
+      fourcc != FOURCC('D','X','T','3') &&
+      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'))
+   {
+      g_message("Unsupported format (FOURCC: %c%c%c%c, hex: %08x).\n",
+                hdr->pixelfmt.fourcc[0],
+                hdr->pixelfmt.fourcc[1],
+                hdr->pixelfmt.fourcc[2],
+                hdr->pixelfmt.fourcc[3],
+                GETL32(hdr->pixelfmt.fourcc));
+      return(0);
+   }
+
+   if(hdr->pixelfmt.flags & DDPF_RGB)
+   {
+      if((hdr->pixelfmt.bpp !=  8) &&
+         (hdr->pixelfmt.bpp != 16) &&
+         (hdr->pixelfmt.bpp != 24) &&
+         (hdr->pixelfmt.bpp != 32))
+      {
+         g_message("Invalid BPP.\n");
+         return(0);
+      }
+   }
+   else if(hdr->pixelfmt.flags & DDPF_LUMINANCE)
+   {
+      if((hdr->pixelfmt.bpp !=  8) &&
+         (hdr->pixelfmt.bpp != 16))
+      {
+         g_message("Invalid BPP.\n");
+         return(0);
+      }
+
+      hdr->pixelfmt.flags |= DDPF_RGB;
+   }
+   else if(hdr->pixelfmt.flags & DDPF_PALETTEINDEXED8)
+   {
+      hdr->pixelfmt.flags |= DDPF_RGB;
+   }
+
+   if(!(hdr->pixelfmt.flags & DDPF_RGB) &&
+      !(hdr->pixelfmt.flags & DDPF_ALPHA) &&
+      !(hdr->pixelfmt.flags & DDPF_FOURCC) &&
+      !(hdr->pixelfmt.flags & DDPF_LUMINANCE))
+   {
+      g_message("Unknown pixel format!  Taking a guess, expect trouble!");
+      switch(fourcc)
+      {
+         case FOURCC('D','X','T','1'):
+         case FOURCC('D','X','T','3'):
+         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:
+                  hdr->pixelfmt.flags |= DDPF_RGB;
+                  break;
+               default:
+                  g_message("Invalid pixel format.");
+                  return(0);
+            }
+            break;
+      }
+   }
+
+   return(1);
+}
+
+/*
+ * This function will set the necessary flags and attributes in the standard
+ * dds header using the information found in the DX10 header.
+ */
+static int setup_dxgi_format(dds_header_t *hdr, dds_header_dx10_t *dx10hdr)
+{
+   if((dx10hdr->resourceDimension == D3D10_RESOURCE_DIMENSION_TEXTURE2D) &&
+      (dx10hdr->miscFlag & D3D10_RESOURCE_MISC_TEXTURECUBE))
+   {
+      hdr->caps.caps2 |= DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_ALL_FACES;
+   }
+   else if(dx10hdr->resourceDimension == D3D10_RESOURCE_DIMENSION_TEXTURE3D)
+   {
+      hdr->flags |= DDSD_DEPTH;
+      hdr->caps.caps2 |= DDSCAPS2_VOLUME;
+   }
+
+   if((dx10hdr->resourceDimension != D3D10_RESOURCE_DIMENSION_TEXTURE1D) &&
+      (dx10hdr->resourceDimension != D3D10_RESOURCE_DIMENSION_TEXTURE2D) &&
+      (dx10hdr->resourceDimension != D3D10_RESOURCE_DIMENSION_TEXTURE3D))
+      return(0);
+
+   // check for a compressed DXGI format
+   if((dx10hdr->dxgiFormat >= DXGI_FORMAT_BC1_TYPELESS) &&
+      (dx10hdr->dxgiFormat <= DXGI_FORMAT_BC5_SNORM))
+   {
+      // set flag and replace FOURCC
+      hdr->pixelfmt.flags |= DDPF_FOURCC;
+
+      switch(dx10hdr->dxgiFormat)
+      {
+         case DXGI_FORMAT_BC1_TYPELESS:
+         case DXGI_FORMAT_BC1_UNORM:
+         case DXGI_FORMAT_BC1_UNORM_SRGB:
+            PUTL32(hdr->pixelfmt.fourcc, FOURCC('D','X','T','1'));
+            break;
+         case DXGI_FORMAT_BC2_TYPELESS:
+         case DXGI_FORMAT_BC2_UNORM:
+         case DXGI_FORMAT_BC2_UNORM_SRGB:
+            PUTL32(hdr->pixelfmt.fourcc, FOURCC('D','X','T','3'));
+            break;
+         case DXGI_FORMAT_BC3_TYPELESS:
+         case DXGI_FORMAT_BC3_UNORM:
+         case DXGI_FORMAT_BC3_UNORM_SRGB:
+            PUTL32(hdr->pixelfmt.fourcc, FOURCC('D','X','T','5'));
+            break;
+         case DXGI_FORMAT_BC4_TYPELESS:
+         case DXGI_FORMAT_BC4_UNORM:
+            PUTL32(hdr->pixelfmt.fourcc, FOURCC('A','T','I','1'));
+            break;
+         case DXGI_FORMAT_BC4_SNORM:
+            PUTL32(hdr->pixelfmt.fourcc, FOURCC('B','C','4','S'));
+            break;
+         case DXGI_FORMAT_BC5_TYPELESS:
+         case DXGI_FORMAT_BC5_UNORM:
+            PUTL32(hdr->pixelfmt.fourcc, FOURCC('A','T','I','2'));
+            break;
+         case DXGI_FORMAT_BC5_SNORM:
+            PUTL32(hdr->pixelfmt.fourcc, FOURCC('B','C','5','S'));
+            break;
+         default:
+            break;
+      }
+   }
+   else
+   {
+      /* unset the FOURCC flag */
+      hdr->pixelfmt.flags &= ~DDPF_FOURCC;
+
+      switch(dx10hdr->dxgiFormat)
+      {
+         case DXGI_FORMAT_B8G8R8A8_TYPELESS:
+         case DXGI_FORMAT_B8G8R8A8_UNORM:
+         case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB:
+            hdr->pixelfmt.bpp = 32;
+            hdr->pixelfmt.flags |= DDPF_ALPHAPIXELS;
+            hdr->pixelfmt.rmask = 0x00ff0000;
+            hdr->pixelfmt.gmask = 0x0000ff00;
+            hdr->pixelfmt.bmask = 0x000000ff;
+            hdr->pixelfmt.amask = 0xff000000;
+            break;
+         case DXGI_FORMAT_B8G8R8X8_TYPELESS:
+         case DXGI_FORMAT_B8G8R8X8_UNORM:
+         case DXGI_FORMAT_B8G8R8X8_UNORM_SRGB:
+            hdr->pixelfmt.bpp = 32;
+            hdr->pixelfmt.flags |= DDPF_ALPHAPIXELS;
+            hdr->pixelfmt.rmask = 0x00ff0000;
+            hdr->pixelfmt.gmask = 0x0000ff00;
+            hdr->pixelfmt.bmask = 0x000000ff;
+            hdr->pixelfmt.amask = 0x00000000;
+            break;
+         case DXGI_FORMAT_R8G8B8A8_TYPELESS:
+         case DXGI_FORMAT_R8G8B8A8_UNORM:
+         case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB:
+         case DXGI_FORMAT_R8G8B8A8_UINT:
+         case DXGI_FORMAT_R8G8B8A8_SNORM:
+         case DXGI_FORMAT_R8G8B8A8_SINT:
+            hdr->pixelfmt.bpp = 32;
+            hdr->pixelfmt.flags |= DDPF_ALPHAPIXELS;
+            hdr->pixelfmt.rmask = 0x000000ff;
+            hdr->pixelfmt.gmask = 0x0000ff00;
+            hdr->pixelfmt.bmask = 0x00ff0000;
+            hdr->pixelfmt.amask = 0xff000000;
+            break;
+         case DXGI_FORMAT_B5G6R5_UNORM:
+            hdr->pixelfmt.bpp = 16;
+            hdr->pixelfmt.rmask = 0x0000f800;
+            hdr->pixelfmt.gmask = 0x000007e0;
+            hdr->pixelfmt.bmask = 0x0000001f;
+            hdr->pixelfmt.amask = 0x00000000;
+            break;
+         case DXGI_FORMAT_B5G5R5A1_UNORM:
+            hdr->pixelfmt.bpp = 16;
+            hdr->pixelfmt.rmask = 0x00007c00;
+            hdr->pixelfmt.gmask = 0x000003e0;
+            hdr->pixelfmt.bmask = 0x0000001f;
+            hdr->pixelfmt.amask = 0x00008000;
+            break;
+         case DXGI_FORMAT_R10G10B10A2_TYPELESS:
+         case DXGI_FORMAT_R10G10B10A2_UNORM:
+         case DXGI_FORMAT_R10G10B10A2_UINT:
+            hdr->pixelfmt.bpp = 32;
+            hdr->pixelfmt.flags |= DDPF_ALPHAPIXELS;
+            hdr->pixelfmt.rmask = 0x000003ff;
+            hdr->pixelfmt.gmask = 0x000ffc00;
+            hdr->pixelfmt.bmask = 0x3ff00000;
+            hdr->pixelfmt.amask = 0xc0000000;
+            break;
+         case DXGI_FORMAT_A8_UNORM:
+            hdr->pixelfmt.bpp = 8;
+            hdr->pixelfmt.flags |= DDPF_ALPHA | DDPF_ALPHAPIXELS;
+            hdr->pixelfmt.rmask = hdr->pixelfmt.gmask = hdr->pixelfmt.bmask = 0;
+            hdr->pixelfmt.amask = 0x000000ff;
+            break;
+         case DXGI_FORMAT_R8_TYPELESS:
+         case DXGI_FORMAT_R8_UNORM:
+         case DXGI_FORMAT_R8_UINT:
+         case DXGI_FORMAT_R8_SNORM:
+         case DXGI_FORMAT_R8_SINT:
+            hdr->pixelfmt.bpp = 8;
+            hdr->pixelfmt.rmask = 0x000000ff;
+            hdr->pixelfmt.gmask = hdr->pixelfmt.bmask = hdr->pixelfmt.amask = 0;
+            break;
+         case DXGI_FORMAT_B4G4R4A4_UNORM:
+            hdr->pixelfmt.bpp = 16;
+            hdr->pixelfmt.flags |= DDPF_ALPHAPIXELS;
+            hdr->pixelfmt.rmask = 0x00000f00;
+            hdr->pixelfmt.gmask = 0x000000f0;
+            hdr->pixelfmt.bmask = 0x0000000f;
+            hdr->pixelfmt.amask = 0x0000f000;
+            break;
+         case DXGI_FORMAT_UNKNOWN:
+            g_message("Unknown DXGI format.  Expect problems...");
+            break;
+         default:  /* unsupported DXGI format */
+            g_message("Unsupported DXGI format (%d)", dx10hdr->dxgiFormat);
+            return(0);
+      }
+   }
+
+   return(1);
+}
+
+static int load_layer(FILE *fp, dds_header_t *hdr, dds_load_info_t *d,
+                      gint32 image, unsigned int level, char *prefix,
+                      unsigned int *l, guchar *pixels, unsigned char *buf)
+{
+   GeglBuffer *buffer;
+   const Babl *bablfmt = NULL;
+   GimpImageType type = GIMP_RGBA_IMAGE;
+   gchar *layer_name;
+   gint x, y, z, n;
+   gint32 layer;
+   unsigned int width = hdr->width >> level;
+   unsigned int height = hdr->height >> level;
+   unsigned int size = hdr->pitch_or_linsize >> (2 * level);
+   unsigned int layerw;
+   int format = DDS_COMPRESS_NONE;
+
+   if(width < 1) width = 1;
+   if(height < 1) height = 1;
+
+   switch(d->bpp)
+   {
+      case 1:
+         if(hdr->pixelfmt.flags & DDPF_PALETTEINDEXED8)
+         {
+            type = GIMP_INDEXED_IMAGE;
+            bablfmt = babl_format("R'G'B' u8");
+         }
+         else if(hdr->pixelfmt.rmask == 0xe0)
+         {
+            type = GIMP_RGB_IMAGE;
+            bablfmt = babl_format("R'G'B' u8");
+         }
+         else if(hdr->pixelfmt.flags & DDPF_ALPHA)
+         {
+            type = GIMP_GRAYA_IMAGE;
+            bablfmt = babl_format("Y'A u8");
+         }
+         else
+         {
+            type = GIMP_GRAY_IMAGE;
+            bablfmt = babl_format("Y' u8");
+         }
+         break;
+      case 2:
+         if(hdr->pixelfmt.amask == 0xf000) //RGBA4
+         {
+            type = GIMP_RGBA_IMAGE;
+            bablfmt = babl_format("R'G'B'A u8");
+         }
+         else if(hdr->pixelfmt.amask == 0xff00) //L8A8
+         {
+            type = GIMP_GRAYA_IMAGE;
+            bablfmt = babl_format("Y'A u8");
+         }
+         else if(hdr->pixelfmt.bmask == 0x1f) //R5G6B5 or RGB5A1
+         {
+            type = (hdr->pixelfmt.amask == 0x8000) ? GIMP_RGBA_IMAGE : GIMP_RGB_IMAGE;
+            bablfmt = (hdr->pixelfmt.amask == 0x8000) ? babl_format("R'G'B'A u8") : babl_format("R'G'B' u8");
+         }
+         else //L16
+         {
+            type = GIMP_GRAY_IMAGE;
+            bablfmt = babl_format("Y' u8");
+         }
+         break;
+      case 3: type = GIMP_RGB_IMAGE;  bablfmt = babl_format("R'G'B' u8");  break;
+      case 4: type = GIMP_RGBA_IMAGE; bablfmt = babl_format("R'G'B'A u8"); break;
+   }
+
+   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_NORMAL_MODE);
+   g_free(layer_name);
+
+   gimp_image_insert_layer(image, layer, 0, *l);
+
+   if((*l)++) gimp_item_set_visible(layer, FALSE);
+
+   buffer = gimp_drawable_get_buffer(layer);
+   
+   layerw = gegl_buffer_get_width(buffer);
+
+   if(hdr->pixelfmt.flags & DDPF_FOURCC)
+   {
+      unsigned int w = (width  + 3) >> 2;
+      unsigned int h = (height + 3) >> 2;
+
+      switch(GETL32(hdr->pixelfmt.fourcc))
+      {
+         case FOURCC('D','X','T','1'): format = DDS_COMPRESS_BC1; break;
+         case FOURCC('D','X','T','3'): format = DDS_COMPRESS_BC2; break;
+         case FOURCC('D','X','T','5'): format = DDS_COMPRESS_BC3; break;
+         case FOURCC('R','X','G','B'): format = DDS_COMPRESS_BC3; break;
+         case FOURCC('A','T','I','1'):
+         case FOURCC('B','C','4','U'):
+         case FOURCC('B','C','4','S'): format = DDS_COMPRESS_BC4; break;
+         case FOURCC('A','T','I','2'):
+         case FOURCC('B','C','5','U'):
+         case FOURCC('B','C','5','S'): format = DDS_COMPRESS_BC5; break;
+      }
+
+      size = w * h;
+      if((format == DDS_COMPRESS_BC1) || (format == DDS_COMPRESS_BC4))
+         size *= 8;
+      else
+         size *= 16;
+   }
+
+   if((hdr->flags & DDSD_LINEARSIZE) &&
+      !fread(buf, size, 1, fp))
+   {
+      g_message("Unexpected EOF.\n");
+      return(0);
+   }
+
+   if((hdr->pixelfmt.flags & DDPF_RGB) || (hdr->pixelfmt.flags & DDPF_ALPHA))
+   {
+      z = 0;
+      for(y = 0, n = 0; y < height; ++y, ++n)
+      {
+         if(n >= d->tile_height)
+         {
+            gegl_buffer_set(buffer, GEGL_RECTANGLE(0, y - n, layerw, n), 1.0,
+                            bablfmt, pixels, GEGL_AUTO_ROWSTRIDE);
+            n = 0;
+            gimp_progress_update((double)y / (double)hdr->height);
+         }
+
+         if((hdr->flags & DDSD_PITCH) &&
+            !fread(buf, width * d->bpp, 1, fp))
+         {
+            g_message("Unexpected EOF.\n");
+            return(0);
+         }
+
+         if(!(hdr->flags & DDSD_LINEARSIZE)) z = 0;
+
+         for(x = 0; x < layerw; ++x)
+         {
+            unsigned int pixel = buf[z];
+            unsigned int pos = (n * layerw + x) * d->gimp_bpp;
+
+            if(d->bpp > 1) pixel += ((unsigned int)buf[z + 1] <<  8);
+            if(d->bpp > 2) pixel += ((unsigned int)buf[z + 2] << 16);
+            if(d->bpp > 3) pixel += ((unsigned int)buf[z + 3] << 24);
+
+            if(d->bpp >= 3)
+            {
+               if(hdr->pixelfmt.amask == 0xc0000000) // handle RGB10A2
+               {
+                  pixels[pos + 0] = (pixel >> d->bshift) >> 2;
+                  pixels[pos + 1] = (pixel >> d->gshift) >> 2;
+                  pixels[pos + 2] = (pixel >> d->rshift) >> 2;
+                  if(hdr->pixelfmt.flags & DDPF_ALPHAPIXELS)
+                     pixels[pos + 3] = (pixel >> d->ashift << (8 - d->abits) & d->amask) * 255 / d->amask;
+               }
+               else
+               {
+                  pixels[pos] =
+                     (pixel >> d->rshift << (8 - d->rbits) & d->rmask) * 255 / d->rmask;
+                  pixels[pos + 1] =
+                     (pixel >> d->gshift << (8 - d->gbits) & d->gmask) * 255 / d->gmask;
+                  pixels[pos + 2] =
+                     (pixel >> d->bshift << (8 - d->bbits) & d->bmask) * 255 / d->bmask;
+                  if(hdr->pixelfmt.flags & DDPF_ALPHAPIXELS)
+                  {
+                     pixels[pos + 3] =
+                        (pixel >> d->ashift << (8 - d->abits) & d->amask) * 255 / d->amask;
+                  }
+               }
+            }
+            else if(d->bpp == 2)
+            {
+               if(hdr->pixelfmt.amask == 0xf000) //RGBA4
+               {
+                  pixels[pos] =
+                     (pixel >> d->rshift << (8 - d->rbits) & d->rmask) * 255 / d->rmask;
+                  pixels[pos + 1] =
+                     (pixel >> d->gshift << (8 - d->gbits) & d->gmask) * 255 / d->gmask;
+                  pixels[pos + 2] =
+                     (pixel >> d->bshift << (8 - d->bbits) & d->bmask) * 255 / d->bmask;
+                  pixels[pos + 3] =
+                     (pixel >> d->ashift << (8 - d->abits) & d->amask) * 255 / d->amask;
+               }
+               else if(hdr->pixelfmt.amask == 0xff00) //L8A8
+               {
+                  pixels[pos] =
+                     (pixel >> d->rshift << (8 - d->rbits) & d->rmask) * 255 / d->rmask;
+                  pixels[pos + 1] =
+                     (pixel >> d->ashift << (8 - d->abits) & d->amask) * 255 / d->amask;
+               }
+               else if(hdr->pixelfmt.bmask == 0x1f) //R5G6B5 or RGB5A1
+               {
+                  pixels[pos] =
+                     (pixel >> d->rshift << (8 - d->rbits) & d->rmask) * 255 / d->rmask;
+                  pixels[pos + 1] =
+                     (pixel >> d->gshift << (8 - d->gbits) & d->gmask) * 255 / d->gmask;
+                  pixels[pos + 2] =
+                     (pixel >> d->bshift << (8 - d->bbits) & d->bmask) * 255 / d->bmask;
+                  if(hdr->pixelfmt.amask == 0x8000)
+                  {
+                     pixels[pos + 3] =
+                         (pixel >> d->ashift << (8 - d->abits) & d->amask) * 255 / d->amask;
+                  }
+               }
+               else //L16
+                  pixels[pos] = (unsigned char)(255 * ((float)(pixel & 0xffff) / 65535.0f));
+            }
+            else
+            {
+               if(hdr->pixelfmt.flags & DDPF_PALETTEINDEXED8)
+               {
+                  pixels[pos] = pixel & 0xff;
+               }
+               else if(hdr->pixelfmt.rmask == 0xe0) // R3G3B2
+               {
+                  pixels[pos] =
+                     (pixel >> d->rshift << (8 - d->rbits) & d->rmask) * 255 / d->rmask;
+                  pixels[pos + 1] =
+                     (pixel >> d->gshift << (8 - d->gbits) & d->gmask) * 255 / d->gmask;
+                  pixels[pos + 2] =
+                     (pixel >> d->bshift << (8 - d->bbits) & d->bmask) * 255 / d->bmask;
+               }
+               else if(hdr->pixelfmt.flags & DDPF_ALPHA)
+               {
+                  pixels[pos + 0] = 255;
+                  pixels[pos + 1] = pixel & 0xff;
+               }
+               else // LUMINANCE
+               {
+                  pixels[pos] = pixel & 0xff;
+               }
+            }
+
+            z += d->bpp;
+         }
+      }
+
+      gegl_buffer_set(buffer, GEGL_RECTANGLE(0, y - n, layerw, n), 1.0,
+                      bablfmt, pixels, GEGL_AUTO_ROWSTRIDE);
+   }
+   else if(hdr->pixelfmt.flags & DDPF_FOURCC)
+   {
+      unsigned char *dst;
+
+      if(!(hdr->flags & DDSD_LINEARSIZE))
+      {
+         g_message("Image marked as compressed, but DDSD_LINEARSIZE is not set.\n");
+         return(0);
+      }
+
+      dst = g_malloc(width * height * d->gimp_bpp);
+      memset(dst, 0, width * height * d->gimp_bpp);
+
+      if(d->gimp_bpp == 4)
+      {
+         for(y = 0; y < height; ++y)
+            for(x = 0; x < width; ++x)
+               dst[y * (width * 4) + (x * 4) + 3] = 255;
+      }
+
+      dxt_decompress(dst, buf, format, size, width, height, d->gimp_bpp,
+                     hdr->pixelfmt.flags & DDPF_NORMAL);
+
+      z = 0;
+      for(y = 0, n = 0; y < height; ++y, ++n)
+      {
+         if(n >= d->tile_height)
+         {
+            gegl_buffer_set(buffer, GEGL_RECTANGLE(0, y - n, layerw, n), 1.0,
+                            bablfmt, pixels, GEGL_AUTO_ROWSTRIDE);
+            n = 0;
+            gimp_progress_update((double)y / (double)hdr->height);
+         }
+
+         memcpy(pixels + n * layerw * d->gimp_bpp,
+                dst + y * layerw * d->gimp_bpp,
+                width * d->gimp_bpp);
+      }
+
+      gegl_buffer_set(buffer, GEGL_RECTANGLE(0, y - n, layerw, n), 1.0,
+                      bablfmt, pixels, GEGL_AUTO_ROWSTRIDE);
+      
+      g_free(dst);
+   }
+
+   gegl_buffer_flush(buffer);
+   
+   g_object_unref(buffer);
+   
+   /* gimp dds specific.  decode encoded images */
+   if(dds_read_vals.decode_images &&
+      hdr->reserved.gimp_dds_special.magic1 == FOURCC('G','I','M','P') &&
+      hdr->reserved.gimp_dds_special.magic2 == FOURCC('-','D','D','S'))
+   {
+      switch(hdr->reserved.gimp_dds_special.extra_fourcc)
+      {
+         case FOURCC('A','E','X','P'):
+            decode_alpha_exp_image(layer, FALSE);
+            break;
+         case FOURCC('Y','C','G','1'):
+            decode_ycocg_image(layer, FALSE);
+            break;
+         case FOURCC('Y','C','G','2'):
+            decode_ycocg_scaled_image(layer, FALSE);
+            break;
+         default:
+            break;
+      }
+   }
+
+   return(1);
+}
+
+static int load_mipmaps(FILE *fp, dds_header_t *hdr, dds_load_info_t *d,
+                        gint32 image, char *prefix, unsigned int *l,
+                        guchar *pixels, unsigned char *buf)
+{
+   unsigned int level;
+
+   if((hdr->flags & DDSD_MIPMAPCOUNT) &&
+      (hdr->caps.caps1 & DDSCAPS_MIPMAP) &&
+      (dds_read_vals.mipmaps != 0))
+   {
+      for(level = 1; level < hdr->num_mipmaps; ++level)
+      {
+         if(!load_layer(fp, hdr, d, image, level, prefix, l, pixels, buf))
+            return(0);
+      }
+   }
+   return(1);
+}
+
+static int load_face(FILE *fp, dds_header_t *hdr, dds_load_info_t *d,
+                     gint32 image, char *prefix, unsigned int *l,
+                     guchar *pixels, unsigned char *buf)
+{
+   if(!load_layer(fp, hdr, d, image, 0, prefix, l, pixels, buf))
+      return(0);
+   return(load_mipmaps(fp, hdr, d, image, prefix, l, pixels, buf));
+}
+
+static unsigned char color_bits(unsigned int mask)
+{
+   unsigned char i = 0;
+
+   while(mask)
+   {
+      if(mask & 1) ++i;
+      mask >>= 1;
+   }
+   return(i);
+}
+
+static unsigned char color_shift(unsigned int mask)
+{
+   unsigned char i = 0;
+
+   if(!mask) return(0);
+   while(!((mask >> i) & 1)) ++i;
+   return(i);
+}
+
+static void load_dialog_response(GtkWidget *widget, gint response_id,
+                                 gpointer data)
+{
+   switch(response_id)
+   {
+      case GTK_RESPONSE_OK:
+         runme = 1;
+      default:
+         gtk_widget_destroy(widget);
+         break;
+   }
+}
+
+static void toggle_clicked(GtkWidget *widget, gpointer data)
+{
+   int *flag = (int*)data;
+   (*flag) = !(*flag);
+}
+
+static int load_dialog(void)
+{
+   GtkWidget *dlg;
+   GtkWidget *vbox;
+   GtkWidget *check;
+
+   dlg = gimp_dialog_new("Load DDS", "dds", NULL, GTK_WIN_POS_MOUSE,
+                         gimp_standard_help_func, LOAD_PROC,
+                         GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+                         GTK_STOCK_OK, GTK_RESPONSE_OK,
+                         NULL);
+
+   gtk_signal_connect(GTK_OBJECT(dlg), "response",
+                      GTK_SIGNAL_FUNC(load_dialog_response),
+                      0);
+   gtk_signal_connect(GTK_OBJECT(dlg), "destroy",
+                      GTK_SIGNAL_FUNC(gtk_main_quit),
+                      0);
+
+   vbox = gtk_vbox_new(0, 8);
+   gtk_container_set_border_width(GTK_CONTAINER(vbox), 8);
+   gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dlg)->vbox), vbox, 1, 1, 0);
+   gtk_widget_show(vbox);
+
+   check = gtk_check_button_new_with_label("Load mipmaps");
+   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), dds_read_vals.mipmaps);
+   gtk_signal_connect(GTK_OBJECT(check), "clicked",
+                      GTK_SIGNAL_FUNC(toggle_clicked), &dds_read_vals.mipmaps);
+   gtk_box_pack_start(GTK_BOX(vbox), check, 1, 1, 0);
+   gtk_widget_show(check);
+
+   check = gtk_check_button_new_with_label("Automatically decode YCoCg/AExp images when detected");
+   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), dds_read_vals.decode_images);
+   gtk_signal_connect(GTK_OBJECT(check), "clicked",
+                      GTK_SIGNAL_FUNC(toggle_clicked), &dds_read_vals.decode_images);
+   gtk_box_pack_start(GTK_BOX(vbox), check, 1, 1, 0);
+   gtk_widget_show(check);
+
+   check = gtk_check_button_new_with_label("Show this dialog");
+   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), dds_read_vals.show_dialog);
+   gtk_signal_connect(GTK_OBJECT(check), "clicked",
+                      GTK_SIGNAL_FUNC(toggle_clicked), &dds_read_vals.show_dialog);
+   gtk_box_pack_start(GTK_BOX(vbox), check, 1, 1, 0);
+   gtk_widget_show(check);
+
+   gtk_widget_show(dlg);
+
+   runme = 0;
+
+   gtk_main();
+
+   return(runme);
+}
diff --git a/plug-ins/file-dds/ddswrite.c b/plug-ins/file-dds/ddswrite.c
new file mode 100644
index 0000000000..b81770f00f
--- /dev/null
+++ b/plug-ins/file-dds/ddswrite.c
@@ -0,0 +1,2122 @@
+/*
+       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.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+
+#include <gtk/gtk.h>
+#include <glib/gstdio.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#include "ddsplugin.h"
+#include "dds.h"
+#include "dxt.h"
+#include "mipmap.h"
+#include "endian.h"
+#include "imath.h"
+#include "color.h"
+
+static gint save_dialog(gint32 image_id, gint32 drawable);
+static void save_dialog_response(GtkWidget *widget, gint response_id, gpointer data);
+static int write_image(FILE *fp, gint32 image_id, gint32 drawable_id);
+
+static int runme = 0;
+
+enum
+{
+   COMBO_VALUE, COMBO_STRING, COMBO_SENSITIVE
+};
+
+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 gint cubemap_faces[6];
+static gint is_cubemap = 0;
+static gint is_volume = 0;
+static gint is_array = 0;
+static gint is_mipmap_chain_valid = 0;
+
+static GtkWidget *compress_opt;
+static GtkWidget *format_opt;
+static GtkWidget *mipmap_opt;
+static GtkWidget *mipmap_filter_opt;
+static GtkWidget *mipmap_wrap_opt;
+static GtkWidget *srgb_chk;
+static GtkWidget *gamma_chk;
+static GtkWidget *gamma_spin;
+static GtkWidget *pm_chk;
+static GtkWidget *alpha_coverage_chk;
+static GtkWidget *alpha_test_threshold_spin;
+
+typedef struct string_value_s
+{
+   int value;
+   char *string;
+} string_value_t;
+
+static string_value_t compression_strings[] =
+{
+   {DDS_COMPRESS_NONE,   "None"},
+   {DDS_COMPRESS_BC1,    "BC1 / DXT1"},
+   {DDS_COMPRESS_BC2,    "BC2 / DXT3"},
+   {DDS_COMPRESS_BC3,    "BC3 / DXT5"},
+   {DDS_COMPRESS_BC3N,   "BC3nm / DXT5nm"},
+   {DDS_COMPRESS_BC4,    "BC4 / ATI1 (3Dc+)"},
+   {DDS_COMPRESS_BC5,    "BC5 / ATI2 (3Dc)"},
+   {DDS_COMPRESS_RXGB,   "RXGB (DXT5)"},
+   {DDS_COMPRESS_AEXP,   "Alpha Exponent (DXT5)"},
+   {DDS_COMPRESS_YCOCG,  "YCoCg (DXT5)"},
+   {DDS_COMPRESS_YCOCGS, "YCoCg scaled (DXT5)"},
+   {-1, 0}
+};
+
+static string_value_t format_strings[] =
+{
+   {DDS_FORMAT_DEFAULT, "Default"},
+   {DDS_FORMAT_RGB8,    "RGB8"},
+   {DDS_FORMAT_RGBA8,   "RGBA8"},
+   {DDS_FORMAT_BGR8,    "BGR8"},
+   {DDS_FORMAT_ABGR8,   "ABGR8"},
+   {DDS_FORMAT_R5G6B5,  "R5G6B5"},
+   {DDS_FORMAT_RGBA4,   "RGBA4"},
+   {DDS_FORMAT_RGB5A1,  "RGB5A1"},
+   {DDS_FORMAT_RGB10A2, "RGB10A2"},
+   {DDS_FORMAT_R3G3B2,  "R3G3B2"},
+   {DDS_FORMAT_A8,      "A8"},
+   {DDS_FORMAT_L8,      "L8"},
+   {DDS_FORMAT_L8A8,    "L8A8"},
+   {DDS_FORMAT_AEXP,    "AExp"},
+   {DDS_FORMAT_YCOCG,   "YCoCg"},
+   {-1, 0}
+};
+
+static string_value_t mipmap_strings[] =
+{
+   {DDS_MIPMAP_NONE,     "No mipmaps"},
+   {DDS_MIPMAP_GENERATE, "Generate mipmaps"},
+   {DDS_MIPMAP_EXISTING, "Use existing mipmaps"},
+   {-1, 0}
+};
+
+static string_value_t mipmap_filter_strings[] =
+{
+   {DDS_MIPMAP_FILTER_DEFAULT,   "Default"},
+   {DDS_MIPMAP_FILTER_NEAREST,   "Nearest"},
+   {DDS_MIPMAP_FILTER_BOX,       "Box"},
+   {DDS_MIPMAP_FILTER_TRIANGLE,  "Triangle"},
+   {DDS_MIPMAP_FILTER_QUADRATIC, "Quadratic"},
+   {DDS_MIPMAP_FILTER_BSPLINE,   "B-Spline"},
+   {DDS_MIPMAP_FILTER_MITCHELL,  "Mitchell"},
+   {DDS_MIPMAP_FILTER_LANCZOS,   "Lanczos"},
+   {DDS_MIPMAP_FILTER_KAISER,    "Kaiser"},
+   {-1, 0}
+};
+
+static string_value_t mipmap_wrap_strings[] =
+{
+   {DDS_MIPMAP_WRAP_DEFAULT, "Default"},
+   {DDS_MIPMAP_WRAP_MIRROR,  "Mirror"},
+   {DDS_MIPMAP_WRAP_REPEAT,  "Repeat"},
+   {DDS_MIPMAP_WRAP_CLAMP,   "Clamp"},
+   {-1, 0}
+};
+
+static string_value_t save_type_strings[] =
+{
+   {DDS_SAVE_SELECTED_LAYER, "Image / Selected layer"},
+   {DDS_SAVE_CUBEMAP,        "As cube map"},
+   {DDS_SAVE_VOLUMEMAP,      "As volume map"},
+   {DDS_SAVE_ARRAY,          "As texture array"},
+   {-1, 0}
+};
+
+static struct
+{
+   int format;
+   DXGI_FORMAT dxgi_format;
+   int bpp;
+   int alpha;
+   unsigned int rmask;
+   unsigned int gmask;
+   unsigned int bmask;
+   unsigned int 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 int check_mipmaps(gint32 image_id, int savetype)
+{
+   gint *layers, num_layers;
+   int i, j, w, h, mipw, miph, num_mipmaps, num_surfaces = 0;
+   int min_surfaces = 1, max_surfaces = 1;
+   int valid = 1;
+   GimpImageType 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 = gimp_image_get_layers(image_id, &num_layers);
+   
+   w = gimp_image_width(image_id);
+   h = gimp_image_height(image_id);
+   
+   num_mipmaps = get_num_mipmaps(w, h);
+   
+   type = gimp_drawable_type(layers[0]);
+   
+   for(i = 0; i < num_layers; ++i)
+   {
+      if(type != gimp_drawable_type(layers[i]))
+         return(0);
+      
+      if((gimp_drawable_width(layers[i])  == w) &&
+         (gimp_drawable_height(layers[i]) == 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)
+   {
+      if((gimp_drawable_width(layers[i])  != w) ||
+         (gimp_drawable_height(layers[i]) != h))
+      {
+         valid = 0;
+         break;
+      }
+      
+      for(j = 1; j < num_mipmaps; ++j)
+      {
+         mipw = w >> j;
+         miph = h >> j;
+         if(mipw < 1) mipw = 1;
+         if(miph < 1) miph = 1;
+         if((gimp_drawable_width(layers[i + j])  != mipw) ||
+            (gimp_drawable_height(layers[i + j]) != miph))
+         {
+            valid = 0;
+            break;
+         }
+      }
+   }
+   
+   return(valid);
+}
+
+static int check_cubemap(gint32 image_id)
+{
+   gint *layers, num_layers;
+   int cubemap = 1, i, j, k, w, h;
+   char *layer_name;
+   GimpImageType type;
+   
+   layers = gimp_image_get_layers(image_id, &num_layers);
+   
+   if(num_layers < 6) return(0);
+   
+   /* 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(image_id, DDS_SAVE_CUBEMAP))
+         return(0);
+      
+      /* invalidate cubemap faces */
+      for(i = 0; i < 6; ++i)
+         cubemap_faces[i] = -1;
+      
+      /* find the mipmap level 0 layers */
+      w = gimp_image_width(image_id);
+      h = gimp_image_height(image_id);
+      
+      for(i = 0; i < num_layers; ++i)
+      {
+         if((gimp_drawable_width(layers[i])  != w) ||
+            (gimp_drawable_height(layers[i]) != h))
+            continue;
+         
+         layer_name = (char*)gimp_item_get_name(layers[i]);
+         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] == -1)
+                  {
+                     cubemap_faces[j] = layers[i];
+                     break;
+                  }
+               }
+            }
+         }
+      }
+      
+      /* check for 6 valid faces */
+      for(i = 0; i < 6; ++i)
+      {
+         if(cubemap_faces[i] == -1)
+         {
+            cubemap = 0;
+            break;
+         }
+      }
+      
+      /* make sure they are all the same type */
+      if(cubemap)
+      {
+         type = gimp_drawable_type(cubemap_faces[0]);
+         for(i = 1; i < 6 && cubemap; ++i)
+         {
+            if(gimp_drawable_type(cubemap_faces[i]) != type)
+               cubemap = 0;
+         }
+      }
+   }   
+   
+   if(num_layers == 6)
+   {
+      /* invalidate cubemap faces */
+      for(i = 0; i < 6; ++i)
+         cubemap_faces[i] = -1;
+      
+      for(i = 0; i < 6; ++i)
+      {
+         layer_name = (char*)gimp_item_get_name(layers[i]);
+         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] == -1)
+                  {
+                     cubemap_faces[j] = layers[i];
+                     break;
+                  }
+               }
+            }
+         }
+      }
+      
+      /* check for 6 valid faces */
+      for(i = 0; i < 6; ++i)
+      {
+         if(cubemap_faces[i] == -1)
+         {
+            cubemap = 0;
+            break;
+         }
+      }
+      
+      /* make sure they are all the same size */
+      if(cubemap)
+      {
+         w = gimp_drawable_width(cubemap_faces[0]);
+         h = gimp_drawable_height(cubemap_faces[0]);
+         
+         for(i = 1; i < 6 && cubemap; ++i)
+         {
+            if((gimp_drawable_width(cubemap_faces[i])  != w) ||
+               (gimp_drawable_height(cubemap_faces[i]) != h))
+               cubemap = 0;
+         }
+      }
+      
+      /* make sure they are all the same type */
+      if(cubemap)
+      {
+         type = gimp_drawable_type(cubemap_faces[0]);
+         for(i = 1; i < 6 && cubemap; ++i)
+         {
+            if(gimp_drawable_type(cubemap_faces[i]) != type)
+               cubemap = 0;
+         }
+      }
+   }
+   
+   return(cubemap);
+}
+
+static int check_volume(gint32 image_id)
+{
+   gint *layers, num_layers;
+   int volume = 0, i, w, h;
+   GimpImageType type;
+   
+   layers = gimp_image_get_layers(image_id, &num_layers);
+   
+   if(num_layers > 1)
+   {
+      volume = 1;
+      
+      /* make sure all layers are the same size */
+      w = gimp_drawable_width(layers[0]);
+      h = gimp_drawable_height(layers[0]);
+      
+      for(i = 1; i < num_layers && volume; ++i)
+      {
+         if((gimp_drawable_width(layers[i])  != w) ||
+            (gimp_drawable_height(layers[i]) != h))
+            volume = 0;
+      }
+      
+      if(volume)
+      {
+         /* make sure all layers are the same type */
+         type = gimp_drawable_type(layers[0]);
+         for(i = 1; i < num_layers && volume; ++i)
+         {
+            if(gimp_drawable_type(layers[i]) != type)
+               volume = 0;
+         }
+      }
+   }
+   
+   return(volume);
+}
+
+static int check_array(gint32 image_id)
+{
+   gint *layers, num_layers;
+   int array = 0, i, w, h;
+   GimpImageType type;
+   
+   if(check_mipmaps(image_id, DDS_SAVE_ARRAY))
+      return(1);
+   
+   layers = gimp_image_get_layers(image_id, &num_layers);
+   
+   if(num_layers > 1)
+   {
+      array = 1;
+      
+      /* make sure all layers are the same size */
+      w = gimp_drawable_width(layers[0]);
+      h = gimp_drawable_height(layers[0]);
+      
+      for(i = 1; i < num_layers && array; ++i)
+      {
+         if((gimp_drawable_width(layers[i])  != w) ||
+            (gimp_drawable_height(layers[i]) != h))
+            array = 0;
+      }
+      
+      if(array)
+      {
+         /* make sure all layers are the same type */
+         type = gimp_drawable_type(layers[0]);
+         for(i = 1; i < num_layers; ++i)
+         {
+            if(gimp_drawable_type(layers[i]) != type)
+            {
+               array = 0;
+               break;
+            }
+         }
+      }
+   }
+   
+   return(array);
+}
+
+static int get_array_size(gint32 image_id)
+{
+   gint *layers, num_layers;
+   int i, w, h, elements = 0;
+   
+   layers = gimp_image_get_layers(image_id, &num_layers);
+   
+   w = gimp_image_width(image_id);
+   h = gimp_image_height(image_id);
+   
+   for(i = 0; i < num_layers; ++i)
+   {
+      if((gimp_drawable_width(layers[i]) == w) && (gimp_drawable_height(layers[i]) == h))
+         ++elements;
+   }
+   
+   return(elements);
+}
+
+GimpPDBStatusType write_dds(gchar *filename, gint32 image_id, gint32 drawable_id)
+{
+   FILE *fp;
+   gchar *tmp;
+   int rc = 0;
+
+   is_mipmap_chain_valid = check_mipmaps(image_id, dds_write_vals.savetype);
+   
+   is_cubemap = check_cubemap(image_id);
+   is_volume = check_volume(image_id);
+   is_array = check_array(image_id);
+   
+   if(interactive_dds)
+   {
+      if(!is_mipmap_chain_valid &&
+         dds_write_vals.mipmaps == DDS_MIPMAP_EXISTING)
+         dds_write_vals.mipmaps = DDS_MIPMAP_NONE;
+
+      if(!save_dialog(image_id, drawable_id))
+         return(GIMP_PDB_CANCEL);
+   }
+   else
+   {
+      if(dds_write_vals.savetype == DDS_SAVE_CUBEMAP && !is_cubemap)
+      {
+         g_message("DDS: Cannot save image as cube map");
+         return(GIMP_PDB_EXECUTION_ERROR);
+      }
+
+      if(dds_write_vals.savetype == DDS_SAVE_VOLUMEMAP && !is_volume)
+      {
+         g_message("DDS: Cannot save image as volume map");
+         return(GIMP_PDB_EXECUTION_ERROR);
+      }
+
+      if(dds_write_vals.savetype == DDS_SAVE_VOLUMEMAP &&
+         dds_write_vals.compression != DDS_COMPRESS_NONE)
+      {
+         g_message("DDS: Cannot save volume map with compression");
+         return(GIMP_PDB_EXECUTION_ERROR);
+      }
+
+      if(dds_write_vals.mipmaps == DDS_MIPMAP_EXISTING &&
+         !is_mipmap_chain_valid)
+      {
+         g_message("DDS: Cannot save with existing mipmaps as the mipmap chain is incomplete");
+         return(GIMP_PDB_EXECUTION_ERROR);
+      }
+   }
+
+   fp = g_fopen(filename, "wb");
+   if(fp == 0)
+   {
+      g_message("Error opening %s", filename);
+      return(GIMP_PDB_EXECUTION_ERROR);
+   }
+
+   if(strrchr(filename, '/'))
+      tmp = g_strdup_printf("Saving %s:", strrchr(filename, '/') + 1);
+   else
+      tmp = g_strdup_printf("Saving %s:", filename);
+   gimp_progress_init(tmp);
+   g_free(tmp);
+
+   rc = write_image(fp, image_id, drawable_id);
+
+   fclose(fp);
+
+   return(rc ? GIMP_PDB_SUCCESS : GIMP_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,
+                             gint32 image_id, gint drawable_id)
+{
+   gint *layers, num_layers;
+   GeglBuffer *buffer;
+   const Babl *format = 0;
+   int i, idx = 0, offset, 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 = gimp_image_get_layers(image_id, &num_layers);
+   
+   for(i = 0; i < num_layers; ++i)
+   {
+      if(layers[i] == drawable_id)
+      {
+         idx = i;
+         break;
+      }
+   }
+   
+   if(i == num_layers) return;
+   
+   offset = 0;
+
+   while(get_next_mipmap_dimensions(&mipw, &miph, w, h))
+   {
+      buffer = gimp_drawable_get_buffer(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, gint32 image_id, gint32 drawable_id,
+                        int w, int h, int bpp, int fmtbpp, int mipmaps)
+{
+   GeglBuffer *buffer;
+   const Babl *format = 0;
+   GimpImageBaseType basetype;
+   GimpImageType type;
+   unsigned char *src, *dst, *fmtdst, *tmp;
+   unsigned char *palette = NULL;
+   int i, c, x, y, size, fmtsize, offset, colors;
+   int compression = dds_write_vals.compression;
+   int flags = 0;
+
+   basetype = gimp_image_base_type(image_id);
+   type = gimp_drawable_type(drawable_id);
+
+   buffer = gimp_drawable_get_buffer(drawable_id);
+   
+   src = g_malloc(w * h * bpp);
+   
+   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 == GIMP_INDEXED)
+   {
+      palette = gimp_image_get_colormap(image_id, &colors);
+
+      if(type == GIMP_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(mipmaps > 1)
+      {
+         /* pre-convert indexed images to RGB for better quality mipmaps
+            if a pixel format conversion is requested */
+         if(dds_write_vals.format > DDS_FORMAT_DEFAULT && basetype == GIMP_INDEXED)
+         {
+            fmtsize = get_mipmapped_size(w, h, 3, 0, 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, mipmaps, DDS_COMPRESS_NONE);
+         dst = g_malloc(size);
+         if(dds_write_vals.mipmaps == DDS_MIPMAP_GENERATE)
+         {
+            generate_mipmaps(dst, src, w, h, bpp, palette != NULL,
+                             mipmaps,
+                             dds_write_vals.mipmap_filter,
+                             dds_write_vals.mipmap_wrap,
+                             dds_write_vals.gamma_correct + dds_write_vals.srgb,
+                             dds_write_vals.gamma,
+                             dds_write_vals.preserve_alpha_coverage,
+                             dds_write_vals.alpha_test_threshold);
+         }
+         else
+         {
+            memcpy(dst, src, w * h * bpp);
+            get_mipmap_chain(dst + (w * h * bpp), w, h, bpp, image_id, drawable_id);
+         }
+
+         if(dds_write_vals.format > DDS_FORMAT_DEFAULT)
+         {
+            fmtsize = get_mipmapped_size(w, h, fmtbpp, 0, mipmaps,
+                                         DDS_COMPRESS_NONE);
+            fmtdst = g_malloc(fmtsize);
+
+            convert_pixels(fmtdst, dst, dds_write_vals.format, w, h, 0, bpp,
+                           palette, mipmaps);
+
+            g_free(dst);
+            dst = fmtdst;
+            bpp = fmtbpp;
+         }
+
+         offset = 0;
+
+         for(i = 0; i < 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(dds_write_vals.format > DDS_FORMAT_DEFAULT)
+         {
+            fmtdst = g_malloc(h * w * fmtbpp);
+            convert_pixels(fmtdst, src, dds_write_vals.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, mipmaps, compression);
+
+      dst = g_malloc(size);
+
+      if(basetype == GIMP_INDEXED)
+      {
+         fmtsize = get_mipmapped_size(w, h, 3, 0, mipmaps,
+                                      DDS_COMPRESS_NONE);
+         fmtdst = g_malloc(fmtsize);
+         convert_pixels(fmtdst, src, DDS_FORMAT_RGB8, w, h, 0, bpp,
+                        palette, mipmaps);
+         g_free(src);
+         src = fmtdst;
+         bpp = 3;
+      }
+
+      if(mipmaps > 1)
+      {
+         fmtsize = get_mipmapped_size(w, h, bpp, 0, mipmaps,
+                                      DDS_COMPRESS_NONE);
+         fmtdst = g_malloc(fmtsize);
+         if(dds_write_vals.mipmaps == DDS_MIPMAP_GENERATE)
+         {
+            generate_mipmaps(fmtdst, src, w, h, bpp, 0, mipmaps,
+                             dds_write_vals.mipmap_filter,
+                             dds_write_vals.mipmap_wrap,
+                             dds_write_vals.gamma_correct + dds_write_vals.srgb,
+                             dds_write_vals.gamma,
+                             dds_write_vals.preserve_alpha_coverage,
+                             dds_write_vals.alpha_test_threshold);
+         }
+         else
+         {
+            memcpy(fmtdst, src, w * h * bpp);
+            get_mipmap_chain(fmtdst + (w * h * bpp), w, h, bpp, image_id, drawable_id);
+         }
+
+         g_free(src);
+         src = fmtdst;
+      }
+
+      flags = 0;
+      if(dds_write_vals.perceptual_metric) flags |= DXT_PERCEPTUAL;
+
+      dxt_compress(dst, src, compression, w, h, bpp, mipmaps, flags);
+
+      fwrite(dst, 1, size, fp);
+
+      g_free(dst);
+   }
+
+   g_free(src);
+
+   g_object_unref(buffer);
+}
+
+static void write_volume_mipmaps(FILE *fp, gint32 image_id, gint32 *layers,
+                                 int w, int h, int d, int bpp, int fmtbpp,
+                                 int mipmaps)
+{
+   int i, size, offset, colors;
+   unsigned char *src, *dst, *tmp, *fmtdst;
+   unsigned char *palette = 0;
+   GeglBuffer *buffer;
+   const Babl *format;
+   GimpImageBaseType type;
+
+   type = gimp_image_base_type(image_id);
+
+   if(dds_write_vals.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(gimp_image_base_type(image_id) == GIMP_INDEXED)
+      palette = gimp_image_get_colormap(image_id, &colors);
+
+   offset = 0;
+   for(i = 0; i < d; ++i)
+   {
+      buffer = gimp_drawable_get_buffer(layers[i]);
+      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(gimp_drawable_type(layers[0]) == GIMP_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(dds_write_vals.format > DDS_FORMAT_DEFAULT && type == GIMP_INDEXED)
+   {
+      size = get_volume_mipmapped_size(w, h, d, 3, 0, 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, mipmaps,
+                                    dds_write_vals.compression);
+
+   dst = g_malloc(size);
+
+   offset = get_volume_mipmapped_size(w, h, d, bpp, 0, 1,
+                                      dds_write_vals.compression);
+
+   generate_volume_mipmaps(dst, src, w, h, d, bpp,
+                           palette != NULL, mipmaps,
+                           dds_write_vals.mipmap_filter,
+                           dds_write_vals.mipmap_wrap,
+                           dds_write_vals.gamma_correct + dds_write_vals.srgb,
+                           dds_write_vals.gamma);
+
+   if(dds_write_vals.format > DDS_FORMAT_DEFAULT)
+   {
+      size = get_volume_mipmapped_size(w, h, d, fmtbpp, 0, mipmaps,
+                                       dds_write_vals.compression);
+      offset = get_volume_mipmapped_size(w, h, d, fmtbpp, 0, 1,
+                                         dds_write_vals.compression);
+      fmtdst = g_malloc(size);
+
+      convert_pixels(fmtdst, dst, dds_write_vals.format, w, h, d, bpp,
+                     palette, mipmaps);
+      g_free(dst);
+      dst = fmtdst;
+   }
+
+   fwrite(dst + offset, 1, size, fp);
+
+   g_free(src);
+   g_free(dst);
+}
+
+static int write_image(FILE *fp, gint32 image_id, gint32 drawable_id)
+{
+   GimpImageType drawable_type;
+   GimpImageBaseType basetype;
+   int i, w, h, bpp = 0, fmtbpp = 0, has_alpha = 0;
+   int num_mipmaps;
+   unsigned char hdr[DDS_HEADERSIZE], hdr10[DDS_HEADERSIZE_DX10];
+   unsigned int flags = 0, pflags = 0, caps = 0, caps2 = 0, size = 0;
+   unsigned int rmask = 0, gmask = 0, bmask = 0, amask = 0;
+   unsigned int fourcc = 0;
+   DXGI_FORMAT dxgi_format = DXGI_FORMAT_UNKNOWN;
+   gint32 num_layers, *layers;
+   guchar *cmap;
+   gint colors;
+   unsigned char zero[4] = {0, 0, 0, 0};
+   int is_dx10 = 0, array_size = 1;
+
+   layers = gimp_image_get_layers(image_id, &num_layers);
+   
+   if(dds_write_vals.mipmaps == DDS_MIPMAP_EXISTING)
+      drawable_id = layers[0];
+
+   if(dds_write_vals.savetype == DDS_SAVE_SELECTED_LAYER)
+   {
+      w = gimp_drawable_width(drawable_id);
+      h = gimp_drawable_height(drawable_id);
+   }
+   else
+   {
+      w = gimp_image_width(image_id);
+      h = gimp_image_height(image_id);
+   }
+
+   basetype = gimp_image_base_type(image_id);
+   drawable_type = gimp_drawable_type(drawable_id);
+
+   switch(drawable_type)
+   {
+      case GIMP_RGB_IMAGE:      bpp = 3; break;
+      case GIMP_RGBA_IMAGE:     bpp = 4; break;
+      case GIMP_GRAY_IMAGE:     bpp = 1; break;
+      case GIMP_GRAYA_IMAGE:    bpp = 2; break;
+      case GIMP_INDEXED_IMAGE:  bpp = 1; break;
+      case GIMP_INDEXEDA_IMAGE: bpp = 2; break;
+      default:
+         break;
+   }
+
+   if(dds_write_vals.format > DDS_FORMAT_DEFAULT)
+   {
+      for(i = 0; ; ++i)
+      {
+         if(format_info[i].format == dds_write_vals.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 == GIMP_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 == GIMP_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(dds_write_vals.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(dds_write_vals.mipmaps)
+   {
+      flags |= DDSD_MIPMAPCOUNT;
+      caps |= (DDSCAPS_COMPLEX | DDSCAPS_MIPMAP);
+      num_mipmaps = get_num_mipmaps(w, h);
+   }
+   else
+      num_mipmaps = 1;
+
+   if((dds_write_vals.savetype == DDS_SAVE_CUBEMAP) && is_cubemap)
+   {
+      caps |= DDSCAPS_COMPLEX;
+      caps2 |= (DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_ALL_FACES);
+   }
+   else if((dds_write_vals.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(dds_write_vals.compression == DDS_COMPRESS_NONE)
+   {
+      flags |= DDSD_PITCH;
+
+      if(dds_write_vals.format > DDS_FORMAT_DEFAULT)
+      {
+         if(dds_write_vals.format == DDS_FORMAT_A8)
+            pflags |= DDPF_ALPHA;
+         else
+         {
+            if(((fmtbpp == 1) || (dds_write_vals.format == DDS_FORMAT_L8A8)) &&
+               (dds_write_vals.format != DDS_FORMAT_R3G3B2))
+               pflags |= DDPF_LUMINANCE;
+            else
+               pflags |= DDPF_RGB;
+         }
+      }
+      else
+      {
+         if(bpp == 1)
+         {
+            if(basetype == GIMP_INDEXED)
+               pflags |= DDPF_PALETTEINDEXED8;
+            else
+               pflags |= DDPF_LUMINANCE;
+         }
+         else if((bpp == 2) && (basetype == GIMP_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 GIMP DDS. When the image
+       is read by the plugin, we can detect the added information to decode
+       the pixels
+      */
+      if(dds_write_vals.format == DDS_FORMAT_AEXP)
+      {
+         PUTL32(hdr + 44, FOURCC('A','E','X','P'));
+      }
+      else if(dds_write_vals.format == DDS_FORMAT_YCOCG)
+      {
+         PUTL32(hdr + 44, FOURCC('Y','C','G','1'));
+      }
+   }
+   else
+   {
+      flags |= DDSD_LINEARSIZE;
+      pflags = DDPF_FOURCC;
+
+      switch(dds_write_vals.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((dds_write_vals.compression == DDS_COMPRESS_BC3N) ||
+         (dds_write_vals.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((dds_write_vals.compression == DDS_COMPRESS_BC1) ||
+         (dds_write_vals.compression == DDS_COMPRESS_BC4))
+         size *= 8;
+      else
+         size *= 16;
+
+      PUTL32(hdr + 20, size); /* linear size */
+
+      /*
+       write extra fourcc info - this is special to GIMP DDS. When the image
+       is read by the plugin, we can detect the added information to decode
+       the pixels
+      */
+      if(dds_write_vals.compression == DDS_COMPRESS_AEXP)
+      {
+         PUTL32(hdr + 44, FOURCC('A','E','X','P'));
+      }
+      else if(dds_write_vals.compression == DDS_COMPRESS_YCOCG)
+      {
+         PUTL32(hdr + 44, FOURCC('Y','C','G','1'));
+      }
+      else if(dds_write_vals.compression == DDS_COMPRESS_YCOCGS)
+      {
+         PUTL32(hdr + 44, FOURCC('Y','C','G','2'));
+      }
+   }
+
+   /* texture arrays require a DX10 header */
+   if(dds_write_vals.savetype == DDS_SAVE_ARRAY)
+      is_dx10 = 1;
+
+   if(is_dx10)
+   {
+      array_size = (dds_write_vals.savetype == DDS_SAVE_SELECTED_LAYER) ? 1 : get_array_size(image_id);
+
+      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 == GIMP_INDEXED) &&
+      (dds_write_vals.format == DDS_FORMAT_DEFAULT) &&
+      (dds_write_vals.compression == DDS_COMPRESS_NONE))
+   {
+      cmap = gimp_image_get_colormap(image_id, &colors);
+      for(i = 0; i < colors; ++i)
+      {
+         fwrite(&cmap[3 * i], 1, 3, fp);
+         if(i == dds_write_vals.transindex)
+            fputc(0, fp);
+         else
+            fputc(255, fp);
+      }
+      for(; i < 256; ++i)
+         fwrite(zero, 1, 4, fp);
+   }
+
+   if(dds_write_vals.savetype == DDS_SAVE_CUBEMAP)
+   {
+      for(i = 0; i < 6; ++i)
+      {
+         write_layer(fp, image_id, cubemap_faces[i], w, h, bpp, fmtbpp,
+                     num_mipmaps);
+         gimp_progress_update((float)(i + 1) / 6.0);
+      }
+   }
+   else if(dds_write_vals.savetype == DDS_SAVE_VOLUMEMAP)
+   {
+      for(i = 0; i < num_layers; ++i)
+      {
+         write_layer(fp, image_id, layers[i], w, h, bpp, fmtbpp, 1);
+         gimp_progress_update((float)i / (float)num_layers);
+      }
+
+      if(num_mipmaps > 1)
+         write_volume_mipmaps(fp, image_id, layers, w, h, num_layers,
+                              bpp, fmtbpp, num_mipmaps);
+   }
+   else if(dds_write_vals.savetype == DDS_SAVE_ARRAY)
+   {
+      for(i = 0; i < num_layers; ++i)
+      {
+         if((gimp_drawable_width(layers[i]) == w) && (gimp_drawable_height(layers[i]) == h))
+            write_layer(fp, image_id, layers[i], w, h, bpp, fmtbpp, num_mipmaps);
+
+         gimp_progress_update((float)i / (float)num_layers);
+      }
+   }
+   else
+   {
+      write_layer(fp, image_id, drawable_id, w, h, bpp, fmtbpp, num_mipmaps);
+   }
+
+   gimp_progress_update(1.0);
+
+   return(1);
+}
+
+static GtkWidget *string_value_combo_new(string_value_t *strings,
+                                         int active_value)
+{
+   GtkWidget *opt;
+   GtkCellRenderer *renderer;
+   GtkListStore *store;
+   GtkTreeIter iter;
+   int i, active = 0;
+
+   store = gtk_list_store_new(3, G_TYPE_INT, G_TYPE_STRING, G_TYPE_BOOLEAN);
+   for(i = 0; strings[i].string; ++i)
+   {
+      if(strings[i].value == active_value) active = i;
+      gtk_list_store_append(store, &iter);
+      gtk_list_store_set(store, &iter,
+                         0, strings[i].value,
+                         1, strings[i].string,
+                         2, 1,
+                         -1);
+   }
+
+   renderer = gtk_cell_renderer_text_new();
+
+   opt = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));
+   gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(opt), renderer, 1);
+   gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(opt), renderer,
+                                  "text", COMBO_STRING,
+                                  "sensitive", COMBO_SENSITIVE,
+                                  NULL);
+
+   gtk_combo_box_set_active(GTK_COMBO_BOX(opt), active);
+
+   g_object_unref(store);
+
+   return(opt);
+}
+
+static void string_value_combo_selected(GtkWidget *widget, gpointer data)
+{
+   int value;
+   GtkTreeIter iter;
+   GtkTreeModel *model;
+
+   model = gtk_combo_box_get_model(GTK_COMBO_BOX(widget));
+   gtk_combo_box_get_active_iter(GTK_COMBO_BOX(widget), &iter);
+   gtk_tree_model_get(model, &iter, COMBO_VALUE, &value, -1);
+
+   *((int *)data) = value;
+}
+
+static void string_value_combo_set_item_sensitive(GtkWidget *widget,
+                                                  int value, int sensitive)
+{
+   GtkTreeIter iter;
+   GtkTreeModel *model;
+   int val;
+
+   model = gtk_combo_box_get_model(GTK_COMBO_BOX(widget));
+   gtk_tree_model_get_iter_first(model, &iter);
+   do
+   {
+      gtk_tree_model_get(model, &iter, COMBO_VALUE, &val, -1);
+      if(val == value)
+      {
+         gtk_list_store_set(GTK_LIST_STORE(model), &iter,
+                            COMBO_SENSITIVE, sensitive, -1);
+         break;
+      }
+   } while(gtk_tree_model_iter_next(model, &iter));
+}
+
+static void string_value_combo_set_active(GtkWidget *widget,
+                                          int value)
+{
+   GtkTreeIter iter;
+   GtkTreeModel *model;
+   int val;
+
+   model = gtk_combo_box_get_model(GTK_COMBO_BOX(widget));
+   gtk_tree_model_get_iter_first(model, &iter);
+   do
+   {
+      gtk_tree_model_get(model, &iter, COMBO_VALUE, &val, -1);
+      if(val == value)
+      {
+         gtk_combo_box_set_active_iter(GTK_COMBO_BOX(widget), &iter);
+         break;
+      }
+   } while(gtk_tree_model_iter_next(model, &iter));
+}
+
+static void save_dialog_response(GtkWidget *widget, gint response_id,
+                                 gpointer data)
+{
+   switch(response_id)
+   {
+      case GTK_RESPONSE_OK:
+         runme = 1;
+      default:
+         gtk_widget_destroy(widget);
+         break;
+   }
+}
+
+static void compression_selected(GtkWidget *widget, gpointer data)
+{
+   GtkTreeIter iter;
+   GtkTreeModel *model;
+   model = gtk_combo_box_get_model(GTK_COMBO_BOX(widget));
+   gtk_combo_box_get_active_iter(GTK_COMBO_BOX(widget), &iter);
+   gtk_tree_model_get(model, &iter, COMBO_VALUE,
+                      &dds_write_vals.compression, -1);
+
+   gtk_widget_set_sensitive(format_opt, dds_write_vals.compression == DDS_COMPRESS_NONE);
+   gtk_widget_set_sensitive(pm_chk, dds_write_vals.compression != DDS_COMPRESS_NONE);
+}
+
+static void savetype_selected(GtkWidget *widget, gpointer data)
+{
+   gint32 image_id = *((gint32 *)data);
+
+   dds_write_vals.savetype = gtk_combo_box_get_active(GTK_COMBO_BOX(widget));
+
+   switch(dds_write_vals.savetype)
+   {
+      case DDS_SAVE_SELECTED_LAYER:
+      case DDS_SAVE_CUBEMAP:
+      case DDS_SAVE_ARRAY:
+         gtk_widget_set_sensitive(compress_opt, 1);
+         break;
+      case DDS_SAVE_VOLUMEMAP:
+         dds_write_vals.compression = DDS_COMPRESS_NONE;
+         gtk_combo_box_set_active(GTK_COMBO_BOX(compress_opt),
+                                  DDS_COMPRESS_NONE);
+         gtk_widget_set_sensitive(compress_opt, 0);
+         break;
+   }
+
+   string_value_combo_set_item_sensitive(mipmap_opt, DDS_MIPMAP_EXISTING,
+                                         check_mipmaps(image_id, dds_write_vals.savetype));   
+}
+
+static void mipmaps_selected(GtkWidget *widget, gpointer data)
+{
+   GtkTreeIter iter;
+   GtkTreeModel *model;
+   model = gtk_combo_box_get_model(GTK_COMBO_BOX(widget));
+   gtk_combo_box_get_active_iter(GTK_COMBO_BOX(widget), &iter);
+   gtk_tree_model_get(model, &iter, COMBO_VALUE,
+                      &dds_write_vals.mipmaps, -1);
+
+   gtk_widget_set_sensitive(mipmap_filter_opt, dds_write_vals.mipmaps == DDS_MIPMAP_GENERATE);
+   gtk_widget_set_sensitive(mipmap_wrap_opt, dds_write_vals.mipmaps == DDS_MIPMAP_GENERATE);
+   gtk_widget_set_sensitive(gamma_chk, dds_write_vals.mipmaps == DDS_MIPMAP_GENERATE);
+   gtk_widget_set_sensitive(srgb_chk, (dds_write_vals.mipmaps == DDS_MIPMAP_GENERATE) && 
dds_write_vals.gamma_correct);
+   gtk_widget_set_sensitive(gamma_spin, (dds_write_vals.mipmaps == DDS_MIPMAP_GENERATE) &&
+                            dds_write_vals.gamma_correct && !dds_write_vals.srgb);
+   gtk_widget_set_sensitive(alpha_coverage_chk, dds_write_vals.mipmaps == DDS_MIPMAP_GENERATE);
+   gtk_widget_set_sensitive(alpha_test_threshold_spin, (dds_write_vals.mipmaps == DDS_MIPMAP_GENERATE) &&
+                            dds_write_vals.preserve_alpha_coverage);
+}
+
+static void toggle_clicked(GtkWidget *widget, gpointer data)
+{
+   int *flag = (int *)data;
+   (*flag) = !(*flag);
+}
+
+static void transindex_clicked(GtkWidget *widget, gpointer data)
+{
+   GtkWidget *spin = GTK_WIDGET(g_object_get_data(G_OBJECT(widget), "spin"));
+
+   if(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)))
+   {
+      dds_write_vals.transindex = 0;
+      gtk_widget_set_sensitive(spin, 1);
+      gtk_spin_button_set_value(GTK_SPIN_BUTTON(spin), 0);
+   }
+   else
+   {
+      gtk_widget_set_sensitive(spin, 0);
+      dds_write_vals.transindex = -1;
+   }
+}
+
+static void transindex_changed(GtkWidget *widget, gpointer data)
+{
+   dds_write_vals.transindex = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(widget));
+}
+
+static void adv_opt_expanded(GtkWidget *widget, gpointer data)
+{
+   dds_write_vals.show_adv_opt = !gtk_expander_get_expanded(GTK_EXPANDER(widget));
+}
+
+static void gamma_correct_clicked(GtkWidget *widget, gpointer data)
+{
+   dds_write_vals.gamma_correct = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
+   gtk_widget_set_sensitive(srgb_chk, dds_write_vals.gamma_correct);
+   gtk_widget_set_sensitive(gamma_spin, dds_write_vals.gamma_correct && !dds_write_vals.srgb);
+}
+
+static void srgb_clicked(GtkWidget *widget, gpointer data)
+{
+   dds_write_vals.srgb = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
+   gtk_widget_set_sensitive(gamma_spin, !dds_write_vals.srgb);
+}
+
+static void gamma_changed(GtkWidget *widget, gpointer data)
+{
+   dds_write_vals.gamma = gtk_spin_button_get_value(GTK_SPIN_BUTTON(widget));
+}
+
+static void alpha_coverage_clicked(GtkWidget *widget, gpointer data)
+{
+   dds_write_vals.preserve_alpha_coverage = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
+   gtk_widget_set_sensitive(alpha_test_threshold_spin, dds_write_vals.preserve_alpha_coverage);
+}
+
+static void alpha_test_threshold_changed(GtkWidget *widget, gpointer data)
+{
+   dds_write_vals.alpha_test_threshold = gtk_spin_button_get_value(GTK_SPIN_BUTTON(widget));
+}
+
+static gint save_dialog(gint32 image_id, gint32 drawable_id)
+{
+   GtkWidget *dlg;
+   GtkWidget *vbox, *vbox2, *hbox;
+   GtkWidget *table;
+   GtkWidget *label;
+   GtkWidget *opt;
+   GtkWidget *check;
+   GtkWidget *spin;
+   GtkWidget *expander;
+   GtkWidget *frame;
+   GimpImageBaseType basetype;
+
+   if(is_cubemap || is_volume || is_array)
+      dds_write_vals.savetype = DDS_SAVE_SELECTED_LAYER;
+
+   basetype = gimp_image_base_type(image_id);
+
+   dlg = gimp_dialog_new("Save as DDS", "dds", NULL, GTK_WIN_POS_MOUSE,
+                         gimp_standard_help_func, SAVE_PROC,
+                         GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+                         GTK_STOCK_OK, GTK_RESPONSE_OK,
+                         NULL);
+
+   gtk_signal_connect(GTK_OBJECT(dlg), "response",
+                      GTK_SIGNAL_FUNC(save_dialog_response),
+                      0);
+   gtk_signal_connect(GTK_OBJECT(dlg), "destroy",
+                      GTK_SIGNAL_FUNC(gtk_main_quit),
+                      0);
+
+   gtk_window_set_resizable(GTK_WINDOW(dlg), 0);
+
+   vbox = gtk_vbox_new(0, 4);
+   gtk_container_set_border_width(GTK_CONTAINER(vbox), 8);
+   gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dlg)->vbox), vbox, 1, 1, 0);
+   gtk_widget_show(vbox);
+
+   table = gtk_table_new(4, 2, 0);
+   gtk_widget_show(table);
+   gtk_box_pack_start(GTK_BOX(vbox), table, 1, 1, 0);
+   gtk_table_set_row_spacings(GTK_TABLE(table), 4);
+   gtk_table_set_col_spacings(GTK_TABLE(table), 8);
+
+   label = gtk_label_new("Compression:");
+   gtk_widget_show(label);
+   gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, 1,
+                    (GtkAttachOptions)(GTK_FILL),
+                    (GtkAttachOptions)(0), 0, 0);
+   gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5);
+
+   opt = string_value_combo_new(compression_strings,
+                                dds_write_vals.compression);
+   gtk_widget_show(opt);
+   gtk_table_attach(GTK_TABLE(table), opt, 1, 2, 0, 1,
+                    (GtkAttachOptions)(GTK_EXPAND | GTK_FILL),
+                    (GtkAttachOptions)(GTK_EXPAND), 0, 0);
+
+   gtk_signal_connect(GTK_OBJECT(opt), "changed",
+                      GTK_SIGNAL_FUNC(compression_selected), 0);
+
+   compress_opt = opt;
+
+   label = gtk_label_new("Format:");
+   gtk_widget_show(label);
+   gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
+                    (GtkAttachOptions)(GTK_FILL),
+                    (GtkAttachOptions)(0), 0, 0);
+   gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5);
+
+   opt = string_value_combo_new(format_strings, dds_write_vals.format);
+   gtk_widget_show(opt);
+   gtk_table_attach(GTK_TABLE(table), opt, 1, 2, 1, 2,
+                    (GtkAttachOptions)(GTK_EXPAND | GTK_FILL),
+                    (GtkAttachOptions)(GTK_EXPAND), 0, 0);
+
+   gtk_signal_connect(GTK_OBJECT(opt), "changed",
+                      GTK_SIGNAL_FUNC(string_value_combo_selected),
+                      &dds_write_vals.format);
+
+   gtk_widget_set_sensitive(opt, dds_write_vals.compression == DDS_COMPRESS_NONE);
+
+   format_opt = opt;
+
+   label = gtk_label_new("Save:");
+   gtk_widget_show(label);
+   gtk_table_attach(GTK_TABLE(table), label, 0, 1, 2, 3,
+                    (GtkAttachOptions)(GTK_FILL),
+                    (GtkAttachOptions)(0), 0, 0);
+   gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5);
+
+   opt = string_value_combo_new(save_type_strings, dds_write_vals.savetype);
+   gtk_widget_show(opt);
+   gtk_table_attach(GTK_TABLE(table), opt, 1, 2, 2, 3,
+                    (GtkAttachOptions)(GTK_EXPAND | GTK_FILL),
+                    (GtkAttachOptions)(GTK_EXPAND), 0, 0);
+
+   gtk_signal_connect(GTK_OBJECT(opt), "changed",
+                      GTK_SIGNAL_FUNC(savetype_selected), &image_id);
+
+   string_value_combo_set_item_sensitive(opt, DDS_SAVE_CUBEMAP, is_cubemap);
+   string_value_combo_set_item_sensitive(opt, DDS_SAVE_VOLUMEMAP, is_volume);
+   string_value_combo_set_item_sensitive(opt, DDS_SAVE_ARRAY, is_array);
+
+   label = gtk_label_new("Mipmaps:");
+   gtk_widget_show(label);
+   gtk_table_attach(GTK_TABLE(table), label, 0, 1, 3, 4,
+                    (GtkAttachOptions)(GTK_FILL),
+                    (GtkAttachOptions)(0), 0, 0);
+   gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5);
+
+   opt = string_value_combo_new(mipmap_strings,
+                                dds_write_vals.mipmaps);
+   gtk_widget_show(opt);
+   gtk_table_attach(GTK_TABLE(table), opt, 1, 2, 3, 4,
+                    (GtkAttachOptions)(GTK_EXPAND | GTK_FILL),
+                    (GtkAttachOptions)(GTK_EXPAND), 0, 0);
+
+   gtk_signal_connect(GTK_OBJECT(opt), "changed",
+                      GTK_SIGNAL_FUNC(mipmaps_selected), &image_id);
+   
+   string_value_combo_set_item_sensitive(opt, DDS_MIPMAP_EXISTING,
+                                         check_mipmaps(image_id, dds_write_vals.savetype));
+
+   mipmap_opt = opt;
+
+   string_value_combo_set_item_sensitive(opt, DDS_MIPMAP_EXISTING,
+                                         !(is_volume || is_cubemap) &&
+                                         is_mipmap_chain_valid);
+
+
+   hbox = gtk_hbox_new(0, 8);
+   gtk_box_pack_start(GTK_BOX(vbox), hbox, 1, 1, 0);
+   gtk_widget_show(hbox);
+
+   check = gtk_check_button_new_with_label("Transparent index:");
+   gtk_box_pack_start(GTK_BOX(hbox), check, 0, 0, 0);
+   gtk_signal_connect(GTK_OBJECT(check), "clicked",
+                      GTK_SIGNAL_FUNC(transindex_clicked), 0);
+   gtk_widget_show(check);
+
+   spin = gtk_spin_button_new(GTK_ADJUSTMENT(gtk_adjustment_new(0, 0, 255, 1, 1, 0)), 1, 0);
+   gtk_box_pack_start(GTK_BOX(hbox), spin, 1, 1, 0);
+   gtk_spin_button_set_update_policy(GTK_SPIN_BUTTON(spin),
+                                     GTK_UPDATE_IF_VALID);
+   gtk_signal_connect(GTK_OBJECT(spin), "value_changed",
+                      GTK_SIGNAL_FUNC(transindex_changed), 0);
+   gtk_widget_show(spin);
+
+   g_object_set_data(G_OBJECT(check), "spin", spin);
+
+   if(basetype != GIMP_INDEXED)
+   {
+      gtk_widget_set_sensitive(check, 0);
+      gtk_widget_set_sensitive(spin, 0);
+   }
+   else if(dds_write_vals.transindex < 0)
+   {
+      gtk_widget_set_sensitive(spin, 0);
+   }
+   else if(dds_write_vals.transindex >= 0)
+   {
+      gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), 1);
+      gtk_spin_button_set_value(GTK_SPIN_BUTTON(spin), dds_write_vals.transindex);
+   }
+
+   if(is_volume && dds_write_vals.savetype == DDS_SAVE_VOLUMEMAP)
+   {
+      dds_write_vals.compression = DDS_COMPRESS_NONE;
+      string_value_combo_set_active(compress_opt, DDS_COMPRESS_NONE);
+      gtk_widget_set_sensitive(compress_opt, 0);
+   }
+
+   expander = gtk_expander_new("<b>Advanced Options</b>");
+   gtk_expander_set_use_markup(GTK_EXPANDER(expander), 1);
+   gtk_expander_set_expanded(GTK_EXPANDER(expander), dds_write_vals.show_adv_opt);
+   gtk_expander_set_spacing(GTK_EXPANDER(expander), 8);
+   gtk_signal_connect(GTK_OBJECT(expander), "activate",
+                      GTK_SIGNAL_FUNC(adv_opt_expanded), 0);
+   gtk_box_pack_start(GTK_BOX(vbox), expander, 1, 1, 0);
+   gtk_widget_show(expander);
+
+
+   vbox2 = gtk_vbox_new(0, 4);
+   gtk_container_add(GTK_CONTAINER(expander), vbox2);
+   gtk_widget_show(vbox2);
+
+   frame = gtk_frame_new("Compression");
+   gtk_box_pack_start(GTK_BOX(vbox2), frame, 1, 1, 0);
+   gtk_widget_show(frame);
+
+   table = gtk_table_new(1, 2, 0);
+   gtk_table_set_row_spacings(GTK_TABLE(table), 4);
+   gtk_table_set_col_spacings(GTK_TABLE(table), 8);
+   gtk_container_set_border_width(GTK_CONTAINER(table), 8);
+   gtk_container_add(GTK_CONTAINER(frame), table);
+   gtk_widget_show(table);
+
+   check = gtk_check_button_new_with_label("Use perceptual error metric");
+   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), dds_write_vals.perceptual_metric);
+   gtk_table_attach(GTK_TABLE(table), check, 0, 2, 0, 1,
+                    (GtkAttachOptions)(GTK_FILL),
+                    (GtkAttachOptions)(0), 0, 0);
+   gtk_signal_connect(GTK_OBJECT(check), "clicked",
+                      GTK_SIGNAL_FUNC(toggle_clicked), &dds_write_vals.perceptual_metric);
+   gtk_widget_show(check);
+
+   pm_chk = check;
+
+   frame = gtk_frame_new("Mipmaps");
+   gtk_box_pack_start(GTK_BOX(vbox2), frame, 1, 1, 0);
+   gtk_widget_show(frame);
+
+   table = gtk_table_new(7, 2, 0);
+   gtk_table_set_row_spacings(GTK_TABLE(table), 4);
+   gtk_table_set_col_spacings(GTK_TABLE(table), 8);
+   gtk_container_set_border_width(GTK_CONTAINER(table), 8);
+   gtk_container_add(GTK_CONTAINER(frame), table);
+   gtk_widget_show(table);
+
+   label = gtk_label_new("Filter:");
+   gtk_widget_show(label);
+   gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, 1,
+                    (GtkAttachOptions)(GTK_FILL),
+                    (GtkAttachOptions)(0), 0, 0);
+   gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5);
+
+   opt = string_value_combo_new(mipmap_filter_strings,
+                                dds_write_vals.mipmap_filter);
+   gtk_widget_show(opt);
+   gtk_table_attach(GTK_TABLE(table), opt, 1, 2, 0, 1,
+                    (GtkAttachOptions)(GTK_EXPAND | GTK_FILL),
+                    (GtkAttachOptions)(GTK_EXPAND), 0, 0);
+
+   gtk_signal_connect(GTK_OBJECT(opt), "changed",
+                      GTK_SIGNAL_FUNC(string_value_combo_selected),
+                      &dds_write_vals.mipmap_filter);
+
+   mipmap_filter_opt = opt;
+
+   label = gtk_label_new("Wrap mode:");
+   gtk_widget_show(label);
+   gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
+                    (GtkAttachOptions)(GTK_FILL),
+                    (GtkAttachOptions)(0), 0, 0);
+   gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5);
+
+   opt = string_value_combo_new(mipmap_wrap_strings,
+                                dds_write_vals.mipmap_wrap);
+   gtk_widget_show(opt);
+   gtk_table_attach(GTK_TABLE(table), opt, 1, 2, 1, 2,
+                    (GtkAttachOptions)(GTK_EXPAND | GTK_FILL),
+                    (GtkAttachOptions)(GTK_EXPAND), 0, 0);
+
+   gtk_signal_connect(GTK_OBJECT(opt), "changed",
+                      GTK_SIGNAL_FUNC(string_value_combo_selected),
+                      &dds_write_vals.mipmap_wrap);
+
+   mipmap_wrap_opt = opt;
+
+   check = gtk_check_button_new_with_label("Apply gamma correction");
+   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), dds_write_vals.gamma_correct && 
dds_write_vals.mipmaps);
+   gtk_table_attach(GTK_TABLE(table), check, 1, 2, 2, 3,
+                    (GtkAttachOptions)(GTK_FILL),
+                    (GtkAttachOptions)(0), 0, 0);
+   gtk_signal_connect(GTK_OBJECT(check), "clicked",
+                      GTK_SIGNAL_FUNC(gamma_correct_clicked), NULL);
+   gtk_widget_show(check);
+
+   gamma_chk = check;
+
+   check = gtk_check_button_new_with_label("Use sRGB colorspace");
+   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), dds_write_vals.gamma_correct && 
dds_write_vals.srgb);
+   gtk_table_attach(GTK_TABLE(table), check, 1, 2, 3, 4,
+                    (GtkAttachOptions)(GTK_FILL),
+                    (GtkAttachOptions)(0), 0, 0);
+   gtk_signal_connect(GTK_OBJECT(check), "clicked",
+                      GTK_SIGNAL_FUNC(srgb_clicked), NULL);
+   gtk_widget_show(check);
+
+   srgb_chk = check;
+
+   label = gtk_label_new("Gamma:");
+   gtk_widget_show(label);
+   gtk_table_attach(GTK_TABLE(table), label, 0, 1, 4, 5,
+                    (GtkAttachOptions)(GTK_FILL),
+                    (GtkAttachOptions)(0), 0, 0);
+   gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5);
+
+   spin = gtk_spin_button_new(GTK_ADJUSTMENT(gtk_adjustment_new(dds_write_vals.gamma, 1e-05, 100, 0.1, 0.5, 
0)), 1, 1);
+   gtk_table_attach(GTK_TABLE(table), spin, 1, 2, 4, 5,
+                    (GtkAttachOptions)(GTK_EXPAND | GTK_FILL),
+                    (GtkAttachOptions)(GTK_EXPAND), 0, 0);
+   gtk_spin_button_set_update_policy(GTK_SPIN_BUTTON(spin), GTK_UPDATE_IF_VALID);
+   gtk_signal_connect(GTK_OBJECT(spin), "value_changed",
+                      GTK_SIGNAL_FUNC(gamma_changed), 0);
+   gtk_widget_show(spin);
+
+   gamma_spin = spin;
+
+   check = gtk_check_button_new_with_label("Preserve alpha test coverage");
+   gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(check), dds_write_vals.preserve_alpha_coverage && 
dds_write_vals.mipmaps);
+   gtk_table_attach(GTK_TABLE(table), check, 1, 2, 5, 6,
+                    (GtkAttachOptions)(GTK_FILL),
+                    (GtkAttachOptions)(0), 0, 0);
+   gtk_signal_connect(GTK_OBJECT(check), "clicked",
+                      GTK_SIGNAL_FUNC(alpha_coverage_clicked), NULL);
+   gtk_widget_show(check);
+
+   alpha_coverage_chk = check;
+
+   label = gtk_label_new("Alpha test threshold:");
+   gtk_widget_show(label);
+   gtk_table_attach(GTK_TABLE(table), label, 0, 1, 6, 7,
+                    (GtkAttachOptions)(GTK_FILL),
+                    (GtkAttachOptions)(0), 0, 0);
+   gtk_misc_set_alignment(GTK_MISC(label), 1, 0.5);
+
+   spin = gtk_spin_button_new(GTK_ADJUSTMENT(gtk_adjustment_new(dds_write_vals.alpha_test_threshold, 0, 1, 
0.01, 0.1, 0)), 1, 2);
+   gtk_table_attach(GTK_TABLE(table), spin, 1, 2, 6, 7,
+                    (GtkAttachOptions)(GTK_EXPAND | GTK_FILL),
+                    (GtkAttachOptions)(GTK_EXPAND), 0, 0);
+   gtk_spin_button_set_update_policy(GTK_SPIN_BUTTON(spin), GTK_UPDATE_IF_VALID);
+   gtk_signal_connect(GTK_OBJECT(spin), "value_changed",
+                      GTK_SIGNAL_FUNC(alpha_test_threshold_changed), 0);
+   gtk_widget_show(spin);
+
+   alpha_test_threshold_spin = spin;
+
+   gtk_widget_set_sensitive(mipmap_filter_opt, dds_write_vals.mipmaps == DDS_MIPMAP_GENERATE);
+   gtk_widget_set_sensitive(mipmap_wrap_opt, dds_write_vals.mipmaps == DDS_MIPMAP_GENERATE);
+   gtk_widget_set_sensitive(gamma_chk, dds_write_vals.mipmaps == DDS_MIPMAP_GENERATE);
+   gtk_widget_set_sensitive(srgb_chk, (dds_write_vals.mipmaps == DDS_MIPMAP_GENERATE) && 
dds_write_vals.gamma_correct);
+   gtk_widget_set_sensitive(gamma_spin, (dds_write_vals.mipmaps == DDS_MIPMAP_GENERATE) &&
+                            dds_write_vals.gamma_correct && !dds_write_vals.srgb);
+   gtk_widget_set_sensitive(pm_chk, dds_write_vals.compression != DDS_COMPRESS_NONE);
+   gtk_widget_set_sensitive(alpha_coverage_chk, dds_write_vals.mipmaps == DDS_MIPMAP_GENERATE);
+   gtk_widget_set_sensitive(alpha_test_threshold_spin, (dds_write_vals.mipmaps == DDS_MIPMAP_GENERATE) &&
+                            dds_write_vals.preserve_alpha_coverage);
+
+   gtk_widget_show(dlg);
+
+   runme = 0;
+
+   gtk_main();
+
+   return(runme);
+}
diff --git a/plug-ins/file-dds/dxt.c b/plug-ins/file-dds/dxt.c
new file mode 100644
index 0000000000..8ebbe98123
--- /dev/null
+++ b/plug-ins/file-dds/dxt.c
@@ -0,0 +1,1412 @@
+/*
+       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.
+*/
+
+/*
+ * Parts of this code have been generously released in the public domain
+ * by Fabian 'ryg' Giesen.  The original code can be found (at the time
+ * of writing) here:  http://mollyrocket.com/forums/viewtopic.php?t=392
+ *
+ * For more information about this code, see the README.dxt file that
+ * came with the source.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <glib.h>
+
+#include "dds.h"
+#include "dxt.h"
+#include "endian.h"
+#include "mipmap.h"
+#include "imath.h"
+#include "vec.h"
+
+#include "dxt_tables.h"
+
+#define SWAP(a, b)  do { typeof(a) t; t = a; a = b; b = t; } while(0)
+
+/* SIMD constants */
+static const vec4_t V4ZERO      = VEC4_CONST1(0.0f);
+static const vec4_t V4ONE       = VEC4_CONST1(1.0f);
+static const vec4_t V4HALF      = VEC4_CONST1(0.5f);
+static const vec4_t V4ONETHIRD  = VEC4_CONST3(1.0f / 3.0f, 1.0f / 3.0f, 1.0f / 3.0f);
+static const vec4_t V4TWOTHIRDS = VEC4_CONST3(2.0f / 3.0f, 2.0f / 3.0f, 2.0f / 3.0f);
+static const vec4_t V4GRID      = VEC4_CONST3(31.0f, 63.0f, 31.0f);
+static const vec4_t V4GRIDRCP   = VEC4_CONST3(1.0f / 31.0f, 1.0f / 63.0f, 1.0f / 31.0f);
+static const vec4_t V4EPSILON   = VEC4_CONST1(1e-04f);
+
+typedef struct
+{
+   unsigned int single;
+   unsigned int alphamask;
+   vec4_t points[16];
+   vec4_t palette[4];
+   vec4_t max;
+   vec4_t min;
+   vec4_t metric;
+} dxtblock_t;
+
+/* extract 4x4 BGRA block */
+static void extract_block(const unsigned char *src, int x, int y,
+                          int w, int h, unsigned char *block)
+{
+   int i, j;
+   int bw = MIN(w - x, 4);
+   int bh = MIN(h - y, 4);
+   int bx, by;
+   const int rem[] =
+   {
+      0, 0, 0, 0,
+      0, 1, 0, 1,
+      0, 1, 2, 0,
+      0, 1, 2, 3
+   };
+
+   for(i = 0; i < 4; ++i)
+   {
+      by = rem[(bh - 1) * 4 + i] + y;
+      for(j = 0; j < 4; ++j)
+      {
+         bx = rem[(bw - 1) * 4 + j] + x;
+         block[(i * 4 * 4) + (j * 4) + 0] =
+            src[(by * (w * 4)) + (bx * 4) + 0];
+         block[(i * 4 * 4) + (j * 4) + 1] =
+            src[(by * (w * 4)) + (bx * 4) + 1];
+         block[(i * 4 * 4) + (j * 4) + 2] =
+            src[(by * (w * 4)) + (bx * 4) + 2];
+         block[(i * 4 * 4) + (j * 4) + 3] =
+            src[(by * (w * 4)) + (bx * 4) + 3];
+      }
+   }
+}
+
+/* pack BGR8 to RGB565 */
+static inline unsigned short pack_rgb565(const unsigned char *c)
+{
+   return((mul8bit(c[2], 31) << 11) |
+          (mul8bit(c[1], 63) <<  5) |
+          (mul8bit(c[0], 31)      ));
+}
+
+/* unpack RGB565 to BGR */
+static void unpack_rgb565(unsigned char *dst, unsigned short v)
+{
+   int r = (v >> 11) & 0x1f;
+   int g = (v >>  5) & 0x3f;
+   int b = (v      ) & 0x1f;
+
+   dst[0] = (b << 3) | (b >> 2);
+   dst[1] = (g << 2) | (g >> 4);
+   dst[2] = (r << 3) | (r >> 2);
+}
+
+/* linear interpolation at 1/3 point between a and b */
+static void lerp_rgb13(unsigned char *dst, unsigned char *a, unsigned char *b)
+{
+#if 0
+   dst[0] = blerp(a[0], b[0], 0x55);
+   dst[1] = blerp(a[1], b[1], 0x55);
+   dst[2] = blerp(a[2], b[2], 0x55);
+#else
+   /*
+   * according to the S3TC/DX10 specs, this is the correct way to do the
+   * interpolation (with no rounding bias)
+   *
+   * dst = (2 * a + b) / 3;
+   */
+   dst[0] = (2 * a[0] + b[0]) / 3;
+   dst[1] = (2 * a[1] + b[1]) / 3;
+   dst[2] = (2 * a[2] + b[2]) / 3;
+#endif
+}
+
+static void vec4_endpoints_to_565(int *start, int *end, const vec4_t a, const vec4_t b)
+{
+   int c[8] __attribute__((aligned(16)));
+   vec4_t ta = a * V4GRID + V4HALF;
+   vec4_t tb = b * V4GRID + V4HALF;
+
+#ifdef USE_SSE
+# ifdef __SSE2__
+   const __m128i C565 = _mm_setr_epi16(31, 63, 31, 0, 31, 63, 31, 0);
+   __m128i ia = _mm_cvttps_epi32(ta);
+   __m128i ib = _mm_cvttps_epi32(tb);
+   __m128i zero = _mm_setzero_si128();
+   __m128i words = _mm_packs_epi32(ia, ib);
+   words = _mm_min_epi16(C565, _mm_max_epi16(zero, words));
+   *((__m128i *)&c[0]) = _mm_unpacklo_epi16(words, zero);
+   *((__m128i *)&c[4]) = _mm_unpackhi_epi16(words, zero);
+# else
+   const __m64 C565 = _mm_setr_pi16(31, 63, 31, 0);
+   __m64 lo, hi, c0, c1;
+   __m64 zero = _mm_setzero_si64();
+   lo = _mm_cvttps_pi32(ta);
+   hi = _mm_cvttps_pi32(_mm_movehl_ps(ta, ta));
+   c0 = _mm_packs_pi32(lo, hi);
+   lo = _mm_cvttps_pi32(tb);
+   hi = _mm_cvttps_pi32(_mm_movehl_ps(tb, tb));
+   c1 = _mm_packs_pi32(lo, hi);
+   c0 = _mm_min_pi16(C565, _mm_max_pi16(zero, c0));
+   c1 = _mm_min_pi16(C565, _mm_max_pi16(zero, c1));
+   *((__m64 *)&c[0]) = _mm_unpacklo_pi16(c0, zero);
+   *((__m64 *)&c[2]) = _mm_unpackhi_pi16(c0, zero);
+   *((__m64 *)&c[4]) = _mm_unpacklo_pi16(c1, zero);
+   *((__m64 *)&c[6]) = _mm_unpackhi_pi16(c1, zero);
+   _mm_empty();
+# endif
+#else
+   c[0] = (int)ta[0]; c[4] = (int)tb[0];
+   c[1] = (int)ta[1]; c[5] = (int)tb[1];
+   c[2] = (int)ta[2]; c[6] = (int)tb[2];
+   c[0] = MIN(31, MAX(0, c[0]));
+   c[1] = MIN(63, MAX(0, c[1]));
+   c[2] = MIN(31, MAX(0, c[2]));
+   c[4] = MIN(31, MAX(0, c[4]));
+   c[5] = MIN(63, MAX(0, c[5]));
+   c[6] = MIN(31, MAX(0, c[6]));
+#endif
+
+   *start = ((c[2] << 11) | (c[1] << 5) | c[0]);
+   *end   = ((c[6] << 11) | (c[5] << 5) | c[4]);
+}
+
+static void dxtblock_init(dxtblock_t *dxtb, const unsigned char *block, int flags)
+{
+   int i, c0, c;
+   int bc1 = (flags & DXT_BC1);
+   float x, y, z;
+   vec4_t min, max, center, t, cov, inset;
+
+   dxtb->single = 1;
+   dxtb->alphamask = 0;
+
+   if(flags & DXT_PERCEPTUAL)
+      /* ITU-R BT.709 luma coefficents */
+      dxtb->metric = vec4_set(0.2126f, 0.7152f, 0.0722f, 0.0f);
+   else
+      dxtb->metric = vec4_set(1.0f, 1.0f, 1.0f, 0.0f);
+
+   c0 = GETL24(block);
+
+   for(i = 0; i < 16; ++i)
+   {
+      if(bc1 && (block[4 * i + 3] < 128))
+         dxtb->alphamask |= (3 << (2 * i));
+
+      x = (float)block[4 * i + 0] / 255.0f;
+      y = (float)block[4 * i + 1] / 255.0f;
+      z = (float)block[4 * i + 2] / 255.0f;
+
+      dxtb->points[i] = vec4_set(x, y, z, 0);
+
+      c = GETL24(&block[4 * i]);
+      dxtb->single = dxtb->single && (c == c0);
+   }
+
+   // no need to continue if this is a single color block
+   if(dxtb->single) return;
+
+   min = vec4_set1(1.0f);
+   max = vec4_zero();
+
+   // get bounding box extents
+   for(i = 0; i < 16; ++i)
+   {
+      min = vec4_min(min, dxtb->points[i]);
+      max = vec4_max(max, dxtb->points[i]);
+   }
+
+   // select diagonal
+   center = (max + min) * V4HALF;
+   cov = vec4_zero();
+   for(i = 0; i < 16; ++i)
+   {
+      t = dxtb->points[i] - center;
+      cov += t * vec4_splatz(t);
+   }
+
+#ifdef USE_SSE
+   {
+      __m128 mask, tmp;
+      // get mask
+      mask = _mm_cmplt_ps(cov, _mm_setzero_ps());
+      // clear high bits (z, w)
+      mask = _mm_movelh_ps(mask, _mm_setzero_ps());
+      // mask and combine
+      tmp = _mm_or_ps(_mm_and_ps(mask, min), _mm_andnot_ps(mask, max));
+      min = _mm_or_ps(_mm_and_ps(mask, max), _mm_andnot_ps(mask, min));
+      max = tmp;
+   }
+#else
+   {
+      float x0, x1, y0, y1;
+      x0 = max[0];
+      y0 = max[1];
+      x1 = min[0];
+      y1 = min[1];
+
+      if(cov[0] < 0) SWAP(x0, x1);
+      if(cov[1] < 0) SWAP(y0, y1);
+
+      max[0] = x0;
+      max[1] = y0;
+      min[0] = x1;
+      min[1] = y1;
+   }
+#endif
+
+   // inset bounding box and clamp to [0,1]
+   inset = (max - min) * vec4_set1(1.0f / 16.0f) - vec4_set1((8.0f / 255.0f) / 16.0f);
+   max = vec4_min(V4ONE, vec4_max(V4ZERO, max - inset));
+   min = vec4_min(V4ONE, vec4_max(V4ZERO, min + inset));
+
+   // clamp to color space and save
+   dxtb->max = vec4_trunc(V4GRID * max + V4HALF) * V4GRIDRCP;
+   dxtb->min = vec4_trunc(V4GRID * min + V4HALF) * V4GRIDRCP;
+}
+
+static void construct_palette3(dxtblock_t *dxtb)
+{
+   dxtb->palette[0] = dxtb->max;
+   dxtb->palette[1] = dxtb->min;
+   dxtb->palette[2] = (dxtb->max * V4HALF) + (dxtb->min * V4HALF);
+   dxtb->palette[3] = vec4_zero();
+}
+
+static void construct_palette4(dxtblock_t *dxtb)
+{
+   dxtb->palette[0] = dxtb->max;
+   dxtb->palette[1] = dxtb->min;
+   dxtb->palette[2] = (dxtb->max * V4TWOTHIRDS) + (dxtb->min * V4ONETHIRD );
+   dxtb->palette[3] = (dxtb->max * V4ONETHIRD ) + (dxtb->min * V4TWOTHIRDS);
+}
+
+/*
+ * from nvidia-texture-tools; see LICENSE.nvtt for copyright information
+ */
+static void optimize_endpoints3(dxtblock_t *dxtb, unsigned int indices,
+                                vec4_t *max, vec4_t *min)
+{
+   float alpha, beta;
+   vec4_t alpha2_sum, alphax_sum;
+   vec4_t beta2_sum, betax_sum;
+   vec4_t alphabeta_sum, a, b, factor;
+   int i, bits;
+
+   alpha2_sum = beta2_sum = alphabeta_sum = vec4_zero();
+   alphax_sum = vec4_zero();
+   betax_sum = vec4_zero();
+
+   for(i = 0; i < 16; ++i)
+   {
+      bits = indices >> (2 * i);
+
+      // skip alpha pixels
+      if((bits & 3) == 3) continue;
+
+      beta = (float)(bits & 1);
+      if(bits & 2) beta = 0.5f;
+      alpha = 1.0f - beta;
+
+      a = vec4_set1(alpha);
+      b = vec4_set1(beta);
+      alpha2_sum += a * a;
+      beta2_sum += b * b;
+      alphabeta_sum += a * b;
+      alphax_sum += dxtb->points[i] * a;
+      betax_sum  += dxtb->points[i] * b;
+   }
+
+   factor = alpha2_sum * beta2_sum - alphabeta_sum * alphabeta_sum;
+   if(vec4_cmplt(factor, V4EPSILON)) return;
+   factor = vec4_rcp(factor);
+
+   a = (alphax_sum * beta2_sum  - betax_sum  * alphabeta_sum) * factor;
+   b = (betax_sum  * alpha2_sum - alphax_sum * alphabeta_sum) * factor;
+
+   // clamp to the color space
+   a = vec4_min(V4ONE, vec4_max(V4ZERO, a));
+   b = vec4_min(V4ONE, vec4_max(V4ZERO, b));
+   a = vec4_trunc(V4GRID * a + V4HALF) * V4GRIDRCP;
+   b = vec4_trunc(V4GRID * b + V4HALF) * V4GRIDRCP;
+
+   *max = a;
+   *min = b;
+}
+
+/*
+ * from nvidia-texture-tools; see LICENSE.nvtt for copyright information
+ */
+static void optimize_endpoints4(dxtblock_t *dxtb, unsigned int indices,
+                                vec4_t *max, vec4_t *min)
+{
+   float alpha, beta;
+   vec4_t alpha2_sum, alphax_sum;
+   vec4_t beta2_sum, betax_sum;
+   vec4_t alphabeta_sum, a, b, factor;
+   int i, bits;
+
+   alpha2_sum = beta2_sum = alphabeta_sum = vec4_zero();
+   alphax_sum = vec4_zero();
+   betax_sum = vec4_zero();
+
+   for(i = 0; i < 16; ++i)
+   {
+      bits = indices >> (2 * i);
+
+      beta = (float)(bits & 1);
+      if(bits & 2) beta = (1.0f + beta) / 3.0f;
+      alpha = 1.0f - beta;
+
+      a = vec4_set1(alpha);
+      b = vec4_set1(beta);
+      alpha2_sum += a * a;
+      beta2_sum += b * b;
+      alphabeta_sum += a * b;
+      alphax_sum += dxtb->points[i] * a;
+      betax_sum  += dxtb->points[i] * b;
+   }
+
+   factor = alpha2_sum * beta2_sum - alphabeta_sum * alphabeta_sum;
+   if(vec4_cmplt(factor, V4EPSILON)) return;
+   factor = vec4_rcp(factor);
+
+   a = (alphax_sum * beta2_sum  - betax_sum  * alphabeta_sum) * factor;
+   b = (betax_sum  * alpha2_sum - alphax_sum * alphabeta_sum) * factor;
+
+   // clamp to the color space
+   a = vec4_min(V4ONE, vec4_max(V4ZERO, a));
+   b = vec4_min(V4ONE, vec4_max(V4ZERO, b));
+   a = vec4_trunc(V4GRID * a + V4HALF) * V4GRIDRCP;
+   b = vec4_trunc(V4GRID * b + V4HALF) * V4GRIDRCP;
+
+   *max = a;
+   *min = b;
+}
+
+static unsigned int match_colors3(dxtblock_t *dxtb)
+{
+   int i, idx;
+   unsigned int indices = 0;
+   vec4_t t0, t1, t2;
+#ifdef USE_SSE
+   vec4_t d, bits, zero = _mm_setzero_ps();
+   int mask;
+#else
+   float d0, d1, d2;
+#endif
+
+   // match each point to the closest color
+   for(i = 0; i < 16; ++i)
+   {
+      // skip alpha pixels
+      if(((dxtb->alphamask >> (2 * i)) & 3) == 3)
+      {
+         indices |= (3 << (2 * i));
+         continue;
+      }
+
+      t0 = (dxtb->points[i] - dxtb->palette[0]) * dxtb->metric;
+      t1 = (dxtb->points[i] - dxtb->palette[1]) * dxtb->metric;
+      t2 = (dxtb->points[i] - dxtb->palette[2]) * dxtb->metric;
+
+#ifdef USE_SSE
+      _MM_TRANSPOSE4_PS(t0, t1, t2, zero);
+      d = t0 * t0 + t1 * t1 + t2 * t2;
+      bits = _mm_cmplt_ps(_mm_shuffle_ps(d, d, _MM_SHUFFLE(3, 1, 0, 0)),
+                          _mm_shuffle_ps(d, d, _MM_SHUFFLE(3, 2, 2, 1)));
+      mask = _mm_movemask_ps(bits);
+      if((mask & 3) == 3) idx = 0;
+      else if(mask & 4)   idx = 1;
+      else                idx = 2;
+#else
+      d0 = vec4_dot(t0, t0);
+      d1 = vec4_dot(t1, t1);
+      d2 = vec4_dot(t2, t2);
+
+      if((d0 < d1) && (d0 < d2))
+         idx = 0;
+      else if(d1 < d2)
+         idx = 1;
+      else
+         idx = 2;
+#endif
+
+      indices |= (idx << (2 * i));
+   }
+
+   return(indices);
+}
+
+static unsigned int match_colors4(dxtblock_t *dxtb)
+{
+   int i;
+   unsigned int idx, indices = 0;
+   unsigned int b0, b1, b2, b3, b4;
+   unsigned int x0, x1, x2;
+   vec4_t t0, t1, t2, t3;
+#ifdef USE_SSE
+   vec4_t d;
+#else
+   float d[4];
+#endif
+
+   // match each point to the closest color
+   for(i = 0; i < 16; ++i)
+   {
+      t0 = (dxtb->points[i] - dxtb->palette[0]) * dxtb->metric;
+      t1 = (dxtb->points[i] - dxtb->palette[1]) * dxtb->metric;
+      t2 = (dxtb->points[i] - dxtb->palette[2]) * dxtb->metric;
+      t3 = (dxtb->points[i] - dxtb->palette[3]) * dxtb->metric;
+
+#ifdef USE_SSE
+      _MM_TRANSPOSE4_PS(t0, t1, t2, t3);
+      d = t0 * t0 + t1 * t1 + t2 * t2;
+#else
+      d[0] = vec4_dot(t0, t0);
+      d[1] = vec4_dot(t1, t1);
+      d[2] = vec4_dot(t2, t2);
+      d[3] = vec4_dot(t3, t3);
+#endif
+
+      b0 = d[0] > d[3];
+      b1 = d[1] > d[2];
+      b2 = d[0] > d[2];
+      b3 = d[1] > d[3];
+      b4 = d[2] > d[3];
+
+      x0 = b1 & b2;
+      x1 = b0 & b3;
+      x2 = b0 & b4;
+
+      idx = x2 | ((x0 | x1) << 1);
+
+      indices |= (idx << (2 * i));
+   }
+
+   return(indices);
+}
+
+static float compute_error3(dxtblock_t *dxtb, unsigned int indices)
+{
+   int i, idx;
+   float error = 0;
+   vec4_t t;
+
+   // compute error
+   for(i = 0; i < 16; ++i)
+   {
+      idx = (indices >> (2 * i)) & 3;
+      // skip alpha pixels
+      if(idx == 3) continue;
+      t = (dxtb->points[i] - dxtb->palette[idx]) * dxtb->metric;
+      error += vec4_dot(t, t);
+   }
+
+   return(error);
+}
+
+static float compute_error4(dxtblock_t *dxtb, unsigned int indices)
+{
+   int i, idx;
+   float error = 0;
+
+#ifdef USE_SSE
+   vec4_t a0, a1, a2, a3;
+   vec4_t b0, b1, b2, b3;
+   vec4_t d;
+
+   for(i = 0; i < 4; ++i)
+   {
+      idx = indices >> (8 * i);
+      a0 = dxtb->points[4 * i + 0];
+      a1 = dxtb->points[4 * i + 1];
+      a2 = dxtb->points[4 * i + 2];
+      a3 = dxtb->points[4 * i + 3];
+      b0 = dxtb->palette[(idx     ) & 3];
+      b1 = dxtb->palette[(idx >> 2) & 3];
+      b2 = dxtb->palette[(idx >> 4) & 3];
+      b3 = dxtb->palette[(idx >> 6) & 3];
+      a0 = (a0 - b0) * dxtb->metric;
+      a1 = (a1 - b1) * dxtb->metric;
+      a2 = (a2 - b2) * dxtb->metric;
+      a3 = (a3 - b3) * dxtb->metric;
+      _MM_TRANSPOSE4_PS(a0, a1, a2, a3);
+      d = a0 * a0 + a1 * a1 + a2 * a2;
+      error += vec4_accum(d);
+   }
+#else
+   vec4_t t;
+
+   // compute error
+   for(i = 0; i < 16; ++i)
+   {
+      idx = (indices >> (2 * i)) & 3;
+      t = (dxtb->points[i] - dxtb->palette[idx]) * dxtb->metric;
+      error += vec4_dot(t, t);
+   }
+#endif
+   return(error);
+}
+
+static unsigned int compress3(dxtblock_t *dxtb)
+{
+   const int MAX_ITERATIONS = 8;
+   int i;
+   unsigned int indices, bestindices;
+   float error, besterror = FLT_MAX;
+   vec4_t oldmax, oldmin;
+
+   construct_palette3(dxtb);
+
+   indices = match_colors3(dxtb);
+   bestindices = indices;
+
+   for(i = 0; i < MAX_ITERATIONS; ++i)
+   {
+      oldmax = dxtb->max;
+      oldmin = dxtb->min;
+
+      optimize_endpoints3(dxtb, indices, &dxtb->max, &dxtb->min);
+      construct_palette3(dxtb);
+      indices = match_colors3(dxtb);
+      error = compute_error3(dxtb, indices);
+
+      if(error < besterror)
+      {
+         besterror = error;
+         bestindices = indices;
+      }
+      else
+      {
+         dxtb->max = oldmax;
+         dxtb->min = oldmin;
+         break;
+      }
+   }
+
+   return(bestindices);
+}
+
+static unsigned int compress4(dxtblock_t *dxtb)
+{
+   const int MAX_ITERATIONS = 8;
+   int i;
+   unsigned int indices, bestindices;
+   float error, besterror = FLT_MAX;
+   vec4_t oldmax, oldmin;
+
+   construct_palette4(dxtb);
+
+   indices = match_colors4(dxtb);
+   bestindices = indices;
+
+   for(i = 0; i < MAX_ITERATIONS; ++i)
+   {
+      oldmax = dxtb->max;
+      oldmin = dxtb->min;
+
+      optimize_endpoints4(dxtb, indices, &dxtb->max, &dxtb->min);
+      construct_palette4(dxtb);
+      indices = match_colors4(dxtb);
+      error = compute_error4(dxtb, indices);
+
+      if(error < besterror)
+      {
+         besterror = error;
+         bestindices = indices;
+      }
+      else
+      {
+         dxtb->max = oldmax;
+         dxtb->min = oldmin;
+         break;
+      }
+   }
+
+   return(bestindices);
+}
+
+static void encode_color_block(unsigned char *dst, unsigned char *block, int flags)
+{
+   dxtblock_t dxtb;
+   int max16, min16;
+   unsigned int indices, mask;
+
+   dxtblock_init(&dxtb, block, flags);
+
+   if(dxtb.single) // single color block
+   {
+      max16 = (omatch5[block[2]][0] << 11) |
+              (omatch6[block[1]][0] <<  5) |
+              (omatch5[block[0]][0]      );
+      min16 = (omatch5[block[2]][1] << 11) |
+              (omatch6[block[1]][1] <<  5) |
+              (omatch5[block[0]][1]      );
+
+      indices = 0xaaaaaaaa; // 101010...
+
+      if((flags & DXT_BC1) && dxtb.alphamask)
+      {
+         // DXT1 compression, non-opaque block.  Add alpha indices.
+         indices |= dxtb.alphamask;
+         if(max16 > min16)
+            SWAP(max16, min16);
+      }
+      else if(max16 < min16)
+      {
+         SWAP(max16, min16);
+         indices ^= 0x55555555; // 010101...
+      }
+   }
+   else if((flags & DXT_BC1) && dxtb.alphamask) // DXT1 compression, non-opaque block
+   {
+      indices = compress3(&dxtb);
+
+      vec4_endpoints_to_565(&max16, &min16, dxtb.max, dxtb.min);
+
+      if(max16 > min16)
+      {
+         SWAP(max16, min16);
+         // remap indices 0 -> 1, 1 -> 0
+         mask = indices & 0xaaaaaaaa;
+         mask = mask | (mask >> 1);
+         indices = (indices & mask) | ((indices ^ 0x55555555) & ~mask);
+      }
+   }
+   else
+   {
+      indices = compress4(&dxtb);
+
+      vec4_endpoints_to_565(&max16, &min16, dxtb.max, dxtb.min);
+
+      if(max16 < min16)
+      {
+         SWAP(max16, min16);
+         indices ^= 0x55555555; // 010101...
+      }
+   }
+
+   PUTL16(dst + 0, max16);
+   PUTL16(dst + 2, min16);
+   PUTL32(dst + 4, indices);
+}
+
+static void get_min_max_YCoCg(const unsigned char *block,
+                              unsigned char *mincolor, unsigned char *maxcolor)
+{
+   int i;
+
+   mincolor[2] = mincolor[1] = 255;
+   maxcolor[2] = maxcolor[1] = 0;
+
+   for(i = 0; i < 16; ++i)
+   {
+      if(block[4 * i + 2] < mincolor[2]) mincolor[2] = block[4 * i + 2];
+      if(block[4 * i + 1] < mincolor[1]) mincolor[1] = block[4 * i + 1];
+      if(block[4 * i + 2] > maxcolor[2]) maxcolor[2] = block[4 * i + 2];
+      if(block[4 * i + 1] > maxcolor[1]) maxcolor[1] = block[4 * i + 1];
+   }
+}
+
+static void scale_YCoCg(unsigned char *block,
+                        unsigned char *mincolor, unsigned char *maxcolor)
+{
+   const int s0 = 128 / 2 - 1;
+   const int s1 = 128 / 4 - 1;
+   int m0, m1, m2, m3;
+   int mask0, mask1, scale;
+   int i;
+
+   m0 = abs(mincolor[2] - 128);
+   m1 = abs(mincolor[1] - 128);
+   m2 = abs(maxcolor[2] - 128);
+   m3 = abs(maxcolor[1] - 128);
+
+   if(m1 > m0) m0 = m1;
+   if(m3 > m2) m2 = m3;
+   if(m2 > m0) m0 = m2;
+
+   mask0 = -(m0 <= s0);
+   mask1 = -(m0 <= s1);
+   scale = 1 + (1 & mask0) + (2 & mask1);
+
+   mincolor[2] = (mincolor[2] - 128) * scale + 128;
+   mincolor[1] = (mincolor[1] - 128) * scale + 128;
+   mincolor[0] = (scale - 1) << 3;
+
+   maxcolor[2] = (maxcolor[2] - 128) * scale + 128;
+   maxcolor[1] = (maxcolor[1] - 128) * scale + 128;
+   maxcolor[0] = (scale - 1) << 3;
+
+   for(i = 0; i < 16; ++i)
+   {
+      block[i * 4 + 2] = (block[i * 4 + 2] - 128) * scale + 128;
+      block[i * 4 + 1] = (block[i * 4 + 1] - 128) * scale + 128;
+   }
+}
+
+#define INSET_SHIFT  4
+
+static void inset_bbox_YCoCg(unsigned char *mincolor, unsigned char *maxcolor)
+{
+   int inset[4], mini[4], maxi[4];
+
+   inset[2] = (maxcolor[2] - mincolor[2]) - ((1 << (INSET_SHIFT - 1)) - 1);
+   inset[1] = (maxcolor[1] - mincolor[1]) - ((1 << (INSET_SHIFT - 1)) - 1);
+
+   mini[2] = ((mincolor[2] << INSET_SHIFT) + inset[2]) >> INSET_SHIFT;
+   mini[1] = ((mincolor[1] << INSET_SHIFT) + inset[1]) >> INSET_SHIFT;
+
+   maxi[2] = ((maxcolor[2] << INSET_SHIFT) - inset[2]) >> INSET_SHIFT;
+   maxi[1] = ((maxcolor[1] << INSET_SHIFT) - inset[1]) >> INSET_SHIFT;
+
+   mini[2] = (mini[2] >= 0) ? mini[2] : 0;
+   mini[1] = (mini[1] >= 0) ? mini[1] : 0;
+
+   maxi[2] = (maxi[2] <= 255) ? maxi[2] : 255;
+   maxi[1] = (maxi[1] <= 255) ? maxi[1] : 255;
+
+   mincolor[2] = (mini[2] & 0xf8) | (mini[2] >> 5);
+   mincolor[1] = (mini[1] & 0xfc) | (mini[1] >> 6);
+
+   maxcolor[2] = (maxi[2] & 0xf8) | (maxi[2] >> 5);
+   maxcolor[1] = (maxi[1] & 0xfc) | (maxi[1] >> 6);
+}
+
+static void select_diagonal_YCoCg(const unsigned char *block,
+                                  unsigned char *mincolor,
+                                  unsigned char *maxcolor)
+{
+   unsigned char mid0, mid1, side, mask, b0, b1, c0, c1;
+   int i;
+
+   mid0 = ((int)mincolor[2] + maxcolor[2] + 1) >> 1;
+   mid1 = ((int)mincolor[1] + maxcolor[1] + 1) >> 1;
+
+   side = 0;
+   for(i = 0; i < 16; ++i)
+   {
+      b0 = block[i * 4 + 2] >= mid0;
+      b1 = block[i * 4 + 1] >= mid1;
+      side += (b0 ^ b1);
+   }
+
+   mask = -(side > 8);
+   mask &= -(mincolor[2] != maxcolor[2]);
+
+   c0 = mincolor[1];
+   c1 = maxcolor[1];
+
+   c0 ^= c1;
+   c1 ^= c0 & mask;
+   c0 ^= c1;
+
+   mincolor[1] = c0;
+   maxcolor[1] = c1;
+}
+
+static void encode_YCoCg_block(unsigned char *dst, unsigned char *block)
+{
+   unsigned char colors[4][3], *maxcolor, *mincolor;
+   unsigned int mask;
+   int c0, c1, d0, d1, d2, d3;
+   int b0, b1, b2, b3, b4;
+   int x0, x1, x2;
+   int i, idx;
+
+   maxcolor = &colors[0][0];
+   mincolor = &colors[1][0];
+
+   get_min_max_YCoCg(block, mincolor, maxcolor);
+   scale_YCoCg(block, mincolor, maxcolor);
+   inset_bbox_YCoCg(mincolor, maxcolor);
+   select_diagonal_YCoCg(block, mincolor, maxcolor);
+
+   lerp_rgb13(&colors[2][0], maxcolor, mincolor);
+   lerp_rgb13(&colors[3][0], mincolor, maxcolor);
+
+   mask = 0;
+
+   for(i = 0; i < 16; ++i)
+   {
+      c0 = block[4 * i + 2];
+      c1 = block[4 * i + 1];
+
+      d0 = abs(colors[0][2] - c0) + abs(colors[0][1] - c1);
+      d1 = abs(colors[1][2] - c0) + abs(colors[1][1] - c1);
+      d2 = abs(colors[2][2] - c0) + abs(colors[2][1] - c1);
+      d3 = abs(colors[3][2] - c0) + abs(colors[3][1] - c1);
+
+      b0 = d0 > d3;
+      b1 = d1 > d2;
+      b2 = d0 > d2;
+      b3 = d1 > d3;
+      b4 = d2 > d3;
+
+      x0 = b1 & b2;
+      x1 = b0 & b3;
+      x2 = b0 & b4;
+
+      idx = (x2 | ((x0 | x1) << 1));
+
+      mask |= idx << (2 * i);
+   }
+
+   PUTL16(dst + 0, pack_rgb565(maxcolor));
+   PUTL16(dst + 2, pack_rgb565(mincolor));
+   PUTL32(dst + 4, mask);
+}
+
+/* write DXT3 alpha block */
+static void encode_alpha_block_BC2(unsigned char *dst,
+                                   const unsigned char *block)
+{
+   int i, a1, a2;
+
+   block += 3;
+
+   for(i = 0; i < 8; ++i)
+   {
+      a1 = mul8bit(block[8 * i + 0], 0x0f);
+      a2 = mul8bit(block[8 * i + 4], 0x0f);
+      *dst++ = (a2 << 4) | a1;
+   }
+}
+
+/* Write DXT5 alpha block */
+static void encode_alpha_block_BC3(unsigned char *dst,
+                                   const unsigned char *block,
+                                   const int offset)
+{
+   int i, v, mn, mx;
+   int dist, bias, dist2, dist4, bits, mask;
+   int a, idx, t;
+
+   block += offset;
+   block += 3;
+
+   /* find min/max alpha pair */
+   mn = mx = block[0];
+   for(i = 0; i < 16; ++i)
+   {
+      v = block[4 * i];
+      if(v > mx) mx = v;
+      if(v < mn) mn = v;
+   }
+
+   /* encode them */
+   *dst++ = mx;
+   *dst++ = mn;
+
+   /*
+    * determine bias and emit indices
+    * given the choice of mx/mn, these indices are optimal:
+    * http://fgiesen.wordpress.com/2009/12/15/dxt5-alpha-block-index-determination/
+    */
+   dist = mx - mn;
+   dist4 = dist * 4;
+   dist2 = dist * 2;
+   bias = (dist < 8) ? (dist - 1) : (dist / 2 + 2);
+   bias -= mn * 7;
+   bits = 0;
+   mask = 0;
+
+   for(i = 0; i < 16; ++i)
+   {
+      a = block[4 * i] * 7 + bias;
+
+      /* select index. this is a "linear scale" lerp factor between 0 (val=min) and 7 (val=max). */
+      t = (a >= dist4) ? -1 : 0; idx =  t & 4; a -= dist4 & t;
+      t = (a >= dist2) ? -1 : 0; idx += t & 2; a -= dist2 & t;
+      idx += (a >= dist);
+
+      /* turn linear scale into DXT index (0/1 are extremal pts) */
+      idx = -idx & 7;
+      idx ^= (2 > idx);
+
+      /* write index */
+      mask |= idx << bits;
+      if((bits += 3) >= 8)
+      {
+         *dst++ = mask;
+         mask >>= 8;
+         bits -= 8;
+      }
+   }
+}
+
+#define BLOCK_COUNT(w, h)          ((((h) + 3) >> 2) * (((w) + 3) >> 2))
+#define BLOCK_OFFSET(x, y, w, bs)  (((y) >> 2) * ((bs) * (((w) + 3) >> 2)) + ((bs) * ((x) >> 2)))
+
+static void compress_BC1(unsigned char *dst, const unsigned char *src,
+                         int w, int h, int flags)
+{
+   const unsigned int block_count = BLOCK_COUNT(w, h);
+   unsigned int i;
+   unsigned char block[64], *p;
+   int x, y;
+
+#ifdef _OPENMP
+   #pragma omp parallel for schedule(dynamic, 256) private(block, p, x, y)
+#endif
+   for(i = 0; i < block_count; ++i)
+   {
+      x = (i % ((w + 3) >> 2)) << 2;
+      y = (i / ((w + 3) >> 2)) << 2;
+      p = dst + BLOCK_OFFSET(x, y, w, 8);
+      extract_block(src, x, y, w, h, block);
+      encode_color_block(p, block, DXT_BC1 | flags);
+   }
+}
+
+static void compress_BC2(unsigned char *dst, const unsigned char *src,
+                         int w, int h, int flags)
+{
+   const unsigned int block_count = BLOCK_COUNT(w, h);
+   unsigned int i;
+   unsigned char block[64], *p;
+   int x, y;
+
+#ifdef _OPENMP
+   #pragma omp parallel for schedule(dynamic, 256) private(block, p, x, y)
+#endif
+   for(i = 0; i < block_count; ++i)
+   {
+      x = (i % ((w + 3) >> 2)) << 2;
+      y = (i / ((w + 3) >> 2)) << 2;
+      p = dst + BLOCK_OFFSET(x, y, w, 16);
+      extract_block(src, x, y, w, h, block);
+      encode_alpha_block_BC2(p, block);
+      encode_color_block(p + 8, block, DXT_BC2 | flags);
+   }
+}
+
+static void compress_BC3(unsigned char *dst, const unsigned char *src,
+                         int w, int h, int flags)
+{
+   const unsigned int block_count = BLOCK_COUNT(w, h);
+   unsigned int i;
+   unsigned char block[64], *p;
+   int x, y;
+
+#ifdef _OPENMP
+   #pragma omp parallel for schedule(dynamic, 256) private(block, p, x, y)
+#endif
+   for(i = 0; i < block_count; ++i)
+   {
+      x = (i % ((w + 3) >> 2)) << 2;
+      y = (i / ((w + 3) >> 2)) << 2;
+      p = dst + BLOCK_OFFSET(x, y, w, 16);
+      extract_block(src, x, y, w, h, block);
+      encode_alpha_block_BC3(p, block, 0);
+      encode_color_block(p + 8, block, DXT_BC3 | flags);
+   }
+}
+
+static void compress_BC4(unsigned char *dst, const unsigned char *src,
+                         int w, int h)
+{
+   const unsigned int block_count = BLOCK_COUNT(w, h);
+   unsigned int i;
+   unsigned char block[64], *p;
+   int x, y;
+
+#ifdef _OPENMP
+   #pragma omp parallel for schedule(dynamic, 256) private(block, p, x, y)
+#endif
+   for(i = 0; i < block_count; ++i)
+   {
+      x = (i % ((w + 3) >> 2)) << 2;
+      y = (i / ((w + 3) >> 2)) << 2;
+      p = dst + BLOCK_OFFSET(x, y, w, 8);
+      extract_block(src, x, y, w, h, block);
+      encode_alpha_block_BC3(p, block, -1);
+   }
+}
+
+static void compress_BC5(unsigned char *dst, const unsigned char *src,
+                         int w, int h)
+{
+   const unsigned int block_count = BLOCK_COUNT(w, h);
+   unsigned int i;
+   unsigned char block[64], *p;
+   int x, y;
+
+#ifdef _OPENMP
+   #pragma omp parallel for schedule(dynamic, 256) private(block, p, x, y)
+#endif
+   for(i = 0; i < block_count; ++i)
+   {
+      x = (i % ((w + 3) >> 2)) << 2;
+      y = (i / ((w + 3) >> 2)) << 2;
+      p = dst + BLOCK_OFFSET(x, y, w, 16);
+      extract_block(src, x, y, w, h, block);
+      encode_alpha_block_BC3(p, block, -2);
+      encode_alpha_block_BC3(p + 8, block, -1);
+   }
+}
+
+static void compress_YCoCg(unsigned char *dst, const unsigned char *src,
+                           int w, int h)
+{
+   const unsigned int block_count = BLOCK_COUNT(w, h);
+   unsigned int i;
+   unsigned char block[64], *p;
+   int x, y;
+
+#ifdef _OPENMP
+   #pragma omp parallel for schedule(dynamic, 256) private(block, p, x, y)
+#endif
+   for(i = 0; i < block_count; ++i)
+   {
+      x = (i % ((w + 3) >> 2)) << 2;
+      y = (i / ((w + 3) >> 2)) << 2;
+      p = dst + BLOCK_OFFSET(x, y, w, 16);
+      extract_block(src, x, y, w, h, block);
+      encode_alpha_block_BC3(p, block, 0);
+      encode_YCoCg_block(p + 8, block);
+   }
+}
+
+int dxt_compress(unsigned char *dst, unsigned char *src, int format,
+                 unsigned int width, unsigned int height, int bpp,
+                 int mipmaps, int flags)
+{
+   int i, size, w, h;
+   unsigned int offset;
+   unsigned char *tmp = NULL;
+   int j;
+   unsigned char *s;
+
+   if(bpp == 1)
+   {
+      /* grayscale promoted to BGRA */
+
+      size = get_mipmapped_size(width, height, 4, 0, mipmaps,
+                                DDS_COMPRESS_NONE);
+      tmp = g_malloc(size);
+
+      for(i = j = 0; j < size; ++i, j += 4)
+      {
+         tmp[j + 0] = src[i];
+         tmp[j + 1] = src[i];
+         tmp[j + 2] = src[i];
+         tmp[j + 3] = 255;
+      }
+
+      bpp = 4;
+   }
+   else if(bpp == 2)
+   {
+      /* gray-alpha promoted to BGRA */
+
+      size = get_mipmapped_size(width, height, 4, 0, mipmaps,
+                                DDS_COMPRESS_NONE);
+      tmp = g_malloc(size);
+
+      for(i = j = 0; j < size; i += 2, j += 4)
+      {
+         tmp[j + 0] = src[i];
+         tmp[j + 1] = src[i];
+         tmp[j + 2] = src[i];
+         tmp[j + 3] = src[i + 1];
+      }
+
+      bpp = 4;
+   }
+   else if(bpp == 3)
+   {
+      size = get_mipmapped_size(width, height, 4, 0, mipmaps,
+                                DDS_COMPRESS_NONE);
+      tmp = g_malloc(size);
+
+      for(i = j = 0; j < size; i += 3, j += 4)
+      {
+         tmp[j + 0] = src[i + 0];
+         tmp[j + 1] = src[i + 1];
+         tmp[j + 2] = src[i + 2];
+         tmp[j + 3] = 255;
+      }
+
+      bpp = 4;
+   }
+
+   offset = 0;
+   w = width;
+   h = height;
+   s = tmp ? tmp : src;
+
+   for(i = 0; i < mipmaps; ++i)
+   {
+      switch(format)
+      {
+         case DDS_COMPRESS_BC1:
+            compress_BC1(dst + offset, s, w, h, flags);
+            break;
+         case DDS_COMPRESS_BC2:
+            compress_BC2(dst + offset, s, w, h, flags);
+            break;
+         case DDS_COMPRESS_BC3:
+         case DDS_COMPRESS_BC3N:
+         case DDS_COMPRESS_RXGB:
+         case DDS_COMPRESS_AEXP:
+         case DDS_COMPRESS_YCOCG:
+            compress_BC3(dst + offset, s, w, h, flags);
+            break;
+         case DDS_COMPRESS_BC4:
+            compress_BC4(dst + offset, s, w, h);
+            break;
+         case DDS_COMPRESS_BC5:
+            compress_BC5(dst + offset, s, w, h);
+            break;
+         case DDS_COMPRESS_YCOCGS:
+            compress_YCoCg(dst + offset, s, w, h);
+            break;
+         default:
+            compress_BC3(dst + offset, s, w, h, flags);
+            break;
+      }
+      s += (w * h * bpp);
+      offset += get_mipmapped_size(w, h, 0, 0, 1, format);
+      w = MAX(1, w >> 1);
+      h = MAX(1, h >> 1);
+   }
+
+   if(tmp) g_free(tmp);
+
+   return(1);
+}
+
+static void decode_color_block(unsigned char *block, unsigned char *src,
+                               int format)
+{
+   int i, x, y;
+   unsigned char *d = block;
+   unsigned int indices, idx;
+   unsigned char colors[4][3];
+   unsigned short c0, c1;
+
+   c0 = GETL16(&src[0]);
+   c1 = GETL16(&src[2]);
+
+   unpack_rgb565(colors[0], c0);
+   unpack_rgb565(colors[1], c1);
+
+   if((c0 > c1) || (format == DDS_COMPRESS_BC3))
+   {
+      lerp_rgb13(colors[2], colors[0], colors[1]);
+      lerp_rgb13(colors[3], colors[1], colors[0]);
+   }
+   else
+   {
+      for(i = 0; i < 3; ++i)
+      {
+         colors[2][i] = (colors[0][i] + colors[1][i] + 1) >> 1;
+         colors[3][i] = 255;
+      }
+   }
+
+   src += 4;
+   for(y = 0; y < 4; ++y)
+   {
+      indices = src[y];
+      for(x = 0; x < 4; ++x)
+      {
+         idx = indices & 0x03;
+         d[0] = colors[idx][2];
+         d[1] = colors[idx][1];
+         d[2] = colors[idx][0];
+         if(format == DDS_COMPRESS_BC1)
+            d[3] = ((c0 <= c1) && idx == 3) ? 0 : 255;
+         indices >>= 2;
+         d += 4;
+      }
+   }
+}
+
+static void decode_alpha_block_BC2(unsigned char *block, unsigned char *src)
+{
+   int x, y;
+   unsigned char *d = block;
+   unsigned int bits;
+
+   for(y = 0; y < 4; ++y)
+   {
+      bits = GETL16(&src[2 * y]);
+      for(x = 0; x < 4; ++x)
+      {
+         d[0] = (bits & 0x0f) * 17;
+         bits >>= 4;
+         d += 4;
+      }
+   }
+}
+
+static void decode_alpha_block_BC3(unsigned char *block, unsigned char *src, int w)
+{
+   int x, y, code;
+   unsigned char *d = block;
+   unsigned char a0 = src[0];
+   unsigned char a1 = src[1];
+   unsigned long long bits = GETL64(src) >> 16;
+
+   for(y = 0; y < 4; ++y)
+   {
+      for(x = 0; x < 4; ++x)
+      {
+         code = ((unsigned int)bits) & 0x07;
+         if(code == 0)
+            d[0] = a0;
+         else if(code == 1)
+            d[0] = a1;
+         else if(a0 > a1)
+            d[0] = ((8 - code) * a0 + (code - 1) * a1) / 7;
+         else if(code >= 6)
+            d[0] = (code == 6) ? 0 : 255;
+         else
+            d[0] = ((6 - code) * a0 + (code - 1) * a1) / 5;
+         bits >>= 3;
+         d += 4;
+      }
+      if(w < 4) bits >>= (3 * (4 - w));
+   }
+}
+
+static void make_normal(unsigned char *dst, unsigned char x, unsigned char y)
+{
+   float nx = 2.0f * ((float)x / 255.0f) - 1.0f;
+   float ny = 2.0f * ((float)y / 255.0f) - 1.0f;
+   float nz = 0.0f;
+   float d = 1.0f - nx * nx + ny * ny;
+   int z;
+
+   if(d > 0) nz = sqrtf(d);
+
+   z = (int)(255.0f * (nz + 1) / 2.0f);
+   z = MAX(0, MIN(255, z));
+
+   dst[0] = x;
+   dst[1] = y;
+   dst[2] = z;
+}
+
+static void normalize_block(unsigned char *block, int format)
+{
+   int x, y, tmp;
+
+   for(y = 0; y < 4; ++y)
+   {
+      for(x = 0; x < 4; ++x)
+      {
+         if(format == DDS_COMPRESS_BC3)
+         {
+            tmp = block[y * 16 + (x * 4)];
+            make_normal(&block[y * 16 + (x * 4)],
+                        block[y * 16 + (x * 4) + 3],
+                        block[y * 16 + (x * 4) + 1]);
+            block[y * 16 + (x * 4) + 3] = tmp;
+         }
+         else if(format == DDS_COMPRESS_BC5)
+         {
+            make_normal(&block[y * 16 + (x * 4)],
+                        block[y * 16 + (x * 4)],
+                        block[y * 16 + (x * 4) + 1]);
+         }
+      }
+   }
+}
+
+static void put_block(unsigned char *dst, unsigned char *block,
+                      unsigned int bx, unsigned int by,
+                      unsigned int width, unsigned height,
+                      int bpp)
+{
+   int x, y, i;
+   unsigned char *d;
+
+   for(y = 0; y < 4 && ((by + y) < height); ++y)
+   {
+      d = dst + ((y + by) * width + bx) * bpp;
+      for(x = 0; x < 4 && ((bx + x) < width); ++x)
+      {
+         for(i = 0; i < bpp; ++ i)
+            *d++ = block[y * 16 + (x * 4) + i];
+      }
+   }
+}
+
+int dxt_decompress(unsigned char *dst, unsigned char *src, int format,
+                   unsigned int size, unsigned int width, unsigned int height,
+                   int bpp, int normals)
+{
+   unsigned char *s;
+   unsigned int x, y;
+   unsigned char block[16 * 4];
+
+   s = src;
+
+   for(y = 0; y < height; y += 4)
+   {
+      for(x = 0; x < width; x += 4)
+      {
+         memset(block, 255, 16 * 4);
+
+         if(format == DDS_COMPRESS_BC1)
+         {
+            decode_color_block(block, s, format);
+            s += 8;
+         }
+         else if(format == DDS_COMPRESS_BC2)
+         {
+            decode_alpha_block_BC2(block + 3, s);
+            decode_color_block(block, s + 8, format);
+            s += 16;
+         }
+         else if(format == DDS_COMPRESS_BC3)
+         {
+            decode_alpha_block_BC3(block + 3, s, width);
+            decode_color_block(block, s + 8, format);
+            s += 16;
+         }
+         else if(format == DDS_COMPRESS_BC4)
+         {
+            decode_alpha_block_BC3(block, s, width);
+            s += 8;
+         }
+         else if(format == DDS_COMPRESS_BC5)
+         {
+            decode_alpha_block_BC3(block, s + 8, width);
+            decode_alpha_block_BC3(block + 1, s, width);
+            s += 16;
+         }
+
+         if(normals)
+            normalize_block(block, format);
+
+         put_block(dst, block, x, y, width, height, bpp);
+      }
+   }
+
+   return(1);
+}
diff --git a/plug-ins/file-dds/dxt.h b/plug-ins/file-dds/dxt.h
new file mode 100644
index 0000000000..a49c85a803
--- /dev/null
+++ b/plug-ins/file-dds/dxt.h
@@ -0,0 +1,41 @@
+/*
+       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.
+*/
+
+#ifndef DXT_H
+#define DXT_H
+
+typedef enum dxt_flags_e
+{
+   DXT_BC1           = 1 << 0,
+   DXT_BC2           = 1 << 1,
+   DXT_BC3           = 1 << 2,
+   DXT_PERCEPTUAL    = 1 << 3,
+} dxt_flags_t;
+
+int dxt_compress(unsigned char *dst, unsigned char *src, int format,
+                 unsigned int width, unsigned int height, int bpp,
+                 int mipmaps, int flags);
+int dxt_decompress(unsigned char *dst, unsigned char *src, int format,
+                   unsigned int size, unsigned int width, unsigned int height,
+                   int bpp, int normals);
+
+#endif
diff --git a/plug-ins/file-dds/dxt_tables.h b/plug-ins/file-dds/dxt_tables.h
new file mode 100644
index 0000000000..14aee2b52a
--- /dev/null
+++ b/plug-ins/file-dds/dxt_tables.h
@@ -0,0 +1,216 @@
+#ifndef DXT_TABLES_H
+#define DXT_TABLES_H
+
+static const unsigned char quantRB[256 + 16] =
+{
+   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
+   0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x08, 
+   0x08, 0x08, 0x08, 0x08, 0x08, 0x10, 0x10, 0x10, 
+   0x10, 0x10, 0x10, 0x10, 0x10, 0x18, 0x18, 0x18, 
+   0x18, 0x18, 0x18, 0x18, 0x18, 0x21, 0x21, 0x21, 
+   0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x29, 0x29, 
+   0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x31, 0x31, 
+   0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x39, 0x39, 
+   0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x42, 0x42, 
+   0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x4a, 0x4a, 
+   0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x52, 
+   0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, 0x5a, 
+   0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x63, 
+   0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x6b, 
+   0x6b, 0x6b, 0x6b, 0x6b, 0x6b, 0x6b, 0x6b, 0x6b, 
+   0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 0x73, 
+   0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 
+   0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 
+   0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 
+   0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 
+   0x94, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 
+   0x9c, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 
+   0xa5, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 
+   0xad, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 
+   0xb5, 0xb5, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 
+   0xbd, 0xbd, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 
+   0xc6, 0xc6, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 
+   0xce, 0xce, 0xd6, 0xd6, 0xd6, 0xd6, 0xd6, 0xd6, 
+   0xd6, 0xd6, 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 
+   0xde, 0xde, 0xde, 0xe7, 0xe7, 0xe7, 0xe7, 0xe7, 
+   0xe7, 0xe7, 0xe7, 0xef, 0xef, 0xef, 0xef, 0xef, 
+   0xef, 0xef, 0xef, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 
+   0xf7, 0xf7, 0xf7, 0xff, 0xff, 0xff, 0xff, 0xff, 
+   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
+};
+
+static const unsigned char quantG[256 + 16] =
+{
+   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
+   0x00, 0x00, 0x00, 0x04, 0x04, 0x04, 0x04, 0x08, 
+   0x08, 0x08, 0x08, 0x0c, 0x0c, 0x0c, 0x0c, 0x10, 
+   0x10, 0x10, 0x10, 0x14, 0x14, 0x14, 0x14, 0x18, 
+   0x18, 0x18, 0x18, 0x1c, 0x1c, 0x1c, 0x1c, 0x20, 
+   0x20, 0x20, 0x20, 0x24, 0x24, 0x24, 0x24, 0x28, 
+   0x28, 0x28, 0x28, 0x2c, 0x2c, 0x2c, 0x2c, 0x30, 
+   0x30, 0x30, 0x30, 0x34, 0x34, 0x34, 0x34, 0x38, 
+   0x38, 0x38, 0x38, 0x3c, 0x3c, 0x3c, 0x3c, 0x41, 
+   0x41, 0x41, 0x41, 0x45, 0x45, 0x45, 0x45, 0x49, 
+   0x49, 0x49, 0x49, 0x4d, 0x4d, 0x4d, 0x4d, 0x51, 
+   0x51, 0x51, 0x51, 0x55, 0x55, 0x55, 0x55, 0x55, 
+   0x59, 0x59, 0x59, 0x59, 0x5d, 0x5d, 0x5d, 0x5d, 
+   0x61, 0x61, 0x61, 0x61, 0x65, 0x65, 0x65, 0x65, 
+   0x69, 0x69, 0x69, 0x69, 0x6d, 0x6d, 0x6d, 0x6d, 
+   0x71, 0x71, 0x71, 0x71, 0x75, 0x75, 0x75, 0x75, 
+   0x79, 0x79, 0x79, 0x79, 0x7d, 0x7d, 0x7d, 0x7d, 
+   0x82, 0x82, 0x82, 0x82, 0x86, 0x86, 0x86, 0x86, 
+   0x8a, 0x8a, 0x8a, 0x8a, 0x8e, 0x8e, 0x8e, 0x8e, 
+   0x92, 0x92, 0x92, 0x92, 0x96, 0x96, 0x96, 0x96, 
+   0x9a, 0x9a, 0x9a, 0x9a, 0x9e, 0x9e, 0x9e, 0x9e, 
+   0xa2, 0xa2, 0xa2, 0xa2, 0xa6, 0xa6, 0xa6, 0xa6, 
+   0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xae, 0xae, 0xae, 
+   0xae, 0xb2, 0xb2, 0xb2, 0xb2, 0xb6, 0xb6, 0xb6, 
+   0xb6, 0xba, 0xba, 0xba, 0xba, 0xbe, 0xbe, 0xbe, 
+   0xbe, 0xc3, 0xc3, 0xc3, 0xc3, 0xc7, 0xc7, 0xc7, 
+   0xc7, 0xcb, 0xcb, 0xcb, 0xcb, 0xcf, 0xcf, 0xcf, 
+   0xcf, 0xd3, 0xd3, 0xd3, 0xd3, 0xd7, 0xd7, 0xd7, 
+   0xd7, 0xdb, 0xdb, 0xdb, 0xdb, 0xdf, 0xdf, 0xdf, 
+   0xdf, 0xe3, 0xe3, 0xe3, 0xe3, 0xe7, 0xe7, 0xe7, 
+   0xe7, 0xeb, 0xeb, 0xeb, 0xeb, 0xef, 0xef, 0xef, 
+   0xef, 0xf3, 0xf3, 0xf3, 0xf3, 0xf7, 0xf7, 0xf7, 
+   0xf7, 0xfb, 0xfb, 0xfb, 0xfb, 0xff, 0xff, 0xff, 
+   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 
+};
+
+static const unsigned char omatch5[256][2] =
+{
+   {0x00, 0x00}, {0x00, 0x00}, {0x00, 0x01}, {0x00, 0x01}, 
+   {0x01, 0x00}, {0x01, 0x00}, {0x01, 0x00}, {0x01, 0x01}, 
+   {0x01, 0x01}, {0x01, 0x01}, {0x01, 0x02}, {0x00, 0x04}, 
+   {0x02, 0x01}, {0x02, 0x01}, {0x02, 0x01}, {0x02, 0x02}, 
+   {0x02, 0x02}, {0x02, 0x02}, {0x02, 0x03}, {0x01, 0x05}, 
+   {0x03, 0x02}, {0x03, 0x02}, {0x04, 0x00}, {0x03, 0x03}, 
+   {0x03, 0x03}, {0x03, 0x03}, {0x03, 0x04}, {0x03, 0x04}, 
+   {0x03, 0x04}, {0x03, 0x05}, {0x04, 0x03}, {0x04, 0x03}, 
+   {0x05, 0x02}, {0x04, 0x04}, {0x04, 0x04}, {0x04, 0x05}, 
+   {0x04, 0x05}, {0x05, 0x04}, {0x05, 0x04}, {0x05, 0x04}, 
+   {0x06, 0x03}, {0x05, 0x05}, {0x05, 0x05}, {0x05, 0x06}, 
+   {0x04, 0x08}, {0x06, 0x05}, {0x06, 0x05}, {0x06, 0x05}, 
+   {0x06, 0x06}, {0x06, 0x06}, {0x06, 0x06}, {0x06, 0x07}, 
+   {0x05, 0x09}, {0x07, 0x06}, {0x07, 0x06}, {0x08, 0x04}, 
+   {0x07, 0x07}, {0x07, 0x07}, {0x07, 0x07}, {0x07, 0x08}, 
+   {0x07, 0x08}, {0x07, 0x08}, {0x07, 0x09}, {0x08, 0x07}, 
+   {0x08, 0x07}, {0x09, 0x06}, {0x08, 0x08}, {0x08, 0x08}, 
+   {0x08, 0x09}, {0x08, 0x09}, {0x09, 0x08}, {0x09, 0x08}, 
+   {0x09, 0x08}, {0x0a, 0x07}, {0x09, 0x09}, {0x09, 0x09}, 
+   {0x09, 0x0a}, {0x08, 0x0c}, {0x0a, 0x09}, {0x0a, 0x09}, 
+   {0x0a, 0x09}, {0x0a, 0x0a}, {0x0a, 0x0a}, {0x0a, 0x0a}, 
+   {0x0a, 0x0b}, {0x09, 0x0d}, {0x0b, 0x0a}, {0x0b, 0x0a}, 
+   {0x0c, 0x08}, {0x0b, 0x0b}, {0x0b, 0x0b}, {0x0b, 0x0b}, 
+   {0x0b, 0x0c}, {0x0b, 0x0c}, {0x0b, 0x0c}, {0x0b, 0x0d}, 
+   {0x0c, 0x0b}, {0x0c, 0x0b}, {0x0d, 0x0a}, {0x0c, 0x0c}, 
+   {0x0c, 0x0c}, {0x0c, 0x0d}, {0x0c, 0x0d}, {0x0d, 0x0c}, 
+   {0x0d, 0x0c}, {0x0d, 0x0c}, {0x0e, 0x0b}, {0x0d, 0x0d}, 
+   {0x0d, 0x0d}, {0x0d, 0x0e}, {0x0c, 0x10}, {0x0e, 0x0d}, 
+   {0x0e, 0x0d}, {0x0e, 0x0d}, {0x0e, 0x0e}, {0x0e, 0x0e}, 
+   {0x0e, 0x0e}, {0x0e, 0x0f}, {0x0d, 0x11}, {0x0f, 0x0e}, 
+   {0x0f, 0x0e}, {0x10, 0x0c}, {0x0f, 0x0f}, {0x0f, 0x0f}, 
+   {0x0f, 0x0f}, {0x0f, 0x10}, {0x0f, 0x10}, {0x0f, 0x10}, 
+   {0x0f, 0x11}, {0x10, 0x0f}, {0x10, 0x0f}, {0x11, 0x0e}, 
+   {0x10, 0x10}, {0x10, 0x10}, {0x10, 0x11}, {0x10, 0x11}, 
+   {0x11, 0x10}, {0x11, 0x10}, {0x11, 0x10}, {0x12, 0x0f}, 
+   {0x11, 0x11}, {0x11, 0x11}, {0x11, 0x12}, {0x10, 0x14}, 
+   {0x12, 0x11}, {0x12, 0x11}, {0x12, 0x11}, {0x12, 0x12}, 
+   {0x12, 0x12}, {0x12, 0x12}, {0x12, 0x13}, {0x11, 0x15}, 
+   {0x13, 0x12}, {0x13, 0x12}, {0x14, 0x10}, {0x13, 0x13}, 
+   {0x13, 0x13}, {0x13, 0x13}, {0x13, 0x14}, {0x13, 0x14}, 
+   {0x13, 0x14}, {0x13, 0x15}, {0x14, 0x13}, {0x14, 0x13}, 
+   {0x15, 0x12}, {0x14, 0x14}, {0x14, 0x14}, {0x14, 0x15}, 
+   {0x14, 0x15}, {0x15, 0x14}, {0x15, 0x14}, {0x15, 0x14}, 
+   {0x16, 0x13}, {0x15, 0x15}, {0x15, 0x15}, {0x15, 0x16}, 
+   {0x14, 0x18}, {0x16, 0x15}, {0x16, 0x15}, {0x16, 0x15}, 
+   {0x16, 0x16}, {0x16, 0x16}, {0x16, 0x16}, {0x16, 0x17}, 
+   {0x15, 0x19}, {0x17, 0x16}, {0x17, 0x16}, {0x18, 0x14}, 
+   {0x17, 0x17}, {0x17, 0x17}, {0x17, 0x17}, {0x17, 0x18}, 
+   {0x17, 0x18}, {0x17, 0x18}, {0x17, 0x19}, {0x18, 0x17}, 
+   {0x18, 0x17}, {0x19, 0x16}, {0x18, 0x18}, {0x18, 0x18}, 
+   {0x18, 0x19}, {0x18, 0x19}, {0x19, 0x18}, {0x19, 0x18}, 
+   {0x19, 0x18}, {0x1a, 0x17}, {0x19, 0x19}, {0x19, 0x19}, 
+   {0x19, 0x1a}, {0x18, 0x1c}, {0x1a, 0x19}, {0x1a, 0x19}, 
+   {0x1a, 0x19}, {0x1a, 0x1a}, {0x1a, 0x1a}, {0x1a, 0x1a}, 
+   {0x1a, 0x1b}, {0x19, 0x1d}, {0x1b, 0x1a}, {0x1b, 0x1a}, 
+   {0x1c, 0x18}, {0x1b, 0x1b}, {0x1b, 0x1b}, {0x1b, 0x1b}, 
+   {0x1b, 0x1c}, {0x1b, 0x1c}, {0x1b, 0x1c}, {0x1b, 0x1d}, 
+   {0x1c, 0x1b}, {0x1c, 0x1b}, {0x1d, 0x1a}, {0x1c, 0x1c}, 
+   {0x1c, 0x1c}, {0x1c, 0x1d}, {0x1c, 0x1d}, {0x1d, 0x1c}, 
+   {0x1d, 0x1c}, {0x1d, 0x1c}, {0x1e, 0x1b}, {0x1d, 0x1d}, 
+   {0x1d, 0x1d}, {0x1d, 0x1e}, {0x1d, 0x1e}, {0x1e, 0x1d}, 
+   {0x1e, 0x1d}, {0x1e, 0x1d}, {0x1e, 0x1e}, {0x1e, 0x1e}, 
+   {0x1e, 0x1e}, {0x1e, 0x1f}, {0x1e, 0x1f}, {0x1f, 0x1e}, 
+   {0x1f, 0x1e}, {0x1f, 0x1e}, {0x1f, 0x1f}, {0x1f, 0x1f}, 
+};
+
+static const unsigned char omatch6[256][2] =
+{
+   {0x00, 0x00}, {0x00, 0x01}, {0x01, 0x00}, {0x01, 0x01}, 
+   {0x01, 0x01}, {0x01, 0x02}, {0x02, 0x01}, {0x02, 0x02}, 
+   {0x02, 0x02}, {0x02, 0x03}, {0x03, 0x02}, {0x03, 0x03}, 
+   {0x03, 0x03}, {0x03, 0x04}, {0x04, 0x03}, {0x04, 0x04}, 
+   {0x04, 0x04}, {0x04, 0x05}, {0x05, 0x04}, {0x05, 0x05}, 
+   {0x05, 0x05}, {0x05, 0x06}, {0x06, 0x05}, {0x00, 0x11}, 
+   {0x06, 0x06}, {0x06, 0x07}, {0x07, 0x06}, {0x02, 0x10}, 
+   {0x07, 0x07}, {0x07, 0x08}, {0x08, 0x07}, {0x03, 0x11}, 
+   {0x08, 0x08}, {0x08, 0x09}, {0x09, 0x08}, {0x05, 0x10}, 
+   {0x09, 0x09}, {0x09, 0x0a}, {0x0a, 0x09}, {0x06, 0x11}, 
+   {0x0a, 0x0a}, {0x0a, 0x0b}, {0x0b, 0x0a}, {0x08, 0x10}, 
+   {0x0b, 0x0b}, {0x0b, 0x0c}, {0x0c, 0x0b}, {0x09, 0x11}, 
+   {0x0c, 0x0c}, {0x0c, 0x0d}, {0x0d, 0x0c}, {0x0b, 0x10}, 
+   {0x0d, 0x0d}, {0x0d, 0x0e}, {0x0e, 0x0d}, {0x0c, 0x11}, 
+   {0x0e, 0x0e}, {0x0e, 0x0f}, {0x0f, 0x0e}, {0x0e, 0x10}, 
+   {0x0f, 0x0f}, {0x0f, 0x10}, {0x10, 0x0e}, {0x10, 0x0f}, 
+   {0x11, 0x0e}, {0x10, 0x10}, {0x10, 0x11}, {0x11, 0x10}, 
+   {0x12, 0x0f}, {0x11, 0x11}, {0x11, 0x12}, {0x12, 0x11}, 
+   {0x14, 0x0e}, {0x12, 0x12}, {0x12, 0x13}, {0x13, 0x12}, 
+   {0x15, 0x0f}, {0x13, 0x13}, {0x13, 0x14}, {0x14, 0x13}, 
+   {0x17, 0x0e}, {0x14, 0x14}, {0x14, 0x15}, {0x15, 0x14}, 
+   {0x18, 0x0f}, {0x15, 0x15}, {0x15, 0x16}, {0x16, 0x15}, 
+   {0x1a, 0x0e}, {0x16, 0x16}, {0x16, 0x17}, {0x17, 0x16}, 
+   {0x1b, 0x0f}, {0x17, 0x17}, {0x17, 0x18}, {0x18, 0x17}, 
+   {0x13, 0x21}, {0x18, 0x18}, {0x18, 0x19}, {0x19, 0x18}, 
+   {0x15, 0x20}, {0x19, 0x19}, {0x19, 0x1a}, {0x1a, 0x19}, 
+   {0x16, 0x21}, {0x1a, 0x1a}, {0x1a, 0x1b}, {0x1b, 0x1a}, 
+   {0x18, 0x20}, {0x1b, 0x1b}, {0x1b, 0x1c}, {0x1c, 0x1b}, 
+   {0x19, 0x21}, {0x1c, 0x1c}, {0x1c, 0x1d}, {0x1d, 0x1c}, 
+   {0x1b, 0x20}, {0x1d, 0x1d}, {0x1d, 0x1e}, {0x1e, 0x1d}, 
+   {0x1c, 0x21}, {0x1e, 0x1e}, {0x1e, 0x1f}, {0x1f, 0x1e}, 
+   {0x1e, 0x20}, {0x1f, 0x1f}, {0x1f, 0x20}, {0x20, 0x1e}, 
+   {0x20, 0x1f}, {0x21, 0x1e}, {0x20, 0x20}, {0x20, 0x21}, 
+   {0x21, 0x20}, {0x22, 0x1f}, {0x21, 0x21}, {0x21, 0x22}, 
+   {0x22, 0x21}, {0x24, 0x1e}, {0x22, 0x22}, {0x22, 0x23}, 
+   {0x23, 0x22}, {0x25, 0x1f}, {0x23, 0x23}, {0x23, 0x24}, 
+   {0x24, 0x23}, {0x27, 0x1e}, {0x24, 0x24}, {0x24, 0x25}, 
+   {0x25, 0x24}, {0x28, 0x1f}, {0x25, 0x25}, {0x25, 0x26}, 
+   {0x26, 0x25}, {0x2a, 0x1e}, {0x26, 0x26}, {0x26, 0x27}, 
+   {0x27, 0x26}, {0x2b, 0x1f}, {0x27, 0x27}, {0x27, 0x28}, 
+   {0x28, 0x27}, {0x23, 0x31}, {0x28, 0x28}, {0x28, 0x29}, 
+   {0x29, 0x28}, {0x25, 0x30}, {0x29, 0x29}, {0x29, 0x2a}, 
+   {0x2a, 0x29}, {0x26, 0x31}, {0x2a, 0x2a}, {0x2a, 0x2b}, 
+   {0x2b, 0x2a}, {0x28, 0x30}, {0x2b, 0x2b}, {0x2b, 0x2c}, 
+   {0x2c, 0x2b}, {0x29, 0x31}, {0x2c, 0x2c}, {0x2c, 0x2d}, 
+   {0x2d, 0x2c}, {0x2b, 0x30}, {0x2d, 0x2d}, {0x2d, 0x2e}, 
+   {0x2e, 0x2d}, {0x2c, 0x31}, {0x2e, 0x2e}, {0x2e, 0x2f}, 
+   {0x2f, 0x2e}, {0x2e, 0x30}, {0x2f, 0x2f}, {0x2f, 0x30}, 
+   {0x30, 0x2e}, {0x30, 0x2f}, {0x31, 0x2e}, {0x30, 0x30}, 
+   {0x30, 0x31}, {0x31, 0x30}, {0x32, 0x2f}, {0x31, 0x31}, 
+   {0x31, 0x32}, {0x32, 0x31}, {0x34, 0x2e}, {0x32, 0x32}, 
+   {0x32, 0x33}, {0x33, 0x32}, {0x35, 0x2f}, {0x33, 0x33}, 
+   {0x33, 0x34}, {0x34, 0x33}, {0x37, 0x2e}, {0x34, 0x34}, 
+   {0x34, 0x35}, {0x35, 0x34}, {0x38, 0x2f}, {0x35, 0x35}, 
+   {0x35, 0x36}, {0x36, 0x35}, {0x3a, 0x2e}, {0x36, 0x36}, 
+   {0x36, 0x37}, {0x37, 0x36}, {0x3b, 0x2f}, {0x37, 0x37}, 
+   {0x37, 0x38}, {0x38, 0x37}, {0x3d, 0x2e}, {0x38, 0x38}, 
+   {0x38, 0x39}, {0x39, 0x38}, {0x3e, 0x2f}, {0x39, 0x39}, 
+   {0x39, 0x3a}, {0x3a, 0x39}, {0x3a, 0x3a}, {0x3a, 0x3a}, 
+   {0x3a, 0x3b}, {0x3b, 0x3a}, {0x3b, 0x3b}, {0x3b, 0x3b}, 
+   {0x3b, 0x3c}, {0x3c, 0x3b}, {0x3c, 0x3c}, {0x3c, 0x3c}, 
+   {0x3c, 0x3d}, {0x3d, 0x3c}, {0x3d, 0x3d}, {0x3d, 0x3d}, 
+   {0x3d, 0x3e}, {0x3e, 0x3d}, {0x3e, 0x3e}, {0x3e, 0x3e}, 
+   {0x3e, 0x3f}, {0x3f, 0x3e}, {0x3f, 0x3f}, {0x3f, 0x3f}, 
+};
+
+#endif
diff --git a/plug-ins/file-dds/endian.h b/plug-ins/file-dds/endian.h
new file mode 100644
index 0000000000..53d32f2dc5
--- /dev/null
+++ b/plug-ins/file-dds/endian.h
@@ -0,0 +1,71 @@
+/*
+       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.
+*/
+
+#ifndef ENDIAN_H
+#define ENDIAN_H
+
+#define GETL64(buf) \
+   (((unsigned long long)(buf)[0]      ) | \
+    ((unsigned long long)(buf)[1] <<  8) | \
+    ((unsigned long long)(buf)[2] << 16) | \
+    ((unsigned long long)(buf)[3] << 24) | \
+    ((unsigned long long)(buf)[4] << 32) | \
+    ((unsigned long long)(buf)[5] << 40) | \
+    ((unsigned long long)(buf)[6] << 48) | \
+    ((unsigned long long)(buf)[7] << 56))
+
+#define GETL32(buf) \
+   (((unsigned int)(buf)[0]      ) | \
+    ((unsigned int)(buf)[1] <<  8) | \
+    ((unsigned int)(buf)[2] << 16) | \
+    ((unsigned int)(buf)[3] << 24))
+
+#define GETL24(buf) \
+   (((unsigned int)(buf)[0]      ) | \
+    ((unsigned int)(buf)[1] <<  8) | \
+    ((unsigned int)(buf)[2] << 16))
+   
+#define GETL16(buf) \
+   (((unsigned short)(buf)[0]     ) | \
+    ((unsigned short)(buf)[1] << 8))
+
+#define PUTL16(buf, s) \
+   (buf)[0] = ((s)     ) & 0xff; \
+   (buf)[1] = ((s) >> 8) & 0xff;
+
+#define PUTL32(buf, l) \
+   (buf)[0] = ((l)      ) & 0xff; \
+       (buf)[1] = ((l) >>  8) & 0xff; \
+       (buf)[2] = ((l) >> 16) & 0xff; \
+       (buf)[3] = ((l) >> 24) & 0xff;
+
+#define PUTL64(buf, ll) \
+   (buf)[0] = ((ll)      ) & 0xff; \
+       (buf)[1] = ((ll) >>  8) & 0xff; \
+       (buf)[2] = ((ll) >> 16) & 0xff; \
+       (buf)[3] = ((ll) >> 24) & 0xff; \
+   (buf)[4] = ((ll) >> 32) & 0xff; \
+   (buf)[5] = ((ll) >> 40) & 0xff; \
+   (buf)[6] = ((ll) >> 48) & 0xff; \
+   (buf)[7] = ((ll) >> 56) & 0xff;
+
+#endif
diff --git a/plug-ins/file-dds/imath.h b/plug-ins/file-dds/imath.h
new file mode 100644
index 0000000000..6f4dbc759f
--- /dev/null
+++ b/plug-ins/file-dds/imath.h
@@ -0,0 +1,67 @@
+/*
+       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.
+*/
+
+#ifndef IMATH_H
+#define IMATH_H
+
+#ifndef MIN
+# ifdef __GNUC__
+#  define MIN(a, b)  ({typeof(a) _a=(a); typeof(b) _b=(b); _a < _b ? _a : _b;})
+# else
+#  define MIN(a, b)  ((a) < (b) ? (a) : (b))
+# endif
+#endif
+
+#ifndef MAX
+# ifdef __GNUC__
+#  define MAX(a, b)  ({typeof(a) _a=(a); typeof(b) _b=(b); _a > _b ? _a : _b;})
+# else
+#  define MAX(a, b)  ((a) > (b) ? (a) : (b))
+# endif
+#endif
+
+#define IS_POW2(x)     (!((x) & ((x) - 1)))
+#define IS_MUL4(x)     (((x) & 3) == 0)
+
+/* round integer x up to next multiple of 4 */
+#define RND_MUL4(x)    ((x) + (4 - ((x) & 3)))
+
+static inline int mul8bit(int a, int b)
+{
+   int t = a * b + 128;
+   return((t + (t >> 8)) >> 8);
+}
+
+static inline int blerp(int a, int b, int x)
+{
+   return(a + mul8bit(b - a, x));
+}
+
+static inline int icerp(int a, int b, int c, int d, int x)
+{
+   int p = (d - c) - (a - b);
+   int q = (a - b) - p;
+   int r = c - a;
+   return((x * (x * (x * p + (q << 7)) + (r << 14)) + (b << 21)) >> 21);
+}
+
+#endif
diff --git a/plug-ins/file-dds/mipmap.c b/plug-ins/file-dds/mipmap.c
new file mode 100644
index 0000000000..71b58d84e5
--- /dev/null
+++ b/plug-ins/file-dds/mipmap.c
@@ -0,0 +1,1013 @@
+/*
+       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.
+*/
+
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <float.h>
+
+#include <gtk/gtk.h>
+
+#ifdef _OPENMP
+#include <omp.h>
+#endif
+
+#include "dds.h"
+#include "mipmap.h"
+#include "imath.h"
+#include "color.h"
+
+typedef float (*filterfunc_t)(float);
+typedef int   (*wrapfunc_t)(int, int);
+typedef void  (*mipmapfunc_t)(unsigned char *, int, int, unsigned char *, int, int, int, filterfunc_t, 
float, wrapfunc_t, int, float);
+typedef void  (*volmipmapfunc_t)(unsigned char *, int, int, int, unsigned char *, int, int, int, int, 
filterfunc_t, float, wrapfunc_t, int, float);
+
+/******************************************************************************
+ * size functions                                                             *
+ ******************************************************************************/
+
+int get_num_mipmaps(int width, int height)
+{
+   int w = width << 1;
+   int h = height << 1;
+   int n = 0;
+
+   while(w != 1 || h != 1)
+   {
+      if(w > 1) w >>= 1;
+      if(h > 1) h >>= 1;
+      ++n;
+   }
+
+   return(n);
+}
+
+unsigned int get_mipmapped_size(int width, int height, int bpp,
+                                int level, int num, int format)
+{
+   int w, h, n = 0;
+   unsigned int size = 0;
+
+   w = width >> level;
+   h = height >> level;
+   w = MAX(1, w);
+   h = MAX(1, h);
+   w <<= 1;
+   h <<= 1;
+
+   while(n < num && (w != 1 || h != 1))
+   {
+      if(w > 1) w >>= 1;
+      if(h > 1) h >>= 1;
+      if(format == DDS_COMPRESS_NONE)
+         size += (w * h);
+      else
+         size += ((w + 3) >> 2) * ((h + 3) >> 2);
+      ++n;
+   }
+
+   if(format == DDS_COMPRESS_NONE)
+      size *= bpp;
+   else
+   {
+      if(format == DDS_COMPRESS_BC1 || format == DDS_COMPRESS_BC4)
+         size *= 8;
+      else
+         size *= 16;
+   }
+
+   return(size);
+}
+
+unsigned int get_volume_mipmapped_size(int width, int height,
+                                       int depth, int bpp, int level,
+                                       int num, int format)
+{
+   int w, h, d, n = 0;
+   unsigned int size = 0;
+
+   w = width >> level;
+   h = height >> level;
+   d = depth >> level;
+   w = MAX(1, w);
+   h = MAX(1, h);
+   d = MAX(1, d);
+   w <<= 1;
+   h <<= 1;
+   d <<= 1;
+
+   while(n < num && (w != 1 || h != 1))
+   {
+      if(w > 1) w >>= 1;
+      if(h > 1) h >>= 1;
+      if(d > 1) d >>= 1;
+      if(format == DDS_COMPRESS_NONE)
+         size += (w * h * d);
+      else
+         size += (((w + 3) >> 2) * ((h + 3) >> 2) * d);
+      ++n;
+   }
+
+   if(format == DDS_COMPRESS_NONE)
+      size *= bpp;
+   else
+   {
+      if(format == DDS_COMPRESS_BC1 || format == DDS_COMPRESS_BC4)
+         size *= 8;
+      else
+         size *= 16;
+   }
+
+   return(size);
+}
+
+int get_next_mipmap_dimensions(int *next_w, int *next_h,
+                               int  curr_w, int  curr_h)
+{
+   if(curr_w == 1 || curr_h == 1)
+      return(0);
+
+   if(next_w) *next_w = curr_w >> 1;
+   if(next_h) *next_h = curr_h >> 1;
+
+   return(1);
+}
+
+/******************************************************************************
+ * wrap modes                                                                 *
+ ******************************************************************************/
+
+static int wrap_mirror(int x, int max)
+{
+   if(max == 1) x = 0;
+   x = abs(x);
+   while(x >= max)
+      x = abs(max + max - x - 2);
+   return(x);
+}
+
+static int wrap_repeat(int x, int max)
+{
+   if(x >= 0) return(x % max);
+   return((x + 1) % max + max - 1);
+}
+
+static int wrap_clamp(int x, int max)
+{
+   return(MAX(0, MIN(max - 1, x)));
+}
+
+/******************************************************************************
+ * gamma-correction                                                           *
+ ******************************************************************************/
+
+static int linear_to_gamma(int gc, int v, float gamma)
+{
+   if(gc == 1)
+   {
+      v = (int)(powf((float)v / 255.0f, gamma) * 255);
+      if(v > 255) v = 255;
+   }
+   else if(gc == 2)
+      v = linear_to_sRGB(v);
+
+   return(v);
+}
+
+static int gamma_to_linear(int gc, int v, float gamma)
+{
+   if(gc == 1)
+   {
+      v = (int)(powf((float)v / 255.0f, 1.0f / gamma) * 255);
+      if(v > 255) v = 255;
+   }
+   else if(gc == 2)
+      v = sRGB_to_linear(v);
+
+   return(v);
+}
+
+/******************************************************************************
+ * filters                                                                    *
+ ******************************************************************************/
+
+static float box_filter(float t)
+{
+   if((t >= -0.5f) && (t < 0.5f))
+      return(1.0f);
+
+   return(0.0f);
+}
+
+static float triangle_filter(float t)
+{
+   if(t < 0.0f) t = -t;
+   if(t < 1.0f) return(1.0f - t);
+   return(0.0f);
+}
+
+static float quadratic_filter(float t)
+{
+   if(t < 0.0f) t = -t;
+   if(t < 0.5f) return(0.75f - t * t);
+   if(t < 1.5f)
+   {
+      t -= 1.5f;
+      return(0.5f * t * t);
+   }
+   return(0.0f);
+}
+
+static float bspline_filter(float t)
+{
+   float tt;
+
+   if(t < 0.0f) t = -t;
+
+   if(t < 1.0f)
+   {
+      tt = t * t;
+      return(((0.5f * tt * t) - tt + (2.0f / 3.0f)));
+   }
+   else if(t < 2.0f)
+   {
+      t = 2.0f - t;
+      return((1.0f / 6.0f) * (t * t * t));
+   }
+
+   return(0.0f);
+}
+
+static float mitchell(float t, const float B, const float C)
+{
+   float tt;
+
+   tt = t * t;
+   if(t < 0.0f) t = -t;
+
+   if(t < 1.0f)
+   {
+      t = (((12.0f - 9.0f * B - 6.0f * C) * (t * tt)) +
+         ((-18.0f + 12.0f * B + 6.0f * C) * tt) +
+         (6.0f - 2.0f * B));
+      return(t / 6.0f);
+   }
+   else if(t < 2.0f)
+   {
+      t = (((-1.0f * B - 6.0f * C) * (t * tt)) +
+         ((6.0f * B + 30.0f * C) * tt) +
+         ((-12.0f * B - 48.0f * C) * t) +
+         (8.0f * B + 24.0f * C));
+      return(t / 6.0f);
+   }
+
+   return(0.0f);
+}
+
+static float mitchell_filter(float t)
+{
+   return(mitchell(t, 1.0f / 3.0f, 1.0f / 3.0f));
+}
+
+static float sinc(float x)
+{
+   x = (x * M_PI);
+   if(fabsf(x) < 1e-04f)
+      return(1.0f + x * x * (-1.0f / 6.0f + x * x * 1.0f / 120.0f));
+
+   return(sinf(x) / x);
+}
+
+static float lanczos_filter(float t)
+{
+   if(t < 0.0f) t = -t;
+   if(t < 3.0f) return(sinc(t) * sinc(t / 3.0f));
+   return(0.0f);
+}
+
+static float bessel0(float x)
+{
+   const float EPSILON = 1e-6f;
+   float xh, sum, pow, ds;
+   int k;
+
+   xh = 0.5f * x;
+   sum = 1.0f;
+   pow = 1.0f;
+   k = 0;
+   ds = 1.0f;
+   while(ds > sum * EPSILON)
+   {
+      ++k;
+      pow = pow * (xh / k);
+      ds = pow * pow;
+      sum += ds;
+   }
+
+   return(sum);
+}
+
+static float kaiser_filter(float t)
+{
+   if(t < 0.0f) t = -t;
+
+   if(t < 3.0f)
+   {
+      const float alpha = 4.0f;
+      const float rb04 = 0.0884805322f; // 1.0f / bessel0(4.0f);
+      const float ratio = t / 3.0f;
+      if((1.0f - ratio * ratio) >= 0)
+         return(sinc(t) * bessel0(alpha * sqrtf(1.0f - ratio * ratio)) * rb04);
+   }
+   return(0.0f);
+}
+
+/******************************************************************************
+ * 2D image scaling                                                           *
+ ******************************************************************************/
+
+static void scale_image_nearest(unsigned char *dst, int dw, int dh,
+                                unsigned char *src, int sw, int sh,
+                                int bpp, filterfunc_t filter, float support,
+                                wrapfunc_t wrap,
+                                int gc, float gamma)
+{
+   int n, x, y;
+   int ix, iy;
+   int srowbytes = sw * bpp;
+   int drowbytes = dw * bpp;
+
+   for(y = 0; y < dh; ++y)
+   {
+      iy = (y * sh + sh / 2) / dh;
+      for(x = 0; x < dw; ++x)
+      {
+         ix = (x * sw + sw / 2) / dw;
+         for(n = 0; n < bpp; ++n)
+         {
+            dst[y * drowbytes + (x * bpp) + n] =
+               src[iy * srowbytes + (ix * bpp) + n];
+         }
+      }
+   }
+}
+
+static void scale_image(unsigned char *dst, int dw, int dh,
+                        unsigned char *src, int sw, int sh,
+                        int bpp, filterfunc_t filter, float support,
+                        wrapfunc_t wrap,
+                        int gc, float gamma)
+{
+   const float blur = 1.0f;
+   const float xfactor = (float)dw / (float)sw;
+   const float yfactor = (float)dh / (float)sh;
+
+   int x, y, start, stop, nmax, n, i;
+   int sstride = sw * bpp;
+   float center, contrib, density, s, r, t;
+
+   unsigned char *d, *row, *col;
+
+   float xscale = MIN(xfactor, 1.0f) / blur;
+   float yscale = MIN(yfactor, 1.0f) / blur;
+   float xsupport = support / xscale;
+   float ysupport = support / yscale;
+
+   if(xsupport <= 0.5f)
+   {
+      xsupport = 0.5f + 1e-10f;
+      xscale = 1.0f;
+   }
+   if(ysupport <= 0.5f)
+   {
+      ysupport = 0.5f + 1e-10f;
+      yscale = 1.0f;
+   }
+
+   unsigned char *tmp;
+
+#ifdef _OPENMP
+   tmp = g_malloc(sw * bpp * omp_get_max_threads());
+#else
+   tmp = g_malloc(sw * bpp);
+#endif
+
+#ifdef _OPENMP
+   #pragma omp parallel for schedule(dynamic) \
+      private(x, y, d, row, col, center, start, stop, nmax, s, i, n, density, r, t, contrib)
+#endif
+   for(y = 0; y < dh; ++y)
+   {
+      /* resample in Y direction to temp buffer */
+      d = tmp;
+#ifdef _OPENMP
+      d += (sw * bpp * omp_get_thread_num());
+#endif
+
+      center = ((float)y + 0.5f) / yfactor;
+      start = (int)(center - ysupport + 0.5f);
+      stop  = (int)(center + ysupport + 0.5f);
+      nmax = stop - start;
+      s = (float)start - center + 0.5f;
+
+      for(x = 0; x < sw; ++x)
+      {
+         col = src + (x * bpp);
+
+         for(i = 0; i < bpp; ++i)
+         {
+            density = 0.0f;
+            r = 0.0f;
+
+            for(n = 0; n < nmax; ++n)
+            {
+               contrib = filter((s + n) * yscale);
+               density += contrib;
+               if(i == 3)
+                  t = col[(wrap(start + n, sh) * sstride) + i];
+               else
+                  t = linear_to_gamma(gc, col[(wrap(start + n, sh) * sstride) + i], gamma);
+               r += t * contrib;
+            }
+
+            if(density != 0.0f && density != 1.0f)
+               r /= density;
+
+            r = MIN(255, MAX(0, r));
+
+            if(i != 3)
+               r = gamma_to_linear(gc, r, gamma);
+
+            d[(x * bpp) + i] = (unsigned char)r;
+         }
+      }
+
+      /* resample in X direction using temp buffer */
+      row = d;
+      d = dst;
+
+      for(x = 0; x < dw; ++x)
+      {
+         center = ((float)x + 0.5f) / xfactor;
+         start = (int)(center - xsupport + 0.5f);
+         stop  = (int)(center + xsupport + 0.5f);
+         nmax = stop - start;
+         s = (float)start - center + 0.5f;
+
+         for(i = 0; i < bpp; ++i)
+         {
+            density = 0.0f;
+            r = 0.0f;
+
+            for(n = 0; n < nmax; ++n)
+            {
+               contrib = filter((s + n) * xscale);
+               density += contrib;
+               if(i == 3)
+                  t = row[(wrap(start + n, sw) * bpp) + i];
+               else
+                  t = linear_to_gamma(gc, row[(wrap(start + n, sw) * bpp) + i], gamma);
+               r += t * contrib;
+            }
+
+            if(density != 0.0f && density != 1.0f)
+               r /= density;
+
+            r = MIN(255, MAX(0, r));
+
+            if(i != 3)
+               r = gamma_to_linear(gc, r, gamma);
+
+            d[(y * (dw * bpp)) + (x * bpp) + i] = (unsigned char)r;
+         }
+      }
+   }
+
+   g_free(tmp);
+}
+
+/******************************************************************************
+ * 3D image scaling                                                           *
+ ******************************************************************************/
+
+static void scale_volume_image_nearest(unsigned char *dst, int dw, int dh, int dd,
+                                       unsigned char *src, int sw, int sh, int sd,
+                                       int bpp, filterfunc_t filter, float support,
+                                       wrapfunc_t wrap,
+                                       int gc, float gamma)
+{
+   int n, x, y, z;
+   int ix, iy, iz;
+
+   for(z = 0; z < dd; ++z)
+   {
+      iz = (z * sd + sd / 2) / dd;
+      for(y = 0; y < dh; ++y)
+      {
+         iy = (y * sh + sh / 2) / dh;
+         for(x = 0; x < dw; ++x)
+         {
+            ix = (x * sw + sw / 2) / dw;
+            for(n = 0; n < bpp; ++n)
+            {
+               dst[(z * (dw * dh)) + (y * dw) + (x * bpp) + n] =
+                  src[(iz * (sw * sh)) + (iy * sw) + (ix * bpp) + n];
+            }
+         }
+      }
+   }
+}
+
+static void scale_volume_image(unsigned char *dst, int dw, int dh, int dd,
+                               unsigned char *src, int sw, int sh, int sd,
+                               int bpp, filterfunc_t filter, float support,
+                               wrapfunc_t wrap,
+                               int gc, float gamma)
+{
+   /* down to a 2D image, use the faster 2D image resampler */
+   if(dd == 1 && sd == 1)
+   {
+      scale_image(dst, dw, dh, src, sw, sh, bpp, filter, support, wrap, gc, gamma);
+      return;
+   }
+
+   const float blur = 1.0f;
+   const float xfactor = (float)dw / (float)sw;
+   const float yfactor = (float)dh / (float)sh;
+   const float zfactor = (float)dd / (float)sd;
+
+   int x, y, z, start, stop, nmax, n, i;
+   int sstride = sw * bpp;
+   int zstride = sh * sw * bpp;
+   float center, contrib, density, s, r, t;
+
+   unsigned char *d, *row, *col, *slice;
+
+   float xscale = MIN(xfactor, 1.0f) / blur;
+   float yscale = MIN(yfactor, 1.0f) / blur;
+   float zscale = MIN(zfactor, 1.0f) / blur;
+   float xsupport = support / xscale;
+   float ysupport = support / yscale;
+   float zsupport = support / zscale;
+
+   if(xsupport <= 0.5f)
+   {
+      xsupport = 0.5f + 1e-10f;
+      xscale = 1.0f;
+   }
+   if(ysupport <= 0.5f)
+   {
+      ysupport = 0.5f + 1e-10f;
+      yscale = 1.0f;
+   }
+   if(zsupport <= 0.5f)
+   {
+      zsupport = 0.5f + 1e-10f;
+      zscale = 1.0f;
+   }
+
+   unsigned char *tmp1, *tmp2;
+
+   tmp1 = g_malloc(sh * sw * bpp);
+   tmp2 = g_malloc(dh * sw * bpp);
+
+   for(z = 0; z < dd; ++z)
+   {
+      /* resample in Z direction */
+      d = tmp1;
+
+      center = ((float)z + 0.5f) / zfactor;
+      start = (int)(center - zsupport + 0.5f);
+      stop =  (int)(center + zsupport + 0.5f);
+      nmax = stop - start;
+      s = (float)start - center + 0.5f;
+
+      #ifdef _OPENMP
+      #pragma omp parallel for schedule(dynamic) \
+      private(x, y, slice, i, n, density, r, t, contrib)
+      #endif
+      for(y = 0; y < sh; ++y)
+      {
+         for(x = 0; x < sw; ++x)
+         {
+            slice = src + (y * (sw * bpp)) + (x * bpp);
+
+            for(i = 0; i < bpp; ++i)
+            {
+               density = 0.0f;
+               r = 0.0f;
+
+               for(n = 0; n < nmax; ++n)
+               {
+                  contrib = filter((s + n) * zscale);
+                  density += contrib;
+                  if(i == 3)
+                     t = slice[(wrap(start + n, sd) * zstride) + i];
+                  else
+                     t = linear_to_gamma(gc, slice[(wrap(start + n, sd) * zstride) + i], gamma);
+                  r += t * contrib;
+               }
+
+               if(density != 0.0f && density != 1.0f)
+                  r /= density;
+
+               r = MIN(255, MAX(0, r));
+
+               if(i != 3)
+                  r = gamma_to_linear(gc, r, gamma);
+
+               d[((y * sw) + x) * bpp + i] = (unsigned char)r;
+            }
+         }
+      }
+
+      /* resample in Y direction */
+      d = tmp2;
+      #ifdef _OPENMP
+      #pragma omp parallel for schedule(dynamic) \
+      private(x, y, col, center, start, stop, nmax, s, i, n, density, r, t, contrib)
+      #endif
+      for(y = 0; y < dh; ++y)
+      {
+         center = ((float)y + 0.5f) / yfactor;
+         start = (int)(center - ysupport + 0.5f);
+         stop =  (int)(center + ysupport + 0.5f);
+         nmax = stop - start;
+         s = (float)start - center + 0.5f;
+
+         for(x = 0; x < sw; ++x)
+         {
+            col = tmp1 + (x * bpp);
+
+            for(i = 0; i < bpp; ++i)
+            {
+               density = 0.0f;
+               r = 0.0f;
+
+               for(n = 0; n < nmax; ++n)
+               {
+                  contrib = filter((s + n) * yscale);
+                  density += contrib;
+                  if(i == 3)
+                     t = col[(wrap(start + n, sh) * sstride) + i];
+                  else
+                     t = linear_to_gamma(gc, col[(wrap(start + n, sh) * sstride) + i], gamma);
+                  r += t * contrib;
+               }
+
+               if(density != 0.0f && density != 1.0f)
+                  r /= density;
+
+               r = MIN(255, MAX(0, r));
+
+               if(i != 3)
+                  r = gamma_to_linear(gc, r, gamma);
+
+               d[((y * sw) + x) * bpp + i] = (unsigned char)r;
+            }
+         }
+      }
+
+      /* resample in X direction */
+      d = dst;
+      #ifdef _OPENMP
+      #pragma omp parallel for schedule(dynamic) \
+      private(x, y, row, center, start, stop, nmax, s, i, n, density, r, t, contrib)
+      #endif
+      for(y = 0; y < dh; ++y)
+      {
+         row = tmp2 + (y * sstride);
+
+         for(x = 0; x < dw; ++x)
+         {
+            center = ((float)x + 0.5f) / xfactor;
+            start = (int)(center - xsupport + 0.5f);
+            stop =  (int)(center + xsupport + 0.5f);
+            nmax = stop - start;
+            s = (float)start - center + 0.5f;
+
+            for(i = 0; i < bpp; ++i)
+            {
+               density = 0.0f;
+               r = 0.0f;
+
+               for(n = 0; n < nmax; ++n)
+               {
+                  contrib = filter((s + n) * xscale);
+                  density += contrib;
+                  if(i == 3)
+                     t = row[(wrap(start + n, sw) * bpp) + i];
+                  else
+                     t = linear_to_gamma(gc, row[(wrap(start + n, sw) * bpp) + i], gamma);
+                  r += t * contrib;
+               }
+
+               if(density != 0.0f && density != 1.0f)
+                  r /= density;
+
+               r = MIN(255, MAX(0, r));
+
+               if(i != 3)
+                  r = gamma_to_linear(gc, r, gamma);
+
+               d[((z * dh * dw) + (y * dw) + x) * bpp + i] = (unsigned char)r;
+            }
+         }
+      }
+   }
+
+   g_free(tmp1);
+   g_free(tmp2);
+}
+
+/******************************************************************************
+ * filter lookup table                                                        *
+ ******************************************************************************/
+
+static struct
+{
+   int filter;
+   filterfunc_t func;
+   float support;
+} filters[] =
+{
+   {DDS_MIPMAP_FILTER_BOX,       box_filter,       0.5f},
+   {DDS_MIPMAP_FILTER_TRIANGLE,  triangle_filter,  1.0f},
+   {DDS_MIPMAP_FILTER_QUADRATIC, quadratic_filter, 1.5f},
+   {DDS_MIPMAP_FILTER_BSPLINE,   bspline_filter,   2.0f},
+   {DDS_MIPMAP_FILTER_MITCHELL,  mitchell_filter,  2.0f},
+   {DDS_MIPMAP_FILTER_LANCZOS,   lanczos_filter,   3.0f},
+   {DDS_MIPMAP_FILTER_KAISER,    kaiser_filter,    3.0f},
+   {DDS_MIPMAP_FILTER_MAX,       NULL,             0.0f}
+};
+
+/*
+ * Alpha test coverage - portion of visible texels after alpha test:
+ *   if (texel_alpha < alpha_test_threshold)
+ *      discard;
+ */
+float calc_alpha_test_coverage(unsigned char *src,
+                               unsigned int width, unsigned int height, int bpp,
+                               float alpha_test_threshold,
+                               float alpha_scale)
+{
+   unsigned int x, y;
+   int rowbytes = width * bpp;
+   int coverage = 0;
+   const int alpha_channel_idx = 3;
+
+   if(bpp <= alpha_channel_idx)
+   {
+      /* No alpha channel */
+      return 1.f;
+   }
+
+   for(y = 0; y < height; ++y)
+   {
+      for(x = 0; x < width; ++x)
+      {
+         const float alpha = src[y * rowbytes + (x * bpp) + alpha_channel_idx];
+         if((alpha * alpha_scale) >= (alpha_test_threshold * 255))
+         {
+            ++coverage;
+         }
+      }
+   }
+
+   return (float)coverage / (width * height);
+}
+
+void scale_alpha_to_coverage(unsigned char *img,
+                             unsigned int width, unsigned int height, int bpp,
+                             float desired_coverage,
+                             float alpha_test_threshold)
+{
+   int i;
+   unsigned int x, y;
+   const int rowbytes = width * bpp;
+   const int alpha_channel_idx = 3;
+   float min_alpha_scale = 0.0f;
+   float max_alpha_scale = 4.0f;
+   float alpha_scale = 1.0f;
+
+   if(bpp <= alpha_channel_idx)
+   {
+      /* No alpha channel */
+      return;
+   }
+
+   /* Binary search */
+   for(i = 0; i < 10; i++)
+   {
+      float cur_coverage = calc_alpha_test_coverage(img, width, height, bpp, alpha_test_threshold, 
alpha_scale);
+
+      if(cur_coverage < desired_coverage)
+      {
+         min_alpha_scale = alpha_scale;
+      }
+      else if (cur_coverage > desired_coverage)
+      {
+         max_alpha_scale = alpha_scale;
+      }
+      else
+      {
+         break;
+      }
+
+      alpha_scale = (min_alpha_scale + max_alpha_scale) / 2;
+   }
+
+   /* Scale alpha channel */
+   for(y = 0; y < height; ++y)
+   {
+      for(x = 0; x < width; ++x)
+      {
+         float new_alpha = img[y * rowbytes + (x * bpp) + alpha_channel_idx] * alpha_scale;
+         if(new_alpha > 255.0f)
+         {
+            new_alpha = 255.0f;
+         }
+
+         img[y * rowbytes + (x * bpp) + alpha_channel_idx] = (unsigned char)new_alpha;
+      }
+   }
+}
+
+/******************************************************************************
+ * mipmap generation                                                          *
+ ******************************************************************************/
+
+int generate_mipmaps(unsigned char *dst, unsigned char *src,
+                     unsigned int width, unsigned int height, int bpp,
+                     int indexed, int mipmaps, int filter, int wrap,
+                     int gc, float gamma,
+                     int preserve_alpha_coverage, float alpha_test_threshold)
+{
+   int i;
+   unsigned int sw, sh, dw, dh;
+   unsigned char *s, *d;
+   mipmapfunc_t mipmap_func = NULL;
+   filterfunc_t filter_func = NULL;
+   wrapfunc_t wrap_func = NULL;
+   float support = 0.0f;
+   const int has_alpha = (bpp >= 3);
+   float alpha_test_coverage = 1;
+
+   if(indexed || filter == DDS_MIPMAP_FILTER_NEAREST)
+   {
+      mipmap_func = scale_image_nearest;
+   }
+   else
+   {
+      if((filter <= DDS_MIPMAP_FILTER_DEFAULT) ||
+         (filter >= DDS_MIPMAP_FILTER_MAX))
+         filter = DDS_MIPMAP_FILTER_BOX;
+
+      mipmap_func = scale_image;
+
+      for(i = 0; filters[i].filter != DDS_MIPMAP_FILTER_MAX; ++i)
+      {
+         if(filter == filters[i].filter)
+         {
+            filter_func = filters[i].func;
+            support = filters[i].support;
+            break;
+         }
+      }
+   }
+
+   switch(wrap)
+   {
+      case DDS_MIPMAP_WRAP_MIRROR: wrap_func = wrap_mirror; break;
+      case DDS_MIPMAP_WRAP_REPEAT: wrap_func = wrap_repeat; break;
+      case DDS_MIPMAP_WRAP_CLAMP:  wrap_func = wrap_clamp;  break;
+      default:                     wrap_func = wrap_clamp;  break;
+   }
+
+   if(has_alpha && preserve_alpha_coverage)
+   {
+      alpha_test_coverage = calc_alpha_test_coverage(src, width, height, bpp,
+                                                     alpha_test_threshold,
+                                                     1.0f);
+   }
+
+   memcpy(dst, src, width * height * bpp);
+
+   s = dst;
+   d = dst + (width * height * bpp);
+
+   sw = width;
+   sh = height;
+
+   for(i = 1; i < mipmaps; ++i)
+   {
+      dw = MAX(1, sw >> 1);
+      dh = MAX(1, sh >> 1);
+
+      mipmap_func(d, dw, dh, s, sw, sh, bpp, filter_func, support, wrap_func, gc, gamma);
+
+      if(has_alpha && preserve_alpha_coverage)
+      {
+         scale_alpha_to_coverage(d, dw, dh, bpp, alpha_test_coverage, alpha_test_threshold);
+      }
+
+      s = d;
+      sw = dw;
+      sh = dh;
+      d += (dw * dh * bpp);
+   }
+
+   return(1);
+}
+
+int generate_volume_mipmaps(unsigned char *dst, unsigned char *src,
+                            unsigned int width, unsigned int height,
+                            unsigned int depth, int bpp, int indexed,
+                            int mipmaps, int filter, int wrap,
+                            int gc, float gamma)
+{
+   int i;
+   unsigned int sw, sh, sd;
+   unsigned int dw, dh, dd;
+   unsigned char *s, *d;
+   volmipmapfunc_t mipmap_func = NULL;
+   filterfunc_t filter_func = NULL;
+   wrapfunc_t wrap_func = NULL;
+   float support = 0.0f;
+
+   if(indexed || filter == DDS_MIPMAP_FILTER_NEAREST)
+   {
+      mipmap_func = scale_volume_image_nearest;
+   }
+   else
+   {
+      if((filter <= DDS_MIPMAP_FILTER_DEFAULT) ||
+         (filter >= DDS_MIPMAP_FILTER_MAX))
+         filter = DDS_MIPMAP_FILTER_BOX;
+
+      mipmap_func = scale_volume_image;
+
+      for(i = 0; filters[i].filter != DDS_MIPMAP_FILTER_MAX; ++i)
+      {
+         if(filter == filters[i].filter)
+         {
+            filter_func = filters[i].func;
+            support = filters[i].support;
+            break;
+         }
+      }
+   }
+
+   switch(wrap)
+   {
+      case DDS_MIPMAP_WRAP_MIRROR: wrap_func = wrap_mirror; break;
+      case DDS_MIPMAP_WRAP_REPEAT: wrap_func = wrap_repeat; break;
+      case DDS_MIPMAP_WRAP_CLAMP:  wrap_func = wrap_clamp;  break;
+      default:                     wrap_func = wrap_clamp;  break;
+   }
+
+   memcpy(dst, src, width * height * depth * bpp);
+
+   s = dst;
+   d = dst + (width * height * depth * bpp);
+
+   sw = width;
+   sh = height;
+   sd = depth;
+
+   for(i = 1; i < mipmaps; ++i)
+   {
+      dw = MAX(1, sw >> 1);
+      dh = MAX(1, sh >> 1);
+      dd = MAX(1, sd >> 1);
+
+      mipmap_func(d, dw, dh, dd, s, sw, sh, sd, bpp, filter_func, support, wrap_func, gc, gamma);
+
+      s = d;
+      sw = dw;
+      sh = dh;
+      sd = dd;
+      d += (dw * dh * dd * bpp);
+   }
+
+   return(1);
+}
diff --git a/plug-ins/file-dds/mipmap.h b/plug-ins/file-dds/mipmap.h
new file mode 100644
index 0000000000..ff77eb3c46
--- /dev/null
+++ b/plug-ins/file-dds/mipmap.h
@@ -0,0 +1,47 @@
+/*
+       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.
+*/
+
+#ifndef MIPMAP_H
+#define MIPMAP_H
+
+int get_num_mipmaps(int width, int height);
+unsigned int get_mipmapped_size(int width, int height, int bpp,
+                                int level, int num, int format);
+unsigned int get_volume_mipmapped_size(int width, int height,
+                                       int depth, int bpp, int level,
+                                       int num, int format);
+int get_next_mipmap_dimensions(int *next_w, int *next_h,
+                               int  curr_w, int  curr_h);
+
+float cubic_interpolate(float a, float b, float c, float d, float x);
+int generate_mipmaps(unsigned char *dst, unsigned char *src,
+                     unsigned int width, unsigned int height, int bpp,
+                     int indexed, int mipmaps, int filter, int wrap,
+                     int gamma_correct, float gamma,
+                     int preserve_alpha_test_coverage, float alpha_test_threshold);
+int generate_volume_mipmaps(unsigned char *dst, unsigned char *src,
+                            unsigned int width, unsigned int height,
+                            unsigned int depth, int bpp, int indexed,
+                            int mipmaps, int filter, int wrap,
+                            int gamma_correct, float gamma);
+
+#endif
diff --git a/plug-ins/file-dds/misc.c b/plug-ins/file-dds/misc.c
new file mode 100644
index 0000000000..4183c27191
--- /dev/null
+++ b/plug-ins/file-dds/misc.c
@@ -0,0 +1,252 @@
+/*
+       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.
+*/
+
+#include <libgimp/gimp.h>
+
+static inline float saturate(float a)
+{
+   if(a < 0) a = 0;
+   if(a > 1) a = 1;
+   return(a);
+}
+
+void decode_ycocg_image(gint32 drawableID, gboolean shadow)
+{
+   GeglBuffer *buffer, *sbuffer;
+   const Babl *format;
+   unsigned char *data;
+   unsigned int i, w, h, num_pixels;
+
+   const float offset = 0.5f * 256.0f / 255.0f;
+   float Y, Co, Cg, R, G, B;
+
+   buffer = gimp_drawable_get_buffer(drawableID);
+
+   if(shadow)
+   {
+      sbuffer = gimp_drawable_get_shadow_buffer(drawableID);
+      gegl_buffer_copy(buffer, NULL, sbuffer, NULL);
+      g_object_unref(buffer);
+      buffer = sbuffer;
+   }
+         
+   format = babl_format("R'G'B'A u8");
+
+   w = gegl_buffer_get_width(buffer);
+   h = gegl_buffer_get_height(buffer);
+   num_pixels = w * h;
+   
+   data = g_malloc(num_pixels * 4);
+   
+   gegl_buffer_get(buffer, GEGL_RECTANGLE(0, 0, w, h), 1.0, format, data,
+                   GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
+
+   gimp_progress_init("Decoding YCoCg pixels...");
+
+   for(i = 0; i < num_pixels; ++i)
+   {
+      Y  = (float)data[4 * i + 3] / 255.0f;
+      Co = (float)data[4 * i + 0] / 255.0f;
+      Cg = (float)data[4 * i + 1] / 255.0f;
+
+      /* convert YCoCg to RGB */
+      Co -= offset;
+      Cg -= offset;
+
+      R = saturate(Y + Co - Cg);
+      G = saturate(Y + Cg);
+      B = saturate(Y - Co - Cg);
+
+      /* copy new alpha from blue */
+      data[4 * i + 3] = data[4 * i + 2];
+
+      data[4 * i + 0] = (unsigned char)(R * 255.0f);
+      data[4 * i + 1] = (unsigned char)(G * 255.0f);
+      data[4 * i + 2] = (unsigned char)(B * 255.0f);
+
+      if((i & 0x7fff) == 0)
+         gimp_progress_update((float)i / (float)num_pixels);
+   }
+
+   gegl_buffer_set(buffer, GEGL_RECTANGLE(0, 0, w, h), 1.0, format, data,
+                   GEGL_AUTO_ROWSTRIDE);
+
+   gimp_progress_update(1.0);
+
+   gegl_buffer_flush(buffer);
+   
+   if(shadow)
+      gimp_drawable_merge_shadow(drawableID, TRUE);
+   
+   gimp_drawable_update(drawableID, 0, 0, w, h);
+
+   g_free(data);
+   
+   g_object_unref(buffer);
+}
+
+void decode_ycocg_scaled_image(gint32 drawableID, gboolean shadow)
+{
+   GeglBuffer *buffer, *sbuffer;
+   const Babl *format;
+   unsigned char *data;
+   unsigned int i, w, h, num_pixels;
+
+   const float offset = 0.5f * 256.0f / 255.0f;
+   float Y, Co, Cg, R, G, B, s;
+
+   buffer = gimp_drawable_get_buffer(drawableID);
+   
+   if(shadow)
+   {
+      sbuffer = gimp_drawable_get_shadow_buffer(drawableID);
+      gegl_buffer_copy(buffer, NULL, sbuffer, NULL);
+      g_object_unref(buffer);
+      buffer = sbuffer;
+   }
+   
+   format = babl_format("R'G'B'A u8");
+   
+   w = gegl_buffer_get_width(buffer);
+   h = gegl_buffer_get_height(buffer);
+   num_pixels = w * h;
+      
+   data = g_malloc(num_pixels * 4);
+   
+   gegl_buffer_get(buffer, GEGL_RECTANGLE(0, 0, w, h), 1.0, format, data,
+                   GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
+   
+   gimp_progress_init("Decoding YCoCg (scaled) pixels...");
+
+   for(i = 0; i < num_pixels; ++i)
+   {
+      Y  = (float)data[4 * i + 3] / 255.0f;
+      Co = (float)data[4 * i + 0] / 255.0f;
+      Cg = (float)data[4 * i + 1] / 255.0f;
+      s  = (float)data[4 * i + 2] / 255.0f;
+
+      /* convert YCoCg to RGB */
+      s = 1.0f / ((255.0f / 8.0f) * s + 1.0f);
+
+      Co = (Co - offset) * s;
+      Cg = (Cg - offset) * s;
+
+      R = saturate(Y + Co - Cg);
+      G = saturate(Y + Cg);
+      B = saturate(Y - Co - Cg);
+
+      data[4 * i + 0] = (unsigned char)(R * 255.0f);
+      data[4 * i + 1] = (unsigned char)(G * 255.0f);
+      data[4 * i + 2] = (unsigned char)(B * 255.0f);
+
+      /* set alpha to 1 */
+      data[4 * i + 3] = 255;
+
+      if((i & 0x7fff) == 0)
+         gimp_progress_update((float)i / (float)num_pixels);
+   }
+
+   gegl_buffer_set(buffer, GEGL_RECTANGLE(0, 0, w, h), 1.0, format, data,
+                   GEGL_AUTO_ROWSTRIDE);
+   
+   gimp_progress_update(1.0);
+   
+   gegl_buffer_flush(buffer);
+
+   if(shadow)
+      gimp_drawable_merge_shadow(drawableID, TRUE);
+
+   gimp_drawable_update(drawableID, 0, 0, w, h);
+   
+   g_free(data);
+   
+   g_object_unref(buffer);
+}
+
+void decode_alpha_exp_image(gint32 drawableID, gboolean shadow)
+{
+   GeglBuffer *buffer, *sbuffer;
+   const Babl *format;
+   unsigned char *data;
+   unsigned int i, w, h, num_pixels;
+   int R, G, B, A;
+
+   buffer = gimp_drawable_get_buffer(drawableID);
+   
+   if(shadow)
+   {
+      sbuffer = gimp_drawable_get_shadow_buffer(drawableID);
+      gegl_buffer_copy(buffer, NULL, sbuffer, NULL);
+      g_object_unref(buffer);
+      buffer = sbuffer;
+   }
+   
+   format = babl_format("R'G'B'A u8");
+   
+   w = gegl_buffer_get_width(buffer);
+   h = gegl_buffer_get_height(buffer);
+   num_pixels = w * h;
+      
+   data = g_malloc(num_pixels * 4);
+   
+   gegl_buffer_get(buffer, GEGL_RECTANGLE(0, 0, w, h), 1.0, format, data,
+                   GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
+   
+   gimp_progress_init("Decoding Alpha-exponent pixels...");
+
+   for(i = 0; i < num_pixels; ++i)
+   {
+      R = data[4 * i + 0];
+      G = data[4 * i + 1];
+      B = data[4 * i + 2];
+      A = data[4 * i + 3];
+
+      R = (R * A + 1) >> 8;
+      G = (G * A + 1) >> 8;
+      B = (B * A + 1) >> 8;
+      A = 255;
+
+      data[4 * i + 0] = R;
+      data[4 * i + 1] = G;
+      data[4 * i + 2] = B;
+      data[4 * i + 3] = A;
+
+      if((i & 0x7fff) == 0)
+         gimp_progress_update((float)i / (float)num_pixels);
+   }
+
+   gegl_buffer_set(buffer, GEGL_RECTANGLE(0, 0, w, h), 1.0, format, data,
+                   GEGL_AUTO_ROWSTRIDE);
+   
+   gimp_progress_update(1.0);
+   
+   gegl_buffer_flush(buffer);
+
+   if(shadow)
+      gimp_drawable_merge_shadow(drawableID, TRUE);
+
+   gimp_drawable_update(drawableID, 0, 0, w, h);
+   
+   g_free(data);
+   
+   g_object_unref(buffer);
+}
diff --git a/plug-ins/file-dds/misc.h b/plug-ins/file-dds/misc.h
new file mode 100644
index 0000000000..d54482a9af
--- /dev/null
+++ b/plug-ins/file-dds/misc.h
@@ -0,0 +1,30 @@
+/*
+       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.
+*/
+
+#ifndef MISC_H
+#define MISC_H
+
+void decode_ycocg_image(gint32 drawableID, gboolean shadow);
+void decode_ycocg_scaled_image(gint32 drawableID, gboolean shadow);
+void decode_alpha_exp_image(gint32 drawableID, gboolean shadow);
+
+#endif
diff --git a/plug-ins/file-dds/mktables.c b/plug-ins/file-dds/mktables.c
new file mode 100644
index 0000000000..80f5818a04
--- /dev/null
+++ b/plug-ins/file-dds/mktables.c
@@ -0,0 +1,128 @@
+#include <stdlib.h>
+#include <stdio.h>
+
+int mul8bit(int a, int b)
+{
+   int t = a * b + 128;
+   return((t + (t >> 8)) >> 8);
+}
+
+int lerp13(int a, int b)
+{
+#if 0
+   return(a + mul8bit(b - a, 0x55));
+#else
+   return((2 * a + b) / 3);
+#endif   
+}
+
+static void prepare_opt_table(unsigned char *tab,
+                              const unsigned char *expand, int size)
+{
+   int i, mn, mx, bestE, minE, maxE, e;
+   
+   for(i = 0; i < 256; ++i)
+   {
+      bestE = 256 * 100;
+      
+      for(mn = 0; mn < size; ++mn)
+      {
+         for(mx = 0; mx < size; ++mx)
+         {
+            minE = expand[mn];
+            maxE = expand[mx];
+            e = abs(lerp13(maxE, minE) - i) * 100;
+            
+            e += abs(mx - mn) * 3;
+            
+            if(e < bestE)
+            {
+               tab[i * 2 + 0] = mx;
+               tab[i * 2 + 1] = mn;
+               bestE = e;
+            }
+         }
+      }
+   }
+}
+
+int main(void)
+{
+   FILE *fp;
+   int i, v;
+   unsigned char expand5[32];
+   unsigned char expand6[64];
+   unsigned char quantRB[256 + 16];
+   unsigned char quantG[256 + 16];
+   unsigned char omatch5[256][2];
+   unsigned char omatch6[256][2];
+   
+   fp = fopen("dxt_tables.h", "w");
+   fprintf(fp,
+           "#ifndef DXT_TABLES_H\n"
+           "#define DXT_TABLES_H\n\n");
+   
+   for(i = 0; i < 32; ++i)
+      expand5[i] = (i << 3) | (i >> 2);
+
+   for(i = 0; i < 64; ++i)
+      expand6[i] = (i << 2) | (i >> 4);
+   
+   for(i = 0; i < 256 + 16; ++i)
+   {
+      v = i - 8;
+      if(v < 0) v = 0;
+      if(v > 255) v = 255;
+      quantRB[i] = expand5[mul8bit(v, 31)];
+      quantG[i] = expand6[mul8bit(v, 63)];
+   }
+ 
+   fprintf(fp,
+           "static const unsigned char quantRB[256 + 16] =\n"
+           "{");
+   for(i = 0; i < 256 + 16; ++i)
+   {
+      if(i % 8 == 0) fprintf(fp, "\n   ");
+      fprintf(fp, "0x%02x, ", quantRB[i]);
+   }
+   fprintf(fp, "\n};\n\n");
+
+   fprintf(fp,
+           "static const unsigned char quantG[256 + 16] =\n"
+           "{");
+   for(i = 0; i < 256 + 16; ++i)
+   {
+      if(i % 8 == 0) fprintf(fp, "\n   ");
+      fprintf(fp, "0x%02x, ", quantG[i]);
+   }
+   fprintf(fp, "\n};\n\n");
+   
+   prepare_opt_table(&omatch5[0][0], expand5, 32);
+   prepare_opt_table(&omatch6[0][0], expand6, 64);
+   
+   fprintf(fp,
+           "static const unsigned char omatch5[256][2] =\n"
+           "{");
+   for(i = 0; i < 256; ++i)
+   {
+      if(i % 4 == 0) fprintf(fp, "\n   ");
+      fprintf(fp, "{0x%02x, 0x%02x}, ", omatch5[i][0], omatch5[i][1]);
+   }
+   fprintf(fp, "\n};\n\n");
+
+   fprintf(fp,
+           "static const unsigned char omatch6[256][2] =\n"
+           "{");
+   for(i = 0; i < 256; ++i)
+   {
+      if(i % 4 == 0) fprintf(fp, "\n   ");
+      fprintf(fp, "{0x%02x, 0x%02x}, ", omatch6[i][0], omatch6[i][1]);
+   }
+   fprintf(fp, "\n};\n\n");
+   
+   fprintf(fp, "#endif\n");
+   
+   fclose(fp);
+   
+   return(0);
+}
diff --git a/plug-ins/file-dds/vec.h b/plug-ins/file-dds/vec.h
new file mode 100644
index 0000000000..fd94de6651
--- /dev/null
+++ b/plug-ins/file-dds/vec.h
@@ -0,0 +1,226 @@
+/*
+       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.
+*/
+
+#ifndef VEC_H
+#define VEC_H
+
+#include <math.h>
+
+#ifdef __SSE__
+#define USE_SSE 1
+#endif
+
+#ifdef USE_SSE
+#include <immintrin.h>
+#endif
+
+#include "imath.h"
+
+typedef float vec4_t __attribute__((vector_size(16)));
+typedef float sym3x3_t[6];
+
+#define VEC4_CONST4(x, y, z, w)  {x, y, z, w}
+#define VEC4_CONST3(x, y, z)     {x, y, z, 0.0f}
+#define VEC4_CONST1(x)           {x, x, x, x}
+
+static inline vec4_t vec4_set(float x, float y, float z, float w)
+{
+#ifdef USE_SSE
+   return(_mm_setr_ps(x, y, z, w));
+#else
+   vec4_t v = {x, y, z, w};
+   return(v);
+#endif
+}
+
+static inline vec4_t vec4_set1(float f)
+{
+#ifdef USE_SSE
+   return(_mm_set1_ps(f));
+#else
+   vec4_t v = {f, f, f, f};
+   return(v);
+#endif
+}
+
+static inline vec4_t vec4_zero()
+{
+#ifdef USE_SSE
+   return(_mm_setzero_ps());
+#else
+   vec4_t v = {0, 0, 0, 0};
+   return(v);
+#endif
+}
+
+static inline void vec4_store(float *f, const vec4_t v)
+{
+#ifdef USE_SSE
+   _mm_store_ps(f, v);
+#else
+   f[0] = v[0]; f[1] = v[1]; f[2] = v[2]; f[3] = v[3];
+#endif
+}
+
+static inline vec4_t vec4_splatx(const vec4_t v)
+{
+#ifdef USE_SSE
+   return(_mm_shuffle_ps(v, v, 0x00));
+#else
+   vec4_t r = {v[0], v[0], v[0], v[0]};
+   return(r);
+#endif
+}
+
+static inline vec4_t vec4_splaty(const vec4_t v)
+{
+#ifdef USE_SSE
+   return(_mm_shuffle_ps(v, v, 0x55));
+#else
+   vec4_t r = {v[1], v[1], v[1], v[1]};
+   return(r);
+#endif
+}
+
+static inline vec4_t vec4_splatz(const vec4_t v)
+{
+#ifdef USE_SSE
+   return(_mm_shuffle_ps(v, v, 0xaa));
+#else
+   vec4_t r = {v[2], v[2], v[2], v[2]};
+   return(r);
+#endif
+}
+
+static inline vec4_t vec4_splatw(const vec4_t v)
+{
+#ifdef USE_SSE
+   return(_mm_shuffle_ps(v, v, 0xff));
+#else
+   vec4_t r = {v[3], v[3], v[3], v[3]};
+   return(r);
+#endif
+}
+
+static inline vec4_t vec4_rcp(const vec4_t v)
+{
+#ifdef USE_SSE
+   __m128 est  = _mm_rcp_ps(v);
+   __m128 diff = _mm_sub_ps(_mm_set1_ps(1.0f), _mm_mul_ps(est, v));
+   return(_mm_add_ps(_mm_mul_ps(diff, est), est));
+#else
+   vec4_t one = {1.0f, 1.0f, 1.0f, 1.0f};
+   return(one / v);
+#endif
+}
+
+static inline vec4_t vec4_min(const vec4_t a, const vec4_t b)
+{
+#ifdef USE_SSE
+   return(_mm_min_ps(a, b));
+#else
+   return(vec4_set(MIN(a[0], b[0]), MIN(a[1], b[1]), MIN(a[2], b[2]), MIN(a[3], b[3])));
+#endif
+}
+
+static inline vec4_t vec4_max(const vec4_t a, const vec4_t b)
+{
+#ifdef USE_SSE
+   return(_mm_max_ps(a, b));
+#else
+   return(vec4_set(MAX(a[0], b[0]), MAX(a[1], b[1]), MAX(a[2], b[2]), MAX(a[3], b[3])));
+#endif
+}
+
+static inline vec4_t vec4_trunc(const vec4_t v)
+{
+#ifdef USE_SSE
+# ifdef __SSE4_1__
+   return(_mm_round_ps(v, _MM_FROUND_TRUNC));
+# elif defined(__SSE2__)
+   return(_mm_cvtepi32_ps(_mm_cvttps_epi32(v)));
+# else
+   // convert to ints
+   __m128 in = v;
+   __m64 lo = _mm_cvttps_pi32(in);
+   __m64 hi = _mm_cvttps_pi32(_mm_movehl_ps(in, in));
+   // convert to floats
+   __m128 part = _mm_movelh_ps(in, _mm_cvtpi32_ps(in, hi));
+   __m128 trunc = _mm_cvtpi32_ps(part, lo);
+   // clear mmx state
+   _mm_empty();
+   return(trunc);
+# endif
+#else
+   vec4_t r = {
+      v[0] > 0.0f ? floorf(v[0]) : ceil(v[0]),
+      v[1] > 0.0f ? floorf(v[1]) : ceil(v[1]),
+      v[2] > 0.0f ? floorf(v[2]) : ceil(v[2]),
+      v[3] > 0.0f ? floorf(v[3]) : ceil(v[3]),
+   };
+   return(r);
+#endif
+}
+
+static inline float vec4_accum(const vec4_t v)
+{
+#ifdef USE_SSE
+   float rv;
+   __m128 t;
+# ifdef __SSE3__
+   t = _mm_hadd_ps(v, v);
+   t = _mm_hadd_ps(t, t);
+# else
+   t = _mm_add_ps(v, _mm_movehl_ps(v, v));
+   t = _mm_add_ss(t, _mm_shuffle_ps(t, t, 0x01));
+# endif
+   _mm_store_ss(&rv, t);
+   return(rv);
+#else
+   return(v[0] + v[1] + v[2] + v[3]);
+#endif
+}
+
+static inline float vec4_dot(const vec4_t a, const vec4_t b)
+{
+#if defined(USE_SSE) && defined(__SSE4_1__)
+   float rv;
+   __m128 t = _mm_dp_ps(a, b, 0xff);
+   _mm_store_ss(&rv, t);
+   return(rv);
+#else
+   return(vec4_accum(a * b));
+#endif
+}
+
+static inline int vec4_cmplt(const vec4_t a, const vec4_t b)
+{
+#ifdef USE_SSE
+   __m128 bits = _mm_cmplt_ps(a, b);
+   int val = _mm_movemask_ps(bits);
+   return(val != 0);
+#else
+   return((a[0] < b[0]) || (a[1] < b[1]) || (a[2] < b[2]) || (a[3] < b[3]));
+#endif
+}
+
+#endif


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]