imageUtils.cpp
Engine/source/gfx/bitmap/imageUtils.cpp
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