Divide Framework 0.1
A free and open-source 3D Framework under heavy development
Loading...
Searching...
No Matches
ImageTools.cpp
Go to the documentation of this file.
2
7
9
10#define STB_IMAGE_IMPLEMENTATION
11#define STBI_FAILURE_USERMSG
12
13#include "stb_image.h"
14#define STB_IMAGE_RESIZE_IMPLEMENTATION
15#include "stb_image_resize.h"
16#define STB_IMAGE_WRITE_IMPLEMENTATION
17#include "stb_image_write.h"
18
19#undef _UNICODE
20#include <IL/il.h>
21#include <IL/ilu.h>
22//#include <IL/ilut.h>
23
24#include <nvtt/nvtt.h>
25#include <glm/detail/type_half.hpp>
26
27namespace Divide::ImageTools {
28 constexpr bool g_KeepDevILDDSCompatibility = true;
29
30namespace nvttHelpers {
31 struct ErrorHandler : public nvtt::ErrorHandler {
32 void error(nvtt::Error e) override {
33 static const char* nvttErrors[] = {
34 "UNKNOWN",
35 "INVALID_INPUT",
36 "UNSUPPORTED_FEATURE",
37 "CUDA_ERROR",
38 "FILE_OPEN",
39 "FILE_WRITE",
40 "UNSUPPORTED_OUTPUT_FORMAT",
41 };
42 static_assert(std::size(nvttErrors) == static_cast<size_t>(nvtt::Error::Error_Count));
43
44 Console::errorfn(LOCALE_STR("ERROR_IMAGE_TOOLS_NVT_ERROR"), nvttErrors[to_size(e)]);
45 }
46 };
47
48 struct OutputHandler : public nvtt::OutputHandler {
49 struct MipMapData {
54 };
58 nvtt::Format _format = nvtt::Format::Format_BC1;
59 bool _discardAlpha = false;
60
61 OutputHandler(nvtt::Format format, bool discardAlpha, const U8 numComponents)
62 : _numComponents(numComponents)
63 , _format(format)
64 , _discardAlpha( discardAlpha )
65 {
66 }
67
69 virtual void beginImage(int size, int width, int height, int depth, [[maybe_unused]] int face, int miplevel) override
70 {
71 MipMapData& data = _mipmaps.push_back();
72 data._width = width;
73 data._height = height;
74 data._depth = depth;
75 data._pixelData.resize(size);
76 _currentMipLevel = miplevel;
77 }
78
79 virtual void endImage() override
80 {
81 }
82
84 virtual bool writeData(const void* data, int size) override
85 {
86 // Copy mipmap data
87 memcpy(_mipmaps[_currentMipLevel]._pixelData.data(), data, size);
88 return true;
89 }
90 };
91
92 [[nodiscard]] static bool isBC1n(const nvtt::Format format, const bool isNormalMap) noexcept
93 {
94 return isNormalMap && format == nvtt::Format_BC1;
95 }
96
97 [[nodiscard]] static nvtt::Format getNVTTFormat(const ImageOutputFormat outputFormat, const bool isNormalMap, const bool hasAlpha, const bool isGreyscale) noexcept
98 {
99 assert(outputFormat != ImageOutputFormat::COUNT);
100
101 if constexpr(g_KeepDevILDDSCompatibility)
102 {
103 return isNormalMap ? nvtt::Format::Format_BC3n : hasAlpha ? nvtt::Format::Format_BC3 : nvtt::Format::Format_BC1;
104 }
105 else
106 {
107 if (outputFormat != ImageOutputFormat::AUTO)
108 {
109 switch (outputFormat)
110 {
111 case ImageOutputFormat::BC1: return nvtt::Format::Format_BC1;
112 case ImageOutputFormat::BC1a: return nvtt::Format::Format_BC1a;
113 case ImageOutputFormat::BC2: return nvtt::Format::Format_BC2;
114 case ImageOutputFormat::BC3: return isNormalMap ? nvtt::Format::Format_BC3n : nvtt::Format::Format_BC3;
115 case ImageOutputFormat::BC4: return nvtt::Format::Format_BC4;
116 case ImageOutputFormat::BC5: return nvtt::Format::Format_BC5;
117 case ImageOutputFormat::BC6: return nvtt::Format::Format_BC6;
118 case ImageOutputFormat::BC7: return nvtt::Format::Format_BC7;
119 //case ImageOutputFormat::BC3_RGBM: return nvtt::Format::Format_BC3_RGBM; //Not supported
120 default: break;
121 };
122 }
123
124 return isNormalMap ? nvtt::Format::Format_BC5 : isGreyscale ? nvtt::Format::Format_BC4 : nvtt::Format::Format_BC7;
125 }
126 }
127
128 [[nodiscard]] static nvtt::MipmapFilter getNVTTMipFilter(const MipMapFilter filter) noexcept
129 {
130 switch (filter) {
131 case MipMapFilter::BOX: return nvtt::MipmapFilter_Box;
132 case MipMapFilter::TRIANGLE: return nvtt::MipmapFilter_Triangle;
133 case MipMapFilter::KAISER: return nvtt::MipmapFilter_Kaiser;
134 default: break;
135 }
136
137 return nvtt::MipmapFilter_Box;
138 }
139}; // namespace nvttHelpers
140
141namespace {
144
145 //ref: https://github.com/nvpro-pipeline/pipeline/blob/master/dp/sg/io/IL/Loader/ILTexLoader.cpp
146 FORCE_INLINE I32 determineFace(const I32 i, const bool isDDS, const bool isCube) noexcept {
147 if (isDDS && isCube) {
148 if (i == 4) {
149 return 5;
150 } else if (i == 5) {
151 return 4;
152 }
153 }
154
155 return i;
156 }
157
158 [[nodiscard]] inline bool checkError(string& messageInOut) noexcept {
159 bool ret = false;
160
161 ILenum error = ilGetError();
162 bool stopInDebugger = true;
163 messageInOut.resize(0);
164 while (error != IL_NO_ERROR)
165 {
166 ret = true;
167 if (stopInDebugger)
168 {
169 stopInDebugger = false;
170 DebugBreak();
171 }
172 messageInOut.append("\n").append( iluErrorString(error) );
173
174 ILenum nextError = ilGetError();
175 while (error == nextError)
176 {
177 nextError = ilGetError();
178 }
179 error = nextError;
180 }
181
182 return ret;
183 }
184};
185
186void OnStartup(const bool upperLeftOrigin) {
187 s_useUpperLeftOrigin = upperLeftOrigin;
188
189 ilInit();
190 iluInit();
191 //ilutInit();
192 ilEnable(IL_FILE_OVERWRITE);
193 ilSetInteger(IL_KEEP_DXTC_DATA, IL_TRUE);
194 //ilutRenderer(ILUT_WIN32);
195 iluImageParameter(ILU_FILTER, ILU_SCALE_MITCHELL);
196}
197
199 ilShutDown();
200}
201
202bool UseUpperLeftOrigin() noexcept {
203 return s_useUpperLeftOrigin;
204}
205
206bool ImageData::loadFromMemory(const Byte* data, const size_t size, const U16 width, const U16 height, const U16 depth, const U8 numComponents)
207{
208 ImageLayer& layer = _layers.emplace_back();
209 return layer.allocateMip(data, size, width, height, depth, numComponents);
210}
211
212bool ImageData::loadFromFile(PlatformContext& context, const bool srgb, const U16 refWidth, const U16 refHeight, const ResourcePath& path, const std::string_view name)
213{
214 ImportOptions options{};
215 options._useDDSCache = false;
216 return loadFromFile(context, srgb, refWidth, refHeight, path, name, options);
217}
218
219namespace
220{
221 #define stbi__float2int(x) ((int) (x))
222 static stbi__uint16* stbi__hdr_to_16( float* data, int x, int y, int comp )
223 {
224 if ( !data )
225 {
226 return nullptr;
227 }
228
229 stbi__uint16* output = (stbi__uint16*)stbi__malloc_mad4( x, y, comp, sizeof(stbi__uint16), 0 );
230 if ( output == NULL )
231 {
232 STBI_FREE( data );
233 return nullptr;
234 }
235
236 // compute number of non-alpha components
237 int n = 0;
238 if ( comp & 1 )
239 {
240 n = comp;
241 }
242 else
243 {
244 n = comp - 1;
245 }
246
247 for (int i = 0; i < x * y; ++i )
248 {
249 int k = 0;
250 for (; k < n; ++k )
251 {
252 float z = (float)pow( data[i * comp + k] * stbi__h2l_scale_i, stbi__h2l_gamma_i ) * 65536 + 0.5f;
253 if ( z < 0 ) z = 0;
254 if ( z > 65536 ) z = 65536;
255 output[i * comp + k] = (stbi__uint16)stbi__float2int( z );
256 }
257 if ( k < comp )
258 {
259 float z = data[i * comp + k] * 65536 + 0.5f;
260 if ( z < 0 ) z = 0;
261 if ( z > 65536 ) z = 65536;
262 output[i * comp + k] = (stbi__uint16)stbi__float2int( z );
263 }
264 }
265
266 STBI_FREE( data );
267 return output;
268 }
269
270 static glm::detail::hdata* stbi__hdr_to_half( float* data, int x, int y, int comp )
271 {
272 if ( !data )
273 {
274 return nullptr;
275 }
276
277 glm::detail::hdata* output = (glm::detail::hdata*)stbi__malloc_mad4( x, y, comp, sizeof(glm::detail::hdata), 0 );
278 if ( output == NULL )
279 {
280 STBI_FREE( data );
281 return nullptr;
282 }
283
284 // compute number of non-alpha components
285 int n = 0;
286 if ( comp & 1 )
287 {
288 n = comp;
289 }
290 else
291 {
292 n = comp - 1;
293 }
294
295 for (int i = 0; i < x * y; ++i )
296 {
297 int k = 0;
298 for (; k < n; ++k )
299 {
300 output[i * comp + k] = glm::detail::toFloat16( data[i * comp + k]);
301 }
302 if ( k < comp )
303 {
304 output[i * comp + k] = glm::detail::toFloat16( data[i * comp + k] );
305 }
306 }
307
308 STBI_FREE( data );
309 return output;
310 }
311
312 static glm::detail::hdata* stbi__ldr_to_half( stbi_uc* data, int x, int y, int comp )
313 {
314 if ( !data )
315 {
316 return nullptr;
317 }
318
319 glm::detail::hdata* output = (glm::detail::hdata*)stbi__malloc_mad4( x, y, comp, sizeof( glm::detail::hdata ), 0 );
320 if ( output == NULL )
321 {
322 STBI_FREE( data );
323 return nullptr;
324 }
325
326 // compute number of non-alpha components
327 int n = 0;
328 if ( comp & 1 )
329 {
330 n = comp;
331 }
332 else
333 {
334 n = comp - 1;
335 }
336
337 for (int i = 0; i < x * y; ++i )
338 {
339 for (int k = 0; k < n; ++k )
340 {
341 output[i * comp + k] = glm::detail::toFloat16( (float)(pow( data[i * comp + k] / 255.0f, stbi__l2h_gamma ) * stbi__l2h_scale));
342 }
343 }
344 if ( n < comp )
345 {
346 for (int i = 0; i < x * y; ++i )
347 {
348 output[i * comp + n] = glm::detail::toFloat16( data[i * comp + n] / 255.0f );
349 }
350 }
351
352 STBI_FREE( data );
353 return output;
354 }
355
356 static glm::detail::hdata* stbi__16_to_half( stbi__uint16* data, int x, int y, int comp )
357 {
358 if ( !data )
359 {
360 return nullptr;
361 }
362
363 glm::detail::hdata* output = (glm::detail::hdata*)stbi__malloc_mad4( x, y, comp, sizeof( glm::detail::hdata ), 0 );
364
365 if ( output == NULL )
366 {
367 STBI_FREE( data );
368 return nullptr;
369 }
370
371 // compute number of non-alpha components
372 int n = 0;
373 if ( comp & 1 )
374 {
375 n = comp;
376 }
377 else
378 {
379 n = comp - 1;
380 }
381
382 for (int i = 0; i < x * y; ++i )
383 {
384 for (int k = 0; k < n; ++k )
385 {
386 output[i * comp + k] = glm::detail::toFloat16((float)(pow( data[i * comp + k] / 65536.0f, stbi__l2h_gamma ) * stbi__l2h_scale));
387 }
388 }
389 if ( n < comp )
390 {
391 for (int i = 0; i < x * y; ++i )
392 {
393 output[i * comp + n] = glm::detail::toFloat16( data[i * comp + n] / 65536.0f );
394 }
395 }
396
397 STBI_FREE( data );
398 return output;
399 }
400
401 static float* stbi__16_to_hdr( stbi__uint16* data, int x, int y, int comp )
402 {
403 if ( !data )
404 {
405 return nullptr;
406 }
407
408 float* output = (float*)stbi__malloc_mad4( x, y, comp, sizeof( float ), 0 );
409 if ( output == NULL )
410 {
411 STBI_FREE( data );
412 return nullptr;
413 }
414
415 // compute number of non-alpha components
416 int n = 0;
417 if ( comp & 1 )
418 {
419 n = comp;
420 }
421 else
422 {
423 n = comp - 1;
424 }
425
426 for (int i = 0; i < x * y; ++i )
427 {
428 for (int k = 0; k < n; ++k )
429 {
430 output[i * comp + k] = (float)(pow( data[i * comp + k] / 65536.0f, stbi__l2h_gamma ) * stbi__l2h_scale);
431 }
432 }
433 if ( n < comp )
434 {
435 for (int i = 0; i < x * y; ++i )
436 {
437 output[i * comp + n] = data[i * comp + n] / 65536.0f;
438 }
439 }
440
441 STBI_FREE( data );
442 return output;
443 }
444
445 template<typename To, typename From>
446 static To* stbi_convert( From* data, int x, int y, int comp )
447 {
448 if ( !data )
449 {
450 return nullptr;
451 }
452
453 To* output = (To*)stbi__malloc_mad4( x, y, comp, sizeof(To), 0 );
454 if ( output == NULL )
455 {
456 STBI_FREE( data );
457 return nullptr;
458 }
459
460 // compute number of non-alpha components
461 int n = 0;
462 if ( comp & 1 )
463 {
464 n = comp;
465 }
466 else
467 {
468 n = comp - 1;
469 }
470
471 for (int i = 0; i < x * y; ++i )
472 {
473 int k = 0;
474 for (; k < n; ++k )
475 {
476 output[i * comp + k] = static_cast<To>( data[i * comp + k] );
477 }
478 if ( k < comp )
479 {
480 output[i * comp + k] = static_cast<To>( data[i * comp + k] );
481 }
482 }
483
484 STBI_FREE( data );
485 return output;
486 }
487}
488
489bool ImageData::loadFromFile(PlatformContext& context, const bool srgb, const U16 refWidth, const U16 refHeight, const ResourcePath& path, const std::string_view name, ImportOptions& options, const bool isRetry)
490{
491 _path = path;
492 _name = name;
493
494 ignoreAlphaChannelTransparency(!options._alphaChannelTransparency);
495
496 // We can handle DDS files directly in a very FATAL way
497 if (hasExtension(_name, Paths::Textures::g_ddsExtension))
498 {
499 _loadingData._loadedDDSData = loadDDS_NVTT(srgb, refWidth, refHeight, _path, _name);
502
503 return true;
504 }
505
506 const string fullPath = (_path / _name).string();
507 FILE* f = stbi__fopen(fullPath.c_str(), "rb");
508 if ( !f )
509 {
510 return false;
511 }
512 SCOPE_EXIT { fclose(f); };
513
514
515 I32 width = 0, height = 0, comp = 0;
516
517 stbi_uc* dataLDR = nullptr;
518 stbi__uint16* data16Bit = nullptr;
519 F32* dataHDR = nullptr;
520 U32* dataUINT = nullptr;
521 glm::detail::hdata* dataHalf = nullptr;
522
523 if ( stbi_is_hdr_from_file( f ) == 1 )
524 {
526 }
527 else if ( stbi_is_16_bit_from_file( f ) == 1 )
528 {
530 }
531 else
532 {
534 }
535
536 bool useCache = Texture::UseTextureDDSCache() && options._useDDSCache;
538 {
539 options._useDDSCache = false;
540 useCache = false;
541 }
542
543 bool createDDS = useCache || _loadingData._createdDDSData;
544
545 // We either want to convert or we already have DDS faces/layers
546 if ( _loadingData._fileIndex > 0u)
547 {
548 useCache = _loadingData._loadedDDSData;
549 options._useDDSCache = useCache;
550 options._waitForDDSConversion = useCache;
551 }
552
553 if ( useCache || createDDS )
554 {
555 STUBBED("Get rid of DevIL completely! It is really really bad for DDS handling (quality/performance) compared to the alternatives -Ionut");
556
558
559 const ResourcePath cachePath = Paths::Textures::g_metadataLocation / _path;
560 const string cacheFileName = Util::StringFormat( "{}.{}", _name, Paths::Textures::g_ddsExtension );
561 const string cacheFilePath = (cachePath / cacheFileName).string();
562
563 // Try and save regular images to DDS for better compression next time
564 if ( createDirectory(cachePath) != FileError::NONE )
565 {
567 }
568
569 Task* ddsConversionTask = nullptr;
570 if ( !fileExists(ResourcePath{ cacheFilePath }) )
571 {
572 ddsConversionTask = CreateTask( [fullPath, cacheFilePath, options]( const Task& )
573 {
574 //LockGuard<Mutex> lock(s_imageLoadingMutex);
575
576 nvtt::Context context;
577 context.enableCudaAcceleration(true);
578
579 nvtt::Surface image;
580 bool hasAlpha = false;
581 if (image.load(fullPath.c_str(), &hasAlpha))
582 {
583 constexpr bool isGreyScale = false;
584 const nvtt::Format outputFormat = nvttHelpers::getNVTTFormat(options._outputFormat, options._isNormalMap, hasAlpha, isGreyScale);
585
586 // Setup compression options.
587 nvtt::CompressionOptions compressionOptions;
588 compressionOptions.setFormat(outputFormat);
589 compressionOptions.setQuality(options._fastCompression ? nvtt::Quality::Quality_Fastest : nvtt::Quality::Quality_Normal);
590 if (outputFormat == nvtt::Format_BC6)
591 {
592 compressionOptions.setPixelType(nvtt::PixelType_UnsignedFloat);
593 }
594 else if (outputFormat == nvtt::Format_BC2)
595 {
596 // Dither alpha when using BC2.
597 compressionOptions.setQuantization(/*color dithering*/false, /*alpha dithering*/true, /*binary alpha*/false);
598 }
599 else if (outputFormat == nvtt::Format_BC1a)
600 {
601 // Binary alpha when using BC1a.
602 compressionOptions.setQuantization(/*color dithering*/false, /*alpha dithering*/true, /*binary alpha*/true, 127);
603 }
604
605 if (nvttHelpers::isBC1n(outputFormat, options._isNormalMap))
606 {
607 compressionOptions.setColorWeights(1, 1, 0);
608 }
609
610 nvtt::OutputOptions outputOptions;
611 outputOptions.setFileName(cacheFilePath.c_str() );
612 nvttHelpers::ErrorHandler errorHandler;
613 outputOptions.setErrorHandler(&errorHandler);
614 if (outputFormat == nvtt::Format_BC6 || outputFormat == nvtt::Format_BC7)
615 {
616 outputOptions.setContainer(nvtt::Container_DDS10);
617 }
618 else
619 {
620 outputOptions.setContainer(nvtt::Container_DDS);
621 }
622 if (options._outputSRGB)
623 {
624 outputOptions.setSrgbFlag(true);
625 }
626
627 image.setNormalMap(options._isNormalMap);
628
629 if (!context.outputHeader(image, image.countMipmaps(), compressionOptions, outputOptions))
630 {
631 DIVIDE_UNEXPECTED_CALL();
632 }
633
634 F32 coverage = 0.f;
635 if (options._isNormalMap)
636 {
637 image.normalizeNormalMap();
638 }
639 else
640 {
641 if (hasAlpha && options._alphaChannelTransparency)
642 {
643 coverage = image.alphaTestCoverage(Config::ALPHA_DISCARD_THRESHOLD);
644 image.setAlphaMode(nvtt::AlphaMode::AlphaMode_Transparency);
645 }
646 else
647 {
648 image.setAlphaMode(nvtt::AlphaMode::AlphaMode_None);
649 }
650 }
651 if (!context.compress(image, 0, 0, compressionOptions, outputOptions))
652 {
653 DIVIDE_UNEXPECTED_CALL();
654 }
655
656 // Build and output mipmaps.
657 if (!options._skipMipMaps)
658 {
659 I32 m = 1;
660 while (image.buildNextMipmap(nvttHelpers::getNVTTMipFilter(options._mipFilter)))
661 {
662 if (options._isNormalMap)
663 {
664 image.normalizeNormalMap();
665 }
666 else
667 {
668 if (hasAlpha && options._alphaChannelTransparency)
669 {
670 image.scaleAlphaToCoverage(coverage, Config::ALPHA_DISCARD_THRESHOLD);
671 }
672 }
673
674 context.compress(image, 0, m, compressionOptions, outputOptions);
675 m++;
676 }
677 }
678
679 }
680 });
681
682 Start(
683 *ddsConversionTask,
684 context.taskPool( TaskPoolType::HIGH_PRIORITY ),
685 options._waitForDDSConversion ? TaskPriority::REALTIME : TaskPriority::DONT_CARE
686 );
687 }
688
689 if ( useCache )
690 {
691 if ( ddsConversionTask != nullptr && (options._waitForDDSConversion || _loadingData._loadedDDSData) )
692 {
693 Wait(*ddsConversionTask, context.taskPool( TaskPoolType::HIGH_PRIORITY ));
694 ddsConversionTask = nullptr;
695 }
696
697 if ( fileExists( ResourcePath{ cacheFilePath }) && !ddsConversionTask )
698 {
699 if (loadDDS_NVTT( srgb, refWidth, refHeight, cachePath, cacheFileName ))
700 {
701 _loadingData._loadedDDSData = true;
702 ++_loadingData._fileIndex;
703 return true;
704 }
705 else
706 {
707 Console::errorfn(LOCALE_STR("ERROR_IMAGE_TOOLS_DDS_LOAD_ERROR"), cachePath / cacheFileName );
708 if (!isRetry)
709 {
710 if (deleteFile( cachePath, cacheFileName ) != FileError::NONE)
711 {
712 Console::errorfn(LOCALE_STR("ERROR_IMAGE_TOOLS_DDS_DELETE_ERROR"), cachePath / cacheFileName );
713 }
714
715 return loadFromFile( context, srgb, refWidth, refHeight, path, name, options, true );
716 }
717
718 }
719 }
720 }
721 }
722
723 // If TRUE: flip the image vertically, so the first pixel in the output array is the bottom left
724 stbi_set_flip_vertically_on_load_thread(UseUpperLeftOrigin() ? 0 : 1);
725
726 _hasDummyAlphaChannel = false;
727 {
728 I32 x, y, n;
729 if (stbi_info_from_file(f, &x, &y, &n) && n == 3)
730 {
731 _hasDummyAlphaChannel = true;
732 Console::warnfn(LOCALE_STR("WARN_IMAGETOOLS_RGB_FORMAT"), fullPath);
733 }
734 }
735
736 const auto req_comp = _hasDummyAlphaChannel ? STBI_rgb_alpha : STBI_default;
737 switch ( _sourceDataType )
738 {
739 case SourceDataType::FLOAT:
740 {
741 dataHDR = stbi_loadf_from_file( f, &width, &height, &comp, req_comp );
742 _dataType = GFXDataFormat::FLOAT_32;
743 } break;
744 case SourceDataType::SHORT:
745 {
746 data16Bit = stbi_load_from_file_16( f, &width, &height, &comp, req_comp );
748 } break;
749 case SourceDataType::BYTE:
750 {
751 dataLDR = stbi_load_from_file( f, &width, &height, &comp, req_comp );
753 } break;
754 default: break;
755 }
756
757 if (dataHDR == nullptr && data16Bit == nullptr && dataLDR == nullptr)
758 {
759 Console::errorfn(LOCALE_STR("ERROR_IMAGETOOLS_INVALID_IMAGE_FILE"), fullPath, stbi_failure_reason() );
760 return false;
761 }
762
763 if (_hasDummyAlphaChannel)
764 {
765 comp = req_comp;
766 }
767
768 ImageLayer& layer = _layers.emplace_back();
769
770 if (refWidth != 0 && refHeight != 0 && (refWidth != width || refHeight != height))
771 {
772 if ( data16Bit != nullptr)
773 {
774 U16* resizedData16 = (U16*)STBI_MALLOC(to_size(refWidth)* refHeight * (_bpp / 8));
775 const I32 ret = stbir_resize_uint16_generic(data16Bit, width, height, 0,
776 resizedData16, refWidth, refHeight, 0,
777 comp, -1, 0,
778 STBIR_EDGE_CLAMP, STBIR_FILTER_DEFAULT, STBIR_COLORSPACE_LINEAR,
779 nullptr);
780 if (ret == 1) {
781 width = refWidth;
782 height = refHeight;
783 stbi_image_free(data16Bit);
784 data16Bit = resizedData16;
785 }
786 }
787 else if ( dataHDR != nullptr )
788 {
789 F32* resizedDataHDR = (F32*)STBI_MALLOC(to_size(refWidth) * refHeight * (_bpp / 8));
790 const I32 ret = stbir_resize_float(dataHDR, width, height, 0, resizedDataHDR, refWidth, refHeight, 0, comp);
791 if (ret == 1) {
792 width = refWidth;
793 height = refHeight;
794 stbi_image_free(dataHDR);
795 dataHDR = resizedDataHDR;
796 }
797 }
798 else
799 {
800 U8* resizedDataLDR = (U8*)STBI_MALLOC(to_size(refWidth) * refHeight * (_bpp / 8));
801 const I32 ret = srgb ? stbir_resize_uint8_srgb(dataLDR, width, height, 0, resizedDataLDR, refWidth, refHeight, 0, comp, -1, 0)
802 : stbir_resize_uint8(dataLDR, width, height, 0, resizedDataLDR, refWidth, refHeight, 0, comp);
803 if (ret == 1) {
804 width = refWidth;
805 height = refHeight;
806 stbi_image_free(dataLDR);
807 dataLDR = resizedDataLDR;
808 }
809 }
810 }
811
812 if (_requestedDataFormat != GFXDataFormat::COUNT && _requestedDataFormat != _dataType)
813 {
814 stbi_ldr_to_hdr_scale( 1.f );
815 stbi_ldr_to_hdr_gamma( 1.f );
816
817 switch (_requestedDataFormat)
818 {
820 {
821 switch ( _sourceDataType )
822 {
823 case SourceDataType::BYTE: break;
824 case SourceDataType::SHORT:
825 {
826 dataLDR = stbi__convert_16_to_8( data16Bit, width, height, comp );
827 data16Bit = nullptr;
828 } break;
829 case SourceDataType::FLOAT:
830 {
831 dataLDR = stbi__hdr_to_ldr( dataHDR, width, height, comp );
832 dataHDR = nullptr;
833 } break;
834 default: DIVIDE_UNEXPECTED_CALL(); break;
835 };
836 _sourceDataType = SourceDataType::BYTE;
837 } break;
839 {
840 switch ( _sourceDataType )
841 {
842 case SourceDataType::BYTE:
843 {
844 data16Bit = stbi__convert_8_to_16( dataLDR, width, height, comp );
845 dataLDR = nullptr;
846 } break;
847 case SourceDataType::SHORT: break;
848 case SourceDataType::FLOAT:
849 {
850 data16Bit = stbi__hdr_to_16( dataHDR, width, height, comp );
851 dataHDR = nullptr;
852 } break;
853 default: DIVIDE_UNEXPECTED_CALL(); break;
854 };
855 _sourceDataType = SourceDataType::SHORT;
856 } break;
858 {
859 switch ( _sourceDataType )
860 {
861 case SourceDataType::BYTE:
862 {
863 dataUINT = stbi_convert<U32, stbi_uc>(dataLDR, width, height, comp);
864 dataLDR = nullptr;
865 } break;
866 case SourceDataType::SHORT:
867 {
868 dataUINT = stbi_convert<U32, stbi__uint16>( data16Bit, width, height, comp );
869 data16Bit = nullptr;
870 } break;
871 case SourceDataType::FLOAT:
872 {
873 data16Bit = stbi__hdr_to_16( dataHDR, width, height, comp );
874 dataHDR = nullptr;
875 dataUINT = stbi_convert<U32, stbi__uint16>( data16Bit, width, height, comp );
876 data16Bit = nullptr;
877 } break;
878 default: DIVIDE_UNEXPECTED_CALL(); break;
879 };
880 _sourceDataType = SourceDataType::UINT;
881 } break;
885 {
886 DIVIDE_UNEXPECTED_CALL_MSG("Signed data types not supported when loading from file!");
887 } break;
889 {
890 switch ( _sourceDataType )
891 {
892 case SourceDataType::BYTE:
893 {
894 dataHalf = stbi__ldr_to_half( dataLDR, width, height, comp );
895 dataLDR = nullptr;
896 }break;
897 case SourceDataType::SHORT:
898 {
899 dataHalf = stbi__16_to_half( data16Bit, width, height, comp );
900 data16Bit = nullptr;
901 }break;
902 case SourceDataType::FLOAT:
903 {
904 dataHalf = stbi__hdr_to_half( dataHDR, width, height, comp );
905 dataHDR = nullptr;
906 }break;
907 default: DIVIDE_UNEXPECTED_CALL(); break;
908 };
909 _sourceDataType = SourceDataType::HALF;
910 } break;
912 {
913 switch ( _sourceDataType )
914 {
915 case SourceDataType::BYTE:
916 {
917 dataHDR = stbi__ldr_to_hdr( dataLDR, width, height, comp );
918 dataLDR = nullptr;
919 }break;
920 case SourceDataType::SHORT:
921 {
922 dataHDR = stbi__16_to_hdr( data16Bit, width, height, comp );
923 data16Bit = nullptr;
924 }break;
925 case SourceDataType::FLOAT: break;
926 default: DIVIDE_UNEXPECTED_CALL(); break;
927 }
928 _sourceDataType = SourceDataType::FLOAT;
929 } break;
930 default: DIVIDE_UNEXPECTED_CALL_MSG("Invalid requested texture format!"); break;
931 }
932
933 _dataType = _requestedDataFormat;
934 }
935
936 DIVIDE_ASSERT(comp != 3, "RGB textures (e.g. 24bit) not supported due to Vulkan limitations");
937
938 switch (comp)
939 {
940 case 1 : _format = GFXImageFormat::RED; break;
941 case 2 : _format = GFXImageFormat::RG; break;
942 case 4 : _format = GFXImageFormat::RGBA; break;
943 default:
945 break;
946 }
947
948 _bpp = to_U8( ((dataUINT != nullptr || dataHDR != nullptr) ? 32u : (data16Bit != nullptr || dataHalf != nullptr) ? 16u : 8u) * comp );
949
950 bool ret = false;
951 const size_t dataSize = to_size(width) * height * comp;
952 if (dataHDR != nullptr)
953 {
954 ret = layer.allocateMip(dataHDR, dataSize, to_U16(width), to_U16(height), 1u, to_U8(comp));
955 stbi_image_free(dataHDR);
956 }
957 else if ( dataUINT != nullptr)
958 {
959 ret = layer.allocateMip( dataUINT, dataSize, to_U16(width), to_U16(height), 1u, to_U8(comp));
960 stbi_image_free( dataUINT );
961 }
962 else if (data16Bit != nullptr)
963 {
964 ret = layer.allocateMip(data16Bit, dataSize, to_U16(width), to_U16(height), 1u, to_U8(comp));
965 stbi_image_free(data16Bit);
966 }
967 else if ( dataHalf != nullptr)
968 {
969 ret = layer.allocateMip( dataHalf, dataSize, to_U16(width), to_U16(height), 1u, to_U8(comp));
970 stbi_image_free( dataHalf );
971 }
972 else if (dataLDR != nullptr)
973 {
974 ret = layer.allocateMip(dataLDR, dataSize, to_U16(width), to_U16(height), 1u, to_U8(comp));
975 stbi_image_free(dataLDR);
976 }
977 else
978 {
980 }
981
982 ++_loadingData._fileIndex;
983 return ret;
984}
985
986bool ImageData::loadDDS_NVTT([[maybe_unused]] const bool srgb, const U16 refWidth, const U16 refHeight, const ResourcePath& path, const std::string_view name)
987{
988 //ToDo: Use a better DDS loader
989 return loadDDS_IL(srgb, refWidth, refHeight, path, name);
990}
991
992bool ImageData::loadDDS_IL([[maybe_unused]] const bool srgb, const U16 refWidth, const U16 refHeight, const ResourcePath& path, const std::string_view name)
993{
994
995 LockGuard<Mutex> lock(s_imageLoadingMutex);
996 string devilErrors;
997
998 ILuint imageID = 0u;
999 ilGenImages(1, &imageID);
1000 ilBindImage(imageID);
1001 SCOPE_EXIT{
1002 ilDeleteImage(imageID);
1003 if ( checkError( devilErrors ) )
1004 {
1005 Console::errorfn( LOCALE_STR( "ERROR_IMAGE_TOOLS_DEVIL_ERROR" ), devilErrors );
1006 }
1007 };
1008
1009 if (checkError( devilErrors ))
1010 {
1011 Console::errorfn( LOCALE_STR( "ERROR_IMAGE_TOOLS_DEVIL_ERROR" ), devilErrors );
1012 }
1013
1014
1015 const string fullPath = (path / name).string();
1016 if (ilLoadImage(fullPath.c_str() ) == IL_FALSE)
1017 {
1018 if ( checkError( devilErrors ) )
1019 {
1020 Console::errorfn( LOCALE_STR( "ERROR_IMAGE_TOOLS_DEVIL_ERROR" ), devilErrors );
1021 }
1022 Console::errorfn(LOCALE_STR("ERROR_IMAGETOOLS_INVALID_IMAGE_FILE"), fullPath, LOCALE_STR("ERROR_UNKNOWN") );
1023 return false;
1024 }
1025
1026 const ILint dxtFormat = ilGetInteger(IL_DXTC_DATA_FORMAT);
1027 const bool compressed = dxtFormat == IL_DXT1 ||
1028 dxtFormat == IL_DXT1A||
1029 dxtFormat == IL_DXT2 ||
1030 dxtFormat == IL_DXT3 ||
1031 dxtFormat == IL_DXT4 ||
1032 dxtFormat == IL_DXT5;
1033
1034
1035 const auto flipActiveMip = [&]() {
1036 if (!s_useUpperLeftOrigin)
1037 {
1038 if (compressed)
1039 {
1040 ilFlipSurfaceDxtcData();
1041 }
1042 else
1043 {
1044 iluFlipImage();
1045 }
1046
1047 if ( checkError( devilErrors ) )
1048 {
1049 Console::errorfn( LOCALE_STR( "ERROR_IMAGE_TOOLS_DEVIL_ERROR" ), devilErrors );
1050 }
1051 }
1052 };
1053
1054 ILinfo imageInfo;
1055 iluGetImageInfo(&imageInfo);
1056 if ( checkError( devilErrors ) )
1057 {
1058 Console::errorfn( LOCALE_STR( "ERROR_IMAGE_TOOLS_DEVIL_ERROR" ), devilErrors );
1059 }
1060
1061 // Avoid, double, longs, unsigned ints, etc
1062 if (imageInfo.Type != IL_BYTE && imageInfo.Type != IL_UNSIGNED_BYTE &&
1063 imageInfo.Type != IL_FLOAT &&
1064 imageInfo.Type != IL_UNSIGNED_SHORT && imageInfo.Type != IL_SHORT) {
1065 ilConvertImage(imageInfo.Format, IL_FLOAT);
1066 imageInfo.Type = IL_FLOAT;
1067 if ( checkError( devilErrors ) )
1068 {
1069 Console::errorfn( LOCALE_STR( "ERROR_IMAGE_TOOLS_DEVIL_ERROR" ), devilErrors );
1070 }
1071 }
1072
1073 ILint channelCount = ilGetInteger(IL_IMAGE_CHANNELS);
1074 // We don't support paletted images (or RGB8 in Vulkan)
1075 if (imageInfo.Format == IL_COLOUR_INDEX || (channelCount == 3 && imageInfo.Type == IL_UNSIGNED_BYTE) )
1076 {
1077 Console::warnfn( LOCALE_STR( "WARN_IMAGETOOLS_RGB_FORMAT" ), fullPath );
1078
1079 ilConvertImage(IL_RGBA, IL_UNSIGNED_BYTE);
1080 imageInfo.Format = IL_RGBA;
1081 imageInfo.Type = IL_UNSIGNED_BYTE;
1082 channelCount = 4u;
1083 if ( checkError( devilErrors ) )
1084 {
1085 Console::errorfn( LOCALE_STR( "ERROR_IMAGE_TOOLS_DEVIL_ERROR" ), devilErrors );
1086 }
1087 }
1088
1089 size_t storageSizeFactor = 1u;
1090 switch (imageInfo.Type) {
1091 case IL_BYTE:
1092 _dataType = GFXDataFormat::SIGNED_BYTE;
1093 _sourceDataType = SourceDataType::BYTE;
1094 break;
1095 case IL_UNSIGNED_BYTE:
1096 _dataType = GFXDataFormat::UNSIGNED_BYTE;
1097 _sourceDataType = SourceDataType::BYTE;
1098 break;
1099 case IL_SHORT:
1100 _dataType = GFXDataFormat::SIGNED_SHORT;
1101 _sourceDataType = SourceDataType::SHORT;
1102 storageSizeFactor = 2;
1103 break;
1104 case IL_UNSIGNED_SHORT:
1106 _sourceDataType = SourceDataType::SHORT;
1107 storageSizeFactor = 2;
1108 break;
1109 case IL_FLOAT:
1110 _dataType = GFXDataFormat::FLOAT_32;
1111 _sourceDataType = SourceDataType::FLOAT;
1112 storageSizeFactor = 4;
1113 break;
1114
1115 default: return false;
1116 };
1117
1118 // Resize if needed
1119 if (refWidth != 0 && refHeight != 0 && (imageInfo.Width != refWidth || imageInfo.Height != refHeight))
1120 {
1121 if (iluScale(refWidth, refHeight, imageInfo.Depth))
1122 {
1123 imageInfo.Width = refWidth;
1124 imageInfo.Height = refHeight;
1125 }
1126 if ( checkError( devilErrors ) )
1127 {
1128 Console::errorfn( LOCALE_STR( "ERROR_IMAGE_TOOLS_DEVIL_ERROR" ), devilErrors );
1129 }
1130 }
1131
1132 if (compressed) {
1133 switch (dxtFormat) {
1134 case IL_DXT1: {
1135 _format = channelCount == 3 ? GFXImageFormat::DXT1_RGB : GFXImageFormat::DXT1_RGBA;
1136 } break;
1137 case IL_DXT3: {
1138 _format = GFXImageFormat::DXT3_RGBA;
1139 } break;
1140 case IL_DXT5: {
1141 _format = GFXImageFormat::DXT5_RGBA;
1142 } break;
1143 default: {
1145 return false;
1146 }
1147 }
1148 } else {
1149 switch (imageInfo.Format) {
1150 default:
1151 case IL_COLOUR_INDEX:
1153 return false;
1154 case IL_ALPHA:
1155 case IL_LUMINANCE:
1156 _format = GFXImageFormat::RED;
1157 break;
1158 case IL_LUMINANCE_ALPHA:
1159 _format = GFXImageFormat::RG;
1160 break;
1161 case IL_RGB:
1162 _format = GFXImageFormat::RGB;
1163 break;
1164 case IL_BGR:
1165 _format = GFXImageFormat::BGR;
1166 break;
1167 case IL_RGBA:
1168 _format = GFXImageFormat::RGBA;
1169 break;
1170 case IL_BGRA:
1171 _format = GFXImageFormat::BGRA;
1172 break;
1173 }
1174 }
1175
1176 const ILint numImages = ilGetInteger(IL_NUM_IMAGES) + 1;
1177 // ^^^^^^^^^^^^^ Querying for IL_NUM_IMAGES returns the number of images following the current one. Add 1 for the right image count!
1178 const bool isCube = ilGetInteger(IL_IMAGE_CUBEFLAGS) != 0 || numImages % 6 == 0;
1179
1180 ILint numFaces = ilGetInteger(IL_NUM_FACES) + 1;
1181
1182 _bpp = imageInfo.Bpp * 8;
1183
1184 _layers.reserve(_layers.size() + to_size(numFaces) * numImages);
1185 for (ILint image = 0; image < numImages; ++image) {
1186 // cube faces within DevIL philosophy are organized like this:
1187 //
1188 // image -> 1st face -> face index 0
1189 // face1 -> 2nd face -> face index 1
1190 // ...
1191 // face5 -> 6th face -> face index 5
1192 numFaces = ilGetInteger(IL_NUM_FACES) + 1;
1193
1194 for (I32 f = 0; f < numFaces; ++f) {
1195 // need to juggle with the faces to get them aligned with
1196 // how OpenGL expects cube faces ...
1197 const I32 face = determineFace(f, true, isCube);
1198 ImageLayer& layer = _layers.emplace_back();
1199
1200 for (ILuint m = 0u; m <= imageInfo.NumMips; ++m)
1201 {
1202 // DevIL frequently loses track of the current state
1203 ilBindImage(imageID);
1204 ilActiveImage(image);
1205 ilActiveFace(face);
1206 ilActiveMipmap(m);
1207 flipActiveMip();
1208 if ( checkError( devilErrors ) )
1209 {
1210 Console::errorfn( LOCALE_STR( "ERROR_IMAGE_TOOLS_DEVIL_ERROR" ), devilErrors );
1211 }
1212 const ILint width = ilGetInteger(IL_IMAGE_WIDTH);
1213 const ILint height = ilGetInteger(IL_IMAGE_HEIGHT);
1214 const ILint depth = ilGetInteger(IL_IMAGE_DEPTH);
1215 const ILuint size = compressed ? ilGetDXTCData( nullptr, 0, dxtFormat )
1216 : static_cast<ILuint>(ilGetInteger( IL_IMAGE_SIZE_OF_DATA ));
1217
1218 if ( checkError( devilErrors ) )
1219 {
1220 Console::errorfn( LOCALE_STR( "ERROR_IMAGE_TOOLS_DEVIL_ERROR" ), devilErrors );
1221 }
1222
1223 Byte* data = layer.allocateMip<Byte>(size,
1224 to_U16(width),
1225 to_U16(height),
1226 to_U16(depth),
1227 compressed ? 0 // 0 here means that our calculated size will be 0, thus our specified size will always be used
1228 : to_U8(channelCount * storageSizeFactor)); //For short and float type data we need to increase the available storage a bit (channel count assumes a Byte per component here)
1229 if (compressed)
1230 {
1231 ilGetDXTCData(data, size, dxtFormat);
1232 }
1233 else
1234 {
1235 memcpy(data, ilGetData(), size);
1236 }
1237 if ( checkError( devilErrors ) )
1238 {
1239 Console::errorfn( LOCALE_STR( "ERROR_IMAGE_TOOLS_DEVIL_ERROR" ), devilErrors );
1240 }
1241
1242
1243 if ( compressed && image == 0 && face == 0 && m == 0 )
1244 {
1245 _decompressedData.resize( to_size( imageInfo.Width ) * imageInfo.Height * imageInfo.Depth * 4 );
1246 ilCopyPixels( 0, 0, 0, imageInfo.Width, imageInfo.Height, imageInfo.Depth, IL_RGBA, IL_UNSIGNED_BYTE, _decompressedData.data() );
1247 if ( checkError( devilErrors ) )
1248 {
1249 Console::errorfn( LOCALE_STR( "ERROR_IMAGE_TOOLS_DEVIL_ERROR" ), devilErrors );
1250 }
1251 }
1252 }
1253 }
1254 }
1255
1256 return true;
1257}
1258
1259UColour4 ImageData::getColour(const I32 x, const I32 y, [[maybe_unused]] U32 layer, const U8 mipLevel) const
1260{
1261 UColour4 returnColour;
1262 getColour(x, y, returnColour.r, returnColour.g, returnColour.b, returnColour.a, mipLevel);
1263 return returnColour;
1264}
1265
1266namespace
1267{
1268 [[nodiscard]] FORCE_INLINE U8 F32ToU8Colour(const F32 val) noexcept { return to_U8((val / F32_MAX) * 255); }
1269 [[nodiscard]] FORCE_INLINE U8 U32ToU8Colour(const U32 val) noexcept { return to_U8(CLAMPED(val, 0u, 255u)); }
1270 [[nodiscard]] FORCE_INLINE U8 U16ToU8Colour(const U16 val) noexcept { return to_U8((val / ( U16_MAX * 1.f )) * 255); }
1271 [[nodiscard]] FORCE_INLINE U8 HalfToU8Colour(const glm::detail::hdata val) noexcept { return F32ToU8Colour(glm::detail::toFloat32(val)); }
1272 [[nodiscard]] FORCE_INLINE U8 U8ToU8Colour(const U8 val) noexcept { return val; }
1273};
1274
1275void ImageData::getColourComponent(const I32 x, const I32 y, const U8 comp, U8& c, const U32 layer, const U8 mipLevel) const {
1276 assert(comp >= 0 && comp < 4);
1277 assert(!IsCompressed(_format) || mipLevel == 0);
1278 assert(_layers.size() > layer);
1279
1280 if (!HasAlphaChannel(_format) && comp == 3) {
1281 c = U8_MAX;
1282 return;
1283 }
1284
1285 const LayerData* mip = _layers[layer].getMip(mipLevel);
1286
1287 // Decompressed data is always UByte-RGBA
1288 const U32 pixelStride = IsCompressed(_format) ? 4 : _bpp / 8;
1289 const I32 idx = ((y * mip->_dimensions.width + x) * pixelStride) + comp;
1290 if (IsCompressed(_format)) {
1291 // Decompressed data is always UByte-RGBA
1292 c = _decompressedData[idx];
1293 } else {
1294 switch(_sourceDataType )
1295 {
1296 case SourceDataType::BYTE :
1297 {
1298 c = U8ToU8Colour( static_cast<const U8*>(mip->data())[idx] );
1299 } break;
1300 case SourceDataType::SHORT:
1301 {
1302 c = U16ToU8Colour( static_cast<const U16*>(mip->data())[idx] );
1303 } break;
1304 case SourceDataType::HALF:
1305 {
1306 c = HalfToU8Colour( static_cast<const glm::detail::hdata*>(mip->data())[idx] );
1307 } break;
1308 case SourceDataType::FLOAT:
1309 {
1310 c = F32ToU8Colour( static_cast<const F32*>(mip->data())[idx] );
1311 } break;
1312 case SourceDataType::UINT:
1313 {
1314 c = U32ToU8Colour( static_cast<const U32*>(mip->data())[idx] );
1315 } break;
1316 }
1317 }
1318}
1319
1320void ImageData::getColour(const I32 x, const I32 y, U8& r, U8& g, U8& b, U8& a, const U32 layer, const U8 mipLevel) const {
1321 assert(!IsCompressed(_format) || mipLevel == 0);
1322 assert(_layers.size() > layer);
1323
1324 const LayerData* mip = _layers[layer].getMip(mipLevel);
1325 // Decompressed data is always UByte-RGBA
1326 const U32 pixelStride = IsCompressed(_format) ? 4 : _bpp / 8;
1327 const I32 idx = ((y * mip->_dimensions.width + x) * pixelStride);
1328
1329 if (IsCompressed(_format)) {
1330 // Decompressed data is always UByte-RGBA
1331 const U8* src = _decompressedData.data();
1332 r = src[idx + 0];
1333 g = src[idx + 1];
1334 b = src[idx + 2];
1335 a = HasAlphaChannel(_format) ? src[idx + 3] : 255;
1336 } else {
1337 switch ( _sourceDataType )
1338 {
1339 case SourceDataType::BYTE:
1340 {
1341 const U8* src = static_cast<U8*>(mip->data());
1342 r = U8ToU8Colour( src[idx + 0] );
1343 g = U8ToU8Colour( src[idx + 1] );
1344 b = U8ToU8Colour( src[idx + 2] );
1345 a = HasAlphaChannel( _format ) ? U8ToU8Colour( src[idx + 3] ) : 255;
1346 } break;
1347 case SourceDataType::SHORT:
1348 {
1349 const U16* src = static_cast<U16*>(mip->data());
1350 r = U16ToU8Colour( src[idx + 0] );
1351 g = U16ToU8Colour( src[idx + 1] );
1352 b = U16ToU8Colour( src[idx + 2] );
1353 a = HasAlphaChannel( _format ) ? U16ToU8Colour( src[idx + 3] ) : 255;
1354 } break;
1355 case SourceDataType::HALF:
1356 {
1357 const U8* src = static_cast<U8*>(mip->data());
1358 r = HalfToU8Colour( src[idx + 0] );
1359 g = HalfToU8Colour( src[idx + 1] );
1360 b = HalfToU8Colour( src[idx + 2] );
1361 a = HasAlphaChannel( _format ) ? HalfToU8Colour( src[idx + 3] ) : 255;
1362 } break;
1363 case SourceDataType::FLOAT:
1364 {
1365 const F32* src = static_cast<F32*>(mip->data());
1366 r = F32ToU8Colour( src[idx + 0] );
1367 g = F32ToU8Colour( src[idx + 1] );
1368 b = F32ToU8Colour( src[idx + 2] );
1369 a = HasAlphaChannel( _format ) ? F32ToU8Colour( src[idx + 3] ) : 255;
1370 } break;
1371 case SourceDataType::UINT:
1372 {
1373 const U8* src = static_cast<U8*>(mip->data());
1374 r = U32ToU8Colour( src[idx + 0] );
1375 g = U32ToU8Colour( src[idx + 1] );
1376 b = U32ToU8Colour( src[idx + 2] );
1377 a = HasAlphaChannel( _format ) ? U32ToU8Colour( src[idx + 3] ) : 255;
1378 } break;
1379 }
1380 }
1381}
1382
1383namespace
1384{
1385 template<size_t src_comp_num, bool source_is_BGR>
1386 void flipAndConvertToRGB8( const Byte* sourceBuffer, Byte* destBuffer, const U16 width, const U16 height, const U8 bytesPerPixel )
1387 {
1388 // The only reason this templated function exist is to collapse conversion to a single loop while also avoiding expensive if checks on the number of components in the middle of it.
1389 // The template allows us to use the compile-time constexpr check for number of source components.
1390
1391 for ( I32 j = height - 1; j >= 0; --j )
1392 {
1393 const Byte* src = sourceBuffer + (bytesPerPixel * width * j);
1394 Byte* dst = destBuffer + (3 * width * (height - 1 - j));
1395
1396 for (U16 i = 0u; i < width; ++i)
1397 {
1398 *dst++ = *(src + (source_is_BGR ? 2 : 0));
1399
1400 if constexpr ( src_comp_num > 1 )
1401 {
1402 *dst++ = *(src + 1);
1403
1404 if constexpr (src_comp_num > 2 )
1405 {
1406 *dst++ = *(src + (source_is_BGR ? 0 : 2));
1407 }
1408 }
1409
1410 src += bytesPerPixel;
1411 }
1412 }
1413 }
1414}
1415
1416bool SaveImage(const ResourcePath& filename, const U16 width, const U16 height, const U8 numberOfComponents, const U8 bytesPerPixel, const bool sourceIsBGR, const Byte* imageData, const SaveImageFormat format) {
1417 // Flip data upside-down and convert to RGB8
1418 if ( width == 0u || height == 0 || numberOfComponents == 0 )
1419 {
1420 return false;
1421 }
1422
1423 vector<Byte> pix( width * height * 3 );
1424 Byte* dest = pix.data();
1425
1426 switch (numberOfComponents )
1427 {
1428 //Granted, this is not pretty, but we have:
1429 // A) a limited number of components to handle (R/RG/RGB/RGBA)
1430 // B) way faster&cleaner to use the template than an if-check or switch and copy-pasting the loop a few times.
1431
1432 case 1: sourceIsBGR ? flipAndConvertToRGB8<1, true>( imageData, dest, width, height, bytesPerPixel ) : flipAndConvertToRGB8<1, false>( imageData, dest, width, height, bytesPerPixel ); break;
1433 case 2: sourceIsBGR ? flipAndConvertToRGB8<2, true>( imageData, dest, width, height, bytesPerPixel ) : flipAndConvertToRGB8<2, false>( imageData, dest, width, height, bytesPerPixel ); break;
1434 case 3: sourceIsBGR ? flipAndConvertToRGB8<3, true>( imageData, dest, width, height, bytesPerPixel ) : flipAndConvertToRGB8<3, false>( imageData, dest, width, height, bytesPerPixel ); break;
1435 case 4: sourceIsBGR ? flipAndConvertToRGB8<4, true>( imageData, dest, width, height, bytesPerPixel ) : flipAndConvertToRGB8<4, false>( imageData, dest, width, height, bytesPerPixel ); break;
1436 default: DIVIDE_UNEXPECTED_CALL(); return false;
1437 }
1438
1439 switch (format)
1440 {
1441 case SaveImageFormat::PNG: return stbi_write_png(filename.string().c_str(), width, height, 3, pix.data(), width * 3 * sizeof(Byte)) == 1;
1442 case SaveImageFormat::BMP: return stbi_write_bmp(filename.string().c_str(), width, height, 3, pix.data()) == 1;
1443 case SaveImageFormat::TGA: return stbi_write_tga(filename.string().c_str(), width, height, 3, pix.data()) == 1;
1444 case SaveImageFormat::JPG: return stbi_write_jpg(filename.string().c_str(), width, height, 3, pix.data(), 85) == 1;
1445 default: DIVIDE_UNEXPECTED_CALL(); break;
1446 }
1447
1448 return false;
1449}
1450
1451bool SaveImageHDR(const ResourcePath& filename, const U16 width, const U16 height, const U8 numberOfComponents, [[maybe_unused]] const U8 bytesPerPixel, [[maybe_unused]] const bool sourceIsBGR, const F32* imageData)
1452{
1453 return stbi_write_hdr(filename.string().c_str(), width, height, numberOfComponents, imageData) == 1;
1454}
1455} // namespace Divide::ImageTools
1456
#define stbi__float2int(x)
Definition: ImageTools.cpp:221
#define LOCALE_STR(X)
Definition: Localization.h:91
#define SCOPE_EXIT
#define DIVIDE_ASSERT(...)
#define STUBBED(x)
#define DIVIDE_UNEXPECTED_CALL()
#define DIVIDE_UNEXPECTED_CALL_MSG(X)
#define FORCE_INLINE
static bool UseTextureDDSCache() noexcept
Definition: Texture.cpp:85
FORCE_INLINE U8 F32ToU8Colour(const F32 val) noexcept
FORCE_INLINE U8 U32ToU8Colour(const U32 val) noexcept
FORCE_INLINE I32 determineFace(const I32 i, const bool isDDS, const bool isCube) noexcept
Definition: ImageTools.cpp:146
FORCE_INLINE U8 U8ToU8Colour(const U8 val) noexcept
FORCE_INLINE U8 U16ToU8Colour(const U16 val) noexcept
static float * stbi__16_to_hdr(stbi__uint16 *data, int x, int y, int comp)
Definition: ImageTools.cpp:401
FORCE_INLINE U8 HalfToU8Colour(const glm::detail::hdata val) noexcept
void flipAndConvertToRGB8(const Byte *sourceBuffer, Byte *destBuffer, const U16 width, const U16 height, const U8 bytesPerPixel)
static glm::detail::hdata * stbi__hdr_to_half(float *data, int x, int y, int comp)
Definition: ImageTools.cpp:270
bool checkError(string &messageInOut) noexcept
Definition: ImageTools.cpp:158
static glm::detail::hdata * stbi__16_to_half(stbi__uint16 *data, int x, int y, int comp)
Definition: ImageTools.cpp:356
static stbi__uint16 * stbi__hdr_to_16(float *data, int x, int y, int comp)
Definition: ImageTools.cpp:222
static To * stbi_convert(From *data, int x, int y, int comp)
Definition: ImageTools.cpp:446
static glm::detail::hdata * stbi__ldr_to_half(stbi_uc *data, int x, int y, int comp)
Definition: ImageTools.cpp:312
static nvtt::MipmapFilter getNVTTMipFilter(const MipMapFilter filter) noexcept
Definition: ImageTools.cpp:128
static nvtt::Format getNVTTFormat(const ImageOutputFormat outputFormat, const bool isNormalMap, const bool hasAlpha, const bool isGreyscale) noexcept
Definition: ImageTools.cpp:97
static bool isBC1n(const nvtt::Format format, const bool isNormalMap) noexcept
Definition: ImageTools.cpp:92
bool SaveImage(const ResourcePath &filename, U16 width, U16 height, U8 numberOfComponents, U8 bytesPerPixel, const bool sourceIsBGR, const Byte *imageData, SaveImageFormat format)
Save an image to file of the desired format. Only R/RG/RGB/RGBA 8 bits per pixel data is supported as...
bool UseUpperLeftOrigin() noexcept
Definition: ImageTools.cpp:202
void OnStartup(bool upperLeftOrigin)
Definition: ImageTools.cpp:186
bool SaveImageHDR(const ResourcePath &filename, U16 width, U16 height, U8 numberOfComponents, U8 bytesPerPixel, const bool sourceIsBGR, const F32 *imageData)
Save an HDR image to file of the desired format.
constexpr bool g_KeepDevILDDSCompatibility
Definition: ImageTools.cpp:28
Str StringFormat(const char *fmt, Args &&...args)
std::lock_guard< mutex > LockGuard
Definition: SharedMutex.h:55
std::byte Byte
bool hasExtension(const ResourcePath &filePath, const std::string_view extensionNoDot)
std::mutex Mutex
Definition: SharedMutex.h:40
constexpr U16 to_U16(const T value)
void Wait(const Task &task, TaskPool &pool)
Definition: Task.cpp:20
int32_t I32
uint8_t U8
bool DebugBreak(const bool condition) noexcept
Task * CreateTask(Predicate &&threadedFunction, bool allowedInIdle=true)
Definition: TaskPool.inl:45
FileError createDirectory(const ResourcePath &path)
eastl::vector< Type > vector
Definition: Vector.h:42
@ REALTIME
not threaded
constexpr U16 U16_MAX
constexpr F32 F32_MAX
uint16_t U16
constexpr U8 U8_MAX
constexpr U8 to_U8(const T value)
::value constexpr T CLAMPED(T n, T min, T max) noexcept
Definition: MathHelper.inl:126
void Start(Task &task, TaskPool &pool, TaskPriority priority=TaskPriority::DONT_CARE, const DELEGATE< void > &onCompletionFunction={})
Definition: Task.cpp:9
constexpr size_t to_size(const T value)
FileError deleteFile(const ResourcePath &filePath, const std::string_view fileName)
bool fileExists(const ResourcePath &filePathAndName)
bool IsCompressed(GFXImageFormat format) noexcept
bool HasAlphaChannel(GFXImageFormat format) noexcept
uint32_t U32
static NO_INLINE void errorfn(const char *format, T &&... args)
static NO_INLINE void warnfn(const char *format, T &&... args)
const std::string_view name() const noexcept
the filename from which the image is created
Definition: ImageTools.h:155
enum Divide::ImageTools::ImageData::SourceDataType _sourceDataType
Str< 256 > _name
the actual image filename
Definition: ImageTools.h:203
bufferPtr data(const U32 layer, const U8 mipLevel) const
set and get the image's actual data
Definition: ImageTools.h:127
ResourcePath _path
the image path
Definition: ImageTools.h:201
bool loadFromMemory(const Byte *data, size_t size, U16 width, U16 height, U16 depth, U8 numComponents)
Definition: ImageTools.cpp:206
bool loadDDS_NVTT(bool srgb, U16 refWidth, U16 refHeight, const ResourcePath &path, std::string_view name)
Definition: ImageTools.cpp:986
bool loadFromFile(PlatformContext &context, bool srgb, U16 refWidth, U16 refHeight, const ResourcePath &path, std::string_view name)
creates this image instance from the specified data
Definition: ImageTools.cpp:212
vector< ImageLayer > _layers
Definition: ImageTools.h:197
FORCE_INLINE ResourcePath fullPath() const noexcept
Definition: ImageTools.h:176
T * allocateMip(const T *data, size_t len, U16 width, U16 height, U16 depth, const U8 numComponents)
Definition: ImageTools.h:85
bool _alphaChannelTransparency
If false, the alpha channel represents arbitrary data (e.g. in splatmaps)
Definition: ImageToolsFwd.h:72
bool _waitForDDSConversion
If false, we will load the src image and convert to DDS in the background. If true,...
Definition: ImageToolsFwd.h:67
virtual bufferPtr data() const =0
vec3< U16 > _dimensions
with and height
Definition: ImageTools.h:48
void error(nvtt::Error e) override
Definition: ImageTools.cpp:32
OutputHandler(nvtt::Format format, bool discardAlpha, const U8 numComponents)
Definition: ImageTools.cpp:61
virtual bool writeData(const void *data, int size) override
Output data. Compressed data is output as soon as it's generated to minimize memory allocations.
Definition: ImageTools.cpp:84
virtual void beginImage(int size, int width, int height, int depth, int face, int miplevel) override
Indicate the start of a new compressed image that's part of the final texture.
Definition: ImageTools.cpp:69
StringReturnType< N > string() const noexcept
Definition: ResourcePath.h:64