text: /******************************************************************************
   *
   * Project:  DDS Driver
   * Purpose:  Implement GDAL DDS Support
   * Author:   Alan Boudreault, aboudreault@mapgears.com
   *
   ******************************************************************************
   * Copyright (c) 2013, Alan Boudreault
   * Copyright (c) 2013, Even Rouault <even dot rouault at mines-paris dot org>
   *
   * 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.
   ******************************************************************************
   *
   * THE CURRENT IMPLEMENTATION IS WRITE ONLY.
   *
   */
  
  /* stdio.h needed before including crnlib.h, since the later needs NULL to be defined */
  #include <stdio.h>
  #include "crnlib.h"
  #include "dds_defs.h"
  #include "gdal_frmts.h"
  #include "gdal_pam.h"
  
  #include <algorithm>
  
  CPL_CVSID("$Id$")
  
  using namespace crnlib;
  
  enum { DDS_COLOR_TYPE_RGB,
         DDS_COLOR_TYPE_RGB_ALPHA };
  
  /************************************************************************/
  /* ==================================================================== */
  /*                              DDSDataset                              */
  /* ==================================================================== */
  /************************************************************************/
  
  class DDSDataset : public GDALPamDataset
  {
  public:
      static GDALDataset* CreateCopy(const char * pszFilename,
                                     GDALDataset *poSrcDS,
                                     int bStrict, char ** papszOptions,
                                     GDALProgressFunc pfnProgress,
                                     void * pProgressData);
  };
  
  /************************************************************************/
  /*                             CreateCopy()                             */
  /************************************************************************/
  
  GDALDataset *
  DDSDataset::CreateCopy(const char * pszFilename, GDALDataset *poSrcDS,
                         int bStrict, char ** papszOptions,
                         GDALProgressFunc pfnProgress, void * pProgressData)
  
  {
      int  nBands = poSrcDS->GetRasterCount();
  
      /* -------------------------------------------------------------------- */
      /*      Some rudimentary checks                                         */
      /* -------------------------------------------------------------------- */
      if (nBands != 3 && nBands != 4)
      {
          CPLError(CE_Failure, CPLE_NotSupported,
                   "DDS driver doesn't support %d bands. Must be 3 (rgb) \n"
                   "or 4 (rgba) bands.\n",
                   nBands);
  
          return NULL;
      }
  
      if (poSrcDS->GetRasterBand(1)->GetRasterDataType() != GDT_Byte)
      {
          CPLError( (bStrict) ? CE_Failure : CE_Warning, CPLE_NotSupported,
                    "DDS driver doesn't support data type %s. "
                    "Only eight bit (Byte) bands supported. %s\n",
                    GDALGetDataTypeName(
                                        poSrcDS->GetRasterBand(1)->GetRasterDataType()),
                    (bStrict) ? "" : "Defaulting to Byte" );
  
          if (bStrict)
              return NULL;
      }
  
      /* -------------------------------------------------------------------- */
      /*      Setup some parameters.                                          */
      /* -------------------------------------------------------------------- */
      int  nColorType = 0;
  
      if (nBands == 3)
        nColorType = DDS_COLOR_TYPE_RGB;
      else if (nBands == 4)
        nColorType = DDS_COLOR_TYPE_RGB_ALPHA;
  
      /* -------------------------------------------------------------------- */
      /*      Create the dataset.                                             */
      /* -------------------------------------------------------------------- */
      VSILFILE *fpImage = VSIFOpenL(pszFilename, "wb");
      if (fpImage == NULL)
      {
          CPLError(CE_Failure, CPLE_OpenFailed,
                   "Unable to create dds file %s.\n",
                   pszFilename);
          return NULL;
      }
  
      /* -------------------------------------------------------------------- */
      /*      Create the Crunch compressor                                    */
      /* -------------------------------------------------------------------- */
  
      /* Default values */
      crn_format fmt = cCRNFmtDXT3;
      const uint cDXTBlockSize = 4;
      crn_dxt_quality dxt_quality = cCRNDXTQualityNormal;
      bool srgb_colorspace = true;
      bool dxt1a_transparency = true;
      //bool generate_mipmaps = true;
  
      /* Check the texture format */
      const char *pszFormat = CSLFetchNameValue( papszOptions, "FORMAT" );
  
      if (pszFormat)
      {
          if (EQUAL(pszFormat, "dxt1"))
              fmt = cCRNFmtDXT1;
          else if (EQUAL(pszFormat, "dxt1a"))
              fmt = cCRNFmtDXT1;
          else if (EQUAL(pszFormat, "dxt3"))
              fmt = cCRNFmtDXT3;
          else if (EQUAL(pszFormat, "dxt5"))
              fmt = cCRNFmtDXT5;
          else if (EQUAL(pszFormat, "etc1"))
              fmt = cCRNFmtETC1;
          else
          {
              CPLError( CE_Failure, CPLE_AppDefined,
                        "Illegal FORMAT value '%s', should be DXT1, DXT1A, DXT3, DXT5 or ETC1",
                        pszFormat );
              return NULL;
          }
      }
  
      /* Check the compression quality */
      const char *pszQuality = CSLFetchNameValue( papszOptions, "QUALITY" );
  
      if (pszQuality)
      {
          if (EQUAL(pszQuality, "SUPERFAST"))
              dxt_quality = cCRNDXTQualitySuperFast;
          else if (EQUAL(pszQuality, "FAST"))
              dxt_quality = cCRNDXTQualityFast;
          else if (EQUAL(pszQuality, "NORMAL"))
              dxt_quality = cCRNDXTQualityNormal;
          else if (EQUAL(pszQuality, "BETTER"))
              dxt_quality = cCRNDXTQualityBetter;
          else if (EQUAL(pszQuality, "UBER"))
              dxt_quality = cCRNDXTQualityUber;
          else
          {
              CPLError( CE_Failure, CPLE_AppDefined,
                        "Illegal QUALITY value '%s', should be SUPERFAST, FAST, NORMAL, BETTER or UBER.",
                        pszQuality );
              return NULL;
          }
      }
  
      int nXSize = poSrcDS->GetRasterXSize();
      int nYSize = poSrcDS->GetRasterYSize();
  
      if ((nXSize%4!=0) || (nYSize%4!=0)) {
        CPLError( CE_Warning, CPLE_AppDefined,
                  "Raster size is not a multiple of 4: %dx%d. "
                  "Extra rows/columns will be ignored during the compression.",
                  nXSize, nYSize );
      }
  
      crn_comp_params comp_params;
      comp_params.m_format = fmt;
      comp_params.m_dxt_quality = dxt_quality;
      comp_params.set_flag(cCRNCompFlagPerceptual, srgb_colorspace);
      comp_params.set_flag(cCRNCompFlagDXT1AForTransparency, dxt1a_transparency);
  
      crn_block_compressor_context_t pContext = crn_create_block_compressor(comp_params);
  
      /* -------------------------------------------------------------------- */
      /*      Write the DDS header to the file.                               */
      /* -------------------------------------------------------------------- */
  
      VSIFWriteL(&crnlib::cDDSFileSignature, 1,
                 sizeof(crnlib::cDDSFileSignature), fpImage);
  
      crnlib::DDSURFACEDESC2 ddsDesc;
      memset(&ddsDesc, 0, sizeof(ddsDesc));
      ddsDesc.dwSize = sizeof(ddsDesc);
      ddsDesc.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_MIPMAPCOUNT | DDSD_PIXELFORMAT | DDSD_DEPTH ;
      ddsDesc.dwWidth = nXSize;
      ddsDesc.dwHeight = nYSize;
      ddsDesc.dwMipMapCount = 1;
  
      ddsDesc.ddpfPixelFormat.dwSize = sizeof(crnlib::DDPIXELFORMAT);
      ddsDesc.ddpfPixelFormat.dwFlags = DDPF_FOURCC;
      ddsDesc.ddpfPixelFormat.dwFourCC = crn_get_format_fourcc(fmt);
      ddsDesc.ddsCaps.dwCaps = DDSCAPS_TEXTURE;
  
      // Set pitch/linearsize field (some DDS readers require this field to be non-zero).
      uint bits_per_pixel = crn_get_format_bits_per_texel(fmt);
      ddsDesc.lPitch = (((ddsDesc.dwWidth + 3) & ~3U) * ((ddsDesc.dwHeight + 3) & ~3U) * bits_per_pixel) >> 3;
      ddsDesc.dwFlags |= DDSD_LINEARSIZE;
  
      /* Endianness problems when serializing structure?? dds on-disk format
         should be verified */
      VSIFWriteL(&ddsDesc, 1, sizeof(ddsDesc), fpImage);
  
      /* -------------------------------------------------------------------- */
      /*      Loop over image, compressing image data.                        */
      /* -------------------------------------------------------------------- */
      const uint bytesPerBlock = crn_get_bytes_per_dxt_block(fmt);
      CPLErr eErr = CE_None;
      const uint nYNumBlocks = (nYSize + cDXTBlockSize - 1) / cDXTBlockSize;
      const uint num_blocks_x = (nXSize + cDXTBlockSize - 1) / cDXTBlockSize;
      const uint total_compressed_size = num_blocks_x * bytesPerBlock;
  
      void *pCompressed_data = CPLMalloc(total_compressed_size);
      GByte* pabyScanlines = (GByte *) CPLMalloc(nBands * nXSize * cDXTBlockSize);
      crn_uint32 *pixels = (crn_uint32*) CPLMalloc(sizeof(crn_uint32)*cDXTBlockSize * cDXTBlockSize);
      crn_uint32 *src_image = NULL;
      if (nColorType == DDS_COLOR_TYPE_RGB)
          src_image = (crn_uint32*) CPLMalloc(sizeof(crn_uint32)*nXSize*cDXTBlockSize);
  
      for (uint iLine = 0; iLine < nYNumBlocks && eErr == CE_None; iLine++)
      {
          const uint size_y = (iLine*cDXTBlockSize+cDXTBlockSize) < (uint)nYSize ?
                             cDXTBlockSize : (cDXTBlockSize-((iLine*cDXTBlockSize+cDXTBlockSize)-(uint)nYSize));
  
          eErr = poSrcDS->RasterIO(GF_Read, 0, iLine*cDXTBlockSize, nXSize, size_y,
                                   pabyScanlines, nXSize, size_y, GDT_Byte,
                                   nBands, NULL,
                                   nBands,
                                   nBands * nXSize, 1, NULL);
  
          if (eErr != CE_None)
              break;
  
          crn_uint32 *pSrc_image = NULL;
          if (nColorType == DDS_COLOR_TYPE_RGB_ALPHA)
              pSrc_image = (crn_uint32*)pabyScanlines;
          else if (nColorType == DDS_COLOR_TYPE_RGB)
          { /* crunch needs 32bits integers */
              int nPixels = nXSize*cDXTBlockSize;
              for (int i=0; i<nPixels;++i)
              {
                  int y = (i*3);
                  src_image[i] = (255<<24) | (pabyScanlines[y+2]<<16) | (pabyScanlines[y+1]<<8) |
                    pabyScanlines[y];
              }
  
              pSrc_image = &(src_image[0]);
          }
  
          for (crn_uint32 block_x = 0; block_x < num_blocks_x; block_x++)
          {
              // Exact block from image, clamping at the sides of non-divisible by
              // 4 images to avoid artifacts.
              crn_uint32 *pDst_pixels = pixels;
              for (uint y = 0; y < cDXTBlockSize; y++)
              {
                  const uint actual_y = std::min(cDXTBlockSize - 1U, y);
                  for (uint x = 0; x < cDXTBlockSize; x++)
                  {
                      const uint actual_x =
                          std::min(nXSize - 1U, (block_x * cDXTBlockSize) + x);
                      *pDst_pixels++ = pSrc_image[actual_x + actual_y * nXSize];
                  }
              }
  
              // Compress the DXTn block.
              crn_compress_block(pContext, pixels, static_cast<crn_uint8 *>(pCompressed_data) + block_x * bytesPerBlock);
          }
  
          if (eErr == CE_None)
              VSIFWriteL(pCompressed_data, 1, total_compressed_size, fpImage);
  
          if (eErr == CE_None
              && !pfnProgress( (iLine+1) / (double) nYNumBlocks,
                               NULL, pProgressData))
          {
              eErr = CE_Failure;
              CPLError(CE_Failure, CPLE_UserInterrupt,
                        "User terminated CreateCopy()");
          }
      }
  
      CPLFree(src_image);
      CPLFree(pixels);
      CPLFree(pCompressed_data);
      CPLFree(pabyScanlines);
      crn_free_block_compressor(pContext);
      pContext = NULL;
  
      VSIFCloseL(fpImage);
  
      if (eErr != CE_None)
          return NULL;
  
      DDSDataset *poDsDummy = new DDSDataset();
  
      return poDsDummy;
  }
  
  /************************************************************************/
  /*                          GDALRegister_DDS()                          */
  /************************************************************************/
  
  void GDALRegister_DDS()
  {
      if( GDALGetDriverByName( "DDS" ) != NULL )
          return;
  
      GDALDriver *poDriver = new GDALDriver();
  
      poDriver->SetDescription("DDS");
      poDriver->SetMetadataItem( GDAL_DCAP_RASTER, "YES" );
      poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
                                "DirectDraw Surface");
      poDriver->SetMetadataItem( GDAL_DMD_HELPTOPIC, "frmt_various.html#DDS" );
      poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "dds");
      poDriver->SetMetadataItem(GDAL_DMD_MIMETYPE, "image/dds");
  
      poDriver->SetMetadataItem(
          GDAL_DMD_CREATIONOPTIONLIST,
          "<CreationOptionList>\n"
          "   <Option name='FORMAT' type='string-select' description='Texture format' default='DXT3'>\n"
          "     <Value>DXT1</Value>\n"
          "     <Value>DXT1A</Value>\n"
          "     <Value>DXT3</Value>\n"
          "     <Value>DXT5</Value>\n"
          "     <Value>ETC1</Value>\n"
          "   </Option>\n"
          "   <Option name='QUALITY' type='string-select' description='Compression Quality' default='NORMAL'>\n"
          "     <Value>SUPERFAST</Value>\n"
          "     <Value>FAST</Value>\n"
          "     <Value>NORMAL</Value>\n"
          "     <Value>BETTER</Value>\n"
          "     <Value>UBER</Value>\n"
          "   </Option>\n"
          "</CreationOptionList>\n" );
  
      poDriver->SetMetadataItem( GDAL_DCAP_VIRTUALIO, "YES" );
      poDriver->pfnCreateCopy = DDSDataset::CreateCopy;
  
      GetGDALDriverManager()->RegisterDriver(poDriver);
  }
  