1//************************************ bs::framework - Copyright 2018 Marko Pintera **************************************//
2//*********** Licensed under the MIT license. See LICENSE.md for full terms. This notice is not to be removed. ***********//
3#include "BsFreeImgImporter.h"
4#include "Resources/BsResource.h"
5#include "Debug/BsDebug.h"
6#include "FileSystem/BsDataStream.h"
7#include "Managers/BsTextureManager.h"
8#include "Image/BsTexture.h"
9#include "Importer/BsTextureImportOptions.h"
10#include "FileSystem/BsFileSystem.h"
11#include "BsCoreApplication.h"
12#include "CoreThread/BsCoreThread.h"
13#include "Math/BsMath.h"
14#include "Math/BsVector2.h"
15#include "Math/BsVector3.h"
16#include "FreeImage.h"
17#include "Utility/BsBitwise.h"
18#include "Renderer/BsRenderer.h"
19
20using namespace std::placeholders;
21
22namespace bs
23{
24 void FreeImageLoadErrorHandler(FREE_IMAGE_FORMAT fif, const char *message)
25 {
26 // Callback method as required by FreeImage to report problems
27 const char* typeName = FreeImage_GetFormatFromFIF(fif);
28 if (typeName)
29 {
30 gDebug().logError("FreeImage error: '" + String(message) + "' when loading format " + typeName);
31 }
32 else
33 {
34 gDebug().logError("FreeImage error: '" + String(message) + "'");
35 }
36 }
37
38 FreeImgImporter::FreeImgImporter()
39 {
40 FreeImage_Initialise(false);
41
42 // Register codecs
43 StringStream strExt;
44 strExt << "Supported formats: ";
45 bool first = true;
46 for (int i = 0; i < FreeImage_GetFIFCount(); ++i)
47 {
48 // Skip DDS codec since FreeImage does not have the option
49 // to keep DXT data compressed, we'll use our own codec
50 if ((FREE_IMAGE_FORMAT)i == FIF_DDS)
51 continue;
52
53 String exts = String(FreeImage_GetFIFExtensionList((FREE_IMAGE_FORMAT)i));
54 if (!first)
55 strExt << ",";
56
57 first = false;
58 strExt << exts;
59
60 // Pull off individual formats (separated by comma by FI)
61 Vector<String> extsVector = StringUtil::split(exts, u8",");
62 for (auto v = extsVector.begin(); v != extsVector.end(); ++v)
63 {
64 auto findIter = std::find(mExtensions.begin(), mExtensions.end(), *v);
65
66 if(findIter == mExtensions.end())
67 {
68 String ext = *v;
69 StringUtil::toLowerCase(ext);
70
71 mExtensionToFID.insert(std::make_pair(ext, i));
72 mExtensions.push_back(ext);
73 }
74 }
75 }
76
77 // Set error handler
78 FreeImage_SetOutputMessage(FreeImageLoadErrorHandler);
79 }
80
81 FreeImgImporter::~FreeImgImporter()
82 {
83 FreeImage_DeInitialise();
84 }
85
86 bool FreeImgImporter::isExtensionSupported(const String& ext) const
87 {
88 String lowerCaseExt = ext;
89 StringUtil::toLowerCase(lowerCaseExt);
90
91 return find(mExtensions.begin(), mExtensions.end(), lowerCaseExt) != mExtensions.end();
92 }
93
94 bool FreeImgImporter::isMagicNumberSupported(const UINT8* magicNumPtr, UINT32 numBytes) const
95 {
96 String ext = magicNumToExtension(magicNumPtr, numBytes);
97
98 return isExtensionSupported(ext);
99 }
100
101 String FreeImgImporter::magicNumToExtension(const UINT8* magic, UINT32 maxBytes) const
102 {
103 // Set error handler
104 FreeImage_SetOutputMessage(FreeImageLoadErrorHandler);
105
106 FIMEMORY* fiMem =
107 FreeImage_OpenMemory((BYTE*)magic, static_cast<DWORD>(maxBytes));
108
109 FREE_IMAGE_FORMAT fif = FreeImage_GetFileTypeFromMemory(fiMem, (int)maxBytes);
110 FreeImage_CloseMemory(fiMem);
111
112 if (fif != FIF_UNKNOWN)
113 {
114 String ext = String(FreeImage_GetFormatFromFIF(fif));
115 StringUtil::toLowerCase(ext);
116 return ext;
117 }
118 else
119 {
120 return StringUtil::BLANK;
121 }
122 }
123
124 SPtr<ImportOptions> FreeImgImporter::createImportOptions() const
125 {
126 return bs_shared_ptr_new<TextureImportOptions>();
127 }
128
129 SPtr<Resource> FreeImgImporter::import(const Path& filePath, SPtr<const ImportOptions> importOptions)
130 {
131 const TextureImportOptions* textureImportOptions = static_cast<const TextureImportOptions*>(importOptions.get());
132
133 SPtr<PixelData> imgData = importRawImage(filePath);
134 if(imgData == nullptr || imgData->getData() == nullptr)
135 return nullptr;
136
137 Vector<SPtr<PixelData>> faceData;
138
139 TextureType texType;
140 if(textureImportOptions->cubemap)
141 {
142 texType = TEX_TYPE_CUBE_MAP;
143
144 std::array<SPtr<PixelData>, 6> cubemapFaces;
145 if (generateCubemap(imgData, textureImportOptions->cubemapSourceType, cubemapFaces))
146 {
147 faceData.insert(faceData.begin(), cubemapFaces.begin(), cubemapFaces.end());
148 }
149 else // Fall-back to 2D texture
150 {
151 texType = TEX_TYPE_2D;
152 faceData.push_back(imgData);
153 }
154 }
155 else
156 {
157 texType = TEX_TYPE_2D;
158 faceData.push_back(imgData);
159 }
160
161 UINT32 numMips = 0;
162 if (textureImportOptions->generateMips &&
163 Bitwise::isPow2(faceData[0]->getWidth()) && Bitwise::isPow2(faceData[0]->getHeight()))
164 {
165 UINT32 maxPossibleMip = PixelUtil::getMaxMipmaps(faceData[0]->getWidth(), faceData[0]->getHeight(),
166 faceData[0]->getDepth(), faceData[0]->getFormat());
167
168 if (textureImportOptions->maxMip == 0)
169 numMips = maxPossibleMip;
170 else
171 numMips = std::min(maxPossibleMip, textureImportOptions->maxMip);
172 }
173
174 int usage = TU_DEFAULT;
175 if (textureImportOptions->cpuCached)
176 usage |= TU_CPUCACHED;
177
178 bool sRGB = textureImportOptions->sRGB;
179
180 TEXTURE_DESC texDesc;
181 texDesc.type = texType;
182 texDesc.width = faceData[0]->getWidth();
183 texDesc.height = faceData[0]->getHeight();
184 texDesc.numMips = numMips;
185 texDesc.format = textureImportOptions->format;
186 texDesc.usage = usage;
187 texDesc.hwGamma = sRGB;
188
189 SPtr<Texture> newTexture = Texture::_createPtr(texDesc);
190
191 UINT32 numFaces = (UINT32)faceData.size();
192 for (UINT32 i = 0; i < numFaces; i++)
193 {
194 Vector<SPtr<PixelData>> mipLevels;
195 if (numMips > 0)
196 {
197 MipMapGenOptions mipOptions;
198 mipOptions.isSRGB = sRGB;
199
200 mipLevels = PixelUtil::genMipmaps(*faceData[i], mipOptions);
201 }
202 else
203 mipLevels.push_back(faceData[i]);
204
205 for (UINT32 mip = 0; mip < (UINT32)mipLevels.size(); ++mip)
206 {
207 SPtr<PixelData> dst = newTexture->getProperties().allocBuffer(0, mip);
208
209 PixelUtil::bulkPixelConversion(*mipLevels[mip], *dst);
210 newTexture->writeData(dst, i, mip);
211 }
212 }
213
214 const String fileName = filePath.getFilename(false);
215 newTexture->setName(fileName);
216
217 return newTexture;
218 }
219
220 SPtr<PixelData> FreeImgImporter::importRawImage(const Path& filePath)
221 {
222 UPtr<MemoryDataStream> memStream;
223 FREE_IMAGE_FORMAT imageFormat;
224 {
225 Lock lock = FileScheduler::getLock(filePath);
226
227 SPtr<DataStream> fileData = FileSystem::openFile(filePath, true);
228 if (fileData->size() > std::numeric_limits<UINT32>::max())
229 {
230 BS_EXCEPT(InternalErrorException, "File size larger than supported!");
231 }
232
233 UINT32 magicLen = std::min((UINT32)fileData->size(), 32u);
234 UINT8 magicBuf[32];
235 fileData->read(magicBuf, magicLen);
236 fileData->seek(0);
237
238 String fileExtension = magicNumToExtension(magicBuf, magicLen);
239 auto findFormat = mExtensionToFID.find(fileExtension);
240 if (findFormat == mExtensionToFID.end())
241 {
242 BS_EXCEPT(InvalidParametersException, "Type of the file provided is not supported by this importer. File type: " + fileExtension);
243 }
244
245 imageFormat = (FREE_IMAGE_FORMAT)findFormat->second;
246
247 // Set error handler
248 FreeImage_SetOutputMessage(FreeImageLoadErrorHandler);
249
250 // Buffer stream into memory (TODO: override IO functions instead?)
251 memStream = bs_unique_ptr_new<MemoryDataStream>(fileData);
252 fileData->close();
253 }
254
255 if(!memStream)
256 return nullptr;
257
258 FIMEMORY* fiMem = FreeImage_OpenMemory(memStream->getPtr(), static_cast<DWORD>(memStream->size()));
259
260 FIBITMAP* fiBitmap = FreeImage_LoadFromMemory(
261 (FREE_IMAGE_FORMAT)imageFormat, fiMem);
262 if (!fiBitmap)
263 {
264 BS_EXCEPT(InternalErrorException, "Error decoding image");
265 }
266
267 UINT32 width = FreeImage_GetWidth(fiBitmap);
268 UINT32 height = FreeImage_GetHeight(fiBitmap);
269 PixelFormat format = PF_UNKNOWN;
270
271 // Must derive format first, this may perform conversions
272
273 FREE_IMAGE_TYPE imageType = FreeImage_GetImageType(fiBitmap);
274 FREE_IMAGE_COLOR_TYPE colourType = FreeImage_GetColorType(fiBitmap);
275 unsigned bpp = FreeImage_GetBPP(fiBitmap);
276 unsigned srcElemSize = 0;
277
278 switch(imageType)
279 {
280 case FIT_UNKNOWN:
281 case FIT_COMPLEX:
282 case FIT_UINT32:
283 case FIT_INT32:
284 case FIT_DOUBLE:
285 default:
286 BS_EXCEPT(InternalErrorException, "Unknown or unsupported image format");
287
288 break;
289 case FIT_BITMAP:
290 // Standard image type
291 // Perform any colour conversions for greyscale
292 if (colourType == FIC_MINISWHITE || colourType == FIC_MINISBLACK)
293 {
294 FIBITMAP* newBitmap = FreeImage_ConvertToGreyscale(fiBitmap);
295 // free old bitmap and replace
296 FreeImage_Unload(fiBitmap);
297 fiBitmap = newBitmap;
298 // get new formats
299 bpp = FreeImage_GetBPP(fiBitmap);
300 colourType = FreeImage_GetColorType(fiBitmap);
301 }
302 // Perform any colour conversions for RGB
303 else if (bpp < 8 || colourType == FIC_PALETTE || colourType == FIC_CMYK)
304 {
305 FIBITMAP* newBitmap = FreeImage_ConvertTo24Bits(fiBitmap);
306 // free old bitmap and replace
307 FreeImage_Unload(fiBitmap);
308 fiBitmap = newBitmap;
309 // get new formats
310 bpp = FreeImage_GetBPP(fiBitmap);
311 colourType = FreeImage_GetColorType(fiBitmap);
312 }
313
314 // by this stage, 8-bit is greyscale, 16/24/32 bit are RGB[A]
315 switch(bpp)
316 {
317 case 8:
318 format = PF_R8;
319 srcElemSize = 1;
320 break;
321 case 16:
322 // Determine 555 or 565 from green mask
323 // cannot be 16-bit greyscale since that's FIT_UINT16
324 if(FreeImage_GetGreenMask(fiBitmap) == FI16_565_GREEN_MASK)
325 {
326 assert(false && "Format not supported by the engine. TODO.");
327 return nullptr;
328 }
329 else
330 {
331 assert(false && "Format not supported by the engine. TODO.");
332 return nullptr;
333 // FreeImage doesn't support 4444 format so must be 1555
334 }
335 srcElemSize = 2;
336 break;
337 case 24:
338 // FreeImage differs per platform
339 // PF_BYTE_BGR[A] for little endian (== PF_ARGB native)
340 // PF_BYTE_RGB[A] for big endian (== PF_RGBA native)
341#if FREEIMAGE_COLORORDER == FREEIMAGE_COLORORDER_RGB
342 format = PF_RGB8;
343#else
344 format = PF_BGR8;
345#endif
346 srcElemSize = 3;
347 break;
348 case 32:
349#if FREEIMAGE_COLORORDER == FREEIMAGE_COLORORDER_RGB
350 format = PF_RGBA8;
351#else
352 format = PF_BGRA8;
353#endif
354 srcElemSize = 4;
355 break;
356
357
358 };
359 break;
360 case FIT_UINT16:
361 case FIT_INT16:
362 // 16-bit greyscale
363 assert(false && "No INT pixel formats supported currently. TODO.");
364 return nullptr;
365 break;
366 case FIT_FLOAT:
367 // Single-component floating point data
368 format = PF_R32F;
369 srcElemSize = 4;
370 break;
371 case FIT_RGB16:
372 format = PF_RGBA16;
373 srcElemSize = 2 * 3;
374 break;
375 case FIT_RGBA16:
376 format = PF_RGBA16;
377 srcElemSize = 2 * 4;
378 break;
379 case FIT_RGBF:
380 format = PF_RGB32F;
381 srcElemSize = 4 * 3;
382 break;
383 case FIT_RGBAF:
384 format = PF_RGBA32F;
385 srcElemSize = 4 * 4;
386 break;
387 };
388
389 unsigned char* srcData = FreeImage_GetBits(fiBitmap);
390 unsigned srcPitch = FreeImage_GetPitch(fiBitmap);
391
392 // Final data - invert image and trim pitch at the same time
393 UINT32 dstElemSize = PixelUtil::getNumElemBytes(format);
394 UINT32 dstPitch = width * PixelUtil::getNumElemBytes(format);
395
396 // Bind output buffer
397 SPtr<PixelData> texData = bs_shared_ptr_new<PixelData>(width, height, 1, format);
398 texData->allocateInternalBuffer();
399 UINT8* output = texData->getData();
400
401 UINT8* pSrc;
402 UINT8* pDst = output;
403
404 // Copy row by row, which is faster
405 if (srcElemSize == dstElemSize)
406 {
407 for (UINT32 y = 0; y < height; ++y)
408 {
409 pSrc = srcData + (height - y - 1) * srcPitch;
410 memcpy(pDst, pSrc, dstPitch);
411 pDst += dstPitch;
412 }
413 }
414 else
415 {
416 for (UINT32 y = 0; y < height; ++y)
417 {
418 pSrc = srcData + (height - y - 1) * srcPitch;
419
420 for(UINT32 x = 0; x < width; ++x)
421 memcpy(pDst + x * dstElemSize, pSrc + x * srcElemSize, srcElemSize);
422
423 pDst += dstPitch;
424 }
425 }
426
427 FreeImage_Unload(fiBitmap);
428 FreeImage_CloseMemory(fiMem);
429
430 return texData;
431 }
432
433 /**
434 * Reads the source texture as a horizontal or vertical list of 6 cubemap faces.
435 *
436 * @param[in] source Source texture to read.
437 * @param[out] output Output array that will contain individual cubemap faces.
438 * @param[in] faceSize Size of a single face, in pixels. Both width & height must match.
439 * @param[in] vertical True if the faces are laid out vertically, false if horizontally.
440 */
441 void readCubemapList(const SPtr<PixelData>& source, std::array<SPtr<PixelData>, 6>& output, UINT32 faceSize, bool vertical)
442 {
443 Vector2I faceStart;
444 for(UINT32 i = 0; i < 6; i++)
445 {
446 output[i] = PixelData::create(faceSize, faceSize, 1, source->getFormat());
447
448 PixelVolume volume(faceStart.x, faceStart.y, faceStart.x + faceSize, faceStart.y + faceSize);
449 PixelUtil::copy(*source, *output[i], faceStart.x, faceStart.y);
450
451 if (vertical)
452 faceStart.y += faceSize;
453 else
454 faceStart.x += faceSize;
455 }
456 }
457
458 /**
459 * Reads the source texture as a horizontal or vertical "cross" of 6 cubemap faces.
460 *
461 * Vertical layout:
462 * +Y
463 * -X +Z +X
464 * -Y
465 * -Z
466 *
467 * Horizontal layout:
468 * +Y
469 * -X +Z +X -Z
470 * -Y
471 *
472 * @param[in] source Source texture to read.
473 * @param[out] output Output array that will contain individual cubemap faces.
474 * @param[in] faceSize Size of a single face, in pixels. Both width & height must match.
475 * @param[in] vertical True if the faces are laid out vertically, false if horizontally.
476 */
477 void readCubemapCross(const SPtr<PixelData>& source, std::array<SPtr<PixelData>, 6>& output, UINT32 faceSize,
478 bool vertical)
479 {
480 const static UINT32 vertFaceIndices[] = { 5, 3, 1, 7, 4, 10 };
481 const static UINT32 horzFaceIndices[] = { 6, 4, 1, 9, 5, 7 };
482
483 const UINT32* faceIndices = vertical ? vertFaceIndices : horzFaceIndices;
484 UINT32 numFacesInRow = vertical ? 3 : 4;
485
486 for (UINT32 i = 0; i < 6; i++)
487 {
488 output[i] = PixelData::create(faceSize, faceSize, 1, source->getFormat());
489
490 UINT32 faceX = (faceIndices[i] % numFacesInRow) * faceSize;
491 UINT32 faceY = (faceIndices[i] / numFacesInRow) * faceSize;
492
493 PixelVolume volume(faceX, faceY, faceX + faceSize, faceY + faceSize);
494 PixelUtil::copy(*source, *output[i], faceX, faceY);
495 }
496
497 // Flip -Z as it's upside down
498 if (vertical)
499 PixelUtil::mirror(*output[5], MirrorModeBits::X | MirrorModeBits::Y);
500 }
501
502 /** Method that maps a direction to a point on a plane in range [0, 1] using spherical mapping. */
503 Vector2 mapCubemapDirToSpherical(const Vector3& dir)
504 {
505 // Using the OpenGL spherical mapping formula
506 Vector3 nrmDir = Vector3::normalize(dir);
507 nrmDir.z += 1.0f;
508
509 float m = 2 * nrmDir.length();
510
511 float u = nrmDir.x / m + 0.5f;
512 float v = nrmDir.y / m + 0.5f;
513
514 return Vector2(u, v);
515 }
516
517 /**
518 * Method that maps a direction to a point on a plane in range [0, 1] using cylindrical mapping. This mapping is also
519 * know as longitude-latitude mapping, Blinn/Newell mapping or equirectangular cylindrical mapping.
520 */
521 Vector2 mapCubemapDirToCylindrical(const Vector3& dir)
522 {
523 Vector3 nrmDir = Vector3::normalize(dir);
524
525 float u = (atan2(nrmDir.x, nrmDir.z) + Math::PI) / Math::TWO_PI;
526 float v = acos(nrmDir.y) / Math::PI;
527
528 return Vector2(u, v);
529 }
530
531 /** Resizes the provided cubemap faces and outputs a new set of resized faces. */
532 void downsampleCubemap(const std::array<SPtr<PixelData>, 6>& input, std::array<SPtr<PixelData>, 6>& output, UINT32 size)
533 {
534 for(UINT32 i = 0; i < 6; i++)
535 {
536 output[i] = PixelData::create(size, size, 1, input[i]->getFormat());
537 PixelUtil::scale(*input[i], *output[i]);
538 }
539 }
540
541 /**
542 * Reads the source texture and remaps its data into six faces of a cubemap.
543 *
544 * @param[in] source Source texture to remap.
545 * @param[out] output Remapped faces of the cubemap.
546 * @param[in] faceSize Width/height of each individual face, in pixels.
547 * @param[in] remap Function to use for remapping the cubemap direction to UV.
548 */
549 void readCubemapUVRemap(const SPtr<PixelData>& source, std::array<SPtr<PixelData>, 6>& output, UINT32 faceSize,
550 const std::function<Vector2(Vector3)>& remap)
551 {
552 struct RemapInfo
553 {
554 int idx[3];
555 Vector3 sign;
556 };
557
558 // Mapping from default (X, Y, 1.0f) plane to relevant cube face. Also flipping Y so it corresponds to how pixel
559 // coordinates are mapped.
560 static const RemapInfo remapLookup[] =
561 {
562 { {2, 1, 0}, { -1.0f, -1.0f, 1.0f }}, // X+
563 { {2, 1, 0}, { 1.0f, -1.0f, -1.0f }}, // X-
564 { {0, 2, 1}, { 1.0f, 1.0f, 1.0f }}, // Y+
565 { {0, 2, 1}, { 1.0f, -1.0f, -1.0f }}, // Y-
566 { {0, 1, 2}, { 1.0f, -1.0f, 1.0f }}, // Z+
567 { {0, 1, 2}, { -1.0f, -1.0f, -1.0f }} // Z-
568 };
569
570 float invSize = 1.0f / faceSize;
571 for (UINT32 faceIdx = 0; faceIdx < 6; faceIdx++)
572 {
573 output[faceIdx] = PixelData::create(faceSize, faceSize, 1, source->getFormat());
574
575 const RemapInfo& remapInfo = remapLookup[faceIdx];
576 for (UINT32 y = 0; y < faceSize; y++)
577 {
578 for (UINT32 x = 0; x < faceSize; x++)
579 {
580 // Map from pixel coordinates to [-1, 1] range.
581 Vector2 sourceCoord = (Vector2((float)x, (float)y) * invSize) * 2.0f - Vector2(1.0f, 1.0f);
582 Vector3 direction = Vector3(sourceCoord.x, sourceCoord.y, 1.0f);
583
584 direction *= remapInfo.sign;
585
586 // Rotate towards current face
587 Vector3 rotatedDir;
588 rotatedDir[remapInfo.idx[0]] = direction.x;
589 rotatedDir[remapInfo.idx[1]] = direction.y;
590 rotatedDir[remapInfo.idx[2]] = direction.z;
591
592 // Find location in the source to sample from
593 Vector2 sourceUV = remap(rotatedDir);
594 Color color = source->sampleColorAt(sourceUV);
595
596 // Write the sampled color
597 output[faceIdx]->setColorAt(color, x, y);
598 }
599 }
600 }
601 }
602
603 bool FreeImgImporter::generateCubemap(const SPtr<PixelData>& source, CubemapSourceType sourceType,
604 std::array<SPtr<PixelData>, 6>& output)
605 {
606 // Note: Expose this as a parameter if needed:
607 UINT32 cubemapSupersampling = 1; // Set to amount of samples
608
609 Vector2I faceSize;
610 bool cross = false;
611 bool vertical = false;
612
613 switch(sourceType)
614 {
615 case CubemapSourceType::Faces:
616 {
617 float aspect = source->getWidth() / (float)source->getHeight();
618
619 if(Math::approxEquals(aspect, 6.0f)) // Horizontal list
620 {
621 faceSize.x = source->getWidth() / 6;
622 faceSize.y = source->getHeight();
623 }
624 else if(Math::approxEquals(aspect, 1.0f / 6.0f)) // Vertical list
625 {
626 faceSize.x = source->getWidth();
627 faceSize.y = source->getHeight() / 6;
628 vertical = true;
629 }
630 else if(Math::approxEquals(aspect, 4.0f / 3.0f)) // Horizontal cross
631 {
632 faceSize.x = source->getWidth() / 4;
633 faceSize.y = source->getHeight() / 3;
634 cross = true;
635 }
636 else if(Math::approxEquals(aspect, 3.0f / 4.0f)) // Vertical cross
637 {
638 faceSize.x = source->getWidth() / 3;
639 faceSize.y = source->getHeight() / 4;
640 cross = true;
641 vertical = true;
642 }
643 else
644 {
645 LOGWRN("Unable to generate a cubemap: unrecognized face configuration.");
646 return false;
647 }
648 }
649 break;
650 case CubemapSourceType::Cylindrical:
651 case CubemapSourceType::Spherical:
652 // Half of the source size will be used for each cube face, which should yield good enough quality
653 faceSize.x = std::max(source->getWidth(), source->getHeight()) / 2;
654 faceSize.x = Bitwise::closestPow2(faceSize.x);
655
656 // Don't allow sizes larger than 4096
657 faceSize.x = std::min(faceSize.x, 4096);
658
659 faceSize.y = faceSize.x;
660
661 break;
662 default: // Assuming single-image
663 faceSize.x = source->getWidth();
664 faceSize.y = source->getHeight();
665 break;
666 }
667
668 if (faceSize.x != faceSize.y)
669 {
670 LOGWRN("Unable to generate a cubemap: width & height must match.");
671 return false;
672 }
673
674 if (!Bitwise::isPow2(faceSize.x))
675 {
676 LOGWRN("Unable to generate a cubemap: width & height must be powers of 2.");
677 return false;
678 }
679
680 switch (sourceType)
681 {
682 case CubemapSourceType::Faces:
683 {
684 if (cross)
685 readCubemapCross(source, output, faceSize.x, vertical);
686 else
687 readCubemapList(source, output, faceSize.x, vertical);
688 }
689 break;
690 case CubemapSourceType::Cylindrical:
691 {
692 UINT32 superSampledFaceSize = faceSize.x * cubemapSupersampling;
693
694 std::array<SPtr<PixelData>, 6> superSampledOutput;
695 readCubemapUVRemap(source, superSampledOutput, superSampledFaceSize, &mapCubemapDirToCylindrical);
696
697 if (faceSize.x != (INT32)superSampledFaceSize)
698 downsampleCubemap(superSampledOutput, output, faceSize.x);
699 else
700 output = superSampledOutput;
701 }
702 break;
703 case CubemapSourceType::Spherical:
704 {
705 UINT32 superSampledFaceSize = faceSize.x * cubemapSupersampling;
706
707 std::array<SPtr<PixelData>, 6> superSampledOutput;
708 readCubemapUVRemap(source, superSampledOutput, superSampledFaceSize, &mapCubemapDirToSpherical);
709
710 if (faceSize.x != (INT32)superSampledFaceSize)
711 downsampleCubemap(superSampledOutput, output, faceSize.x);
712 else
713 output = superSampledOutput;
714 }
715 break;
716 default: // Single-image
717 for (UINT32 i = 0; i < 6; i++)
718 output[i] = source;
719
720 break;
721 }
722
723 return true;
724 }
725}
726