diff --git a/3rdparty/nvtt/nvtt.cpp b/3rdparty/nvtt/nvtt.cpp index aa1c2d2..0cc0df4 100644 --- a/3rdparty/nvtt/nvtt.cpp +++ b/3rdparty/nvtt/nvtt.cpp @@ -50,9 +50,9 @@ namespace nvtt for (uint32_t blockX = 0; blockX < ZOH::Tile::TILE_W; ++blockX) { nv::Vector4 color = srcRgba[blockY*srcRgbaStride + blockX]; - zohTile.data[blockY][blockX].x = float(int16_t(bx::halfFromFloat(color.x) ) ); - zohTile.data[blockY][blockX].y = float(int16_t(bx::halfFromFloat(color.y) ) ); - zohTile.data[blockY][blockX].z = float(int16_t(bx::halfFromFloat(color.z) ) ); + zohTile.data[blockY][blockX].x = float(int16_t(bx::halfFromFloat(bx::max(color.x, 0.0f) ) ) ); + zohTile.data[blockY][blockX].y = float(int16_t(bx::halfFromFloat(bx::max(color.y, 0.0f) ) ) ); + zohTile.data[blockY][blockX].z = float(int16_t(bx::halfFromFloat(bx::max(color.z, 0.0f) ) ) ); } } diff --git a/3rdparty/tinyexr/tinyexr.h b/3rdparty/tinyexr/tinyexr.h index 850cc4c..7ef09af 100644 --- a/3rdparty/tinyexr/tinyexr.h +++ b/3rdparty/tinyexr/tinyexr.h @@ -295,6 +295,9 @@ extern int FreeEXRHeader(EXRHeader *exr_header); // Free's internal data of EXRImage struct extern int FreeEXRImage(EXRImage *exr_image); +// Free's error message +extern void FreeEXRErrorMessage(const char *msg); + // Parse EXR version header of a file. extern int ParseEXRVersionFromFile(EXRVersion *version, const char *filename); @@ -303,10 +306,14 @@ extern int ParseEXRVersionFromMemory(EXRVersion *version, const unsigned char *memory, size_t size); // Parse single-part OpenEXR header from a file and initialize `EXRHeader`. +// When there was an error message, Application must free `err` with +// FreeEXRErrorMessage() extern int ParseEXRHeaderFromFile(EXRHeader *header, const EXRVersion *version, const char *filename, const char **err); // Parse single-part OpenEXR header from a memory and initialize `EXRHeader`. +// When there was an error message, Application must free `err` with +// FreeEXRErrorMessage() extern int ParseEXRHeaderFromMemory(EXRHeader *header, const EXRVersion *version, const unsigned char *memory, size_t size, @@ -314,6 +321,8 @@ extern int ParseEXRHeaderFromMemory(EXRHeader *header, // Parse multi-part OpenEXR headers from a file and initialize `EXRHeader*` // array. +// When there was an error message, Application must free `err` with +// FreeEXRErrorMessage() extern int ParseEXRMultipartHeaderFromFile(EXRHeader ***headers, int *num_headers, const EXRVersion *version, @@ -322,6 +331,8 @@ extern int ParseEXRMultipartHeaderFromFile(EXRHeader ***headers, // Parse multi-part OpenEXR headers from a memory and initialize `EXRHeader*` // array +// When there was an error message, Application must free `err` with +// FreeEXRErrorMessage() extern int ParseEXRMultipartHeaderFromMemory(EXRHeader ***headers, int *num_headers, const EXRVersion *version, @@ -333,6 +344,8 @@ extern int ParseEXRMultipartHeaderFromMemory(EXRHeader ***headers, // Application can free EXRImage using `FreeEXRImage` // Returns negative value and may set error string in `err` when there's an // error +// When there was an error message, Application must free `err` with +// FreeEXRErrorMessage() extern int LoadEXRImageFromFile(EXRImage *image, const EXRHeader *header, const char *filename, const char **err); @@ -342,6 +355,8 @@ extern int LoadEXRImageFromFile(EXRImage *image, const EXRHeader *header, // Application can free EXRImage using `FreeEXRImage` // Returns negative value and may set error string in `err` when there's an // error +// When there was an error message, Application must free `err` with +// FreeEXRErrorMessage() extern int LoadEXRImageFromMemory(EXRImage *image, const EXRHeader *header, const unsigned char *memory, const size_t size, const char **err); @@ -352,6 +367,8 @@ extern int LoadEXRImageFromMemory(EXRImage *image, const EXRHeader *header, // Application can free EXRImage using `FreeEXRImage` // Returns negative value and may set error string in `err` when there's an // error +// When there was an error message, Application must free `err` with +// FreeEXRErrorMessage() extern int LoadEXRMultipartImageFromFile(EXRImage *images, const EXRHeader **headers, unsigned int num_parts, @@ -364,6 +381,8 @@ extern int LoadEXRMultipartImageFromFile(EXRImage *images, // Application can free EXRImage using `FreeEXRImage` // Returns negative value and may set error string in `err` when there's an // error +// When there was an error message, Application must free `err` with +// FreeEXRErrorMessage() extern int LoadEXRMultipartImageFromMemory(EXRImage *images, const EXRHeader **headers, unsigned int num_parts, @@ -373,6 +392,8 @@ extern int LoadEXRMultipartImageFromMemory(EXRImage *images, // Saves multi-channel, single-frame OpenEXR image to a file. // Returns negative value and may set error string in `err` when there's an // error +// When there was an error message, Application must free `err` with +// FreeEXRErrorMessage() extern int SaveEXRImageToFile(const EXRImage *image, const EXRHeader *exr_header, const char *filename, const char **err); @@ -382,6 +403,8 @@ extern int SaveEXRImageToFile(const EXRImage *image, // Return the number of bytes if succes. // Returns negative value and may set error string in `err` when there's an // error +// When there was an error message, Application must free `err` with +// FreeEXRErrorMessage() extern size_t SaveEXRImageToMemory(const EXRImage *image, const EXRHeader *exr_header, unsigned char **memory, const char **err); @@ -390,6 +413,8 @@ extern size_t SaveEXRImageToMemory(const EXRImage *image, // Application must free memory of variables in DeepImage(image, offset_table) // Returns negative value and may set error string in `err` when there's an // error +// When there was an error message, Application must free `err` with +// FreeEXRErrorMessage() extern int LoadDeepEXR(DeepImage *out_image, const char *filename, const char **err); @@ -412,6 +437,8 @@ extern int LoadDeepEXR(DeepImage *out_image, const char *filename, // RGB(A) channels. // Returns negative value and may set error string in `err` when there's an // error +// When there was an error message, Application must free `err` with +// FreeEXRErrorMessage() extern int LoadEXRFromMemory(float **out_rgba, int *width, int *height, const unsigned char *memory, size_t size, const char **err); @@ -431,6 +458,7 @@ extern int LoadEXRFromMemory(float **out_rgba, int *width, int *height, #include #include #include +#include #include #include @@ -4409,9 +4437,8 @@ mz_uint tdefl_create_comp_flags_from_zip_params(int level, int window_bits, // C and C99, so no big deal) #pragma warning(disable : 4244) // 'initializing': conversion from '__int64' to // 'int', possible loss of data -#pragma warning( \ - disable : 4267) // 'argument': conversion from '__int64' to 'int', - // possible loss of data +#pragma warning(disable : 4267) // 'argument': conversion from '__int64' to + // 'int', possible loss of data #pragma warning(disable : 4996) // 'strdup': The POSIX name for this item is // deprecated. Instead, use the ISO C and C++ // conformant name: _strdup. @@ -6914,7 +6941,7 @@ void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, #ifdef _MSC_VER #pragma warning(pop) #endif -} +} // namespace miniz #else // Reuse MINIZ_LITTE_ENDIAN macro @@ -6939,6 +6966,16 @@ void *mz_zip_extract_archive_file_to_heap(const char *pZip_filename, // return bint.c[0] == 1; //} +static void SetErrorMessage(const std::string &msg, const char **err) { + if (err) { +#ifdef _WIN32 + (*err) = _strdup(msg.c_str()); +#else + (*err) = strdup(msg.c_str()); +#endif + } +} + static const int kEXRVersionSize = 8; static void cpy2(unsigned short *dst_val, const unsigned short *src_val) { @@ -7331,6 +7368,12 @@ static bool ReadChannelInfo(std::vector &channels, return false; } + const unsigned char *data_end = + reinterpret_cast(p) + 16; + if (data_end >= (data.data() + data.size())) { + return false; + } + memcpy(&info.pixel_type, p, sizeof(int)); p += 4; info.p_linear = static_cast(p[0]); // uchar @@ -7551,9 +7594,8 @@ static bool DecompressZip(unsigned char *dst, // C and C99, so no big deal) #pragma warning(disable : 4244) // 'initializing': conversion from '__int64' to // 'int', possible loss of data -#pragma warning( \ - disable : 4267) // 'argument': conversion from '__int64' to 'int', - // possible loss of data +#pragma warning(disable : 4267) // 'argument': conversion from '__int64' to + // 'int', possible loss of data #pragma warning(disable : 4996) // 'strdup': The POSIX name for this item is // deprecated. Instead, use the ISO C and C++ // conformant name: _strdup. @@ -8741,26 +8783,62 @@ static int hufEncode // return: output size (in bits) lc += 8; \ } -#define getCode(po, rlc, c, lc, in, out, oe) \ - { \ - if (po == rlc) { \ - if (lc < 8) getChar(c, lc, in); \ - \ - lc -= 8; \ - \ - unsigned char cs = (c >> lc); \ - \ - if (out + cs > oe) return false; \ - \ - unsigned short s = out[-1]; \ - \ - while (cs-- > 0) *out++ = s; \ - } else if (out < oe) { \ - *out++ = po; \ - } else { \ - return false; \ - } \ +#if 0 +#define getCode(po, rlc, c, lc, in, out, ob, oe) \ + { \ + if (po == rlc) { \ + if (lc < 8) getChar(c, lc, in); \ + \ + lc -= 8; \ + \ + unsigned char cs = (c >> lc); \ + \ + if (out + cs > oe) return false; \ + \ + /* TinyEXR issue 78 */ \ + unsigned short s = out[-1]; \ + \ + while (cs-- > 0) *out++ = s; \ + } else if (out < oe) { \ + *out++ = po; \ + } else { \ + return false; \ + } \ } +#else +static bool getCode(int po, int rlc, long long &c, int &lc, const char *&in, + const char *in_end, unsigned short *&out, + const unsigned short *ob, const unsigned short *oe) { + (void)ob; + if (po == rlc) { + if (lc < 8) { + /* TinyEXR issue 78 */ + if ((in + 1) >= in_end) { + return false; + } + + getChar(c, lc, in); + } + + lc -= 8; + + unsigned char cs = (c >> lc); + + if (out + cs > oe) return false; + + // Bounds check for safety + if ((out - 1) <= ob) return false; + unsigned short s = out[-1]; + + while (cs-- > 0) *out++ = s; + } else if (out < oe) { + *out++ = po; + } else { + return false; + } + return true; +} +#endif // // Decode (uncompress) ni bits based on encoding & decoding tables: @@ -8776,8 +8854,8 @@ static bool hufDecode(const long long *hcode, // i : encoding table { long long c = 0; int lc = 0; - unsigned short *outb = out; - unsigned short *oe = out + no; + unsigned short *outb = out; // begin + unsigned short *oe = out + no; // end const char *ie = in + (ni + 7) / 8; // input byte size // @@ -8800,7 +8878,16 @@ static bool hufDecode(const long long *hcode, // i : encoding table // lc -= pl.len; - getCode(pl.lit, rlc, c, lc, in, out, oe); + // std::cout << "lit = " << pl.lit << std::endl; + // std::cout << "rlc = " << rlc << std::endl; + // std::cout << "c = " << c << std::endl; + // std::cout << "lc = " << lc << std::endl; + // std::cout << "in = " << in << std::endl; + // std::cout << "out = " << out << std::endl; + // std::cout << "oe = " << oe << std::endl; + if (!getCode(pl.lit, rlc, c, lc, in, ie, out, outb, oe)) { + return false; + } } else { if (!pl.p) { return false; @@ -8827,7 +8914,9 @@ static bool hufDecode(const long long *hcode, // i : encoding table // lc -= l; - getCode(pl.p[j], rlc, c, lc, in, out, oe); + if (!getCode(pl.p[j], rlc, c, lc, in, ie, out, outb, oe)) { + return false; + } break; } } @@ -8854,7 +8943,9 @@ static bool hufDecode(const long long *hcode, // i : encoding table if (pl.len) { lc -= pl.len; - getCode(pl.lit, rlc, c, lc, in, out, oe); + if (!getCode(pl.lit, rlc, c, lc, in, ie, out, outb, oe)) { + return false; + } } else { return false; // invalidCode(); // wrong (long) code @@ -8927,9 +9018,9 @@ static int hufCompress(const unsigned short raw[], int nRaw, } static bool hufUncompress(const char compressed[], int nCompressed, - unsigned short raw[], int nRaw) { + std::vector *raw) { if (nCompressed == 0) { - if (nRaw != 0) return false; + if (raw->size() != 0) return false; return false; } @@ -8970,7 +9061,8 @@ static bool hufUncompress(const char compressed[], int nCompressed, } hufBuildDecTable(&freq.at(0), im, iM, &hdec.at(0)); - hufDecode(&freq.at(0), &hdec.at(0), ptr, nBits, iM, nRaw, raw); + hufDecode(&freq.at(0), &hdec.at(0), ptr, nBits, iM, raw->size(), + raw->data()); } // catch (...) //{ @@ -9198,9 +9290,9 @@ static bool DecompressPiz(unsigned char *outPtr, const unsigned char *inPtr, memset(bitmap.data(), 0, BITMAP_SIZE); const unsigned char *ptr = inPtr; - //minNonZero = *(reinterpret_cast(ptr)); + // minNonZero = *(reinterpret_cast(ptr)); tinyexr::cpy2(&minNonZero, reinterpret_cast(ptr)); - //maxNonZero = *(reinterpret_cast(ptr + 2)); + // maxNonZero = *(reinterpret_cast(ptr + 2)); tinyexr::cpy2(&maxNonZero, reinterpret_cast(ptr + 2)); ptr += 4; @@ -9224,13 +9316,12 @@ static bool DecompressPiz(unsigned char *outPtr, const unsigned char *inPtr, int length; - //length = *(reinterpret_cast(ptr)); + // length = *(reinterpret_cast(ptr)); tinyexr::cpy4(&length, reinterpret_cast(ptr)); ptr += sizeof(int); std::vector tmpBuffer(tmpBufSize); - hufUncompress(reinterpret_cast(ptr), length, &tmpBuffer.at(0), - static_cast(tmpBufSize)); + hufUncompress(reinterpret_cast(ptr), length, &tmpBuffer); // // Wavelet decoding @@ -9508,6 +9599,11 @@ static bool DecodePixelData(/* out */ unsigned char **out_images, const std::vector &channel_offset_list) { if (compression_type == TINYEXR_COMPRESSIONTYPE_PIZ) { // PIZ #if TINYEXR_USE_PIZ + if ((width == 0) || (num_lines == 0) || (pixel_data_size == 0)) { + // Invalid input #90 + return false; + } + // Allocate original data size. std::vector outBuf(static_cast( static_cast(width * num_lines) * pixel_data_size)); @@ -10007,6 +10103,12 @@ static bool DecodePixelData(/* out */ unsigned char **out_images, outLine += (height - 1 - y) * x_stride; } + if (reinterpret_cast(line_ptr + width) > + (data_ptr + data_len)) { + // Insufficient data size + return false; + } + for (int u = 0; u < width; u++) { tinyexr::FP16 hf; @@ -10035,6 +10137,12 @@ static bool DecodePixelData(/* out */ unsigned char **out_images, outLine += (height - 1 - y) * x_stride; } + if (reinterpret_cast(line_ptr + width) > + (data_ptr + data_len)) { + // Insufficient data size + return false; + } + for (int u = 0; u < width; u++) { float val; tinyexr::cpy4(&val, line_ptr + u); @@ -10055,6 +10163,12 @@ static bool DecodePixelData(/* out */ unsigned char **out_images, } for (int u = 0; u < width; u++) { + if (reinterpret_cast(line_ptr + u) >= + (data_ptr + data_len)) { + // Corrupsed data? + return false; + } + unsigned int val; tinyexr::cpy4(&val, line_ptr + u); @@ -10102,7 +10216,7 @@ static void DecodeTiledPixelData( num_channels, channels, channel_offset_list); } -static void ComputeChannelLayout(std::vector *channel_offset_list, +static bool ComputeChannelLayout(std::vector *channel_offset_list, int *pixel_data_size, size_t *channel_offset, int num_channels, const EXRChannelInfo *channels) { @@ -10123,9 +10237,11 @@ static void ComputeChannelLayout(std::vector *channel_offset_list, (*pixel_data_size) += sizeof(unsigned int); (*channel_offset) += sizeof(unsigned int); } else { - assert(0); + // ??? + return false; } } + return true; } static unsigned char **AllocateImage(int num_channels, @@ -10235,6 +10351,9 @@ static int ParseEXRHeader(HeaderInfo *info, bool *empty_header, size_t orig_size = size; for (size_t nattr = 0; nattr < TINYEXR_MAX_HEADER_ATTRIBUTES; nattr++) { if (0 == size) { + if (err) { + (*err) += "Insufficient data size for attributes.\n"; + } return TINYEXR_ERROR_INVALID_DATA; } else if (marker[0] == '\0') { size--; @@ -10247,6 +10366,9 @@ static int ParseEXRHeader(HeaderInfo *info, bool *empty_header, size_t marker_size; if (!tinyexr::ReadAttribute(&attr_name, &attr_type, &data, &marker_size, marker, size)) { + if (err) { + (*err) += "Failed to read attribute.\n"; + } return TINYEXR_ERROR_INVALID_DATA; } marker += marker_size; @@ -10317,14 +10439,14 @@ static int ParseEXRHeader(HeaderInfo *info, bool *empty_header, if (!ReadChannelInfo(info->channels, data)) { if (err) { - (*err) = "Failed to parse channel info."; + (*err) += "Failed to parse channel info.\n"; } return TINYEXR_ERROR_INVALID_DATA; } if (info->channels.size() < 1) { if (err) { - (*err) = "# of channels is zero."; + (*err) += "# of channels is zero.\n"; } return TINYEXR_ERROR_INVALID_DATA; } @@ -10558,7 +10680,7 @@ static void ConvertHeader(EXRHeader *exr_header, const HeaderInfo &info) { static int DecodeChunk(EXRImage *exr_image, const EXRHeader *exr_header, const std::vector &offsets, - const unsigned char *head, const size_t size) { + const unsigned char *head, const size_t size, std::string *err) { int num_channels = exr_header->num_channels; int num_scanline_blocks = 1; @@ -10578,9 +10700,14 @@ static int DecodeChunk(EXRImage *exr_image, const EXRHeader *exr_header, std::vector channel_offset_list; int pixel_data_size = 0; size_t channel_offset = 0; - tinyexr::ComputeChannelLayout(&channel_offset_list, &pixel_data_size, - &channel_offset, num_channels, - exr_header->channels); + if (!tinyexr::ComputeChannelLayout(&channel_offset_list, &pixel_data_size, + &channel_offset, num_channels, + exr_header->channels)) { + if (err) { + (*err) += "Failed to compute channel layout.\n"; + } + return TINYEXR_ERROR_INVALID_DATA; + } bool invalid_data = false; // TODO(LTE): Use atomic lock for MT safety. @@ -10600,6 +10727,9 @@ static int DecodeChunk(EXRImage *exr_image, const EXRHeader *exr_header, // 4 byte : data size // ~ : data(uncompressed or compressed) if (offsets[tile_idx] + sizeof(int) * 5 > size) { + if (err) { + (*err) += "Insufficient data size.\n"; + } return TINYEXR_ERROR_INVALID_DATA; } @@ -10628,6 +10758,9 @@ static int DecodeChunk(EXRImage *exr_image, const EXRHeader *exr_header, tinyexr::swap4(reinterpret_cast(&data_len)); if (data_len < 4 || size_t(data_len) > data_size) { + if (err) { + (*err) += "Insufficient data length.\n"; + } return TINYEXR_ERROR_INVALID_DATA; } @@ -10785,9 +10918,7 @@ static int DecodeEXRImage(EXRImage *exr_image, const EXRHeader *exr_header, const char **err) { if (exr_image == NULL || exr_header == NULL || head == NULL || marker == NULL || (size <= tinyexr::kEXRVersionSize)) { - if (err) { - (*err) = "Invalid argument."; - } + tinyexr::SetErrorMessage("Invalid argument for DecodeEXRImage().", err); return TINYEXR_ERROR_INVALID_ARGUMENT; } @@ -10803,26 +10934,20 @@ static int DecodeEXRImage(EXRImage *exr_image, const EXRHeader *exr_header, int data_width = exr_header->data_window[2] - exr_header->data_window[0]; if (data_width >= std::numeric_limits::max()) { // Issue 63 - if (err) { - (*err) = "Invalid data window value."; - } + tinyexr::SetErrorMessage("Invalid data window value", err); return TINYEXR_ERROR_INVALID_DATA; } data_width++; int data_height = exr_header->data_window[3] - exr_header->data_window[1]; if (data_height >= std::numeric_limits::max()) { - if (err) { - (*err) = "Invalid data height value."; - } + tinyexr::SetErrorMessage("Invalid data height value", err); return TINYEXR_ERROR_INVALID_DATA; } data_height++; if ((data_width < 0) || (data_height < 0)) { - if (err) { - (*err) = "Invalid data window value."; - } + tinyexr::SetErrorMessage("data window or data height is negative.", err); return TINYEXR_ERROR_INVALID_DATA; } @@ -10861,12 +10986,16 @@ static int DecodeEXRImage(EXRImage *exr_image, const EXRHeader *exr_header, for (size_t y = 0; y < num_blocks; y++) { tinyexr::tinyexr_uint64 offset; + // Issue #81 + if ((marker + sizeof(tinyexr_uint64)) >= (head + size)) { + tinyexr::SetErrorMessage("Insufficient data size in offset table.", err); + return TINYEXR_ERROR_INVALID_DATA; + } + memcpy(&offset, marker, sizeof(tinyexr::tinyexr_uint64)); tinyexr::swap8(&offset); if (offset >= size) { - if (err) { - (*err) = "Invalid offset value."; - } + tinyexr::SetErrorMessage("Invalid offset value in DecodeEXRImage.", err); return TINYEXR_ERROR_INVALID_DATA; } marker += sizeof(tinyexr::tinyexr_uint64); // = 8 @@ -10889,15 +11018,38 @@ static int DecodeEXRImage(EXRImage *exr_image, const EXRHeader *exr_header, // OK break; } else { - if (err) { - (*err) = "Cannot reconstruct lineOffset table."; - } + tinyexr::SetErrorMessage( + "Cannot reconstruct lineOffset table in DecodeEXRImage.", err); return TINYEXR_ERROR_INVALID_DATA; } } } - return DecodeChunk(exr_image, exr_header, offsets, head, size); + { + std::string e; + int ret = DecodeChunk(exr_image, exr_header, offsets, head, size, &e); + + if (ret != TINYEXR_SUCCESS) { + if (!e.empty()) { + tinyexr::SetErrorMessage(e, err); + } + + // release memory(if exists) + if ((exr_header->num_channels > 0) && exr_image && exr_image->images) { + for (size_t c = 0; c < size_t(exr_header->num_channels); c++) { + if (exr_image->images[c]) { + free(exr_image->images[c]); + exr_image->images[c] = NULL; + } + } + free(exr_image->images); + exr_image->images = NULL; + } + + } + + return ret; + } } } // namespace tinyexr @@ -10905,9 +11057,7 @@ static int DecodeEXRImage(EXRImage *exr_image, const EXRHeader *exr_header, int LoadEXR(float **out_rgba, int *width, int *height, const char *filename, const char **err) { if (out_rgba == NULL) { - if (err) { - (*err) = "Invalid argument.\n"; - } + tinyexr::SetErrorMessage("Invalid argument for LoadEXR()", err); return TINYEXR_ERROR_INVALID_ARGUMENT; } @@ -10920,13 +11070,14 @@ int LoadEXR(float **out_rgba, int *width, int *height, const char *filename, { int ret = ParseEXRVersionFromFile(&exr_version, filename); if (ret != TINYEXR_SUCCESS) { + tinyexr::SetErrorMessage("Invalid EXR header.", err); return ret; } if (exr_version.multipart || exr_version.non_image) { - if (err) { - (*err) = "Loading multipart or DeepImage is not supported yet.\n"; - } + tinyexr::SetErrorMessage( + "Loading multipart or DeepImage is not supported in LoadEXR() API", + err); return TINYEXR_ERROR_INVALID_DATA; // @fixme. } } @@ -10934,6 +11085,7 @@ int LoadEXR(float **out_rgba, int *width, int *height, const char *filename, { int ret = ParseEXRHeaderFromFile(&exr_header, &exr_version, filename, err); if (ret != TINYEXR_SUCCESS) { + FreeEXRHeader(&exr_header); return ret; } } @@ -10948,6 +11100,7 @@ int LoadEXR(float **out_rgba, int *width, int *height, const char *filename, { int ret = LoadEXRImageFromFile(&exr_image, &exr_header, filename, err); if (ret != TINYEXR_SUCCESS) { + FreeEXRHeader(&exr_header); return ret; } } @@ -10989,27 +11142,24 @@ int LoadEXR(float **out_rgba, int *width, int *height, const char *filename, // Assume RGB(A) if (idxR == -1) { - if (err) { - (*err) = "R channel not found\n"; - } + tinyexr::SetErrorMessage("R channel not found", err); // @todo { free exr_image } + FreeEXRHeader(&exr_header); return TINYEXR_ERROR_INVALID_DATA; } if (idxG == -1) { - if (err) { - (*err) = "G channel not found\n"; - } + tinyexr::SetErrorMessage("G channel not found", err); // @todo { free exr_image } + FreeEXRHeader(&exr_header); return TINYEXR_ERROR_INVALID_DATA; } if (idxB == -1) { - if (err) { - (*err) = "B channel not found\n"; - } + tinyexr::SetErrorMessage("B channel not found", err); // @todo { free exr_image } + FreeEXRHeader(&exr_header); return TINYEXR_ERROR_INVALID_DATA; } @@ -11080,15 +11230,19 @@ int ParseEXRHeaderFromMemory(EXRHeader *exr_header, const EXRVersion *version, const unsigned char *memory, size_t size, const char **err) { if (memory == NULL || exr_header == NULL) { - if (err) { - (*err) = "Invalid argument.\n"; - } + tinyexr::SetErrorMessage( + "Invalid argument. `memory` or `exr_header` argument is null in " + "ParseEXRHeaderFromMemory()", + err); // Invalid argument return TINYEXR_ERROR_INVALID_ARGUMENT; } if (size < tinyexr::kEXRVersionSize) { + tinyexr::SetErrorMessage( + "Insufficient header/data size.\n", + err); return TINYEXR_ERROR_INVALID_DATA; } @@ -11103,11 +11257,7 @@ int ParseEXRHeaderFromMemory(EXRHeader *exr_header, const EXRVersion *version, if (ret != TINYEXR_SUCCESS) { if (err && !err_str.empty()) { -#ifdef _WIN32 - (*err) = _strdup(err_str.c_str()); // May leak -#else - (*err) = strdup(err_str.c_str()); // May leak -#endif + tinyexr::SetErrorMessage(err_str, err); } } @@ -11123,9 +11273,7 @@ int LoadEXRFromMemory(float **out_rgba, int *width, int *height, const unsigned char *memory, size_t size, const char **err) { if (out_rgba == NULL || memory == NULL) { - if (err) { - (*err) = "Invalid argument.\n"; - } + tinyexr::SetErrorMessage("Invalid argument for LoadEXRFromMemory", err); return TINYEXR_ERROR_INVALID_ARGUMENT; } @@ -11137,6 +11285,7 @@ int LoadEXRFromMemory(float **out_rgba, int *width, int *height, int ret = ParseEXRVersionFromMemory(&exr_version, memory, size); if (ret != TINYEXR_SUCCESS) { + tinyexr::SetErrorMessage("Failed to parse EXR version", err); return ret; } @@ -11176,26 +11325,20 @@ int LoadEXRFromMemory(float **out_rgba, int *width, int *height, } if (idxR == -1) { - if (err) { - (*err) = "R channel not found\n"; - } + tinyexr::SetErrorMessage("R channel not found", err); // @todo { free exr_image } return TINYEXR_ERROR_INVALID_DATA; } if (idxG == -1) { - if (err) { - (*err) = "G channel not found\n"; - } + tinyexr::SetErrorMessage("G channel not found", err); // @todo { free exr_image } return TINYEXR_ERROR_INVALID_DATA; } if (idxB == -1) { - if (err) { - (*err) = "B channel not found\n"; - } + tinyexr::SetErrorMessage("B channel not found", err); // @todo { free exr_image } return TINYEXR_ERROR_INVALID_DATA; } @@ -11231,9 +11374,7 @@ int LoadEXRFromMemory(float **out_rgba, int *width, int *height, int LoadEXRImageFromFile(EXRImage *exr_image, const EXRHeader *exr_header, const char *filename, const char **err) { if (exr_image == NULL) { - if (err) { - (*err) = "Invalid argument."; - } + tinyexr::SetErrorMessage("Invalid argument for LoadEXRImageFromFile", err); return TINYEXR_ERROR_INVALID_ARGUMENT; } @@ -11244,9 +11385,7 @@ int LoadEXRImageFromFile(EXRImage *exr_image, const EXRHeader *exr_header, FILE *fp = fopen(filename, "rb"); #endif if (!fp) { - if (err) { - (*err) = "Cannot read file."; - } + tinyexr::SetErrorMessage("Cannot read file " + std::string(filename), err); return TINYEXR_ERROR_CANT_OPEN_FILE; } @@ -11256,6 +11395,11 @@ int LoadEXRImageFromFile(EXRImage *exr_image, const EXRHeader *exr_header, filesize = static_cast(ftell(fp)); fseek(fp, 0, SEEK_SET); + if (filesize < 16) { + tinyexr::SetErrorMessage("File size too short " + std::string(filename), err); + return TINYEXR_ERROR_INVALID_FILE; + } + std::vector buf(filesize); // @todo { use mmap } { size_t ret; @@ -11274,16 +11418,13 @@ int LoadEXRImageFromMemory(EXRImage *exr_image, const EXRHeader *exr_header, const char **err) { if (exr_image == NULL || memory == NULL || (size < tinyexr::kEXRVersionSize)) { - if (err) { - (*err) = "Invalid argument."; - } + tinyexr::SetErrorMessage("Invalid argument for LoadEXRImageFromMemory", + err); return TINYEXR_ERROR_INVALID_ARGUMENT; } if (exr_header->header_len == 0) { - if (err) { - (*err) = "EXRHeader is not initialized."; - } + tinyexr::SetErrorMessage("EXRHeader variable is not initialized.", err); return TINYEXR_ERROR_INVALID_ARGUMENT; } @@ -11300,26 +11441,22 @@ size_t SaveEXRImageToMemory(const EXRImage *exr_image, unsigned char **memory_out, const char **err) { if (exr_image == NULL || memory_out == NULL || exr_header->compression_type < 0) { - if (err) { - (*err) = "Invalid argument."; - } + tinyexr::SetErrorMessage("Invalid argument for SaveEXRImageToMemory", err); return 0; // @fixme } #if !TINYEXR_USE_PIZ if (exr_header->compression_type == TINYEXR_COMPRESSIONTYPE_PIZ) { - if (err) { - (*err) = "PIZ compression is not supported in this build."; - } + tinyexr::SetErrorMessage("PIZ compression is not supported in this build", + err); return 0; } #endif #if !TINYEXR_USE_ZFP if (exr_header->compression_type == TINYEXR_COMPRESSIONTYPE_ZFP) { - if (err) { - (*err) = "ZFP compression is not supported in this build."; - } + tinyexr::SetErrorMessage("ZFP compression is not supported in this build", + err); return 0; } #endif @@ -11327,9 +11464,8 @@ size_t SaveEXRImageToMemory(const EXRImage *exr_image, #if TINYEXR_USE_ZFP for (size_t i = 0; i < static_cast(exr_header->num_channels); i++) { if (exr_header->requested_pixel_types[i] != TINYEXR_PIXELTYPE_FLOAT) { - if (err) { - (*err) = "Pixel type must be FLOAT for ZFP compression."; - } + tinyexr::SetErrorMessage("Pixel type must be FLOAT for ZFP compression", + err); return 0; } } @@ -11539,6 +11675,11 @@ size_t SaveEXRImageToMemory(const EXRImage *exr_image, if (exr_header->pixel_types[c] == TINYEXR_PIXELTYPE_HALF) { if (exr_header->requested_pixel_types[c] == TINYEXR_PIXELTYPE_FLOAT) { for (int y = 0; y < h; y++) { + // Assume increasing Y + float *line_ptr = reinterpret_cast(&buf.at( + static_cast(pixel_data_size * y * exr_image->width) + + channel_offset_list[c] * + static_cast(exr_image->width))); for (int x = 0; x < exr_image->width; x++) { tinyexr::FP16 h16; h16.u = reinterpret_cast( @@ -11548,11 +11689,6 @@ size_t SaveEXRImageToMemory(const EXRImage *exr_image, tinyexr::swap4(reinterpret_cast(&f32.f)); - // Assume increasing Y - float *line_ptr = reinterpret_cast(&buf.at( - static_cast(pixel_data_size * y * exr_image->width) + - channel_offset_list[c] * - static_cast(exr_image->width))); // line_ptr[x] = f32.f; tinyexr::cpy4(line_ptr + x, &(f32.f)); } @@ -11560,18 +11696,18 @@ size_t SaveEXRImageToMemory(const EXRImage *exr_image, } else if (exr_header->requested_pixel_types[c] == TINYEXR_PIXELTYPE_HALF) { for (int y = 0; y < h; y++) { + // Assume increasing Y + unsigned short *line_ptr = reinterpret_cast( + &buf.at(static_cast(pixel_data_size * y * + exr_image->width) + + channel_offset_list[c] * + static_cast(exr_image->width))); for (int x = 0; x < exr_image->width; x++) { unsigned short val = reinterpret_cast( exr_image->images)[c][(y + start_y) * exr_image->width + x]; tinyexr::swap2(&val); - // Assume increasing Y - unsigned short *line_ptr = reinterpret_cast( - &buf.at(static_cast(pixel_data_size * y * - exr_image->width) + - channel_offset_list[c] * - static_cast(exr_image->width))); // line_ptr[x] = val; tinyexr::cpy2(line_ptr + x, &val); } @@ -11583,6 +11719,12 @@ size_t SaveEXRImageToMemory(const EXRImage *exr_image, } else if (exr_header->pixel_types[c] == TINYEXR_PIXELTYPE_FLOAT) { if (exr_header->requested_pixel_types[c] == TINYEXR_PIXELTYPE_HALF) { for (int y = 0; y < h; y++) { + // Assume increasing Y + unsigned short *line_ptr = reinterpret_cast( + &buf.at(static_cast(pixel_data_size * y * + exr_image->width) + + channel_offset_list[c] * + static_cast(exr_image->width))); for (int x = 0; x < exr_image->width; x++) { tinyexr::FP32 f32; f32.f = reinterpret_cast( @@ -11593,12 +11735,6 @@ size_t SaveEXRImageToMemory(const EXRImage *exr_image, tinyexr::swap2(reinterpret_cast(&h16.u)); - // Assume increasing Y - unsigned short *line_ptr = reinterpret_cast( - &buf.at(static_cast(pixel_data_size * y * - exr_image->width) + - channel_offset_list[c] * - static_cast(exr_image->width))); // line_ptr[x] = h16.u; tinyexr::cpy2(line_ptr + x, &(h16.u)); } @@ -11606,17 +11742,17 @@ size_t SaveEXRImageToMemory(const EXRImage *exr_image, } else if (exr_header->requested_pixel_types[c] == TINYEXR_PIXELTYPE_FLOAT) { for (int y = 0; y < h; y++) { + // Assume increasing Y + float *line_ptr = reinterpret_cast(&buf.at( + static_cast(pixel_data_size * y * exr_image->width) + + channel_offset_list[c] * + static_cast(exr_image->width))); for (int x = 0; x < exr_image->width; x++) { float val = reinterpret_cast( exr_image->images)[c][(y + start_y) * exr_image->width + x]; tinyexr::swap4(reinterpret_cast(&val)); - // Assume increasing Y - float *line_ptr = reinterpret_cast(&buf.at( - static_cast(pixel_data_size * y * exr_image->width) + - channel_offset_list[c] * - static_cast(exr_image->width))); // line_ptr[x] = val; tinyexr::cpy4(line_ptr + x, &val); } @@ -11626,17 +11762,16 @@ size_t SaveEXRImageToMemory(const EXRImage *exr_image, } } else if (exr_header->pixel_types[c] == TINYEXR_PIXELTYPE_UINT) { for (int y = 0; y < h; y++) { + // Assume increasing Y + unsigned int *line_ptr = reinterpret_cast(&buf.at( + static_cast(pixel_data_size * y * exr_image->width) + + channel_offset_list[c] * static_cast(exr_image->width))); for (int x = 0; x < exr_image->width; x++) { unsigned int val = reinterpret_cast( exr_image->images)[c][(y + start_y) * exr_image->width + x]; tinyexr::swap4(&val); - // Assume increasing Y - unsigned int *line_ptr = reinterpret_cast(&buf.at( - static_cast(pixel_data_size * y * exr_image->width) + - channel_offset_list[c] * - static_cast(exr_image->width))); // line_ptr[x] = val; tinyexr::cpy4(line_ptr + x, &val); } @@ -11807,26 +11942,22 @@ int SaveEXRImageToFile(const EXRImage *exr_image, const EXRHeader *exr_header, const char *filename, const char **err) { if (exr_image == NULL || filename == NULL || exr_header->compression_type < 0) { - if (err) { - (*err) = "Invalid argument."; - } + tinyexr::SetErrorMessage("Invalid argument for SaveEXRImageToFile", err); return TINYEXR_ERROR_INVALID_ARGUMENT; } #if !TINYEXR_USE_PIZ if (exr_header->compression_type == TINYEXR_COMPRESSIONTYPE_PIZ) { - if (err) { - (*err) = "PIZ compression is not supported in this build."; - } + tinyexr::SetErrorMessage("PIZ compression is not supported in this build", + err); return 0; } #endif #if !TINYEXR_USE_ZFP if (exr_header->compression_type == TINYEXR_COMPRESSIONTYPE_ZFP) { - if (err) { - (*err) = "ZFP compression is not supported in this build."; - } + tinyexr::SetErrorMessage("ZFP compression is not supported in this build", + err); return 0; } #endif @@ -11838,9 +11969,7 @@ int SaveEXRImageToFile(const EXRImage *exr_image, const EXRHeader *exr_header, FILE *fp = fopen(filename, "wb"); #endif if (!fp) { - if (err) { - (*err) = "Cannot write a file."; - } + tinyexr::SetErrorMessage("Cannot write a file", err); return TINYEXR_ERROR_CANT_OPEN_FILE; } @@ -11859,9 +11988,7 @@ int SaveEXRImageToFile(const EXRImage *exr_image, const EXRHeader *exr_header, int LoadDeepEXR(DeepImage *deep_image, const char *filename, const char **err) { if (deep_image == NULL) { - if (err) { - (*err) = "Invalid argument."; - } + tinyexr::SetErrorMessage("Invalid argument for LoadDeepEXR", err); return TINYEXR_ERROR_INVALID_ARGUMENT; } @@ -11869,17 +11996,15 @@ int LoadDeepEXR(DeepImage *deep_image, const char *filename, const char **err) { FILE *fp = NULL; errno_t errcode = fopen_s(&fp, filename, "rb"); if ((0 != errcode) || (!fp)) { - if (err) { - (*err) = "Cannot read file."; - } + tinyexr::SetErrorMessage("Cannot read a file " + std::string(filename), + err); return TINYEXR_ERROR_CANT_OPEN_FILE; } #else FILE *fp = fopen(filename, "rb"); if (!fp) { - if (err) { - (*err) = "Cannot read file."; - } + tinyexr::SetErrorMessage("Cannot read a file " + std::string(filename), + err); return TINYEXR_ERROR_CANT_OPEN_FILE; } #endif @@ -11892,9 +12017,8 @@ int LoadDeepEXR(DeepImage *deep_image, const char *filename, const char **err) { if (filesize == 0) { fclose(fp); - if (err) { - (*err) = "File size is zero."; - } + tinyexr::SetErrorMessage("File size is zero : " + std::string(filename), + err); return TINYEXR_ERROR_INVALID_FILE; } @@ -11915,9 +12039,7 @@ int LoadDeepEXR(DeepImage *deep_image, const char *filename, const char **err) { const char header[] = {0x76, 0x2f, 0x31, 0x01}; if (memcmp(marker, header, 4) != 0) { - if (err) { - (*err) = "Invalid magic number."; - } + tinyexr::SetErrorMessage("Invalid magic number", err); return TINYEXR_ERROR_INVALID_MAGIC_NUMBER; } marker += 4; @@ -11928,9 +12050,7 @@ int LoadDeepEXR(DeepImage *deep_image, const char *filename, const char **err) { // ver 2.0, scanline, deep bit on(0x800) // must be [2, 0, 0, 0] if (marker[0] != 2 || marker[1] != 8 || marker[2] != 0 || marker[3] != 0) { - if (err) { - (*err) = "Unsupported version or scanline."; - } + tinyexr::SetErrorMessage("Unsupported version or scanline", err); return TINYEXR_ERROR_UNSUPPORTED_FORMAT; } @@ -11971,9 +12091,9 @@ int LoadDeepEXR(DeepImage *deep_image, const char *filename, const char **err) { if (attr_name.compare("compression") == 0) { compression_type = data[0]; if (compression_type > TINYEXR_COMPRESSIONTYPE_PIZ) { - if (err) { - (*err) = "Unsupported compression type."; - } + std::stringstream ss; + ss << "Unsupported compression type : " << compression_type; + tinyexr::SetErrorMessage(ss.str(), err); return TINYEXR_ERROR_UNSUPPORTED_FORMAT; } @@ -11990,18 +12110,14 @@ int LoadDeepEXR(DeepImage *deep_image, const char *filename, const char **err) { // ySampling: int if (!tinyexr::ReadChannelInfo(channels, data)) { - if (err) { - (*err) = "Failed to parse channel info."; - } + tinyexr::SetErrorMessage("Failed to parse channel info", err); return TINYEXR_ERROR_INVALID_DATA; } num_channels = static_cast(channels.size()); if (num_channels < 1) { - if (err) { - (*err) = "Invalid channels format."; - } + tinyexr::SetErrorMessage("Invalid channels format", err); return TINYEXR_ERROR_INVALID_DATA; } @@ -12073,9 +12189,7 @@ int LoadDeepEXR(DeepImage *deep_image, const char *filename, const char **err) { #endif // OK } else { - if (err) { - (*err) = "Unsupported format."; - } + tinyexr::SetErrorMessage("Unsupported compression format", err); return TINYEXR_ERROR_UNSUPPORTED_FORMAT; } @@ -12267,6 +12381,13 @@ void InitEXRImage(EXRImage *exr_image) { exr_image->num_tiles = 0; } +void FreeEXRErrorMessage(const char *msg) { + if (msg) { + free(reinterpret_cast(const_cast(msg))); + } + return; +} + void InitEXRHeader(EXRHeader *exr_header) { if (exr_header == NULL) { return; @@ -12340,9 +12461,8 @@ int FreeEXRImage(EXRImage *exr_image) { int ParseEXRHeaderFromFile(EXRHeader *exr_header, const EXRVersion *exr_version, const char *filename, const char **err) { if (exr_header == NULL || exr_version == NULL || filename == NULL) { - if (err) { - (*err) = "Invalid argument."; - } + tinyexr::SetErrorMessage("Invalid argument for ParseEXRHeaderFromFile", + err); return TINYEXR_ERROR_INVALID_ARGUMENT; } @@ -12353,9 +12473,7 @@ int ParseEXRHeaderFromFile(EXRHeader *exr_header, const EXRVersion *exr_version, FILE *fp = fopen(filename, "rb"); #endif if (!fp) { - if (err) { - (*err) = "Cannot read file."; - } + tinyexr::SetErrorMessage("Cannot read file " + std::string(filename), err); return TINYEXR_ERROR_CANT_OPEN_FILE; } @@ -12373,9 +12491,8 @@ int ParseEXRHeaderFromFile(EXRHeader *exr_header, const EXRVersion *exr_version, fclose(fp); if (ret != filesize) { - if (err) { - (*err) = "fread error."; - } + tinyexr::SetErrorMessage("fread() error on " + std::string(filename), + err); return TINYEXR_ERROR_INVALID_FILE; } } @@ -12392,10 +12509,14 @@ int ParseEXRMultipartHeaderFromMemory(EXRHeader ***exr_headers, if (memory == NULL || exr_headers == NULL || num_headers == NULL || exr_version == NULL) { // Invalid argument + tinyexr::SetErrorMessage( + "Invalid argument for ParseEXRMultipartHeaderFromMemory", err); return TINYEXR_ERROR_INVALID_ARGUMENT; } if (size < tinyexr::kEXRVersionSize) { + tinyexr::SetErrorMessage( + "Data size too short", err); return TINYEXR_ERROR_INVALID_DATA; } @@ -12414,13 +12535,7 @@ int ParseEXRMultipartHeaderFromMemory(EXRHeader ***exr_headers, marker, marker_size); if (ret != TINYEXR_SUCCESS) { - if (err) { -#ifdef _WIN32 - (*err) = _strdup(err_str.c_str()); // may leak -#else - (*err) = strdup(err_str.c_str()); // may leak -#endif - } + tinyexr::SetErrorMessage(err_str, err); return ret; } @@ -12431,9 +12546,8 @@ int ParseEXRMultipartHeaderFromMemory(EXRHeader ***exr_headers, // `chunkCount` must exist in the header. if (info.chunk_count == 0) { - if (err) { - (*err) = "`chunkCount' attribute is not found in the header."; - } + tinyexr::SetErrorMessage( + "`chunkCount' attribute is not found in the header.", err); return TINYEXR_ERROR_INVALID_DATA; } @@ -12468,9 +12582,8 @@ int ParseEXRMultipartHeaderFromFile(EXRHeader ***exr_headers, int *num_headers, const char *filename, const char **err) { if (exr_headers == NULL || num_headers == NULL || exr_version == NULL || filename == NULL) { - if (err) { - (*err) = "Invalid argument."; - } + tinyexr::SetErrorMessage( + "Invalid argument for ParseEXRMultipartHeaderFromFile()", err); return TINYEXR_ERROR_INVALID_ARGUMENT; } @@ -12481,9 +12594,7 @@ int ParseEXRMultipartHeaderFromFile(EXRHeader ***exr_headers, int *num_headers, FILE *fp = fopen(filename, "rb"); #endif if (!fp) { - if (err) { - (*err) = "Cannot read file."; - } + tinyexr::SetErrorMessage("Cannot read file " + std::string(filename), err); return TINYEXR_ERROR_CANT_OPEN_FILE; } @@ -12501,9 +12612,7 @@ int ParseEXRMultipartHeaderFromFile(EXRHeader ***exr_headers, int *num_headers, fclose(fp); if (ret != filesize) { - if (err) { - (*err) = "fread error."; - } + tinyexr::SetErrorMessage("`fread' error. file may be corrupted.", err); return TINYEXR_ERROR_INVALID_FILE; } } @@ -12612,9 +12721,8 @@ int LoadEXRMultipartImageFromMemory(EXRImage *exr_images, const size_t size, const char **err) { if (exr_images == NULL || exr_headers == NULL || num_parts == 0 || memory == NULL || (size <= tinyexr::kEXRVersionSize)) { - if (err) { - (*err) = "Invalid argument."; - } + tinyexr::SetErrorMessage( + "Invalid argument for LoadEXRMultipartImageFromMemory()", err); return TINYEXR_ERROR_INVALID_ARGUMENT; } @@ -12622,9 +12730,7 @@ int LoadEXRMultipartImageFromMemory(EXRImage *exr_images, size_t total_header_size = 0; for (unsigned int i = 0; i < num_parts; i++) { if (exr_headers[i]->header_len == 0) { - if (err) { - (*err) = "EXRHeader is not initialized."; - } + tinyexr::SetErrorMessage("EXRHeader variable is not initialized.", err); return TINYEXR_ERROR_INVALID_ARGUMENT; } @@ -12659,9 +12765,8 @@ int LoadEXRMultipartImageFromMemory(EXRImage *exr_images, tinyexr::swap8(&offset); if (offset >= size) { - if (err) { - (*err) = "Invalid offset size."; - } + tinyexr::SetErrorMessage("Invalid offset size in EXR header chunks.", + err); return TINYEXR_ERROR_INVALID_DATA; } @@ -12686,14 +12791,19 @@ int LoadEXRMultipartImageFromMemory(EXRImage *exr_images, tinyexr::swap4(&part_no); if (part_no != i) { - assert(0); + tinyexr::SetErrorMessage("Invalid `part number' in EXR header chunks.", + err); return TINYEXR_ERROR_INVALID_DATA; } } + std::string e; int ret = tinyexr::DecodeChunk(&exr_images[i], exr_headers[i], offset_table, - memory, size); + memory, size, &e); if (ret != TINYEXR_SUCCESS) { + if (!e.empty()) { + tinyexr::SetErrorMessage(e, err); + } return ret; } } @@ -12706,9 +12816,8 @@ int LoadEXRMultipartImageFromFile(EXRImage *exr_images, unsigned int num_parts, const char *filename, const char **err) { if (exr_images == NULL || exr_headers == NULL || num_parts == 0) { - if (err) { - (*err) = "Invalid argument."; - } + tinyexr::SetErrorMessage( + "Invalid argument for LoadEXRMultipartImageFromFile", err); return TINYEXR_ERROR_INVALID_ARGUMENT; } @@ -12719,9 +12828,7 @@ int LoadEXRMultipartImageFromFile(EXRImage *exr_images, FILE *fp = fopen(filename, "rb"); #endif if (!fp) { - if (err) { - (*err) = "Cannot read file."; - } + tinyexr::SetErrorMessage("Cannot read file " + std::string(filename), err); return TINYEXR_ERROR_CANT_OPEN_FILE; } diff --git a/include/bimg/bimg.h b/include/bimg/bimg.h index b35f7ef..9893e94 100644 --- a/include/bimg/bimg.h +++ b/include/bimg/bimg.h @@ -9,7 +9,7 @@ #include // uint32_t #include // NULL -#define BIMG_API_VERSION UINT32_C(6) +#define BIMG_API_VERSION UINT32_C(7) namespace bx { @@ -73,7 +73,7 @@ namespace bimg ASTC6x6, //!< ASTC 6x6 3.56 BPP ASTC8x5, //!< ASTC 8x5 3.20 BPP ASTC8x6, //!< ASTC 8x6 2.67 BPP - ASTC10x5, //!< ASTC 10x5 2.56 BPP + ASTC10x5, //!< ASTC 10x5 2.56 BPP Unknown, // Compressed formats above. @@ -334,6 +334,16 @@ namespace bimg , const void* _src ); + /// + void imageRgba32fDownsample2x2( + void* _dst + , uint32_t _width + , uint32_t _height + , uint32_t _depth + , uint32_t _srcPitch + , const void* _src + ); + /// void imageRgba32fDownsample2x2NormalMap( void* _dst @@ -493,6 +503,18 @@ namespace bimg , bx::Error* _err ); + /// + int32_t imageWriteHdr( + bx::WriterI* _writer + , uint32_t _width + , uint32_t _height + , uint32_t _srcPitch + , const void* _src + , TextureFormat::Enum _format + , bool _yflip + , bx::Error* _err + ); + /// int32_t imageWriteDds( bx::WriterI* _writer @@ -628,14 +650,6 @@ namespace bimg , ImageMip& _mip ); - /// - ImageContainer* imageCubemapFromLatLongRgba32F( - bx::AllocatorI* _allocator - , const ImageContainer& _input - , bool _useBilinearInterpolation - , bx::Error* _err - ); - } // namespace bimg #endif // BIMG_IMAGE_H_HEADER_GUARD diff --git a/include/bimg/encode.h b/include/bimg/encode.h index b508745..7e1de03 100644 --- a/include/bimg/encode.h +++ b/include/bimg/encode.h @@ -127,6 +127,49 @@ namespace bimg , float _alphaRef ); + /// + ImageContainer* imageCubemapFromLatLongRgba32F( + bx::AllocatorI* _allocator + , const ImageContainer& _input + , bool _useBilinearInterpolation + , bx::Error* _err + ); + + /// + ImageContainer* imageCubemapFromStripRgba32F( + bx::AllocatorI* _allocator + , const ImageContainer& _input + , bx::Error* _err + ); + + /// + ImageContainer* imageGenerateMips( + bx::AllocatorI* _allocator + , const ImageContainer& _image + ); + + struct LightingModel + { + enum Enum + { + Phong, + PhongBrdf, + Blinn, + BlinnBrdf, + Ggx, + + Count + }; + }; + + /// + ImageContainer* imageCubemapRadianceFilter( + bx::AllocatorI* _allocator + , const ImageContainer& _image + , LightingModel::Enum _lightingModel + , bx::Error* _err + ); + } // namespace bimg #endif // BIMG_ENCODE_H_HEADER_GUARD diff --git a/makefile b/makefile index ea63344..c9a4aa0 100644 --- a/makefile +++ b/makefile @@ -308,7 +308,7 @@ texturev: .build/projects/$(BUILD_PROJECT_DIR) ## Build texturev tool. $(SILENT) $(MAKE) -C .build/projects/$(BUILD_PROJECT_DIR) texturev config=$(BUILD_TOOLS_CONFIG) $(SILENT) cp .build/$(BUILD_OUTPUT_DIR)/bin/texturev$(BUILD_TOOLS_SUFFIX)$(EXE) tools/bin/$(OS)/texturev$(EXE) -tools: geometryc shaderc texturec texturev ## Build tools. +tools: texturec ## Build tools. clean-tools: ## Clean tools projects. -$(SILENT) rm -r .build/projects/$(BUILD_PROJECT_DIR) diff --git a/scripts/bimg_encode.lua b/scripts/bimg_encode.lua index cb00082..9001bed 100644 --- a/scripts/bimg_encode.lua +++ b/scripts/bimg_encode.lua @@ -17,6 +17,7 @@ project "bimg_encode" files { path.join(BIMG_DIR, "include/**"), path.join(BIMG_DIR, "src/image_encode.*"), + path.join(BIMG_DIR, "src/image_cubemap_filter.*"), path.join(BIMG_DIR, "3rdparty/libsquish/**.cpp"), path.join(BIMG_DIR, "3rdparty/libsquish/**.h"), path.join(BIMG_DIR, "3rdparty/edtaa3/**.cpp"), diff --git a/src/bimg_p.h b/src/bimg_p.h index a28a2f7..9e3b400 100644 --- a/src/bimg_p.h +++ b/src/bimg_p.h @@ -43,7 +43,7 @@ namespace bimg { if (_hasMips) { - const uint32_t max = bx::uint32_max(bx::uint32_max(_width, _height), _depth); + const uint32_t max = bx::max(_width, _height, _depth); const uint32_t num = 1 + uint32_t(bx::log2(float(max) ) ); return uint8_t(num); diff --git a/src/image.cpp b/src/image.cpp index 12164e1..1638118 100644 --- a/src/image.cpp +++ b/src/image.cpp @@ -10,7 +10,7 @@ namespace bimg { static const ImageBlockInfo s_imageBlockInfo[] = { - // +-------------------------------------------- bits per pixel + // +--------------------------------------------- bits per pixel // | +----------------------------------------- block width // | | +-------------------------------------- block height // | | | +---------------------------------- block size @@ -38,15 +38,15 @@ namespace bimg { 4, 4, 4, 8, 2, 2, 0, 0, 0, 0, 0, 0, uint8_t(bx::EncodingType::Unorm) }, // PTC14A { 2, 8, 4, 8, 2, 2, 0, 0, 0, 0, 0, 0, uint8_t(bx::EncodingType::Unorm) }, // PTC22 { 4, 4, 4, 8, 2, 2, 0, 0, 0, 0, 0, 0, uint8_t(bx::EncodingType::Unorm) }, // PTC24 - { 4, 4, 4, 8, 1, 1, 0, 0, 0, 0, 0, 0, uint8_t(bx::EncodingType::Unorm) }, // ATC - { 8, 4, 4, 16, 1, 1, 0, 0, 0, 0, 0, 0, uint8_t(bx::EncodingType::Unorm) }, // ATCE - { 8, 4, 4, 16, 1, 1, 0, 0, 0, 0, 0, 0, uint8_t(bx::EncodingType::Unorm) }, // ATCI - { 8, 4, 4, 16, 1, 1, 0, 0, 0, 0, 0, 0, uint8_t(bx::EncodingType::Unorm) }, // ASTC4x4 - { 6, 5, 5, 16, 1, 1, 0, 0, 0, 0, 0, 0, uint8_t(bx::EncodingType::Unorm) }, // ASTC5x5 - { 4, 6, 6, 16, 1, 1, 0, 0, 0, 0, 0, 0, uint8_t(bx::EncodingType::Unorm) }, // ASTC6x6 - { 4, 8, 5, 16, 1, 1, 0, 0, 0, 0, 0, 0, uint8_t(bx::EncodingType::Unorm) }, // ASTC8x5 - { 3, 8, 6, 16, 1, 1, 0, 0, 0, 0, 0, 0, uint8_t(bx::EncodingType::Unorm) }, // ASTC8x6 - { 3, 10, 5, 16, 1, 1, 0, 0, 0, 0, 0, 0, uint8_t(bx::EncodingType::Unorm) }, // ASTC10x5 + { 4, 4, 4, 8, 1, 1, 0, 0, 0, 0, 0, 0, uint8_t(bx::EncodingType::Unorm) }, // ATC + { 8, 4, 4, 16, 1, 1, 0, 0, 0, 0, 0, 0, uint8_t(bx::EncodingType::Unorm) }, // ATCE + { 8, 4, 4, 16, 1, 1, 0, 0, 0, 0, 0, 0, uint8_t(bx::EncodingType::Unorm) }, // ATCI + { 8, 4, 4, 16, 1, 1, 0, 0, 0, 0, 0, 0, uint8_t(bx::EncodingType::Unorm) }, // ASTC4x4 + { 6, 5, 5, 16, 1, 1, 0, 0, 0, 0, 0, 0, uint8_t(bx::EncodingType::Unorm) }, // ASTC5x5 + { 4, 6, 6, 16, 1, 1, 0, 0, 0, 0, 0, 0, uint8_t(bx::EncodingType::Unorm) }, // ASTC6x6 + { 4, 8, 5, 16, 1, 1, 0, 0, 0, 0, 0, 0, uint8_t(bx::EncodingType::Unorm) }, // ASTC8x5 + { 3, 8, 6, 16, 1, 1, 0, 0, 0, 0, 0, 0, uint8_t(bx::EncodingType::Unorm) }, // ASTC8x6 + { 3, 10, 5, 16, 1, 1, 0, 0, 0, 0, 0, 0, uint8_t(bx::EncodingType::Unorm) }, // ASTC10x5 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, uint8_t(bx::EncodingType::Count) }, // Unknown { 1, 8, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, uint8_t(bx::EncodingType::Unorm) }, // R1 { 8, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 8, uint8_t(bx::EncodingType::Unorm) }, // A8 @@ -128,15 +128,15 @@ namespace bimg "PTC14A", // PTC14A "PTC22", // PTC22 "PTC24", // PTC24 - "ATC", // ATC - "ATCE", // ATCE - "ATCI", // ATCI - "ASTC4x4", // ASTC4x4 - "ASTC5x5", // ASTC5x5 - "ASTC6x6", // ASTC6x6 - "ASTC8x5", // ASTC8x5 - "ASTC8x6", // ASTC8x6 - "ASTC10x5", // ASTC10x5 + "ATC", // ATC + "ATCE", // ATCE + "ATCI", // ATCI + "ASTC4x4", // ASTC4x4 + "ASTC5x5", // ASTC5x5 + "ASTC6x6", // ASTC6x6 + "ASTC8x5", // ASTC8x5 + "ASTC8x6", // ASTC8x6 + "ASTC10x5", // ASTC10x5 "", // Unknown "R1", // R1 "A8", // A8 @@ -276,9 +276,9 @@ namespace bimg const uint16_t minBlockX = blockInfo.minBlockX; const uint16_t minBlockY = blockInfo.minBlockY; - _width = bx::uint16_max(blockWidth * minBlockX, ( (_width + blockWidth - 1) / blockWidth )*blockWidth); - _height = bx::uint16_max(blockHeight * minBlockY, ( (_height + blockHeight - 1) / blockHeight)*blockHeight); - _depth = bx::uint16_max(1, _depth); + _width = bx::max(blockWidth * minBlockX, ( (_width + blockWidth - 1) / blockWidth )*blockWidth); + _height = bx::max(blockHeight * minBlockY, ( (_height + blockHeight - 1) / blockHeight)*blockHeight); + _depth = bx::max(1, _depth); uint8_t numMips = calcNumMips(true, _width, _height, _depth); @@ -289,12 +289,15 @@ namespace bimg { const ImageBlockInfo& blockInfo = getBlockInfo(_format); const uint8_t bpp = blockInfo.bitsPerPixel; - const uint16_t blockSize = blockInfo.blockSize; const uint16_t blockWidth = blockInfo.blockWidth; const uint16_t blockHeight = blockInfo.blockHeight; const uint16_t minBlockX = blockInfo.minBlockX; const uint16_t minBlockY = blockInfo.minBlockY; + const uint8_t blockSize = blockInfo.blockSize; + _width = bx::max(blockWidth * minBlockX, ( (_width + blockWidth - 1) / blockWidth)*blockWidth); + _height = bx::max(blockHeight * minBlockY, ( (_height + blockHeight - 1) / blockHeight)*blockHeight); + _depth = bx::max(1, _depth); const uint8_t numMips = calcNumMips(_hasMips, _width, _height, _depth); const uint32_t sides = _cubeMap ? 6 : 1; @@ -303,22 +306,18 @@ namespace bimg uint32_t depth = _depth; uint32_t size = 0; - if (_format != TextureFormat::Unknown) - { - for (uint32_t lod = 0; lod < numMips; ++lod) - { - depth = bx::uint32_max(1, depth); + for (uint32_t lod = 0; lod < numMips; ++lod) + { + width = bx::max(blockWidth * minBlockX, ( (width + blockWidth - 1) / blockWidth )*blockWidth); + height = bx::max(blockHeight * minBlockY, ( (height + blockHeight - 1) / blockHeight)*blockHeight); + depth = bx::max(1, depth); - uint16_t blocksX = bx::uint32_max(minBlockX, ((width + blockWidth - 1) / blockWidth )); - uint16_t blocksY = bx::uint32_max(minBlockY, ((height + blockHeight - 1) / blockHeight)); + size += uint32_t(uint64_t(width/blockWidth * height/blockHeight * depth)*blockSize * sides); - size += blocksX * blocksY * blockSize * depth * sides; - - width >>= 1; - height >>= 1; - depth >>= 1; - } - } + width >>= 1; + height >>= 1; + depth >>= 1; + } size *= _numLayers; @@ -338,7 +337,7 @@ namespace bimg return size; } - void imageSolid(void* _dst, uint32_t _width, uint32_t _height, uint32_t _solid) + void imageSolid(void* _dst, uint32_t _width, uint32_t _height, uint32_t _solid) { uint32_t* dst = (uint32_t*)_dst; for (uint32_t ii = 0, num = _width*_height; ii < num; ++ii) @@ -711,6 +710,136 @@ namespace bimg imageRgba32fLinearDownsample2x2Ref(_dst, _width, _height, _depth, _srcPitch, _src); } + void imageRgba32fDownsample2x2Ref(void* _dst, uint32_t _width, uint32_t _height, uint32_t _depth, uint32_t _srcPitch, const void* _src) + { + const uint32_t dstWidth = _width/2; + const uint32_t dstHeight = _height/2; + const uint32_t dstDepth = _depth/2; + + if (0 == dstWidth + || 0 == dstHeight) + { + return; + } + + const uint8_t* src = (const uint8_t*)_src; + uint8_t* dst = (uint8_t*)_dst; + + if (0 == dstDepth) + { + for (uint32_t yy = 0, ystep = _srcPitch*2; yy < dstHeight; ++yy, src += ystep) + { + const float* rgba0 = (const float*)&src[0]; + const float* rgba1 = (const float*)&src[_srcPitch]; + for (uint32_t xx = 0; xx < dstWidth; ++xx, rgba0 += 8, rgba1 += 8, dst += 16) + { + float xyz[4]; + + xyz[0] = bx::toLinear(rgba0[0]); + xyz[1] = bx::toLinear(rgba0[1]); + xyz[2] = bx::toLinear(rgba0[2]); + xyz[3] = rgba0[3]; + + xyz[0] += bx::toLinear(rgba0[4]); + xyz[1] += bx::toLinear(rgba0[5]); + xyz[2] += bx::toLinear(rgba0[6]); + xyz[3] += rgba0[7]; + + xyz[0] += bx::toLinear(rgba1[0]); + xyz[1] += bx::toLinear(rgba1[1]); + xyz[2] += bx::toLinear(rgba1[2]); + xyz[3] += rgba1[3]; + + xyz[0] += bx::toLinear(rgba1[4]); + xyz[1] += bx::toLinear(rgba1[5]); + xyz[2] += bx::toLinear(rgba1[6]); + xyz[3] += rgba1[7]; + + xyz[0] = bx::toGamma(xyz[0]/4.0f); + xyz[1] = bx::toGamma(xyz[1]/4.0f); + xyz[2] = bx::toGamma(xyz[2]/4.0f); + xyz[3] = xyz[3]/4.0f; + + bx::packRgba32F(dst, xyz); + } + } + } + else + { + const uint32_t slicePitch = _srcPitch*_height; + + for (uint32_t zz = 0; zz < dstDepth; ++zz, src += slicePitch) + { + for (uint32_t yy = 0, ystep = _srcPitch*2; yy < dstHeight; ++yy, src += ystep) + { + const float* rgba0 = (const float*)&src[0]; + const float* rgba1 = (const float*)&src[_srcPitch]; + const float* rgba2 = (const float*)&src[slicePitch]; + const float* rgba3 = (const float*)&src[slicePitch+_srcPitch]; + for (uint32_t xx = 0 + ; xx < dstWidth + ; ++xx, rgba0 += 8, rgba1 += 8, rgba2 += 8, rgba3 += 8, dst += 16 + ) + { + float xyz[4]; + + xyz[0] = bx::toLinear(rgba0[0]); + xyz[1] = bx::toLinear(rgba0[1]); + xyz[2] = bx::toLinear(rgba0[2]); + xyz[3] = rgba0[3]; + + xyz[0] += bx::toLinear(rgba0[4]); + xyz[1] += bx::toLinear(rgba0[5]); + xyz[2] += bx::toLinear(rgba0[6]); + xyz[3] += rgba0[7]; + + xyz[0] += bx::toLinear(rgba1[0]); + xyz[1] += bx::toLinear(rgba1[1]); + xyz[2] += bx::toLinear(rgba1[2]); + xyz[3] += rgba1[3]; + + xyz[0] += bx::toLinear(rgba1[4]); + xyz[1] += bx::toLinear(rgba1[5]); + xyz[2] += bx::toLinear(rgba1[6]); + xyz[3] += rgba1[7]; + + xyz[0] += bx::toLinear(rgba2[0]); + xyz[1] += bx::toLinear(rgba2[1]); + xyz[2] += bx::toLinear(rgba2[2]); + xyz[3] += rgba2[3]; + + xyz[0] += bx::toLinear(rgba2[4]); + xyz[1] += bx::toLinear(rgba2[5]); + xyz[2] += bx::toLinear(rgba2[6]); + xyz[3] += rgba2[7]; + + xyz[0] += bx::toLinear(rgba3[0]); + xyz[1] += bx::toLinear(rgba3[1]); + xyz[2] += bx::toLinear(rgba3[2]); + xyz[3] += rgba3[3]; + + xyz[0] += bx::toLinear(rgba3[4]); + xyz[1] += bx::toLinear(rgba3[5]); + xyz[2] += bx::toLinear(rgba3[6]); + xyz[3] += rgba3[7]; + + xyz[0] = bx::toGamma(xyz[0]/8.0f); + xyz[1] = bx::toGamma(xyz[1]/8.0f); + xyz[2] = bx::toGamma(xyz[2]/8.0f); + xyz[3] = xyz[3]/8.0f; + + bx::packRgba32F(dst, xyz); + } + } + } + } + } + + void imageRgba32fDownsample2x2(void* _dst, uint32_t _width, uint32_t _height, uint32_t _depth, uint32_t _srcPitch, const void* _src) + { + imageRgba32fDownsample2x2Ref(_dst, _width, _height, _depth, _srcPitch, _src); + } + void imageRgba32fDownsample2x2NormalMapRef(void* _dst, uint32_t _width, uint32_t _height, uint32_t _srcPitch, uint32_t _dstPitch, const void* _src) { const uint32_t dstWidth = _width/2; @@ -872,15 +1001,15 @@ namespace bimg { NULL, NULL }, // PTC14A { NULL, NULL }, // PTC22 { NULL, NULL }, // PTC24 - { NULL, NULL }, // ATC - { NULL, NULL }, // ATCE - { NULL, NULL }, // ATCI - { NULL, NULL }, // ASTC4x4 - { NULL, NULL }, // ASTC5x5 - { NULL, NULL }, // ASTC6x6 - { NULL, NULL }, // ASTC8x5 - { NULL, NULL }, // ASTC8x6 - { NULL, NULL }, // ASTC10x5 + { NULL, NULL }, // ATC + { NULL, NULL }, // ATCE + { NULL, NULL }, // ATCI + { NULL, NULL }, // ASTC4x4 + { NULL, NULL }, // ASTC5x5 + { NULL, NULL }, // ASTC6x6 + { NULL, NULL }, // ASTC8x5 + { NULL, NULL }, // ASTC8x6 + { NULL, NULL }, // ASTC10x5 { NULL, NULL }, // Unknown { NULL, NULL }, // R1 { bx::packR8, bx::unpackR8 }, // A8 @@ -1039,7 +1168,7 @@ namespace bimg if (_dstFormat == _srcFormat) { - bx::memCopy(_dst, _src, _width*_height*_depth*srcBpp/8); + bx::memCopy(_dst, _src, _width*_height*_depth*(srcBpp/8) ); return true; } @@ -2267,61 +2396,61 @@ namespace bimg } } - // BC6H, BC7 + // ATC // - void decodeBlockATC(uint8_t _dst[16*4], const uint8_t _src[8]) - { - uint8_t colors[4*4]; // You can see from comparison with decodeBlockDXT just how little sense the ATI patent-avoiding(?) modification makes + void decodeBlockATC(uint8_t _dst[16*4], const uint8_t _src[8]) + { + uint8_t colors[4*4]; - uint32_t c0 = _src[0] | (_src[1] << 8); - uint32_t c1 = _src[2] | (_src[3] << 8); + uint32_t c0 = _src[0] | (_src[1] << 8); + uint32_t c1 = _src[2] | (_src[3] << 8); - if ((c0 & 0x8000) == 0) - { - colors[0] = bitRangeConvert( (c0>> 0)&0x1f, 5, 8); - colors[1] = bitRangeConvert( (c0>> 5)&0x1f, 5, 8); - colors[2] = bitRangeConvert( (c0>>10)&0x1f, 5, 8); + if (0 == (c0 & 0x8000) ) + { + colors[ 0] = bitRangeConvert( (c0>> 0)&0x1f, 5, 8); + colors[ 1] = bitRangeConvert( (c0>> 5)&0x1f, 5, 8); + colors[ 2] = bitRangeConvert( (c0>>10)&0x1f, 5, 8); - colors[12] = bitRangeConvert( (c1>> 0)&0x1f, 5, 8); - colors[13] = bitRangeConvert( (c1>> 5)&0x3f, 6, 8); - colors[14] = bitRangeConvert( (c1>>11)&0x1f, 5, 8); + colors[12] = bitRangeConvert( (c1>> 0)&0x1f, 5, 8); + colors[13] = bitRangeConvert( (c1>> 5)&0x3f, 6, 8); + colors[14] = bitRangeConvert( (c1>>11)&0x1f, 5, 8); - colors[ 4] = (2 * colors[0] + colors[12]) / 3; - colors[ 5] = (2 * colors[1] + colors[13]) / 3; - colors[ 6] = (2 * colors[2] + colors[14]) / 3; + colors[ 4] = (2 * colors[0] + colors[12]) / 3; + colors[ 5] = (2 * colors[1] + colors[13]) / 3; + colors[ 6] = (2 * colors[2] + colors[14]) / 3; - colors[ 8] = (colors[0] + 2 * colors[12]) / 3; - colors[ 9] = (colors[1] + 2 * colors[13]) / 3; - colors[10] = (colors[2] + 2 * colors[14]) / 3; - } - else - { - colors[ 0] = 0; - colors[ 1] = 0; - colors[ 2] = 0; + colors[ 8] = (colors[0] + 2 * colors[12]) / 3; + colors[ 9] = (colors[1] + 2 * colors[13]) / 3; + colors[10] = (colors[2] + 2 * colors[14]) / 3; + } + else + { + colors[ 0] = 0; + colors[ 1] = 0; + colors[ 2] = 0; - colors[ 8] = bitRangeConvert( (c0>> 0)&0x1f, 5, 8); - colors[ 9] = bitRangeConvert( (c0>> 5)&0x1f, 5, 8); - colors[10] = bitRangeConvert( (c0>>10)&0x1f, 5, 8); + colors[ 8] = bitRangeConvert( (c0>> 0)&0x1f, 5, 8); + colors[ 9] = bitRangeConvert( (c0>> 5)&0x1f, 5, 8); + colors[10] = bitRangeConvert( (c0>>10)&0x1f, 5, 8); - colors[12] = bitRangeConvert( (c1>> 0)&0x1f, 5, 8); - colors[13] = bitRangeConvert( (c1>> 5)&0x3f, 6, 8); - colors[14] = bitRangeConvert( (c1>>11)&0x1f, 5, 8); + colors[12] = bitRangeConvert( (c1>> 0)&0x1f, 5, 8); + colors[13] = bitRangeConvert( (c1>> 5)&0x3f, 6, 8); + colors[14] = bitRangeConvert( (c1>>11)&0x1f, 5, 8); - colors[ 4] = colors[ 8] - colors[12] / 4; - colors[ 5] = colors[ 9] - colors[13] / 4; - colors[ 6] = colors[10] - colors[14] / 4; - } + colors[ 4] = colors[ 8] - colors[12] / 4; + colors[ 5] = colors[ 9] - colors[13] / 4; + colors[ 6] = colors[10] - colors[14] / 4; + } - for (uint32_t ii = 0, next = 8*4; ii < 16*4; ii += 4, next += 2) - { - int idx = ( (_src[next>>3] >> (next & 7) ) & 3) * 4; - _dst[ii+0] = colors[idx+0]; - _dst[ii+1] = colors[idx+1]; - _dst[ii+2] = colors[idx+2]; - _dst[ii+3] = colors[idx+3]; - } - } + for (uint32_t ii = 0, next = 8*4; ii < 16*4; ii += 4, next += 2) + { + int32_t idx = ( (_src[next>>3] >> (next & 7) ) & 3) * 4; + _dst[ii+0] = colors[idx+0]; + _dst[ii+1] = colors[idx+1]; + _dst[ii+2] = colors[idx+2]; + _dst[ii+3] = colors[idx+3]; + } + } static const int32_t s_etc1Mod[8][4] = { @@ -2968,10 +3097,10 @@ namespace bimg const uint16_t minBlockX = blockInfo.minBlockX; const uint16_t minBlockY = blockInfo.minBlockY; - _width = bx::uint16_max(blockWidth * minBlockX, ( (_width + blockWidth - 1) / blockWidth)*blockWidth); - _height = bx::uint16_max(blockHeight * minBlockY, ( (_height + blockHeight - 1) / blockHeight)*blockHeight); - _depth = bx::uint16_max(1, _depth); - _numLayers = bx::uint16_max(1, _numLayers); + _width = bx::max(blockWidth * minBlockX, ( (_width + blockWidth - 1) / blockWidth)*blockWidth); + _height = bx::max(blockHeight * minBlockY, ( (_height + blockHeight - 1) / blockHeight)*blockHeight); + _depth = bx::max(1, _depth); + _numLayers = bx::max(1, _numLayers); const uint8_t numMips = _hasMips ? imageGetNumMips(_format, _width, _height, _depth) : 1; uint32_t size = imageGetSize(NULL, _width, _height, _depth, _cubeMap, _hasMips, _numLayers, _format); @@ -3617,15 +3746,15 @@ namespace bimg { KTX_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG, KTX_COMPRESSED_SRGB_ALPHA_PVRTC_4BPPV1_EXT, KTX_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG, KTX_ZERO, }, // PTC14A { KTX_COMPRESSED_RGBA_PVRTC_2BPPV2_IMG, KTX_ZERO, KTX_COMPRESSED_RGBA_PVRTC_2BPPV2_IMG, KTX_ZERO, }, // PTC22 { KTX_COMPRESSED_RGBA_PVRTC_4BPPV2_IMG, KTX_ZERO, KTX_COMPRESSED_RGBA_PVRTC_4BPPV2_IMG, KTX_ZERO, }, // PTC24 - { KTX_ATC_RGB_AMD, KTX_ZERO, KTX_ATC_RGB_AMD, KTX_ZERO, }, // ATC - { KTX_ATC_RGBA_EXPLICIT_ALPHA_AMD, KTX_ZERO, KTX_ATC_RGBA_EXPLICIT_ALPHA_AMD, KTX_ZERO, }, // ATCE - { KTX_ATC_RGBA_INTERPOLATED_ALPHA_AMD, KTX_ZERO, KTX_ATC_RGBA_INTERPOLATED_ALPHA_AMD, KTX_ZERO, }, // ATCI - { KTX_COMPRESSED_RGBA_ASTC_4x4_KHR, KTX_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR, KTX_COMPRESSED_RGBA_ASTC_4x4_KHR, KTX_ZERO, }, // ASTC4x4 - { KTX_COMPRESSED_RGBA_ASTC_5x5_KHR, KTX_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR, KTX_COMPRESSED_RGBA_ASTC_5x5_KHR, KTX_ZERO, }, // ASTC5x5 - { KTX_COMPRESSED_RGBA_ASTC_6x6_KHR, KTX_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR, KTX_COMPRESSED_RGBA_ASTC_6x6_KHR, KTX_ZERO, }, // ASTC6x6 - { KTX_COMPRESSED_RGBA_ASTC_8x5_KHR, KTX_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR, KTX_COMPRESSED_RGBA_ASTC_8x5_KHR, KTX_ZERO, }, // ASTC8x5 - { KTX_COMPRESSED_RGBA_ASTC_8x6_KHR, KTX_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR, KTX_COMPRESSED_RGBA_ASTC_8x6_KHR, KTX_ZERO, }, // ASTC8x6 - { KTX_COMPRESSED_RGBA_ASTC_10x5_KHR, KTX_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR, KTX_COMPRESSED_RGBA_ASTC_10x5_KHR, KTX_ZERO, }, // ASTC10x5 + { KTX_ATC_RGB_AMD, KTX_ZERO, KTX_ATC_RGB_AMD, KTX_ZERO, }, // ATC + { KTX_ATC_RGBA_EXPLICIT_ALPHA_AMD, KTX_ZERO, KTX_ATC_RGBA_EXPLICIT_ALPHA_AMD, KTX_ZERO, }, // ATCE + { KTX_ATC_RGBA_INTERPOLATED_ALPHA_AMD, KTX_ZERO, KTX_ATC_RGBA_INTERPOLATED_ALPHA_AMD, KTX_ZERO, }, // ATCI + { KTX_COMPRESSED_RGBA_ASTC_4x4_KHR, KTX_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR, KTX_COMPRESSED_RGBA_ASTC_4x4_KHR, KTX_ZERO, }, // ASTC4x4 + { KTX_COMPRESSED_RGBA_ASTC_5x5_KHR, KTX_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR, KTX_COMPRESSED_RGBA_ASTC_5x5_KHR, KTX_ZERO, }, // ASTC5x5 + { KTX_COMPRESSED_RGBA_ASTC_6x6_KHR, KTX_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR, KTX_COMPRESSED_RGBA_ASTC_6x6_KHR, KTX_ZERO, }, // ASTC6x6 + { KTX_COMPRESSED_RGBA_ASTC_8x5_KHR, KTX_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR, KTX_COMPRESSED_RGBA_ASTC_8x5_KHR, KTX_ZERO, }, // ASTC8x5 + { KTX_COMPRESSED_RGBA_ASTC_8x6_KHR, KTX_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR, KTX_COMPRESSED_RGBA_ASTC_8x6_KHR, KTX_ZERO, }, // ASTC8x6 + { KTX_COMPRESSED_RGBA_ASTC_10x5_KHR, KTX_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR, KTX_COMPRESSED_RGBA_ASTC_10x5_KHR, KTX_ZERO, }, // ASTC10x5 { KTX_ZERO, KTX_ZERO, KTX_ZERO, KTX_ZERO, }, // Unknown { KTX_ZERO, KTX_ZERO, KTX_ZERO, KTX_ZERO, }, // R1 { KTX_ALPHA, KTX_ZERO, KTX_ALPHA, KTX_UNSIGNED_BYTE, }, // A8 @@ -3784,8 +3913,8 @@ namespace bimg _imageContainer.m_depth = depth; _imageContainer.m_format = format; _imageContainer.m_orientation = Orientation::R0; - _imageContainer.m_numLayers = uint16_t(bx::uint32_max(numberOfArrayElements, 1) ); - _imageContainer.m_numMips = uint8_t(bx::uint32_max(numMips, 1) ); + _imageContainer.m_numLayers = uint16_t(bx::max(numberOfArrayElements, 1) ); + _imageContainer.m_numMips = uint8_t(bx::max(numMips, 1) ); _imageContainer.m_hasAlpha = hasAlpha; _imageContainer.m_cubeMap = numFaces > 1; _imageContainer.m_ktx = true; @@ -3950,7 +4079,7 @@ namespace bimg _imageContainer.m_format = format; _imageContainer.m_orientation = Orientation::R0; _imageContainer.m_numLayers = 1; - _imageContainer.m_numMips = uint8_t(bx::uint32_max(numMips, 1) ); + _imageContainer.m_numMips = uint8_t(bx::max(numMips, 1) ); _imageContainer.m_hasAlpha = hasAlpha; _imageContainer.m_cubeMap = numFaces > 1; _imageContainer.m_ktx = false; @@ -4605,28 +4734,35 @@ namespace bimg for (uint8_t lod = 0, num = _imageContainer.m_numMips; lod < num; ++lod) { - uint32_t sourceSize = bx::toHostEndian(*(const uint32_t*)&data[offset], _imageContainer.m_ktxLE); + width = bx::max(blockWidth * minBlockX, ( (width + blockWidth - 1) / blockWidth )*blockWidth); + height = bx::max(blockHeight * minBlockY, ( (height + blockHeight - 1) / blockHeight)*blockHeight); + depth = bx::max(1, depth); + + const uint32_t mipSize = width/blockWidth * height/blockHeight * depth * blockSize; + if (mipSize != width*height*depth*bpp/8) + { + BX_TRACE("x"); + } + + const uint32_t size = mipSize*numSides; + uint32_t imageSize = bx::toHostEndian(*(const uint32_t*)&data[offset], _imageContainer.m_ktxLE); + BX_CHECK(size == imageSize, "KTX: Image size mismatch %d (expected %d).", size, imageSize); + BX_UNUSED(size, imageSize); + offset += sizeof(uint32_t); - depth = bx::uint32_max(1, depth); - - uint32_t blocksX = bx::uint32_max(minBlockX, ((width + blockWidth - 1) / blockWidth )); - uint32_t blocksY = bx::uint32_max(minBlockY, ((height + blockHeight - 1) / blockHeight)); - - uint32_t destSize = blocksX * blocksY * blockSize * depth; - - BX_CHECK(sourceSize == destSize, "KTX: Image size mismatch %d (expected %d).", sourceSize, destSize); - for (uint16_t side = 0; side < numSides; ++side) { + BX_CHECK(offset <= _size, "Reading past size of data buffer! (offset %d, size %d)", offset, _size); + if (side == _side && lod == _lod) { - _mip.m_width = blocksX * blockWidth; - _mip.m_height = blocksY * blockHeight; + _mip.m_width = width; + _mip.m_height = height; _mip.m_depth = depth; _mip.m_blockSize = blockSize; - _mip.m_size = destSize; + _mip.m_size = mipSize; _mip.m_data = &data[offset]; _mip.m_bpp = bpp; _mip.m_format = format; @@ -4634,9 +4770,8 @@ namespace bimg return true; } - offset += sourceSize; + offset += mipSize; - BX_CHECK(offset <= _size, "Reading past size of data buffer! (offset %d, size %d)", offset, _size); BX_UNUSED(_size); } @@ -4655,21 +4790,22 @@ namespace bimg for (uint8_t lod = 0, num = _imageContainer.m_numMips; lod < num; ++lod) { - depth = bx::uint32_max(1, depth); + BX_CHECK(offset <= _size, "Reading past size of data buffer! (offset %d, size %d)", offset, _size); - uint32_t blocksX = bx::uint32_max(minBlockX, ((width + blockWidth - 1) / blockWidth )); - uint32_t blocksY = bx::uint32_max(minBlockY, ((height + blockHeight - 1) / blockHeight)); + width = bx::max(blockWidth * minBlockX, ( (width + blockWidth - 1) / blockWidth )*blockWidth); + height = bx::max(blockHeight * minBlockY, ( (height + blockHeight - 1) / blockHeight)*blockHeight); + depth = bx::max(1, depth); - uint32_t size = blocksX * blocksY * blockSize * depth; + uint32_t mipSize = width/blockWidth * height/blockHeight * depth * blockSize; if (side == _side && lod == _lod) { - _mip.m_width = blocksX * blockWidth; - _mip.m_height = blocksY * blockHeight; + _mip.m_width = width; + _mip.m_height = height; _mip.m_depth = depth; _mip.m_blockSize = blockSize; - _mip.m_size = size; + _mip.m_size = mipSize; _mip.m_data = &data[offset]; _mip.m_bpp = bpp; _mip.m_format = format; @@ -4677,9 +4813,8 @@ namespace bimg return true; } - offset += size; + offset += mipSize; - BX_CHECK(offset <= _size, "Reading past size of data buffer! (offset %d, size %d)", offset, _size); BX_UNUSED(_size); width >>= 1; @@ -4891,13 +5026,15 @@ namespace bimg total += bx::writeLE(_writer, uint32_t(18*4+1), _err); const uint8_t cdata[] = { 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0 }; - total += bx::write(_writer, 'R', _err); - total += bx::write(_writer, cdata, BX_COUNTOF(cdata), _err); - total += bx::write(_writer, 'G', _err); + // Order is always ABGR order because Photoshop and GIMP ignore these fields and + // assume it's in ABGR order. + total += bx::write(_writer, 'A', _err); total += bx::write(_writer, cdata, BX_COUNTOF(cdata), _err); total += bx::write(_writer, 'B', _err); total += bx::write(_writer, cdata, BX_COUNTOF(cdata), _err); - total += bx::write(_writer, 'A', _err); + total += bx::write(_writer, 'G', _err); + total += bx::write(_writer, cdata, BX_COUNTOF(cdata), _err); + total += bx::write(_writer, 'R', _err); total += bx::write(_writer, cdata, BX_COUNTOF(cdata), _err); total += bx::write(_writer, '\0', _err); @@ -4973,12 +5110,7 @@ namespace bimg for (uint32_t xx = 0; xx < _width && _err->isOk(); ++xx) { - total += bx::write(_writer, &data[xx*bpp/8+0*bytesPerChannel], bytesPerChannel, _err); - } - - for (uint32_t xx = 0; xx < _width && _err->isOk(); ++xx) - { - total += bx::write(_writer, &data[xx*bpp/8+1*bytesPerChannel], bytesPerChannel, _err); + total += bx::write(_writer, &data[xx*bpp/8+3*bytesPerChannel], bytesPerChannel, _err); } for (uint32_t xx = 0; xx < _width && _err->isOk(); ++xx) @@ -4988,7 +5120,53 @@ namespace bimg for (uint32_t xx = 0; xx < _width && _err->isOk(); ++xx) { - total += bx::write(_writer, &data[xx*bpp/8+3*bytesPerChannel], bytesPerChannel, _err); + total += bx::write(_writer, &data[xx*bpp/8+1*bytesPerChannel], bytesPerChannel, _err); + } + + for (uint32_t xx = 0; xx < _width && _err->isOk(); ++xx) + { + total += bx::write(_writer, &data[xx*bpp/8+0*bytesPerChannel], bytesPerChannel, _err); + } + + data += _srcPitch; + } + + return total; + } + + int32_t imageWriteHdr(bx::WriterI* _writer, uint32_t _width, uint32_t _height, uint32_t _srcPitch, const void* _src, TextureFormat::Enum _format, bool _yflip, bx::Error* _err) + { + BX_ERROR_SCOPE(_err); + + int32_t total = 0; + total += bx::write(_writer, "#?RADIANCE\n" , _err); + total += bx::write(_writer, "FORMAT=32-bit_rle_rgbe\n" , _err); + total += bx::write(_writer, '\n' , _err); + + total += bx::writePrintf(_writer, "%cY %d +X %d\n", _yflip ? '+' : '-', _height, _width); + + UnpackFn unpack = getUnpack(_format); + const uint32_t bpp = getBitsPerPixel(_format); + + const uint8_t* data = (const uint8_t*)_src; + for (uint32_t yy = 0; yy < _height && _err->isOk(); ++yy) + { + for (uint32_t xx = 0; xx < _width && _err->isOk(); ++xx) + { + float rgba[4]; + unpack(rgba, &data[xx*bpp/8]); + + const float maxVal = bx::max(rgba[0], rgba[1], rgba[2]); + const float exp = bx::ceil(bx::log2(maxVal) ); + const float toRgb8 = 255.0f * 1.0f/bx::ldexp(1.0f, int(exp) ); + + uint8_t rgbe[4]; + rgbe[0] = uint8_t(rgba[0] * toRgb8); + rgbe[1] = uint8_t(rgba[1] * toRgb8); + rgbe[2] = uint8_t(rgba[2] * toRgb8); + rgbe[3] = uint8_t(exp+128.0f); + + total += bx::write(_writer, rgbe, 4, _err); } data += _srcPitch; @@ -5191,15 +5369,15 @@ namespace bimg } const ImageBlockInfo& blockInfo = s_imageBlockInfo[_format]; - const uint8_t bpp = blockInfo.bitsPerPixel; const uint32_t blockWidth = blockInfo.blockWidth; const uint32_t blockHeight = blockInfo.blockHeight; const uint32_t minBlockX = blockInfo.minBlockX; const uint32_t minBlockY = blockInfo.minBlockY; + const uint8_t blockSize = blockInfo.blockSize; const uint8_t* src = (const uint8_t*)_src; - const uint32_t numLayers = bx::uint32_max(_numLayers, 1); + const uint32_t numLayers = bx::max(_numLayers, 1); const uint32_t numSides = _cubeMap ? 6 : 1; uint32_t width = _width; @@ -5208,12 +5386,12 @@ namespace bimg for (uint8_t lod = 0; lod < _numMips && _err->isOk(); ++lod) { - width = bx::uint32_max(blockWidth * minBlockX, ( (width + blockWidth - 1) / blockWidth )*blockWidth); - height = bx::uint32_max(blockHeight * minBlockY, ( (height + blockHeight - 1) / blockHeight)*blockHeight); - depth = bx::uint32_max(1, depth); + width = bx::max(blockWidth * minBlockX, ( (width + blockWidth - 1) / blockWidth )*blockWidth); + height = bx::max(blockHeight * minBlockY, ( (height + blockHeight - 1) / blockHeight)*blockHeight); + depth = bx::max(1, depth); - const uint32_t mipSize = width*height*depth*bpp/8; - const uint32_t size = mipSize*numLayers*numSides; + const uint32_t mipSize = width/blockWidth * height/blockHeight * depth * blockSize; + const uint32_t size = mipSize * numLayers * numSides; total += bx::write(_writer, size, _err); for (uint32_t layer = 0; layer < numLayers && _err->isOk(); ++layer) @@ -5255,7 +5433,7 @@ namespace bimg } const uint32_t numMips = _imageContainer.m_numMips; - const uint32_t numLayers = bx::uint32_max(_imageContainer.m_numLayers, 1); + const uint32_t numLayers = bx::max(_imageContainer.m_numLayers, 1); const uint32_t numSides = _imageContainer.m_cubeMap ? 6 : 1; for (uint8_t lod = 0; lod < numMips && _err->isOk(); ++lod) @@ -5281,185 +5459,4 @@ namespace bimg return total; } - // +----------+ - // |-z 2| - // | ^ +y | - // | | | - // | +---->+x | - // +----------+----------+----------+----------+ - // |+y 1|+y 4|+y 0|+y 5| - // | ^ -x | ^ +z | ^ +x | ^ -z | - // | | | | | | | | | - // | +---->+z | +---->+x | +---->-z | +---->-x | - // +----------+----------+----------+----------+ - // |+z 3| - // | ^ -y | - // | | | - // | +---->+x | - // +----------+ - // - struct CubeMapFace - { - float uv[3][3]; - }; - - static const CubeMapFace s_cubeMapFace[] = - { - {{ // +x face - { 0.0f, 0.0f, -1.0f }, // u -> -z - { 0.0f, -1.0f, 0.0f }, // v -> -y - { 1.0f, 0.0f, 0.0f }, // +x face - }}, - {{ // -x face - { 0.0f, 0.0f, 1.0f }, // u -> +z - { 0.0f, -1.0f, 0.0f }, // v -> -y - { -1.0f, 0.0f, 0.0f }, // -x face - }}, - {{ // +y face - { 1.0f, 0.0f, 0.0f }, // u -> +x - { 0.0f, 0.0f, 1.0f }, // v -> +z - { 0.0f, 1.0f, 0.0f }, // +y face - }}, - {{ // -y face - { 1.0f, 0.0f, 0.0f }, // u -> +x - { 0.0f, 0.0f, -1.0f }, // v -> -z - { 0.0f, -1.0f, 0.0f }, // -y face - }}, - {{ // +z face - { 1.0f, 0.0f, 0.0f }, // u -> +x - { 0.0f, -1.0f, 0.0f }, // v -> -y - { 0.0f, 0.0f, 1.0f }, // +z face - }}, - {{ // -z face - { -1.0f, 0.0f, 0.0f }, // u -> -x - { 0.0f, -1.0f, 0.0f }, // v -> -y - { 0.0f, 0.0f, -1.0f }, // -z face - }}, - }; - - /// _u and _v should be center addressing and in [-1.0+invSize..1.0-invSize] range. - void texelUvToDir(float* _result, uint8_t _side, float _u, float _v) - { - const CubeMapFace& face = s_cubeMapFace[_side]; - - float tmp[3]; - tmp[0] = face.uv[0][0] * _u + face.uv[1][0] * _v + face.uv[2][0]; - tmp[1] = face.uv[0][1] * _u + face.uv[1][1] * _v + face.uv[2][1]; - tmp[2] = face.uv[0][2] * _u + face.uv[1][2] * _v + face.uv[2][2]; - bx::vec3Norm(_result, tmp); - } - - ImageContainer* imageCubemapFromLatLongRgba32F(bx::AllocatorI* _allocator, const ImageContainer& _input, bool _useBilinearInterpolation, bx::Error* _err) - { - BX_ERROR_SCOPE(_err); - - if (_input.m_depth != 1 - && _input.m_numLayers != 1 - && _input.m_format != TextureFormat::RGBA32F - && _input.m_width/2 != _input.m_height) - { - BX_ERROR_SET(_err, BIMG_ERROR, "Input image format is not equirectangular projection."); - return NULL; - } - - const uint32_t srcWidthMinusOne = _input.m_width-1; - const uint32_t srcHeightMinusOne = _input.m_height-1; - const uint32_t srcPitch = _input.m_width*16; - const uint32_t dstWidth = _input.m_height/2; - const uint32_t dstPitch = dstWidth*16; - const float invDstWidth = 1.0f / float(dstWidth); - - ImageContainer* output = imageAlloc(_allocator - , _input.m_format - , uint16_t(dstWidth) - , uint16_t(dstWidth) - , uint16_t(1) - , 1 - , true - , false - ); - - const uint8_t* srcData = (const uint8_t*)_input.m_data; - - for (uint8_t side = 0; side < 6 && _err->isOk(); ++side) - { - ImageMip mip; - imageGetRawData(*output, side, 0, output->m_data, output->m_size, mip); - - for (uint32_t yy = 0; yy < dstWidth; ++yy) - { - for (uint32_t xx = 0; xx < dstWidth; ++xx) - { - float* dstData = (float*)&mip.m_data[yy*dstPitch+xx*16]; - - const float uu = 2.0f*xx*invDstWidth - 1.0f; - const float vv = 2.0f*yy*invDstWidth - 1.0f; - - float dir[3]; - texelUvToDir(dir, side, uu, vv); - - float srcU, srcV; - bx::vec3ToLatLong(&srcU, &srcV, dir); - - srcU *= srcWidthMinusOne; - srcV *= srcHeightMinusOne; - - if (_useBilinearInterpolation) - { - const uint32_t x0 = uint32_t(srcU); - const uint32_t y0 = uint32_t(srcV); - const uint32_t x1 = bx::min(x0 + 1, srcWidthMinusOne); - const uint32_t y1 = bx::min(y0 + 1, srcHeightMinusOne); - - const float* src0 = (const float*)&srcData[y0*srcPitch + x0*16]; - const float* src1 = (const float*)&srcData[y0*srcPitch + x1*16]; - const float* src2 = (const float*)&srcData[y1*srcPitch + x0*16]; - const float* src3 = (const float*)&srcData[y1*srcPitch + x1*16]; - - const float tx = srcU - float(int32_t(x0) ); - const float ty = srcV - float(int32_t(y0) ); - const float omtx = 1.0f - tx; - const float omty = 1.0f - ty; - - float p0[4]; - bx::vec4Mul(p0, src0, omtx*omty); - - float p1[4]; - bx::vec4Mul(p1, src1, tx*omty); - - float p2[4]; - bx::vec4Mul(p2, src2, omtx*ty); - - float p3[4]; - bx::vec4Mul(p3, src3, tx*ty); - - const float rr = p0[0] + p1[0] + p2[0] + p3[0]; - const float gg = p0[1] + p1[1] + p2[1] + p3[1]; - const float bb = p0[2] + p1[2] + p2[2] + p3[2]; - const float aa = p0[3] + p1[3] + p2[3] + p3[3]; - - dstData[0] = rr; - dstData[1] = gg; - dstData[2] = bb; - dstData[3] = aa; - } - else - { - const uint32_t x0 = uint32_t(srcU); - const uint32_t y0 = uint32_t(srcV); - const float* src0 = (const float*)&srcData[y0*srcPitch + x0*16]; - - dstData[0] = src0[0]; - dstData[1] = src0[1]; - dstData[2] = src0[2]; - dstData[3] = src0[3]; - } - - } - } - } - - return output; - } - } // namespace bimg diff --git a/src/image_cubemap_filter.cpp b/src/image_cubemap_filter.cpp new file mode 100644 index 0000000..37a44d1 --- /dev/null +++ b/src/image_cubemap_filter.cpp @@ -0,0 +1,1207 @@ +/* + * Copyright 2011-2018 Branimir Karadzic. All rights reserved. + * License: https://github.com/bkaradzic/bimg#license-bsd-2-clause + */ + +#include "bimg_p.h" +#include +#include + +namespace bimg +{ + /* + * Copyright 2014-2015 Dario Manesku. All rights reserved. + * License: http://www.opensource.org/licenses/BSD-2-Clause + */ + + // +----------+ + // |-z 2| + // | ^ +y | + // | | | + // | +---->+x | + // +----------+----------+----------+----------+ + // |+y 1|+y 4|+y 0|+y 5| + // | ^ -x | ^ +z | ^ +x | ^ -z | + // | | | | | | | | | + // | +---->+z | +---->+x | +---->-z | +---->-x | + // +----------+----------+----------+----------+ + // |+z 3| + // | ^ -y | + // | | | + // | +---->+x | + // +----------+ + // + struct CubeMapFace + { + enum Enum + { + PositiveX, + NegativeX, + PositiveY, + NegativeY, + PositiveZ, + NegativeZ, + + Count + }; + + struct Edge + { + enum Enum + { + Left, + Right, + Top, + Bottom, + + Count + }; + }; + + // --> U _____ + // | | | + // v | +Y | + // V _____|_____|_____ _____ + // | | | | | + // | -X | +Z | +X | -Z | + // |_____|_____|_____|_____| + // | | + // | -Y | + // |_____| + // + // Neighbour faces in order: left, right, top, bottom. + // FaceEdge is the edge that belongs to the neighbour face. + struct Neighbour + { + uint8_t m_faceIdx; + uint8_t m_faceEdge; + }; + + float uv[3][3]; + }; + + static const CubeMapFace s_cubeMapFace[] = + { + {{ // +x face + { 0.0f, 0.0f, -1.0f }, // u -> -z + { 0.0f, -1.0f, 0.0f }, // v -> -y + { 1.0f, 0.0f, 0.0f }, // +x face + }}, + {{ // -x face + { 0.0f, 0.0f, 1.0f }, // u -> +z + { 0.0f, -1.0f, 0.0f }, // v -> -y + { -1.0f, 0.0f, 0.0f }, // -x face + }}, + {{ // +y face + { 1.0f, 0.0f, 0.0f }, // u -> +x + { 0.0f, 0.0f, 1.0f }, // v -> +z + { 0.0f, 1.0f, 0.0f }, // +y face + }}, + {{ // -y face + { 1.0f, 0.0f, 0.0f }, // u -> +x + { 0.0f, 0.0f, -1.0f }, // v -> -z + { 0.0f, -1.0f, 0.0f }, // -y face + }}, + {{ // +z face + { 1.0f, 0.0f, 0.0f }, // u -> +x + { 0.0f, -1.0f, 0.0f }, // v -> -y + { 0.0f, 0.0f, 1.0f }, // +z face + }}, + {{ // -z face + { -1.0f, 0.0f, 0.0f }, // u -> -x + { 0.0f, -1.0f, 0.0f }, // v -> -y + { 0.0f, 0.0f, -1.0f }, // -z face + }}, + }; + + static const CubeMapFace::Neighbour s_cubeMapFaceNeighbours[6][4] = + { + { // +X + { CubeMapFace::PositiveZ, CubeMapFace::Edge::Right }, + { CubeMapFace::NegativeZ, CubeMapFace::Edge::Left }, + { CubeMapFace::PositiveY, CubeMapFace::Edge::Right }, + { CubeMapFace::NegativeY, CubeMapFace::Edge::Right }, + }, + { // -X + { CubeMapFace::NegativeZ, CubeMapFace::Edge::Right }, + { CubeMapFace::PositiveZ, CubeMapFace::Edge::Left }, + { CubeMapFace::PositiveY, CubeMapFace::Edge::Left }, + { CubeMapFace::NegativeY, CubeMapFace::Edge::Left }, + }, + { // +Y + { CubeMapFace::NegativeX, CubeMapFace::Edge::Top }, + { CubeMapFace::PositiveX, CubeMapFace::Edge::Top }, + { CubeMapFace::NegativeZ, CubeMapFace::Edge::Top }, + { CubeMapFace::PositiveZ, CubeMapFace::Edge::Top }, + }, + { // -Y + { CubeMapFace::NegativeX, CubeMapFace::Edge::Bottom }, + { CubeMapFace::PositiveX, CubeMapFace::Edge::Bottom }, + { CubeMapFace::PositiveZ, CubeMapFace::Edge::Bottom }, + { CubeMapFace::NegativeZ, CubeMapFace::Edge::Bottom }, + }, + { // +Z + { CubeMapFace::NegativeX, CubeMapFace::Edge::Right }, + { CubeMapFace::PositiveX, CubeMapFace::Edge::Left }, + { CubeMapFace::PositiveY, CubeMapFace::Edge::Bottom }, + { CubeMapFace::NegativeY, CubeMapFace::Edge::Top }, + }, + { // -Z + { CubeMapFace::PositiveX, CubeMapFace::Edge::Right }, + { CubeMapFace::NegativeX, CubeMapFace::Edge::Left }, + { CubeMapFace::PositiveY, CubeMapFace::Edge::Top }, + { CubeMapFace::NegativeY, CubeMapFace::Edge::Bottom }, + }, + }; + + /// _u and _v should be center addressing and in [-1.0+invSize..1.0-invSize] range. + void texelUvToDir(float* _outDir, uint8_t _side, float _u, float _v) + { + const CubeMapFace& face = s_cubeMapFace[_side]; + + float tmp[3]; + tmp[0] = face.uv[0][0] * _u + face.uv[1][0] * _v + face.uv[2][0]; + tmp[1] = face.uv[0][1] * _u + face.uv[1][1] * _v + face.uv[2][1]; + tmp[2] = face.uv[0][2] * _u + face.uv[1][2] * _v + face.uv[2][2]; + bx::vec3Norm(_outDir, tmp); + } + + void dirToTexelUv(float& _outU, float& _outV, uint8_t& _outSide, const float* _dir) + { + float absVec[3]; + bx::vec3Abs(absVec, _dir); + + const float max = bx::max(absVec[0], absVec[1], absVec[2]); + + if (max == absVec[0]) + { + _outSide = (_dir[0] >= 0.0f) ? uint8_t(CubeMapFace::PositiveX) : uint8_t(CubeMapFace::NegativeX); + } + else if (max == absVec[1]) + { + _outSide = (_dir[1] >= 0.0f) ? uint8_t(CubeMapFace::PositiveY) : uint8_t(CubeMapFace::NegativeY); + } + else + { + _outSide = (_dir[2] >= 0.0f) ? uint8_t(CubeMapFace::PositiveZ) : uint8_t(CubeMapFace::NegativeZ); + } + + float faceVec[3]; + bx::vec3Mul(faceVec, _dir, 1.0f/max); + + _outU = (bx::vec3Dot(s_cubeMapFace[_outSide].uv[0], faceVec) + 1.0f) * 0.5f; + _outV = (bx::vec3Dot(s_cubeMapFace[_outSide].uv[1], faceVec) + 1.0f) * 0.5f; + } + + ImageContainer* imageCubemapFromLatLongRgba32F(bx::AllocatorI* _allocator, const ImageContainer& _input, bool _useBilinearInterpolation, bx::Error* _err) + { + BX_ERROR_SCOPE(_err); + + if (_input.m_depth != 1 + && _input.m_numLayers != 1 + && _input.m_format != TextureFormat::RGBA32F + && _input.m_width/2 != _input.m_height) + { + BX_ERROR_SET(_err, BIMG_ERROR, "Input image format is not equirectangular projection."); + return NULL; + } + + const uint32_t srcWidthMinusOne = _input.m_width-1; + const uint32_t srcHeightMinusOne = _input.m_height-1; + const uint32_t srcPitch = _input.m_width*16; + const uint32_t dstWidth = _input.m_height/2; + const uint32_t dstPitch = dstWidth*16; + const float invDstWidth = 1.0f / float(dstWidth); + + ImageContainer* output = imageAlloc(_allocator + , _input.m_format + , uint16_t(dstWidth) + , uint16_t(dstWidth) + , uint16_t(1) + , 1 + , true + , false + ); + + const uint8_t* srcData = (const uint8_t*)_input.m_data; + + for (uint8_t side = 0; side < 6 && _err->isOk(); ++side) + { + ImageMip mip; + imageGetRawData(*output, side, 0, output->m_data, output->m_size, mip); + + for (uint32_t yy = 0; yy < dstWidth; ++yy) + { + for (uint32_t xx = 0; xx < dstWidth; ++xx) + { + float* dstData = (float*)&mip.m_data[yy*dstPitch+xx*16]; + + const float uu = 2.0f*xx*invDstWidth - 1.0f; + const float vv = 2.0f*yy*invDstWidth - 1.0f; + + float dir[3]; + texelUvToDir(dir, side, uu, vv); + + float srcU, srcV; + bx::vec3ToLatLong(&srcU, &srcV, dir); + + srcU *= srcWidthMinusOne; + srcV *= srcHeightMinusOne; + + if (_useBilinearInterpolation) + { + const uint32_t x0 = uint32_t(srcU); + const uint32_t y0 = uint32_t(srcV); + const uint32_t x1 = bx::min(x0 + 1, srcWidthMinusOne); + const uint32_t y1 = bx::min(y0 + 1, srcHeightMinusOne); + + const float* src0 = (const float*)&srcData[y0*srcPitch + x0*16]; + const float* src1 = (const float*)&srcData[y0*srcPitch + x1*16]; + const float* src2 = (const float*)&srcData[y1*srcPitch + x0*16]; + const float* src3 = (const float*)&srcData[y1*srcPitch + x1*16]; + + const float tx = srcU - float(int32_t(x0) ); + const float ty = srcV - float(int32_t(y0) ); + const float omtx = 1.0f - tx; + const float omty = 1.0f - ty; + + float p0[4]; + bx::vec4Mul(p0, src0, omtx*omty); + + float p1[4]; + bx::vec4Mul(p1, src1, tx*omty); + + float p2[4]; + bx::vec4Mul(p2, src2, omtx*ty); + + float p3[4]; + bx::vec4Mul(p3, src3, tx*ty); + + const float rr = p0[0] + p1[0] + p2[0] + p3[0]; + const float gg = p0[1] + p1[1] + p2[1] + p3[1]; + const float bb = p0[2] + p1[2] + p2[2] + p3[2]; + const float aa = p0[3] + p1[3] + p2[3] + p3[3]; + + dstData[0] = rr; + dstData[1] = gg; + dstData[2] = bb; + dstData[3] = aa; + } + else + { + const uint32_t x0 = uint32_t(srcU); + const uint32_t y0 = uint32_t(srcV); + const float* src0 = (const float*)&srcData[y0*srcPitch + x0*16]; + + dstData[0] = src0[0]; + dstData[1] = src0[1]; + dstData[2] = src0[2]; + dstData[3] = src0[3]; + } + + } + } + } + + return output; + } + + ImageContainer* imageCubemapFromStripRgba32F(bx::AllocatorI* _allocator, const ImageContainer& _input, bx::Error* _err) + { + BX_ERROR_SCOPE(_err); + + if (_input.m_depth != 1 + && _input.m_numLayers != 1 + && _input.m_format != TextureFormat::RGBA32F + && _input.m_width/6 != _input.m_height) + { + BX_ERROR_SET(_err, BIMG_ERROR, "Input image format is not strip projection."); + return NULL; + } + + const uint32_t srcPitch = _input.m_width*16; + const uint32_t dstWidth = _input.m_height; + const uint32_t dstPitch = dstWidth*16; + + ImageContainer* output = imageAlloc(_allocator + , _input.m_format + , uint16_t(dstWidth) + , uint16_t(dstWidth) + , uint16_t(1) + , 1 + , true + , false + ); + + const uint8_t* srcData = (const uint8_t*)_input.m_data; + + for (uint8_t side = 0; side < 6 && _err->isOk(); ++side, srcData += dstPitch) + { + ImageMip dstMip; + imageGetRawData(*output, side, 0, output->m_data, output->m_size, dstMip); + + bx::memCopy(const_cast(dstMip.m_data), srcData, dstPitch, dstWidth, srcPitch, dstPitch); + } + + return output; + } + + inline float areaElement(float _x, float _y) + { + return bx::atan2(_x*_y, bx::sqrt(_x*_x + _y*_y + 1.0f)); + } + + float texelSolidAngle(float _u, float _v, float _invFaceSize) + { + // Reference: + // - https://web.archive.org/web/20180614195754/http://www.mpia.de/~mathar/public/mathar20051002.pdf + // - https://web.archive.org/web/20180614195725/http://www.rorydriscoll.com/2012/01/15/cubemap-texel-solid-angle/ + // + const float x0 = _u - _invFaceSize; + const float x1 = _u + _invFaceSize; + const float y0 = _v - _invFaceSize; + const float y1 = _v + _invFaceSize; + + return + + areaElement(x1, y1) + - areaElement(x0, y1) + - areaElement(x1, y0) + + areaElement(x0, y0) + ; + } + + ImageContainer* imageCubemapNormalSolidAngle(bx::AllocatorI* _allocator, uint32_t _size) + { + const uint32_t dstWidth = _size; + const uint32_t dstPitch = dstWidth*16; + const float texelSize = 1.0f / float(dstWidth); + + ImageContainer* output = imageAlloc(_allocator, TextureFormat::RGBA32F, uint16_t(dstWidth), uint16_t(dstWidth), 1, 1, true, false); + + for (uint8_t side = 0; side < 6; ++side) + { + ImageMip mip; + imageGetRawData(*output, side, 0, output->m_data, output->m_size, mip); + + for (uint32_t yy = 0; yy < dstWidth; ++yy) + { + for (uint32_t xx = 0; xx < dstWidth; ++xx) + { + float* dstData = (float*)&mip.m_data[yy*dstPitch+xx*16]; + + const float uu = float(xx)*texelSize*2.0f - 1.0f; + const float vv = float(yy)*texelSize*2.0f - 1.0f; + + texelUvToDir(dstData, side, uu, vv); + dstData[3] = texelSolidAngle(uu, vv, texelSize); + } + } + } + + return output; + } + + struct Aabb + { + Aabb() + { + m_min[0] = bx::kFloatMax; + m_min[1] = bx::kFloatMax; + m_max[0] = -bx::kFloatMax; + m_max[1] = -bx::kFloatMax; + } + + void add(float _x, float _y) + { + m_min[0] = bx::min(m_min[0], _x); + m_min[1] = bx::min(m_min[1], _y); + m_max[0] = bx::max(m_max[0], _x); + m_max[1] = bx::max(m_max[1], _y); + } + + void clamp(float _min, float _max) + { + m_min[0] = bx::clamp(m_min[0], _min, _max); + m_min[1] = bx::clamp(m_min[1], _min, _max); + m_max[0] = bx::clamp(m_max[0], _min, _max); + m_max[1] = bx::clamp(m_max[1], _min, _max); + } + + bool isEmpty() const + { + // Has to have at least two points added so that no value is equal to initial state. + return ( (m_min[0] == bx::kFloatMax) + || (m_min[1] == bx::kFloatMax) + || (m_max[0] == -bx::kFloatMax) + || (m_max[1] == -bx::kFloatMax) + ); + } + + float m_min[2]; + float m_max[2]; + }; + + void calcFilterArea(Aabb* _outFilterArea, const float* _dir, float _filterSize) + { + /// ______ + /// | | + /// | | + /// | x | + /// |______| + /// + // Get face and hit coordinates. + float uu, vv; + uint8_t hitFaceIdx; + dirToTexelUv(uu, vv, hitFaceIdx, _dir); + + /// ........ + /// . . + /// . ___. + /// . | x | + /// ...|___| + /// + // Calculate hit face filter bounds. + Aabb hitFaceFilterBounds; + hitFaceFilterBounds.add(uu-_filterSize, vv-_filterSize); + hitFaceFilterBounds.add(uu+_filterSize, vv+_filterSize); + hitFaceFilterBounds.clamp(0.0f, 1.0f); + + // Output result for hit face. + bx::memCopy(&_outFilterArea[hitFaceIdx], &hitFaceFilterBounds, sizeof(Aabb)); + + /// Filter area might extend on neighbour faces. + /// Case when extending over the right edge: + /// + /// --> U + /// | ...... + /// v . . + /// V . . + /// . . + /// ....... ...... ....... + /// . . . . + /// . . .....__min . + /// . . . . | -> amount + /// ....... .....x.__|.... + /// . . . max + /// . ........ + /// . . + /// ...... + /// . . + /// . . + /// . . + /// ...... + /// + + struct NeighourFaceBleed + { + float m_amount; + float m_bbMin; + float m_bbMax; + }; + + const NeighourFaceBleed bleed[CubeMapFace::Edge::Count] = + { + { // Left + _filterSize - uu, + hitFaceFilterBounds.m_min[1], + hitFaceFilterBounds.m_max[1], + }, + { // Right + uu + _filterSize - 1.0f, + hitFaceFilterBounds.m_min[1], + hitFaceFilterBounds.m_max[1], + }, + { // Top + _filterSize - vv, + hitFaceFilterBounds.m_min[0], + hitFaceFilterBounds.m_max[0], + }, + { // Bottom + vv + _filterSize - 1.0f, + hitFaceFilterBounds.m_min[0], + hitFaceFilterBounds.m_max[0], + }, + }; + + // Determine bleeding for each side. + for (uint8_t side = 0; side < 4; ++side) + { + uint8_t currentFaceIdx = hitFaceIdx; + + for (float bleedAmount = bleed[side].m_amount; bleedAmount > 0.0f; bleedAmount -= 1.0f) + { + uint8_t neighbourFaceIdx = s_cubeMapFaceNeighbours[currentFaceIdx][side].m_faceIdx; + uint8_t neighbourFaceEdge = s_cubeMapFaceNeighbours[currentFaceIdx][side].m_faceEdge; + currentFaceIdx = neighbourFaceIdx; + + /// https://code.google.com/p/cubemapgen/source/browse/trunk/CCubeMapProcessor.cpp#773 + /// + /// Handle situations when bbMin and bbMax should be flipped. + /// + /// L - Left ....................T-T + /// R - Right v . + /// T - Top __________ . + /// B - Bottom . | . + /// . | . + /// . |<...R-T . + /// . | v v + /// .......... ..........|__________ __________ + /// . . . . . + /// . . . . . + /// . . . . . + /// . . . . . + /// __________ .......... .......... __________ + /// ^ | . ^ + /// . | . . + /// B-L..>| . . + /// | . . + /// |__________. . + /// ^ . + /// ....................B-B + /// + /// Those are: + /// B-L, B-B + /// T-R, T-T + /// (and in reverse order, R-T and L-B) + /// + /// If we add, R-R and L-L (which never occur), we get: + /// B-L, B-B + /// T-R, T-T + /// R-T, R-R + /// L-B, L-L + /// + /// And if L = 0, R = 1, T = 2, B = 3 as in NeighbourSides enumeration, + /// a general rule can be derived for when to flip bbMin and bbMax: + /// if ((a+b) == 3 || (a == b)) + /// { + /// ..flip bbMin and bbMax + /// } + /// + float bbMin = bleed[side].m_bbMin; + float bbMax = bleed[side].m_bbMax; + if ( (side == neighbourFaceEdge) + || (3 == (side + neighbourFaceEdge) ) ) + { + // Flip. + bbMin = 1.0f - bbMin; + bbMax = 1.0f - bbMax; + } + + switch (neighbourFaceEdge) + { + case CubeMapFace::Edge::Left: + { + /// --> U + /// | ............. + /// v . . + /// V x___ . + /// | | . + /// | | . + /// |___x . + /// . . + /// ............. + /// + _outFilterArea[neighbourFaceIdx].add(0.0f, bbMin); + _outFilterArea[neighbourFaceIdx].add(bleedAmount, bbMax); + } + break; + + case CubeMapFace::Edge::Right: + { + /// --> U + /// | ............. + /// v . . + /// V . x___. + /// . | | + /// . | | + /// . |___x + /// . . + /// ............. + /// + _outFilterArea[neighbourFaceIdx].add(1.0f - bleedAmount, bbMin); + _outFilterArea[neighbourFaceIdx].add(1.0f, bbMax); + } + break; + + case CubeMapFace::Edge::Top: + { + /// --> U + /// | ...x____ ... + /// v . | | . + /// V . |____x . + /// . . + /// . . + /// . . + /// ............ + /// + _outFilterArea[neighbourFaceIdx].add(bbMin, 0.0f); + _outFilterArea[neighbourFaceIdx].add(bbMax, bleedAmount); + } + break; + + case CubeMapFace::Edge::Bottom: + { + /// --> U + /// | ............ + /// v . . + /// V . . + /// . . + /// . x____ . + /// . | | . + /// ...|____x... + /// + _outFilterArea[neighbourFaceIdx].add(bbMin, 1.0f - bleedAmount); + _outFilterArea[neighbourFaceIdx].add(bbMax, 1.0f); + } + break; + } + + // Clamp bounding box to face size. + _outFilterArea[neighbourFaceIdx].clamp(0.0f, 1.0f); + } + } + } + + struct Sampler + { + Sampler(const ImageContainer& _image, uint16_t _side, float _lod, float (*func)(float) ) + { + const float lod = bx::clamp(_lod, 0.0f, float(_image.m_numMips - 1) ); + imageGetRawData( + _image + , _side + , uint8_t(func(lod) ) + , _image.m_data + , _image.m_size + , mip + ); + } + + ImageMip mip; + }; + + void texelFetch(float* _rgba, const Sampler& _sampler, uint32_t _u, uint32_t _v) + { + const uint32_t bpp = _sampler.mip.m_bpp; + const uint32_t pitch = _sampler.mip.m_width*bpp/8; + const uint8_t* texel = _sampler.mip.m_data + _v*pitch + _u*bpp/8; + + UnpackFn unpack = getUnpack(_sampler.mip.m_format); + unpack(_rgba, texel); + } + + void sampleCubeMap(float* _rgba, const ImageContainer& _image, const float* _dir, float _lod) + { + float uu, vv; + uint8_t side; + dirToTexelUv(uu, vv, side, _dir); + + const float fu = bx::fract(uu); + const float fv = bx::fract(vv); + const float fl = bx::fract(_lod); + + float rgbaA[4]; + { + Sampler sampler(_image, side, _lod, bx::floor); + const uint32_t widthMinusOne = sampler.mip.m_width-1; + + const uint32_t u0 = uint32_t(uu*widthMinusOne+0.5f); + const uint32_t v0 = uint32_t(vv*widthMinusOne+0.5f); + const uint32_t u1 = bx::min(u0 + 1, widthMinusOne); + const uint32_t v1 = bx::min(v0 + 1, widthMinusOne); + + float rgba00[4]; + texelFetch(rgba00, sampler, u0, v0); + + float rgba01[4]; + texelFetch(rgba01, sampler, u0, v1); + + float rgba10[4]; + texelFetch(rgba10, sampler, u1, v0); + + float rgba11[4]; + texelFetch(rgba11, sampler, u1, v1); + + rgbaA[0] = bx::lerp(bx::lerp(rgba00[0], rgba01[0], fv), bx::lerp(rgba10[0], rgba11[0], fv), fu); + rgbaA[1] = bx::lerp(bx::lerp(rgba00[1], rgba01[1], fv), bx::lerp(rgba10[1], rgba11[1], fv), fu); + rgbaA[2] = bx::lerp(bx::lerp(rgba00[2], rgba01[2], fv), bx::lerp(rgba10[2], rgba11[2], fv), fu); + rgbaA[3] = bx::lerp(bx::lerp(rgba00[3], rgba01[3], fv), bx::lerp(rgba10[3], rgba11[3], fv), fu); + } + + float rgbaB[4]; + { + Sampler sampler(_image, side, _lod, bx::ceil); + const uint32_t widthMinusOne = sampler.mip.m_width-1; + + const uint32_t u0 = uint32_t(uu*widthMinusOne+0.5f); + const uint32_t v0 = uint32_t(vv*widthMinusOne+0.5f); + const uint32_t u1 = bx::min(u0 + 1, widthMinusOne); + const uint32_t v1 = bx::min(v0 + 1, widthMinusOne); + + float rgba00[4]; + texelFetch(rgba00, sampler, u0, v0); + + float rgba01[4]; + texelFetch(rgba01, sampler, u0, v1); + + float rgba10[4]; + texelFetch(rgba10, sampler, u1, v0); + + float rgba11[4]; + texelFetch(rgba11, sampler, u1, v1); + + rgbaB[0] = bx::lerp(bx::lerp(rgba00[0], rgba01[0], fv), bx::lerp(rgba10[0], rgba11[0], fv), fu); + rgbaB[1] = bx::lerp(bx::lerp(rgba00[1], rgba01[1], fv), bx::lerp(rgba10[1], rgba11[1], fv), fu); + rgbaB[2] = bx::lerp(bx::lerp(rgba00[2], rgba01[2], fv), bx::lerp(rgba10[2], rgba11[2], fv), fu); + rgbaB[3] = bx::lerp(bx::lerp(rgba00[3], rgba01[3], fv), bx::lerp(rgba10[3], rgba11[3], fv), fu); + } + + _rgba[0] = bx::lerp(rgbaA[0], rgbaB[0], fl); + _rgba[1] = bx::lerp(rgbaA[1], rgbaB[1], fl); + _rgba[2] = bx::lerp(rgbaA[2], rgbaB[2], fl); + _rgba[3] = bx::lerp(rgbaA[3], rgbaB[3], fl); + } + + void importanceSampleGgx(float* _result, float _u, float _v, float _roughness, const float* _normal, const float* _tangentX, const float* _tangentY) + { + const float aa = bx::square(_roughness); + const float phi = bx::kPi2 * _u; + const float cosTheta = bx::sqrt( (1.0f - _v) / (1.0f + (bx::square(aa) - 1.0f) * _v) ); + const float sinTheta = bx::sqrt(bx::abs(1.0f - bx::square(cosTheta) ) ); + + const float hh[3] = + { + sinTheta * bx::cos(phi), + sinTheta * bx::sin(phi), + cosTheta, + }; + + _result[0] = _tangentX[0] * hh[0] + _tangentY[0] * hh[1] + _normal[0] * hh[2]; + _result[1] = _tangentX[1] * hh[0] + _tangentY[1] * hh[1] + _normal[1] * hh[2]; + _result[2] = _tangentX[2] * hh[0] + _tangentY[2] * hh[1] + _normal[2] * hh[2]; + } + + float normalDistributionGgx(float _ndoth, float _roughness) + { + const float alpha = bx::square(_roughness); + const float alphaSq = bx::square(alpha); + const float denom = bx::square(_ndoth) * (alphaSq - 1.0f) + 1.0f; + const float denomSq = bx::square(denom); + return alphaSq/(bx::kPi * denomSq); + } + + void processFilterAreaGgx( + float* _result + , const ImageContainer& _image + , uint8_t _lod + , const float* _dir + , float _roughness + ) + { + ImageMip mip; + imageGetRawData(_image, 0, _lod, _image.m_data, _image.m_size, mip); + + const uint32_t bpp = getBitsPerPixel(_image.m_format); + + constexpr int32_t kNumSamples = 512; + const uint32_t pitch = mip.m_width*bpp/8; + const float widthMinusOne = float(mip.m_width-1); + const float mipBias = 0.5f*bx::log2(bx::square(float(_image.m_width) )/float(kNumSamples) ); + + UnpackFn unpack = getUnpack(_image.m_format); + + float color[3] = { 0.0f, 0.0f, 0.0f }; + float totalWeight = 0.0f; + + // Golden Ratio Sequences for Low-Discrepancy Sampling + // https://web.archive.org/web/20180717194847/https://www.graphics.rwth-aachen.de/publication/2/jgt.pdf + // + // kGoldenSection = (0.5f*bx::sqrt(5.0f) + 0.5f) - 1.0f = 0.61803398875f + // + const float kGoldenSection = 0.61803398875f; + float offset = kGoldenSection; + + float tangentX[3]; + float tangentY[3]; + bx::vec3TangentFrame(_dir, tangentX, tangentY); + + for (uint32_t ii = 0; ii < kNumSamples; ++ii) + { + offset += kGoldenSection; + const float vv = ii/float(kNumSamples); + + float hh[3]; + importanceSampleGgx(hh, offset, vv, _roughness, _dir, tangentX, tangentY); + + const float ddoth2 = 2.0f * bx::vec3Dot(_dir, hh); + + float ll[3]; + ll[0] = ddoth2 * hh[0] - _dir[0]; + ll[1] = ddoth2 * hh[1] - _dir[1]; + ll[2] = ddoth2 * hh[2] - _dir[2]; + + const float ndotl = bx::clamp(bx::vec3Dot(_dir, ll), 0.0f, 1.0f); + + if (ndotl > 0.0f) + { + const float ndoth = bx::clamp(bx::vec3Dot(_dir, hh), 0.0f, 1.0f); + const float vdoth = ndoth; + + // Chapter 20. GPU-Based Importance Sampling + // http://archive.today/2018.07.14-004914/https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch20.html + // + const float pdf = normalDistributionGgx(ndoth, _roughness) * ndoth / (4.0f * vdoth); + const float lod = bx::max(0.0f, mipBias - 0.5f*bx::log2(pdf)); + + float rgba[4]; + sampleCubeMap(rgba, _image, ll, lod); + + // Optimized Reversible Tonemapper for Resolve + // https://web.archive.org/web/20180717182019/https://gpuopen.com/optimized-reversible-tonemapper-for-resolve/ + // "a single sample with a large HDR value can over-power all other samples" + // "instead accept a bias in the resolve and reduce the weighting of samples + // as a function of how bright they are" + // Include ndotl here to "fold the weighting into the tonemap operation" + // + const float tm = ndotl / (bx::max(rgba[0], rgba[1], rgba[2]) + 1.0f); + + color[0] += rgba[0] * tm; + color[1] += rgba[1] * tm; + color[2] += rgba[2] * tm; + totalWeight += ndotl; + } + } + + if (0.0f < totalWeight) + { + // Optimized Reversible Tonemapper for Resovle + // https://web.archive.org/web/20180717182019/https://gpuopen.com/optimized-reversible-tonemapper-for-resolve/ + // Average, then reverse the tonemapper + // + const float invWeight = 1.0f/totalWeight; + color[0] = color[0] * invWeight; + color[1] = color[1] * invWeight; + color[2] = color[2] * invWeight; + + const float invTm = 1.0f / (1.0f - bx::max(0.00001f, bx::max(color[0], color[1], color[2]))); + _result[0] = color[0] * invTm; + _result[1] = color[1] * invTm; + _result[2] = color[2] * invTm; + } + else + { + float uu, vv; + uint8_t face; + dirToTexelUv(uu, vv, face, _dir); + + imageGetRawData(_image, face, 0, _image.m_data, _image.m_size, mip); + + const uint32_t xx = uint32_t(uu*widthMinusOne); + const uint32_t yy = uint32_t(vv*widthMinusOne); + + float rgba[4]; + unpack(rgba, mip.m_data + yy*pitch + xx*bpp/8); + + _result[0] = rgba[0]; + _result[1] = rgba[1]; + _result[2] = rgba[2]; + } + } + + void processFilterArea( + float* _result + , const ImageContainer& _image + , const ImageContainer& _nsa + , uint8_t _lod + , const Aabb* _aabb + , const float* _dir + , float _specularPower + , float _specularAngle + ) + { + float color[3] = { 0.0f, 0.0f, 0.0f }; + float totalWeight = 0.0f; + + const uint32_t bpp = getBitsPerPixel(_image.m_format); + + UnpackFn unpack = getUnpack(_image.m_format); + + for (uint8_t side = 0; side < 6; ++side) + { + if (_aabb[side].isEmpty() ) + { + continue; + } + + ImageMip nsaMip; + imageGetRawData(_nsa, side, 0, _nsa.m_data, _nsa.m_size, nsaMip); + + ImageMip mip; + if (imageGetRawData(_image, side, _lod, _image.m_data, _image.m_size, mip) ) + { + const uint32_t pitch = mip.m_width*bpp/8; + const float widthMinusOne = float(mip.m_width-1); + const float texelSize = 1.0f/float(mip.m_width); + BX_UNUSED(texelSize); + + const uint32_t minX = uint32_t(_aabb[side].m_min[0] * widthMinusOne); + const uint32_t maxX = uint32_t(_aabb[side].m_max[0] * widthMinusOne); + const uint32_t minY = uint32_t(_aabb[side].m_min[1] * widthMinusOne); + const uint32_t maxY = uint32_t(_aabb[side].m_max[1] * widthMinusOne); + + for (uint32_t yy = minY; yy <= maxY; ++yy) + { + const uint8_t* row = mip.m_data + yy*pitch; + BX_UNUSED(row); + + for (uint32_t xx = minX; xx <= maxX; ++xx) + { + const float* normal = (const float*)&nsaMip.m_data[(yy*nsaMip.m_width+xx)*(nsaMip.m_bpp/8)]; + const float solidAngle = normal[3]; + const float ndotl = bx::clamp(bx::vec3Dot(normal, _dir), 0.0f, 1.0f); + + if (ndotl >= _specularAngle) + { + const float weight = solidAngle * bx::pow(ndotl, _specularPower); + float rgba[4]; + unpack(rgba, row + xx*bpp/8); + + color[0] += rgba[0] * weight; + color[1] += rgba[1] * weight; + color[2] += rgba[2] * weight; + totalWeight += weight; + } + } + } + + if (0.0f < totalWeight) + { + const float invWeight = 1.0f/totalWeight; + _result[0] = color[0] * invWeight; + _result[1] = color[1] * invWeight; + _result[2] = color[2] * invWeight; + } + else + { + float uu, vv; + uint8_t face; + dirToTexelUv(uu, vv, face, _dir); + + imageGetRawData(_image, face, 0, _image.m_data, _image.m_size, mip); + + const uint32_t xx = uint32_t(uu*widthMinusOne); + const uint32_t yy = uint32_t(vv*widthMinusOne); + + float rgba[4]; + unpack(rgba, mip.m_data + yy*pitch + xx*bpp/8); + + _result[0] = rgba[0]; + _result[1] = rgba[1]; + _result[2] = rgba[2]; + } + } + } + } + + ImageContainer* imageGenerateMips(bx::AllocatorI* _allocator, const ImageContainer& _image) + { + if (_image.m_format != TextureFormat::RGBA8 + && _image.m_format != TextureFormat::RGBA32F) + { + return NULL; + } + + ImageContainer* output = imageAlloc(_allocator, _image.m_format, uint16_t(_image.m_width), uint16_t(_image.m_height), uint16_t(_image.m_depth), _image.m_numLayers, _image.m_cubeMap, true); + + const uint32_t numMips = output->m_numMips; + const uint32_t numLayers = output->m_numLayers; + const uint32_t numSides = output->m_cubeMap ? 6 : 1; + + for (uint32_t layer = 0; layer < numLayers; ++layer) + { + for (uint8_t side = 0; side < numSides; ++side) + { + ImageMip mip; + if (imageGetRawData(_image, uint16_t(layer*numSides + side), 0, _image.m_data, _image.m_size, mip) ) + { + for (uint8_t lod = 0; lod < numMips; ++lod) + { + ImageMip srcMip; + imageGetRawData(*output, uint16_t(layer*numSides + side), lod == 0 ? 0 : lod-1, output->m_data, output->m_size, srcMip); + + ImageMip dstMip; + imageGetRawData(*output, uint16_t(layer*numSides + side), lod, output->m_data, output->m_size, dstMip); + + uint8_t* dstData = const_cast(dstMip.m_data); + + if (0 == lod) + { + bx::memCopy(dstData, mip.m_data, mip.m_size); + } + else if (output->m_format == TextureFormat::RGBA8) + { + imageRgba8Downsample2x2( + dstData + , srcMip.m_width + , srcMip.m_height + , srcMip.m_depth + , srcMip.m_width*4 + , dstMip.m_width*4 + , srcMip.m_data + ); + } + else if (output->m_format == TextureFormat::RGBA32F) + { + imageRgba32fDownsample2x2( + dstData + , srcMip.m_width + , srcMip.m_height + , srcMip.m_depth + , srcMip.m_width*16 + , srcMip.m_data + ); + } + } + } + } + } + + return output; + } + + /// Returns the angle of cosine power function where the results are above a small empirical treshold. + static float cosinePowerFilterAngle(float _cosinePower) + { + // Bigger value leads to performance improvement but might hurt the results. + // 0.00001f was tested empirically and it gives almost the same values as reference. + const float treshold = 0.00001f; + + // Cosine power filter is: pow(cos(angle), power). + // We want the value of the angle above each result is <= treshold. + // So: angle = acos(pow(treshold, 1.0 / power)) + return bx::acos(bx::pow(treshold, 1.0f / _cosinePower)); + } + + float glossinessFor(float _mip, float _mipCount) + { + return bx::max(0.0f, 1.0f - _mip/(_mipCount-1.0000001f) ); + } + + float applyLightingModel(float _specularPower, LightingModel::Enum _lightingModel) + { + // Reference: + // - https://web.archive.org/web/20180622232018/https://seblagarde.wordpress.com/2012/06/10/amd-cubemapgen-for-physically-based-rendering/ + // - https://web.archive.org/web/20180622232041/https://seblagarde.wordpress.com/2012/03/29/relationship-between-phong-and-blinn-lighting-model/ + // + switch (_lightingModel) + { + case LightingModel::PhongBrdf: return _specularPower + 1.0f; + case LightingModel::Blinn: return _specularPower/4.0f; + case LightingModel::BlinnBrdf: return _specularPower/4.0f + 1.0f; + default: break; + }; + + return _specularPower; + } + + ImageContainer* imageCubemapRadianceFilter(bx::AllocatorI* _allocator, const ImageContainer& _image, LightingModel::Enum _lightingModel, bx::Error* _err) + { + if (!_image.m_cubeMap) + { + BX_ERROR_SET(_err, BIMG_ERROR, "Input image is not cubemap."); + return NULL; + } + + ImageContainer* input = imageConvert(_allocator, TextureFormat::RGBA32F, _image, true); + + if (1 >= input->m_numMips) + { + ImageContainer* temp = imageGenerateMips(_allocator, *input); + imageFree(input); + input = temp; + } + + ImageContainer* output = imageAlloc(_allocator, TextureFormat::RGBA32F, uint16_t(input->m_width), uint16_t(input->m_width), 1, 1, true, true); + + for (uint8_t side = 0; side < 6; ++side) + { + ImageMip srcMip; + imageGetRawData(*input, side, 0, input->m_data, input->m_size, srcMip); + + ImageMip dstMip; + imageGetRawData(*output, side, 0, output->m_data, output->m_size, dstMip); + + uint8_t* dstData = const_cast(dstMip.m_data); + + bx::memCopy(dstData, srcMip.m_data, srcMip.m_size); + } + + const float glossScale = 10.0f; + const float glossBias = 1.0f; + + for (uint8_t lod = 1, numMips = input->m_numMips; lod < numMips; ++lod) + { + ImageContainer* nsa = NULL; + + if (LightingModel::Ggx != _lightingModel) + { + nsa = imageCubemapNormalSolidAngle(_allocator, bx::max(_image.m_width>>lod, 1) ); + } + + for (uint8_t side = 0; side < 6; ++side) + { + ImageMip mip; + imageGetRawData(*output, side, lod, output->m_data, output->m_size, mip); + + const uint32_t dstWidth = mip.m_width; + const uint32_t dstPitch = dstWidth*16; + + const float minAngle = bx::atan2(1.0f, float(dstWidth) ); + const float maxAngle = bx::kPiHalf; + const float toFilterSize = 1.0f/(minAngle*dstWidth*2.0f); + const float glossiness = glossinessFor(lod, float(numMips) ); + const float roughness = 1.0f-glossiness; + const float specularPowerRef = bx::pow(2.0f, glossiness*glossScale + glossBias); + const float specularPower = applyLightingModel(specularPowerRef, _lightingModel); + const float filterAngle = bx::clamp(cosinePowerFilterAngle(specularPower), minAngle, maxAngle); + const float cosAngle = bx::max(0.0f, bx::cos(filterAngle) ); + const float texelSize = 1.0f/float(dstWidth); + const float filterSize = bx::max(texelSize, filterAngle * toFilterSize); + + for (uint32_t yy = 0; yy < dstWidth; ++yy) + { + for (uint32_t xx = 0; xx < dstWidth; ++xx) + { + float* dstData = (float*)&mip.m_data[yy*dstPitch+xx*16]; + + const float uu = float(xx)*texelSize*2.0f - 1.0f; + const float vv = float(yy)*texelSize*2.0f - 1.0f; + + float dir[3]; + texelUvToDir(dir, side, uu, vv); + + if (LightingModel::Ggx == _lightingModel) + { + processFilterAreaGgx(dstData, *input, lod, dir, roughness); + } + else + { + Aabb aabb[6]; + calcFilterArea(aabb, dir, filterSize); + + processFilterArea(dstData, *input, *nsa, lod, aabb, dir, specularPower, cosAngle); + } + } + } + } + + if (NULL != nsa) + { + imageFree(nsa); + } + } + + return output; + } + +} // namespace bimg diff --git a/src/image_encode.cpp b/src/image_encode.cpp index 3e29ef1..ec67ac8 100644 --- a/src/image_encode.cpp +++ b/src/image_encode.cpp @@ -228,7 +228,10 @@ namespace bimg break; default: - BX_ERROR_SET(_err, BIMG_ERROR, "Unable to convert between input/output formats!"); + if (!imageConvert(_allocator, _dst, _dstFormat, _src, _srcFormat, _width, _height, 1) ) + { + BX_ERROR_SET(_err, BIMG_ERROR, "Unable to convert between input/output formats!"); + } break; } } @@ -260,17 +263,18 @@ namespace bimg imageGetRawData(*output, side, lod, output->m_data, output->m_size, dstMip); uint8_t* dstData = const_cast(dstMip.m_data); - imageEncode(_allocator - , dstData - , mip.m_data - , mip.m_format - , mip.m_width - , mip.m_height - , mip.m_depth - , _dstFormat - , _quality - , &err - ); + imageEncode( + _allocator + , dstData + , mip.m_data + , mip.m_format + , mip.m_width + , mip.m_height + , mip.m_depth + , _dstFormat + , _quality + , &err + ); } } } diff --git a/tools/texturec/texturec.cpp b/tools/texturec/texturec.cpp index 58321cb..6d5b4b6 100644 --- a/tools/texturec/texturec.cpp +++ b/tools/texturec/texturec.cpp @@ -26,25 +26,12 @@ #include #define BIMG_TEXTUREC_VERSION_MAJOR 1 -#define BIMG_TEXTUREC_VERSION_MINOR 15 +#define BIMG_TEXTUREC_VERSION_MINOR 17 + +BX_ERROR_RESULT(TEXTRUREC_ERROR, BX_MAKEFOURCC('t', 'c', 0, 0) ); struct Options { - Options() - : maxSize(UINT32_MAX) - , edge(0.0f) - , format(bimg::TextureFormat::Count) - , quality(bimg::Quality::Default) - , mips(false) - , normalMap(false) - , equirect(false) - , iqa(false) - , pma(false) - , sdf(false) - , alphaTest(false) - { - } - void dump() { DBG("Options:\n" @@ -56,6 +43,9 @@ struct Options "\t iqa: %s\n" "\t pma: %s\n" "\t sdf: %s\n" + "\t radiance: %s\n" + "\t equirect: %s\n" + "\t strip: %s\n" , maxSize , edge , bimg::getName(format) @@ -64,20 +54,25 @@ struct Options , iqa ? "true" : "false" , pma ? "true" : "false" , sdf ? "true" : "false" + , radiance ? "true" : "false" + , equirect ? "true" : "false" + , strip ? "true" : "false" ); } - uint32_t maxSize; - float edge; - bimg::TextureFormat::Enum format; - bimg::Quality::Enum quality; - bool mips; - bool normalMap; - bool equirect; - bool iqa; - bool pma; - bool sdf; - bool alphaTest; + uint32_t maxSize = UINT32_MAX; + float edge = 0.0f; + bimg::TextureFormat::Enum format = bimg::TextureFormat::Count; + bimg::Quality::Enum quality = bimg::Quality::Default; + bimg::LightingModel::Enum radiance = bimg::LightingModel::Count; + bool mips = false; + bool normalMap = false; + bool equirect = false; + bool strip = false; + bool iqa = false; + bool pma = false; + bool sdf = false; + bool alphaTest = false; }; void imageRgba32fNormalize(void* _dst, uint32_t _width, uint32_t _height, uint32_t _srcPitch, const void* _src) @@ -168,9 +163,47 @@ bimg::ImageContainer* convert(bx::AllocatorI* _allocator, const void* _inputData uint32_t outputHeight = bx::uint32_max(blockHeight * minBlockY, ( (input->m_height + blockHeight - 1) / blockHeight)*blockHeight); uint32_t outputDepth = input->m_depth; - if (outputWidth > _options.maxSize - || outputHeight > _options.maxSize - || outputDepth > _options.maxSize) + if (_options.equirect) + { + if (outputDepth == 1 + && outputWidth/2 == outputHeight) + { + if (outputWidth/2 > _options.maxSize) + { + outputWidth = _options.maxSize*4; + outputHeight = _options.maxSize*2; + } + } + else + { + bimg::imageFree(input); + + BX_ERROR_SET(_err, TEXTRUREC_ERROR, "Input image format is not equirectangular projection."); + return NULL; + } + } + else if (_options.strip) + { + if (outputDepth == 1 + && outputWidth/6 == outputHeight) + { + if (outputWidth/6 > _options.maxSize) + { + outputWidth = _options.maxSize*6; + outputHeight = _options.maxSize; + } + } + else + { + bimg::imageFree(input); + + BX_ERROR_SET(_err, TEXTRUREC_ERROR, "Input image format is not horizontal strip."); + return NULL; + } + } + else if (outputWidth > _options.maxSize + || outputHeight > _options.maxSize + || outputDepth > _options.maxSize) { if (outputDepth > outputWidth && outputDepth > outputHeight) @@ -204,9 +237,10 @@ bimg::ImageContainer* convert(bx::AllocatorI* _allocator, const void* _inputData && !_options.sdf && !_options.alphaTest && !_options.normalMap - && !_options.equirect + && !(_options.equirect || _options.strip) && !_options.iqa && !_options.pma + && (bimg::LightingModel::Count == _options.radiance) ; if (needResize) @@ -261,13 +295,24 @@ bimg::ImageContainer* convert(bx::AllocatorI* _allocator, const void* _inputData return output; } - if (_options.equirect) + if (_options.equirect + || _options.strip) { bimg::ImageContainer* src = bimg::imageConvert(_allocator, bimg::TextureFormat::RGBA32F, *input); bimg::imageFree(input); - bimg::ImageContainer* dst = bimg::imageCubemapFromLatLongRgba32F(_allocator, *src, true, _err); - bimg::imageFree(src); + bimg::ImageContainer* dst; + + if (outputWidth/2 == outputHeight) + { + dst = bimg::imageCubemapFromLatLongRgba32F(_allocator, *src, true, _err); + bimg::imageFree(src); + } + else + { + dst = bimg::imageCubemapFromStripRgba32F(_allocator, *src, _err); + bimg::imageFree(src); + } if (!_err->isOk() ) { @@ -278,6 +323,27 @@ bimg::ImageContainer* convert(bx::AllocatorI* _allocator, const void* _inputData bimg::imageFree(dst); } + if (bimg::LightingModel::Count != _options.radiance) + { + output = bimg::imageCubemapRadianceFilter(_allocator, *input, _options.radiance, _err); + + if (!_err->isOk() ) + { + return NULL; + } + + if (bimg::TextureFormat::RGBA32F != outputFormat) + { + bimg::ImageContainer* temp = bimg::imageEncode(_allocator, outputFormat, _options.quality, *output); + bimg::imageFree(output); + + output = temp; + } + + bimg::imageFree(input); + return output; + } + output = bimg::imageAlloc( _allocator , outputFormat @@ -749,7 +815,7 @@ void help(const char* _error = NULL, bool _showHelp = true) " *.exr (input, output) OpenEXR.\n" " *.gif (input) Graphics Interchange Format.\n" " *.jpg (input) JPEG Interchange Format.\n" - " *.hdr (input) Radiance RGBE.\n" + " *.hdr (input, output) Radiance RGBE.\n" " *.ktx (input, output) Khronos Texture.\n" " *.png (input, output) Portable Network Graphics.\n" " *.psd (input) Photoshop Document.\n" @@ -766,13 +832,15 @@ void help(const char* _error = NULL, bool _showHelp = true) " -q Encoding quality (default, fastest, highest).\n" " -m, --mips Generate mip-maps.\n" " -n, --normalmap Input texture is normal map.\n" - " --equirect Input texture equirectangular projection of cubemap.\n" + " --equirect Input texture is equirectangular projection of cubemap.\n" + " --strip Input texture is horizontal strip of cubemap.\n" " --sdf Compute SDF texture.\n" " --ref Alpha reference value.\n" " --iqa Image Quality Assessment\n" " --pma Premultiply alpha into RGB channel.\n" " --max Maximum width/height (image will be scaled down and\n" " aspect ratio will be preserved.\n" + " --radiance Radiance cubemap filter. (Lighting model: Phong, PhongBrdf, Blinn, BlinnBrdf, GGX)\n" " --as Save as.\n" " --validate *DEBUG* Validate that output image produced matches after loading.\n" @@ -860,6 +928,7 @@ int main(int _argc, const char* _argv[]) saveAs = NULL == saveAs ? bx::strFindI(outputFileName, ".dds") : saveAs; saveAs = NULL == saveAs ? bx::strFindI(outputFileName, ".png") : saveAs; saveAs = NULL == saveAs ? bx::strFindI(outputFileName, ".exr") : saveAs; + saveAs = NULL == saveAs ? bx::strFindI(outputFileName, ".hdr") : saveAs; if (NULL == saveAs) { help("Output file format must be specified."); @@ -890,16 +959,28 @@ int main(int _argc, const char* _argv[]) } } - options.mips = cmdLine.hasArg('m', "mips"); - options.normalMap = cmdLine.hasArg('n', "normalmap"); + options.mips = cmdLine.hasArg('m', "mips"); + options.normalMap = cmdLine.hasArg('n', "normalmap"); options.equirect = cmdLine.hasArg("equirect"); + options.strip = cmdLine.hasArg("strip"); options.iqa = cmdLine.hasArg("iqa"); options.pma = cmdLine.hasArg("pma"); + if (options.equirect + && options.strip) + { + help("Image can't be equirect and strip at the same time."); + return bx::kExitFailure; + } + const char* maxSize = cmdLine.findOption("max"); if (NULL != maxSize) { - options.maxSize = atoi(maxSize); + if (!bx::fromString(&options.maxSize, maxSize) ) + { + help("Parsing max size failed."); + return bx::kExitFailure; + } } options.format = bimg::TextureFormat::Count; @@ -954,6 +1035,21 @@ int main(int _argc, const char* _argv[]) } } + const char* radiance = cmdLine.findOption("radiance"); + if (NULL != radiance) + { + if (0 == bx::strCmpI(radiance, "phong" ) ) { options.radiance = bimg::LightingModel::Phong; } + else if (0 == bx::strCmpI(radiance, "phongbrdf") ) { options.radiance = bimg::LightingModel::PhongBrdf; } + else if (0 == bx::strCmpI(radiance, "blinn" ) ) { options.radiance = bimg::LightingModel::Blinn; } + else if (0 == bx::strCmpI(radiance, "blinnbrdf") ) { options.radiance = bimg::LightingModel::BlinnBrdf; } + else if (0 == bx::strCmpI(radiance, "ggx" ) ) { options.radiance = bimg::LightingModel::Ggx; } + else + { + help("Invalid radiance lighting model specified."); + return bx::kExitFailure; + } + } + const bool validate = cmdLine.hasArg("validate"); bx::Error err; @@ -1019,7 +1115,8 @@ int main(int _argc, const char* _argv[]) , mip.m_data , output->m_format , false - , &err); + , &err + ); } else if (NULL != bx::strFindI(saveAs, "exr") ) { @@ -1032,7 +1129,22 @@ int main(int _argc, const char* _argv[]) , mip.m_data , output->m_format , false - , &err); + , &err + ); + } + else if (NULL != bx::strFindI(saveAs, "hdr") ) + { + bimg::ImageMip mip; + bimg::imageGetRawData(*output, 0, 0, output->m_data, output->m_size, mip); + bimg::imageWriteHdr(&writer + , mip.m_width + , mip.m_height + , mip.m_width*getBitsPerPixel(mip.m_format)/8 + , mip.m_data + , output->m_format + , false + , &err + ); } bx::close(&writer);