1 : /******************************************************************************
2 : * $Id: $
3 : *
4 : * Project: DDS Driver
5 : * Purpose: Implement GDAL DDS Support
6 : * Author: Alan Boudreault, aboudreault@mapgears.com
7 : *
8 : ******************************************************************************
9 : * Copyright (c) 2013, Alan Boudreault
10 : *
11 : * Permission is hereby granted, free of charge, to any person obtaining a
12 : * copy of this software and associated documentation files (the "Software"),
13 : * to deal in the Software without restriction, including without limitation
14 : * the rights to use, copy, modify, merge, publish, distribute, sublicense,
15 : * and/or sell copies of the Software, and to permit persons to whom the
16 : * Software is furnished to do so, subject to the following conditions:
17 : *
18 : * The above copyright notice and this permission notice shall be included
19 : * in all copies or substantial portions of the Software.
20 : *
21 : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
22 : * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 : * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
24 : * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 : * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26 : * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27 : * DEALINGS IN THE SOFTWARE.
28 : ******************************************************************************
29 : *
30 : * THE CURRENT IMPLEMENTATION IS WRITE ONLY.
31 : *
32 : */
33 :
34 : #include "gdal_pam.h"
35 : #include "crnlib.h"
36 : #include "dds_defs.h"
37 :
38 : CPL_CVSID("$Id: $");
39 :
40 : CPL_C_START
41 : void GDALRegister_DDS(void);
42 : CPL_C_END
43 :
44 : using namespace crnlib;
45 :
46 : enum { DDS_COLOR_TYPE_RGB,
47 : DDS_COLOR_TYPE_RGB_ALPHA };
48 :
49 :
50 : /************************************************************************/
51 : /* ==================================================================== */
52 : /* DDSDataset */
53 : /* ==================================================================== */
54 : /************************************************************************/
55 :
56 : class DDSDataset : public GDALPamDataset
57 4 : {
58 : public:
59 : static GDALDataset* CreateCopy(const char * pszFilename,
60 : GDALDataset *poSrcDS,
61 : int bStrict, char ** papszOptions,
62 : GDALProgressFunc pfnProgress,
63 : void * pProgressData);
64 : };
65 :
66 :
67 : /************************************************************************/
68 : /* CreateCopy() */
69 : /************************************************************************/
70 :
71 : GDALDataset *
72 20 : DDSDataset::CreateCopy(const char * pszFilename, GDALDataset *poSrcDS,
73 : int bStrict, char ** papszOptions,
74 : GDALProgressFunc pfnProgress, void * pProgressData)
75 :
76 : {
77 20 : int nBands = poSrcDS->GetRasterCount();
78 20 : int nXSize = poSrcDS->GetRasterXSize();
79 20 : int nYSize = poSrcDS->GetRasterYSize();
80 :
81 : /* -------------------------------------------------------------------- */
82 : /* Some rudimentary checks */
83 : /* -------------------------------------------------------------------- */
84 20 : if (nBands != 3 && nBands != 4)
85 : {
86 : CPLError(CE_Failure, CPLE_NotSupported,
87 : "DDS driver doesn't support %d bands. Must be 3 (rgb) \n"
88 : "or 4 (rgba) bands.\n",
89 16 : nBands);
90 :
91 16 : return NULL;
92 : }
93 :
94 4 : if (poSrcDS->GetRasterBand(1)->GetRasterDataType() != GDT_Byte)
95 : {
96 : CPLError( (bStrict) ? CE_Failure : CE_Warning, CPLE_NotSupported,
97 : "DDS driver doesn't support data type %s. "
98 : "Only eight bit (Byte) bands supported. %s\n",
99 : GDALGetDataTypeName(
100 : poSrcDS->GetRasterBand(1)->GetRasterDataType()),
101 0 : (bStrict) ? "" : "Defaulting to Byte" );
102 :
103 0 : if (bStrict)
104 0 : return NULL;
105 : }
106 :
107 : /* -------------------------------------------------------------------- */
108 : /* Setup some parameters. */
109 : /* -------------------------------------------------------------------- */
110 4 : int nColorType = 0;
111 :
112 4 : if (nBands == 3)
113 2 : nColorType = DDS_COLOR_TYPE_RGB;
114 2 : else if (nBands == 4)
115 2 : nColorType = DDS_COLOR_TYPE_RGB_ALPHA;
116 :
117 : /* -------------------------------------------------------------------- */
118 : /* Create the dataset. */
119 : /* -------------------------------------------------------------------- */
120 : VSILFILE *fpImage;
121 :
122 4 : fpImage = VSIFOpenL(pszFilename, "wb");
123 4 : if (fpImage == NULL)
124 : {
125 : CPLError(CE_Failure, CPLE_OpenFailed,
126 : "Unable to create dds file %s.\n",
127 2 : pszFilename);
128 2 : return NULL;
129 : }
130 :
131 : /* -------------------------------------------------------------------- */
132 : /* Create the Crunch compressor */
133 : /* -------------------------------------------------------------------- */
134 :
135 : /* Default values */
136 2 : crn_format fmt = cCRNFmtDXT3;
137 2 : const uint cDXTBlockSize = 4;
138 2 : crn_dxt_quality dxt_quality = cCRNDXTQualityNormal;
139 2 : bool srgb_colorspace = true;
140 2 : bool dxt1a_transparency = true;
141 :
142 : /* Check the texture format */
143 2 : const char *pszFormat = CSLFetchNameValue( papszOptions, "FORMAT" );
144 :
145 2 : if (pszFormat)
146 : {
147 0 : if (EQUAL(pszFormat, "dxt1"))
148 0 : fmt = cCRNFmtDXT1;
149 0 : else if (EQUAL(pszFormat, "dxt1a"))
150 0 : fmt = cCRNFmtDXT1;
151 0 : else if (EQUAL(pszFormat, "dxt3"))
152 0 : fmt = cCRNFmtDXT3;
153 0 : else if (EQUAL(pszFormat, "dxt5"))
154 0 : fmt = cCRNFmtDXT5;
155 : else
156 : {
157 : CPLError( CE_Failure, CPLE_AppDefined,
158 : "Illegal FORMAT value '%s', should be DXT1, DXT1A, DXT3 or DXT5.",
159 0 : pszFormat );
160 0 : return NULL;
161 : }
162 : }
163 :
164 : /* Check the compression quality */
165 2 : const char *pszQuality = CSLFetchNameValue( papszOptions, "QUALITY" );
166 :
167 2 : if (pszQuality)
168 : {
169 0 : if (EQUAL(pszQuality, "SUPERFAST"))
170 0 : dxt_quality = cCRNDXTQualitySuperFast;
171 0 : else if (EQUAL(pszQuality, "FAST"))
172 0 : dxt_quality = cCRNDXTQualityFast;
173 0 : else if (EQUAL(pszQuality, "NORMAL"))
174 0 : dxt_quality = cCRNDXTQualityNormal;
175 0 : else if (EQUAL(pszQuality, "BETTER"))
176 0 : dxt_quality = cCRNDXTQualityBetter;
177 0 : else if (EQUAL(pszQuality, "UBER"))
178 0 : dxt_quality = cCRNDXTQualityUber;
179 : else
180 : {
181 : CPLError( CE_Failure, CPLE_AppDefined,
182 : "Illegal QUALITY value '%s', should be SUPERFAST, FAST, NORMAL, BETTER or UBER.",
183 0 : pszQuality );
184 0 : return NULL;
185 : }
186 : }
187 :
188 2 : if ((nXSize%4!=0) || (nYSize%4!=0)) {
189 : CPLError(CE_Warning, CPLE_AppDefined,
190 : "Raster size is not a multiple of 4: %dx%d. "
191 : "Extra rows/colums will be ignored during the compression.",
192 2 : nXSize, nYSize);
193 : }
194 :
195 2 : crn_comp_params comp_params;
196 2 : comp_params.m_format = fmt;
197 2 : comp_params.m_dxt_quality = dxt_quality;
198 2 : comp_params.set_flag(cCRNCompFlagPerceptual, srgb_colorspace);
199 2 : comp_params.set_flag(cCRNCompFlagDXT1AForTransparency, dxt1a_transparency);
200 :
201 2 : crn_block_compressor_context_t pContext = crn_create_block_compressor(comp_params);
202 :
203 : /* -------------------------------------------------------------------- */
204 : /* Write the DDS header to the file. */
205 : /* -------------------------------------------------------------------- */
206 :
207 : VSIFWriteL(&crnlib::cDDSFileSignature, 1,
208 2 : sizeof(crnlib::cDDSFileSignature), fpImage);
209 :
210 : crnlib::DDSURFACEDESC2 ddsDesc;
211 2 : memset(&ddsDesc, 0, sizeof(ddsDesc));
212 2 : ddsDesc.dwSize = sizeof(ddsDesc);
213 2 : ddsDesc.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT;
214 2 : ddsDesc.dwWidth = nXSize;
215 2 : ddsDesc.dwHeight = nYSize;
216 :
217 2 : ddsDesc.ddpfPixelFormat.dwSize = sizeof(crnlib::DDPIXELFORMAT);
218 2 : ddsDesc.ddpfPixelFormat.dwFlags = DDPF_FOURCC;
219 2 : ddsDesc.ddpfPixelFormat.dwFourCC = crn_get_format_fourcc(fmt);
220 2 : ddsDesc.ddsCaps.dwCaps = DDSCAPS_TEXTURE;
221 :
222 : // Set pitch/linearsize field (some DDS readers require this field to be non-zero).
223 2 : uint bits_per_pixel = crn_get_format_bits_per_texel(fmt);
224 2 : ddsDesc.lPitch = (((ddsDesc.dwWidth + 3) & ~3) * ((ddsDesc.dwHeight + 3) & ~3) * bits_per_pixel) >> 3;
225 2 : ddsDesc.dwFlags |= DDSD_LINEARSIZE;
226 :
227 : /* Endianness problems when serializing structure?? dds on-disk format
228 : should be verified */
229 2 : VSIFWriteL(&ddsDesc, 1, sizeof(ddsDesc), fpImage);
230 :
231 : /* -------------------------------------------------------------------- */
232 : /* Loop over image, compressing image data. */
233 : /* -------------------------------------------------------------------- */
234 2 : const uint bytesPerBlock = crn_get_bytes_per_dxt_block(fmt);
235 2 : CPLErr eErr = CE_None;
236 2 : const uint nYNumBlocks = (nYSize + cDXTBlockSize - 1) / cDXTBlockSize;
237 2 : const uint num_blocks_x = (nXSize + cDXTBlockSize - 1) / cDXTBlockSize;
238 2 : const uint total_compressed_size = num_blocks_x * bytesPerBlock;
239 :
240 2 : void *pCompressed_data = CPLMalloc(total_compressed_size);
241 2 : GByte* pabyScanlines = (GByte *) CPLMalloc(nBands * nXSize * cDXTBlockSize);
242 2 : crn_uint32 *pixels = (crn_uint32*) CPLMalloc(sizeof(crn_uint32)*cDXTBlockSize * cDXTBlockSize);
243 2 : crn_uint32 *src_image = NULL;
244 2 : if (nColorType == DDS_COLOR_TYPE_RGB)
245 1 : src_image = (crn_uint32*) CPLMalloc(sizeof(crn_uint32)*nXSize*cDXTBlockSize);
246 :
247 8 : for (uint iLine = 0; iLine < nYNumBlocks && eErr == CE_None; iLine++)
248 : {
249 : const uint size_y = (iLine*cDXTBlockSize+cDXTBlockSize) < (uint)nYSize ?
250 6 : cDXTBlockSize : (cDXTBlockSize-((iLine*cDXTBlockSize+cDXTBlockSize)-(uint)nYSize));
251 :
252 : eErr = poSrcDS->RasterIO(GF_Read, 0, iLine*cDXTBlockSize, nXSize, size_y,
253 : pabyScanlines, nXSize, size_y, GDT_Byte,
254 : nBands, NULL,
255 : nBands,
256 6 : nBands * nXSize, 1);
257 :
258 6 : if (eErr != CE_None)
259 0 : break;
260 :
261 6 : crn_uint32 *pSrc_image = NULL;
262 6 : if (nColorType == DDS_COLOR_TYPE_RGB_ALPHA)
263 3 : pSrc_image = (crn_uint32*)pabyScanlines;
264 3 : else if (nColorType == DDS_COLOR_TYPE_RGB)
265 : { /* crunch needs 32bits integers */
266 3 : int nPixels = nXSize*cDXTBlockSize;
267 123 : for (int i=0; i<nPixels;++i)
268 : {
269 120 : int y = (i*3);
270 240 : src_image[i] = (255<<24) | (pabyScanlines[y+2]<<16) | (pabyScanlines[y+1]<<8) |
271 240 : pabyScanlines[y];
272 : }
273 :
274 3 : pSrc_image = &(src_image[0]);
275 : }
276 :
277 24 : for (crn_uint32 block_x = 0; block_x < num_blocks_x; block_x++)
278 : {
279 : // Exact block from image, clamping at the sides of non-divisible by
280 : // 4 images to avoid artifacts.
281 18 : crn_uint32 *pDst_pixels = pixels;
282 90 : for (uint y = 0; y < cDXTBlockSize; y++)
283 : {
284 72 : const uint actual_y = MIN(cDXTBlockSize - 1U, y);
285 360 : for (uint x = 0; x < cDXTBlockSize; x++)
286 : {
287 288 : const uint actual_x = MIN(nXSize - 1U, (block_x * cDXTBlockSize) + x);
288 288 : *pDst_pixels++ = pSrc_image[actual_x + actual_y * nXSize];
289 : }
290 : }
291 :
292 : // Compress the DXTn block.
293 18 : crn_compress_block(pContext, pixels, static_cast<crn_uint8 *>(pCompressed_data) + block_x * bytesPerBlock);
294 : }
295 :
296 6 : if (eErr == CE_None)
297 6 : VSIFWriteL(pCompressed_data, 1, total_compressed_size, fpImage);
298 :
299 6 : if (eErr == CE_None
300 : && !pfnProgress( (iLine+1) / (double) nYNumBlocks,
301 : NULL, pProgressData))
302 : {
303 0 : eErr = CE_Failure;
304 : CPLError(CE_Failure, CPLE_UserInterrupt,
305 0 : "User terminated CreateCopy()");
306 : }
307 :
308 : }
309 :
310 2 : CPLFree(src_image);
311 2 : CPLFree(pixels);
312 2 : CPLFree(pCompressed_data);
313 2 : CPLFree(pabyScanlines);
314 2 : crn_free_block_compressor(pContext);
315 2 : pContext = NULL;
316 :
317 2 : VSIFCloseL(fpImage);
318 :
319 2 : if (eErr != CE_None)
320 0 : return NULL;
321 :
322 2 : DDSDataset *poDsDummy = new DDSDataset();
323 :
324 2 : return poDsDummy;
325 : }
326 :
327 : /************************************************************************/
328 : /* GDALRegister_DDS() */
329 : /************************************************************************/
330 :
331 610 : void GDALRegister_DDS()
332 : {
333 : GDALDriver *poDriver;
334 :
335 610 : if (GDALGetDriverByName( "DDS" ) == NULL)
336 : {
337 588 : poDriver = new GDALDriver();
338 :
339 588 : poDriver->SetDescription("DDS");
340 : poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
341 588 : "DirectDraw Surface");
342 588 : poDriver->SetMetadataItem( GDAL_DMD_HELPTOPIC, "frmt_various.html#DDS" );
343 588 : poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "dds");
344 588 : poDriver->SetMetadataItem(GDAL_DMD_MIMETYPE, "image/dds");
345 :
346 : poDriver->SetMetadataItem(GDAL_DMD_CREATIONOPTIONLIST,
347 : "<CreationOptionList>\n"
348 : " <Option name='FORMAT' type='string-select' description='Texture format' default='DXT3'>\n"
349 : " <Value>DXT1</Value>\n"
350 : " <Value>DXT1A</Value>\n"
351 : " <Value>DXT3</Value>\n"
352 : " <Value>DXT5</Value>\n"
353 : " </Option>\n"
354 : " <Option name='QUALITY' type='string-select' description='Compression Quality' default='NORMAL'>\n"
355 : " <Value>SUPERFAST</Value>\n"
356 : " <Value>FAST</Value>\n"
357 : " <Value>NORMAL</Value>\n"
358 : " <Value>BETTER</Value>\n"
359 : " <Value>UBER</Value>\n"
360 : " </Option>\n"
361 588 : "</CreationOptionList>\n" );
362 :
363 588 : poDriver->SetMetadataItem( GDAL_DCAP_VIRTUALIO, "YES" );
364 :
365 588 : poDriver->pfnCreateCopy = DDSDataset::CreateCopy;
366 :
367 588 : GetGDALDriverManager()->RegisterDriver(poDriver);
368 : }
369 610 : }
|