imageUtils.cpp

Engine/source/gfx/bitmap/imageUtils.cpp

More...

Classes:

Namespaces:

namespace

Detailed Description

  1
  2//-----------------------------------------------------------------------------
  3// Copyright (c) 2016 GarageGames, LLC
  4//
  5// Permission is hereby granted, free of charge, to any person obtaining a copy
  6// of this software and associated documentation files (the "Software"), to
  7// deal in the Software without restriction, including without limitation the
  8// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  9// sell copies of the Software, and to permit persons to whom the Software is
 10// furnished to do so, subject to the following conditions:
 11//
 12// The above copyright notice and this permission notice shall be included in
 13// all copies or substantial portions of the Software.
 14//
 15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 20// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 21// IN THE SOFTWARE.
 22//-----------------------------------------------------------------------------
 23
 24#include "platform/platform.h"
 25#include "gfx/bitmap/imageUtils.h"
 26#include "gfx/bitmap/ddsFile.h"
 27#include "platform/threads/threadPool.h"
 28#include "squish/squish.h"
 29
 30namespace ImageUtil
 31{
 32   // get squish quality flag
 33   S32 _getSquishQuality(const CompressQuality quality)
 34   {
 35      switch (quality)
 36      {
 37      case LowQuality:
 38         return squish::kColourRangeFit;
 39      case MediumQuality:
 40         return squish::kColourClusterFit;
 41      case HighQuality:
 42         return squish::kColourIterativeClusterFit;
 43      default:
 44         return squish::kColourRangeFit;//default is low quality
 45      }
 46   }
 47
 48   // get squish compression flag
 49   S32 _getSquishFormat(const GFXFormat compressFormat)
 50   {
 51      switch (compressFormat)
 52      {
 53      case GFXFormatBC1:
 54         return squish::kDxt1;
 55      case GFXFormatBC2:
 56         return squish::kDxt3;
 57      case GFXFormatBC3:
 58         return squish::kDxt5;
 59      case GFXFormatBC4:
 60         return squish::kBc4;
 61      case GFXFormatBC5:
 62         return squish::kBc5;
 63      default:
 64         return squish::kDxt1;
 65      }
 66   }
 67
 68   //Thread work job for compression
 69   struct CompressJob : public ThreadPool::WorkItem
 70   {
 71      S32 width;
 72      S32 height;
 73      const U8 *pSrc;
 74      U8 *pDst;
 75      GFXFormat format;
 76      CompressQuality quality;
 77
 78      CompressJob(const U8 *srcRGBA, U8 *dst, const S32 w, const S32 h, const GFXFormat compressFormat, const CompressQuality compressQuality)
 79         : pSrc(srcRGBA),pDst(dst), width(w), height(h), format(compressFormat),quality(compressQuality) {}
 80
 81   protected:
 82      virtual void execute()
 83      {
 84         rawCompress(pSrc,pDst, width, height, format,quality);
 85      }
 86   };
 87
 88
 89   // compress raw pixel data, expects rgba format
 90   bool rawCompress(const U8 *srcRGBA, U8 *dst, const S32 width, const S32 height, const GFXFormat compressFormat, const CompressQuality compressQuality)
 91   {
 92      if (!isCompressedFormat(compressFormat))
 93         return false;
 94
 95      S32 squishFlags = _getSquishQuality(compressQuality);
 96      S32 squishFormat = _getSquishFormat(compressFormat);
 97
 98      squishFlags |= squishFormat;
 99
100      squish::CompressImage(srcRGBA, width,height,dst,squishFlags);
101
102      return true;
103   }
104
105   // compress DDSFile
106   bool ddsCompress(DDSFile *srcDDS, const GFXFormat compressFormat,const CompressQuality compressQuality)
107   {
108      if (srcDDS->mBytesPerPixel != 4)
109      {
110         Con::errorf("ImageCompress::ddsCompress: data must be 32bit");
111         return false;
112      }
113
114      //can't compress DDSFile if it is already compressed
115      if (ImageUtil::isCompressedFormat(srcDDS->mFormat))
116      {
117         Con::errorf("ImageCompress::ddsCompress: file is already compressed");
118         return false;
119      }
120
121      const bool cubemap = srcDDS->mFlags.test(DDSFile::CubeMapFlag);
122      const U32 mipCount = srcDDS->mMipMapCount;
123      // We got this far, so assume we can finish (gosh I hope so)
124      srcDDS->mFormat = compressFormat;
125      srcDDS->mFlags.set(DDSFile::CompressedData);
126
127      //grab global thread pool
128      ThreadPool* pThreadPool = &ThreadPool::GLOBAL();
129
130      if (cubemap)
131      {
132         static U32 nCubeFaces = 6;
133         Vector<U8*> dstDataStore;
134         dstDataStore.setSize(nCubeFaces * mipCount);
135
136         for (S32 cubeFace = 0; cubeFace < nCubeFaces; cubeFace++)
137         {
138            DDSFile::SurfaceData *srcSurface = srcDDS->mSurfaces[cubeFace];
139            for (U32 currentMip = 0; currentMip < mipCount; currentMip++)
140            {
141               const U32 dataIndex = cubeFace * mipCount + currentMip;
142               const U8 *srcBits = srcSurface->mMips[currentMip];
143               const U32 mipSz = srcDDS->getSurfaceSize(currentMip);
144               U8 *dstBits = new U8[mipSz];
145               dstDataStore[dataIndex] = dstBits;
146
147               ThreadSafeRef<CompressJob> item(new CompressJob(srcBits, dstBits, srcDDS->getWidth(currentMip), srcDDS->getHeight(currentMip), compressFormat, compressQuality));
148               pThreadPool->queueWorkItem(item);
149
150            }
151         }
152
153         //wait for work items to finish
154         pThreadPool->waitForAllItems();
155
156         for (S32 cubeFace = 0; cubeFace < nCubeFaces; cubeFace++)
157         {
158            DDSFile::SurfaceData *pSrcSurface = srcDDS->mSurfaces[cubeFace];
159            for (U32 currentMip = 0; currentMip < mipCount; currentMip++)
160            {
161               const U32 dataIndex = cubeFace * mipCount + currentMip;
162               delete[] pSrcSurface->mMips[currentMip];
163               pSrcSurface->mMips[currentMip] = dstDataStore[dataIndex];
164            }
165         }
166      }
167      else
168      {
169         // The source surface is the original surface of the file
170         DDSFile::SurfaceData *pSrcSurface = srcDDS->mSurfaces.last();
171
172         // Create a new surface, this will be the DXT compressed surface. Once we
173         // are done, we can discard the old surface, and replace it with this one.
174         DDSFile::SurfaceData *pNewSurface = new DDSFile::SurfaceData();
175         //no point using threading if only 1 mip
176         const bool useThreading = bool(mipCount > 1);
177         for (U32 currentMip = 0; currentMip < mipCount; currentMip++)
178         {
179            const U8 *pSrcBits = pSrcSurface->mMips[currentMip];
180
181            const U32 mipSz = srcDDS->getSurfaceSize(currentMip);
182            U8 *pDstBits = new U8[mipSz];
183            pNewSurface->mMips.push_back(pDstBits);
184
185            if (useThreading)
186            {
187               // Create CompressJob item
188               ThreadSafeRef<CompressJob> item(new CompressJob(pSrcBits, pDstBits, srcDDS->getWidth(currentMip), srcDDS->getHeight(currentMip), compressFormat, compressQuality));
189               pThreadPool->queueWorkItem(item);
190            }
191            else
192               rawCompress(pSrcBits, pDstBits, srcDDS->getWidth(currentMip), srcDDS->getHeight(currentMip), compressFormat, compressQuality);
193
194         }
195         //block and wait for CompressJobs to finish
196         if(useThreading)
197            pThreadPool->waitForAllItems();
198
199         // Now delete the source surface and replace with new compressed surface
200         srcDDS->mSurfaces.pop_back();
201         delete pSrcSurface;
202         srcDDS->mSurfaces.push_back(pNewSurface);
203      }
204
205      return true;
206   }
207
208   bool decompress(const U8 *src, U8 *dstRGBA,const S32 width,const S32 height, const GFXFormat srcFormat)
209   {
210      if (!isCompressedFormat(srcFormat))
211         return false;
212
213      S32 squishFlag = _getSquishFormat(srcFormat);
214      squish::DecompressImage(dstRGBA, width, height, src, squishFlag);  
215
216      return true;
217   }
218
219   void swizzleDDS(DDSFile *srcDDS, const Swizzle<U8, 4> &swizzle)
220   {
221      if (srcDDS->mFlags.test(DDSFile::CubeMapFlag))
222      {
223         for (S32 cubeFace = 0; cubeFace < DDSFile::Cubemap_Surface_Count; cubeFace++)
224         {
225            for (S32 i = 0; i < srcDDS->mMipMapCount; i++)
226            {
227               swizzle.InPlace(srcDDS->mSurfaces[cubeFace]->mMips[i], srcDDS->getSurfaceSize(i));
228            }
229         }
230      }
231      else
232      {
233         for (S32 i = 0; i < srcDDS->mMipMapCount; i++)
234         {
235            swizzle.InPlace(srcDDS->mSurfaces.last()->mMips[i], srcDDS->getSurfaceSize(i));
236         }
237      }
238   }
239
240   bool isCompressedFormat(const GFXFormat format)
241   {
242      if (format >= GFXFormatBC1 && format <= GFXFormatBC3_SRGB)
243         return true;
244      else
245         return false;
246   }
247
248   bool isAlphaFormat(const GFXFormat format)
249   {
250      switch (format)
251      {
252         case GFXFormatA8:
253         case GFXFormatA4L4:
254         case GFXFormatA8L8:
255         case GFXFormatR5G5B5A1:
256         case GFXFormatR8G8B8A8:
257         case GFXFormatB8G8R8A8:
258         case GFXFormatR16G16B16A16F:
259         case GFXFormatR32G32B32A32F:
260         case GFXFormatR10G10B10A2:
261         //case GFXFormatBC1://todo BC1 can store alpha
262         case GFXFormatBC2:
263         case GFXFormatBC3:
264            return true;
265         default:
266            return false;
267      }
268   }
269
270   bool isSRGBFormat(const GFXFormat format)
271   {
272      switch (format)
273      {
274      case GFXFormatR8G8B8_SRGB:
275      case GFXFormatR8G8B8A8_SRGB:
276      case GFXFormatBC1_SRGB:
277      case GFXFormatBC2_SRGB:
278      case GFXFormatBC3_SRGB:
279         return true;
280      default:
281         return false;
282      };
283   }
284
285   GFXFormat toSRGBFormat(const GFXFormat format)
286   {
287      switch (format)
288      {
289      case GFXFormatR8G8B8:
290         return GFXFormatR8G8B8_SRGB;
291      case GFXFormatR8G8B8X8:
292      case GFXFormatR8G8B8A8:
293         return GFXFormatR8G8B8A8_SRGB;
294      case GFXFormatBC1:
295         return GFXFormatBC1_SRGB;
296      case GFXFormatBC2:
297         return GFXFormatBC2_SRGB;
298      case GFXFormatBC3:
299         return GFXFormatBC3_SRGB;
300      default:
301         return format;
302      };
303   }
304
305   U32 getMaxMipCount(const U32 width, const U32 height)
306   {
307      return mFloor(mLog2(mMax(width, height))) + 1;
308   }
309}
310