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 | |
20 | using namespace std::placeholders; |
21 | |
22 | namespace 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 | |