diff --git a/Auxiliary/DirectXTexJPEG.cpp b/Auxiliary/DirectXTexJPEG.cpp index ca53da7..358aab8 100644 --- a/Auxiliary/DirectXTexJPEG.cpp +++ b/Auxiliary/DirectXTexJPEG.cpp @@ -91,9 +91,10 @@ namespace { jpeg_error_mgr err; jpeg_decompress_struct dec; + JPEG_FLAGS userFlags; public: - JPEGDecompress() : err{}, dec{} + JPEGDecompress(JPEG_FLAGS flags) : err{}, dec{}, userFlags(flags) { jpeg_std_error(&err); err.error_exit = &OnJPEGError; @@ -110,23 +111,14 @@ namespace jpeg_stdio_src(&dec, fin); } - static DXGI_FORMAT TranslateColor(J_COLOR_SPACE colorspace) noexcept + DXGI_FORMAT TranslateColor(J_COLOR_SPACE colorspace) noexcept { switch (colorspace) { case JCS_GRAYSCALE: // 1 component return DXGI_FORMAT_R8_UNORM; case JCS_RGB: // 3 component, Standard RGB - return DXGI_FORMAT_R8G8B8A8_UNORM; - case JCS_CMYK: // 4 component. WIC retuns this for CMYK - return DXGI_FORMAT_R8G8B8A8_UNORM; - #ifdef LIBJPEG_TURBO_VERSION - case JCS_EXT_RGBX: // 4 component - case JCS_EXT_RGBA: - return DXGI_FORMAT_R8G8B8A8_UNORM; - case JCS_RGB565: - return DXGI_FORMAT_B5G6R5_UNORM; - #endif + return (userFlags & JPEG_FLAGS_DEFAULT_LINEAR) ? DXGI_FORMAT_R8G8B8A8_UNORM : DXGI_FORMAT_R8G8B8A8_UNORM_SRGB; case JCS_YCbCr: // 3 component, YCbCr default: return DXGI_FORMAT_UNKNOWN; @@ -146,7 +138,8 @@ namespace { throw std::runtime_error{ "unexpected out_color_space in jpeg_decompress_struct" }; } - if (metadata.format == DXGI_FORMAT_R8G8B8A8_UNORM) + if (metadata.format == DXGI_FORMAT_R8G8B8A8_UNORM + || metadata.format == DXGI_FORMAT_R8G8B8A8_UNORM_SRGB) { metadata.miscFlags2 |= TEX_ALPHA_MODE_OPAQUE; } @@ -168,19 +161,22 @@ namespace return S_OK; } - #if !defined(LIBJPEG_TURBO_VERSION) + #ifndef LIBJPEG_TURBO_VERSION // shift pixels with padding in reverse order (to make it work in-memory) - void ShiftPixels(ScratchImage& image) noexcept + void ShiftPixels(const Image& image) noexcept { - size_t num_pixels = dec.output_width * dec.output_height; - uint8_t* dst = image.GetPixels(); - const uint8_t* src = dst; - for (size_t i = num_pixels - 1; i > 0; i -= 1) + uint8_t* scanline = image.pixels; + for (size_t y = 0; y < image.height; ++y) { - dst[4*i + 0] = src[3*i + 0]; - dst[4*i + 1] = src[3*i + 1]; - dst[4*i + 2] = src[3*i + 2]; - dst[4*i + 3] = 0; + for (size_t i = (image.width - 1); i > 0; i -= 1) + { + scanline[4*i + 0] = scanline[3*i + 0]; + scanline[4*i + 1] = scanline[3*i + 1]; + scanline[4*i + 2] = scanline[3*i + 2]; + scanline[4*i + 3] = 0xff; + } + + scanline += image.rowPitch; } } #endif @@ -199,31 +195,26 @@ namespace break; } GetMetadata(metadata); - // if the JPEG library doesn't have color space conversion, it should be ERROR_NOT_SUPPORTED - if (dec.jpeg_color_space == JCS_YCCK) - { - // CMYK is the known case - if (dec.out_color_space == JCS_CMYK) - return HRESULT_E_NOT_SUPPORTED; - } - if (auto hr = image.Initialize2D(metadata.format, metadata.width, metadata.height, metadata.arraySize, metadata.mipLevels); FAILED(hr)) + if (auto hr = image.Initialize2D(metadata.format, metadata.width, metadata.height, 1u, 1u); FAILED(hr)) return hr; #ifdef LIBJPEG_TURBO_VERSION - // grayscale is the only color space which uses 1 component if (dec.out_color_space != JCS_GRAYSCALE) - // if there is no proper conversion to 4 component, E_FAIL... - dec.out_color_space = JCS_EXT_RGBX; + { + dec.out_color_space = JCS_EXT_RGBA; + } #endif if (jpeg_start_decompress(&dec) == false) return E_FAIL; - uint8_t* dest = image.GetPixels(); - const size_t stride = dec.output_width * static_cast(dec.output_components); + const auto img = *image.GetImage(0, 0, 0); + std::vector rows(dec.output_height); for (size_t i = 0u; i < dec.output_height; ++i) - rows[i] = dest + (stride * i); + { + rows[i] = img.pixels + (i * img.rowPitch); + } JDIMENSION leftover = dec.output_height; while (leftover > 0) @@ -231,14 +222,18 @@ namespace JDIMENSION consumed = jpeg_read_scanlines(&dec, &rows[dec.output_scanline], leftover); leftover -= consumed; } + if (jpeg_finish_decompress(&dec) == false) return E_FAIL; - #if !defined(LIBJPEG_TURBO_VERSION) + #ifndef LIBJPEG_TURBO_VERSION // if NOT TurboJPEG, we need to make 3 component images to 4 component image if (dec.out_color_space != JCS_GRAYSCALE) - ShiftPixels(image); + { + ShiftPixels(img); + } #endif + return S_OK; } @@ -251,11 +246,12 @@ namespace class JPEGCompress final { - jpeg_error_mgr err{}; - jpeg_compress_struct enc{}; + jpeg_error_mgr err; + jpeg_compress_struct enc; + JPEG_FLAGS userFlags; public: - JPEGCompress() : err{}, enc{} + JPEGCompress(JPEG_FLAGS flags) : err{}, enc{}, userFlags(flags) { jpeg_std_error(&err); err.error_exit = &OnJPEGError; @@ -272,7 +268,6 @@ namespace jpeg_stdio_dest(&enc, fout); } - /// @todo More correct DXGI_FORMAT mapping HRESULT WriteImage(const Image& image) noexcept(false) { switch (image.format) @@ -283,34 +278,74 @@ namespace break; #ifdef LIBJPEG_TURBO_VERSION case DXGI_FORMAT_R8G8B8A8_UNORM: + case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB: enc.input_components = 4; enc.in_color_space = JCS_EXT_RGBA; break; case DXGI_FORMAT_B8G8R8A8_UNORM: + case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB: enc.input_components = 4; enc.in_color_space = JCS_EXT_BGRA; break; + case DXGI_FORMAT_B8G8R8X8_UNORM: + case DXGI_FORMAT_B8G8R8X8_UNORM_SRGB: + enc.input_components = 4; + enc.in_color_space = JCS_EXT_BGRX; + break; #else case DXGI_FORMAT_R8G8B8A8_UNORM: + case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB: enc.input_components = 3; enc.in_color_space = JCS_RGB; + break; #endif default: return HRESULT_E_NOT_SUPPORTED; } + + // TODO: Add sRGB intent? + enc.image_width = static_cast(image.width); enc.image_height = static_cast(image.height); jpeg_set_defaults(&enc); jpeg_set_quality(&enc, 100, true); + // we will write a row each time ... jpeg_start_compress(&enc, true); - const size_t stride = enc.image_width * static_cast(enc.input_components); - while (enc.next_scanline < enc.image_height) + + #ifndef LIBJPEG_TURBO_VERSION + if (enc.input_components == 3) { - JSAMPROW rows[1]{ image.pixels + stride * enc.next_scanline }; - jpeg_write_scanlines(&enc, rows, 1); + const size_t stride = enc.image_width * static_cast(enc.input_components); + auto scanline = std::make_unique(stride); + JSAMPROW rows[1]{ scanline.get() }; + + while (enc.next_scanline < enc.image_height) + { + // Copy 4 to 3 components + const uint8_t* src = image.pixels + enc.next_scanline * image.rowPitch; + uint8_t* dst = scanline.get(); + for(size_t i=0; i < image.width; ++i) + { + dst[3*i + 0] = src[4*i + 0]; + dst[3*i + 1] = src[4*i + 1]; + dst[3*i + 2] = src[4*i + 2]; + } + + jpeg_write_scanlines(&enc, rows, 1); + } } + else + #endif + { + while (enc.next_scanline < enc.image_height) + { + JSAMPROW rows[1]{ image.pixels + image.rowPitch * enc.next_scanline }; + jpeg_write_scanlines(&enc, rows, 1); + } + } + jpeg_finish_compress(&enc); return S_OK; } @@ -320,6 +355,7 @@ namespace _Use_decl_annotations_ HRESULT DirectX::GetMetadataFromJPEGFile( const wchar_t* file, + JPEG_FLAGS flags, TexMetadata& metadata) { if (!file) @@ -328,7 +364,7 @@ HRESULT DirectX::GetMetadataFromJPEGFile( try { auto fin = OpenFILE(file); - JPEGDecompress decoder{}; + JPEGDecompress decoder(flags); decoder.UseInput(fin.get()); return decoder.GetHeader(metadata); } @@ -353,6 +389,7 @@ HRESULT DirectX::GetMetadataFromJPEGFile( _Use_decl_annotations_ HRESULT DirectX::LoadFromJPEGFile( const wchar_t* file, + JPEG_FLAGS flags, TexMetadata* metadata, ScratchImage&image) { @@ -364,7 +401,7 @@ HRESULT DirectX::LoadFromJPEGFile( try { auto fin = OpenFILE(file); - JPEGDecompress decoder{}; + JPEGDecompress decoder(flags); decoder.UseInput(fin.get()); if (!metadata) return decoder.GetImage(image); @@ -394,6 +431,7 @@ HRESULT DirectX::LoadFromJPEGFile( _Use_decl_annotations_ HRESULT DirectX::SaveToJPEGFile( const Image& image, + JPEG_FLAGS flags, const wchar_t* file) { if (!file) @@ -402,7 +440,7 @@ HRESULT DirectX::SaveToJPEGFile( try { auto fout = CreateFILE(file); - JPEGCompress encoder{}; + JPEGCompress encoder(flags); encoder.UseOutput(fout.get()); return encoder.WriteImage(image); } diff --git a/Auxiliary/DirectXTexJPEG.h b/Auxiliary/DirectXTexJPEG.h index e76af96..747332d 100644 --- a/Auxiliary/DirectXTexJPEG.h +++ b/Auxiliary/DirectXTexJPEG.h @@ -18,16 +18,39 @@ namespace DirectX { + enum JPEG_FLAGS : uint32_t + { + JPEG_FLAGS_NONE = 0x0, + + JPEG_FLAGS_DEFAULT_LINEAR = 0x1, + // Return non-SRGB formats intead of sRGB + }; + DIRECTX_TEX_API HRESULT __cdecl GetMetadataFromJPEGFile( _In_z_ const wchar_t* szFile, + JPEG_FLAGS flags, _Out_ TexMetadata& metadata); DIRECTX_TEX_API HRESULT __cdecl LoadFromJPEGFile( _In_z_ const wchar_t* szFile, + JPEG_FLAGS flags, _Out_opt_ TexMetadata* metadata, _Out_ ScratchImage& image); DIRECTX_TEX_API HRESULT __cdecl SaveToJPEGFile( _In_ const Image& image, + JPEG_FLAGS flags, _In_z_ const wchar_t* szFile); + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-dynamic-exception-spec" +#pragma clang diagnostic ignored "-Wextra-semi-stmt" +#endif + +DEFINE_ENUM_FLAG_OPERATORS(JPEG_FLAGS); + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif } diff --git a/README.md b/README.md index 6accf7b..07103ce 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,8 @@ For a full change history, see [CHANGELOG.md](https://github.com/microsoft/Direc * The CMake projects require 3.21 or later. VS 2019 users will need to install a standalone version of CMake 3.21 or later and add it to their PATH. +* Starting with the October 2025 release, the _Auxiliary_ functions for loading and saving JPEG and PNG files now take a flag parameter after the filename. + * Starting with the March 2025 release, Windows 7 and Windows 8.0 support has been retired. * Starting with the July 2022 release, the ``bool forceSRGB`` parameter for the **CreateTextureEx** and **CreateShaderResourceViewEx** functions is now a ``CREATETEX_FLAGS`` typed enum bitmask flag parameter. This may have a _breaking change_ impact to client code. Replace ``true`` with ``CREATETEX_FORCE_SRGB`` and ``false`` with ``CREATETEX_DEFAULT``. diff --git a/Texassemble/texassemble.cpp b/Texassemble/texassemble.cpp index 55946b3..70f2266 100644 --- a/Texassemble/texassemble.cpp +++ b/Texassemble/texassemble.cpp @@ -618,7 +618,7 @@ namespace #endif #ifdef USE_LIBJPEG case CODEC_JPEG: - return SaveToJPEGFile(img, szOutputFile); + return SaveToJPEGFile(img, JPEG_FLAGS_NONE, szOutputFile); #endif #ifdef USE_LIBPNG case CODEC_PNG: @@ -1423,7 +1423,7 @@ int __cdecl wmain(_In_ int argc, _In_z_count_(argc) wchar_t* argv[]) #ifdef USE_LIBJPEG else if (_wcsicmp(ext.c_str(), L".jpg") == 0 || _wcsicmp(ext.c_str(), L".jpeg") == 0) { - hr = LoadFromJPEGFile(curpath.c_str(), &info, *image); + hr = LoadFromJPEGFile(curpath.c_str(), JPEG_FLAGS_NONE, &info, *image); if (FAILED(hr)) { wprintf(L" FAILED (%08X%ls)\n", static_cast(hr), GetErrorDesc(hr)); diff --git a/Texconv/texconv.cpp b/Texconv/texconv.cpp index ee54818..d8a7e0c 100644 --- a/Texconv/texconv.cpp +++ b/Texconv/texconv.cpp @@ -2157,7 +2157,13 @@ int __cdecl wmain(_In_ int argc, _In_z_count_(argc) wchar_t* argv[]) #ifdef USE_LIBJPEG else if (_wcsicmp(ext.c_str(), L".jpg") == 0 || _wcsicmp(ext.c_str(), L".jpeg") == 0) { - hr = LoadFromJPEGFile(curpath.c_str(), &info, *image); + JPEG_FLAGS jpegFlags = JPEG_FLAGS_NONE; + if (dwOptions & (UINT64_C(1) << OPT_IGNORE_SRGB_METADATA)) + { + jpegFlags |= JPEG_FLAGS_DEFAULT_LINEAR; + } + + hr = LoadFromJPEGFile(curpath.c_str(), jpegFlags, &info, *image); if (FAILED(hr)) { wprintf(L" FAILED (%08X%ls)\n", static_cast(hr), GetErrorDesc(hr)); @@ -3800,7 +3806,7 @@ int __cdecl wmain(_In_ int argc, _In_z_count_(argc) wchar_t* argv[]) #endif #ifdef USE_LIBJPEG case CODEC_JPEG: - hr = SaveToJPEGFile(img[0], destName.c_str()); + hr = SaveToJPEGFile(img[0], JPEG_FLAGS_NONE, destName.c_str()); break; #endif #ifdef USE_LIBPNG diff --git a/Texdiag/texdiag.cpp b/Texdiag/texdiag.cpp index ead26bb..6501966 100644 --- a/Texdiag/texdiag.cpp +++ b/Texdiag/texdiag.cpp @@ -585,7 +585,7 @@ namespace #ifdef USE_LIBJPEG else if (_wcsicmp(ext.c_str(), L".jpg") == 0 || _wcsicmp(ext.c_str(), L".jpeg") == 0) { - return LoadFromJPEGFile(fileName, &info, *image); + return LoadFromJPEGFile(fileName, JPEG_FLAGS_NONE, &info, *image); } #endif #ifdef USE_LIBPNG @@ -639,7 +639,7 @@ namespace #endif #ifdef USE_LIBJPEG case CODEC_JPEG: - return SaveToJPEGFile(*image, fileName); + return SaveToJPEGFile(*image, JPEG_FLAGS_NONE, fileName); #endif #ifdef USE_LIBPNG case CODEC_PNG: