diff --git a/CMakeLists.txt b/CMakeLists.txt index 907310f704..7e871381b8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -377,13 +377,18 @@ macro(commom_lib_settings lib write) $ $ - $ - $ - $ $ ) + target_include_directories( + ${lib} + SYSTEM + PRIVATE + $ + $ + ) + if( LIB_TYPE STREQUAL STATIC ) target_compile_definitions(${lib} PUBLIC KHRONOS_STATIC) endif() @@ -681,7 +686,7 @@ elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang") # version. Also future proofing for when xcode catches up. set_source_files_properties( ${BASISU_ENCODER_CXX_SRC} - PROPERTIES COMPILE_OPTIONS "-Wno-sign-compare;-Wno-unused-variable;-Wno-unused-parameter" + PROPERTIES COMPILE_OPTIONS "-Wno-sign-compare;-Wno-unused-variable;-Wno-unused-parameter;-Wno-deprecated-copy-with-user-provided-copy" ) set_source_files_properties( lib/basisu/transcoder/basisu_transcoder.cpp @@ -935,6 +940,7 @@ else() endif() # FMT +set(FMT_SYSTEM_HEADERS ON) add_subdirectory(other_projects/fmt) # Tools diff --git a/cmake/cputypetest.cmake b/cmake/cputypetest.cmake index 31d38882d7..d839d2d4cc 100644 --- a/cmake/cputypetest.cmake +++ b/cmake/cputypetest.cmake @@ -1,8 +1,6 @@ # Copyright 2016, Simon Werta (@webmaster128). # SPDX-License-Identifier: Apache-2.0 -cmake_minimum_required(VERSION 2.8.12) - set(cputypetest_code " // // https://gist.github.com/webmaster128/e08067641df1dd784eb195282fd0912f diff --git a/tests/ktx2check-tests.cmake b/tests/ktx2check-tests.cmake index f335bff237..2bc09ba022 100644 --- a/tests/ktx2check-tests.cmake +++ b/tests/ktx2check-tests.cmake @@ -95,12 +95,10 @@ add_test( NAME ktx2check-test-stdin-read WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/testimages ) -if(NOT WIN32) # Disable due to bug in Git for Windows 2.41.0.windows.1 pipe. - add_test( NAME ktx2check-test-pipe-read - COMMAND ${BASH_EXECUTABLE} -c "cat color_grid_uastc_zstd.ktx2 | $" - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/testimages - ) -endif() +add_test( NAME ktx2check-test-pipe-read + COMMAND ${BASH_EXECUTABLE} -c "cat color_grid_uastc_zstd.ktx2 | $" + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/testimages +) add_test( NAME ktx2check-test-invalid-face-count-and-padding COMMAND ktx2check invalid_face_count_and_padding.ktx2 diff --git a/tests/loadtests/CMakeLists.txt b/tests/loadtests/CMakeLists.txt index 20ef27e77d..311748c7ac 100644 --- a/tests/loadtests/CMakeLists.txt +++ b/tests/loadtests/CMakeLists.txt @@ -111,12 +111,17 @@ PUBLIC appfwSDL $ ${PROJECT_SOURCE_DIR}/lib - ${PROJECT_SOURCE_DIR}/other_include ${PROJECT_SOURCE_DIR}/utils common geom ) +target_include_directories( + appfwSDL +SYSTEM PUBLIC + ${PROJECT_SOURCE_DIR}/other_include +) + if(${KTX_FEATURE_LOADTEST_APPS} MATCHES "OpenGL") add_library( GLAppSDL STATIC appfwSDL/GLAppSDL.cpp @@ -136,13 +141,18 @@ if(${KTX_FEATURE_LOADTEST_APPS} MATCHES "OpenGL") target_include_directories( GLAppSDL PUBLIC - ${PROJECT_SOURCE_DIR}/other_include - appfwSDL + $ common glloadtests glloadtests/utils ) + target_include_directories( + GLAppSDL + SYSTEM PRIVATE + $ + ) + if(OPENGL_FOUND) target_include_directories( GLAppSDL diff --git a/tests/loadtests/glloadtests.cmake b/tests/loadtests/glloadtests.cmake index 10a3996a6c..01923aedae 100644 --- a/tests/loadtests/glloadtests.cmake +++ b/tests/loadtests/glloadtests.cmake @@ -22,12 +22,17 @@ function( create_gl_target target version sources common_resources test_images target_include_directories( ${target} PRIVATE - $ $ $ $ ) + target_include_directories( + ${target} + SYSTEM PRIVATE + ${PROJECT_SOURCE_DIR}/other_include + ) + set_target_properties(${target} PROPERTIES CXX_VISIBILITY_PRESET ${STATIC_APP_LIB_SYMBOL_VISIBILITY} ) diff --git a/tests/loadtests/vkloadtests.cmake b/tests/loadtests/vkloadtests.cmake index fd02eb39dd..0eac90a2fc 100644 --- a/tests/loadtests/vkloadtests.cmake +++ b/tests/loadtests/vkloadtests.cmake @@ -175,6 +175,11 @@ PRIVATE vkloadtests/utils ) +target_include_directories(vkloadtests + SYSTEM PRIVATE + ${PROJECT_SOURCE_DIR}/other_include +) + target_link_libraries(vkloadtests ktx ${KTX_ZLIB_LIBRARIES} diff --git a/tests/testimages/astc_mipmap_ldr_10x5_posx.ktx2 b/tests/testimages/astc_mipmap_ldr_10x5_posx.ktx2 index 8dca3cab6e..ff52514b96 100644 --- a/tests/testimages/astc_mipmap_ldr_10x5_posx.ktx2 +++ b/tests/testimages/astc_mipmap_ldr_10x5_posx.ktx2 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3c7cc1fd85fff96838e7b4bee0ff4404df01e0af5d30e79da0aa640036858003 +oid sha256:5b978587e50e4b15bca76546031157179b9dbf3d479ffbbd5ac51d2119a97104 size 1798048 diff --git a/tests/testimages/astc_mipmap_ldr_12x10_posx.ktx2 b/tests/testimages/astc_mipmap_ldr_12x10_posx.ktx2 index 0157b73a29..3a9bed3ad2 100644 --- a/tests/testimages/astc_mipmap_ldr_12x10_posx.ktx2 +++ b/tests/testimages/astc_mipmap_ldr_12x10_posx.ktx2 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c6bac6b02bc42485199e8e70d58ac2868b5b8338afd664573b8befb2ab2d1dd0 +oid sha256:3667e3473032541874e88ad3397ff8cd280bb7803474411116eaf668963118c9 size 751376 diff --git a/tests/testimages/astc_mipmap_ldr_12x12_posx.ktx2 b/tests/testimages/astc_mipmap_ldr_12x12_posx.ktx2 index 4abcfaea49..c949c0f546 100644 --- a/tests/testimages/astc_mipmap_ldr_12x12_posx.ktx2 +++ b/tests/testimages/astc_mipmap_ldr_12x12_posx.ktx2 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:79e22cc1c551fd565b7c5255ad930566953b98e7811319a2ba1a2328bd86f5ad +oid sha256:2d804de26e7889e6fabb9ddaa8ef67f1a7d71f0f111c093649a0b16255af1716 size 626864 diff --git a/tests/testimages/astc_mipmap_ldr_4x4_posx.ktx2 b/tests/testimages/astc_mipmap_ldr_4x4_posx.ktx2 index 1b1cc6de9b..5373657d2e 100644 --- a/tests/testimages/astc_mipmap_ldr_4x4_posx.ktx2 +++ b/tests/testimages/astc_mipmap_ldr_4x4_posx.ktx2 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3629ddf601ee9a1e09df0deec309a491ea3122aaf3b97e1acf342cfdd05b2940 +oid sha256:a3cebe0eea9b24a72f397e6761b0074938d87d87127f3c2bc2fa769ca6f2af0c size 5592992 diff --git a/tests/testimages/astc_mipmap_ldr_6x5_posx.ktx2 b/tests/testimages/astc_mipmap_ldr_6x5_posx.ktx2 index 46f21a2f59..a132f2e12a 100644 --- a/tests/testimages/astc_mipmap_ldr_6x5_posx.ktx2 +++ b/tests/testimages/astc_mipmap_ldr_6x5_posx.ktx2 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bc871b4a5724732709d481e51d85828305ffe98f4958507d66c775fb37b1f287 +oid sha256:f60006efda6cc9e649d94578a9827e9278d6c465f82462ce7a7b37bec099d4da size 2994880 diff --git a/tests/testimages/astc_mipmap_ldr_6x6_kodim17_fast.ktx2 b/tests/testimages/astc_mipmap_ldr_6x6_kodim17_fast.ktx2 index ec990f3cc9..703b806200 100644 --- a/tests/testimages/astc_mipmap_ldr_6x6_kodim17_fast.ktx2 +++ b/tests/testimages/astc_mipmap_ldr_6x6_kodim17_fast.ktx2 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4ff7d50b334b6a95785c77228393c542b224ba6aca1d16432b3957520890068d +oid sha256:5f65f3723a4ccddda8619fa670867ec0eb4615b5093edc3ea8a2d3cdecd6af1e size 235840 diff --git a/tests/testimages/astc_mipmap_ldr_6x6_kodim17_fastest.ktx2 b/tests/testimages/astc_mipmap_ldr_6x6_kodim17_fastest.ktx2 index 6f789a5fb6..d93f3258ff 100644 --- a/tests/testimages/astc_mipmap_ldr_6x6_kodim17_fastest.ktx2 +++ b/tests/testimages/astc_mipmap_ldr_6x6_kodim17_fastest.ktx2 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:682cbddef53b126cdab2f06d31e7b00f94d2193465b8456114533d23949a1a80 +oid sha256:bcf4ee7b504cee8a3d3dce0d7dccff9490750d5ccfae31edde55919dc4bcf2be size 235840 diff --git a/tests/testimages/astc_mipmap_ldr_6x6_kodim17_medium.ktx2 b/tests/testimages/astc_mipmap_ldr_6x6_kodim17_medium.ktx2 index bf2d3cd4ee..4cd21678dc 100644 --- a/tests/testimages/astc_mipmap_ldr_6x6_kodim17_medium.ktx2 +++ b/tests/testimages/astc_mipmap_ldr_6x6_kodim17_medium.ktx2 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fb015d988715a9735b680a950eb7da55607da2942675de112d86a60de968127f +oid sha256:8234648b6ba7cc18fe46f46be746e602a5d00ba85ada54f7a5a2544e35f6b566 size 235840 diff --git a/tests/testimages/astc_mipmap_ldr_6x6_posx.ktx2 b/tests/testimages/astc_mipmap_ldr_6x6_posx.ktx2 index 9865380fe0..1eeab6809b 100644 --- a/tests/testimages/astc_mipmap_ldr_6x6_posx.ktx2 +++ b/tests/testimages/astc_mipmap_ldr_6x6_posx.ktx2 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:70d14cd9ed70d58af80eea857e5be1ac3d3a137233a686f64740b3eb55f3dc3d +oid sha256:4f8c41334f9c0bd189efd87b37cbf0e31175e5e205b9ae0c93e3c36fc4f76a0f size 2498272 diff --git a/tests/testimages/astc_mipmap_ldr_6x6_posy.ktx2 b/tests/testimages/astc_mipmap_ldr_6x6_posy.ktx2 index 5291fe6393..89bcda4bf5 100644 --- a/tests/testimages/astc_mipmap_ldr_6x6_posy.ktx2 +++ b/tests/testimages/astc_mipmap_ldr_6x6_posy.ktx2 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dd5a5790fdf622d508b13ebc054786a515b480bfe22908f133f445645abd330c +oid sha256:17d665404ac3cbb3c38abca5a4aaee5cf96d409b3d4d873ea73901bccca8d68d size 2498272 diff --git a/tests/testimages/astc_mipmap_ldr_6x6_posz.ktx2 b/tests/testimages/astc_mipmap_ldr_6x6_posz.ktx2 index 645ef11e7a..2c58458d92 100644 --- a/tests/testimages/astc_mipmap_ldr_6x6_posz.ktx2 +++ b/tests/testimages/astc_mipmap_ldr_6x6_posz.ktx2 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:859a8c8e234bc49ab1cf143cc7d523f208e4b32f2c2ccb15622b711723a12a1c +oid sha256:37c14138479f5164f046ae51871ff4c5f253a3984cceae1432a1c92fb1986e53 size 2498272 diff --git a/tests/testimages/astc_mipmap_ldr_8x6_posx.ktx2 b/tests/testimages/astc_mipmap_ldr_8x6_posx.ktx2 index 320825d519..eb32ca7c6a 100644 --- a/tests/testimages/astc_mipmap_ldr_8x6_posx.ktx2 +++ b/tests/testimages/astc_mipmap_ldr_8x6_posx.ktx2 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:149166e041228384d55397fc97e5d7f0ce656b8f9467a6e79dafeef61f822e5b +oid sha256:43594dce206afcb8686f30140055e57f9f588779331af24dd193610aa7d883ab size 1869280 diff --git a/tests/testimages/astc_mipmap_ldr_8x8_posx.ktx2 b/tests/testimages/astc_mipmap_ldr_8x8_posx.ktx2 index f7138bed0e..deb5d42f9c 100644 --- a/tests/testimages/astc_mipmap_ldr_8x8_posx.ktx2 +++ b/tests/testimages/astc_mipmap_ldr_8x8_posx.ktx2 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c944145afddc468691f14348c74f185c67e839930f8bef451033ed638b6e5934 +oid sha256:90016e26cb31c4f3a0b36482b13b2114c30a0f3db75e381f86d7740babe67b9b size 1398704 diff --git a/tests/testimages/astc_mipmap_ldr_cubemap_6x6.ktx2 b/tests/testimages/astc_mipmap_ldr_cubemap_6x6.ktx2 index 550a41e7ca..5c5e482985 100644 --- a/tests/testimages/astc_mipmap_ldr_cubemap_6x6.ktx2 +++ b/tests/testimages/astc_mipmap_ldr_cubemap_6x6.ktx2 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c5253636e35a8c8b821e5cad80392d3d357e2f61024b43b674207e8431ac8204 +oid sha256:a6683fd3c10ad9686c6d7270160f60bb848b8b39b754bcae7061907bb170aa7e size 14986832 diff --git a/tests/testimages/cimg5293_uastc.ktx2 b/tests/testimages/cimg5293_uastc.ktx2 index 6ad3d0dee6..e4311dac40 100644 --- a/tests/testimages/cimg5293_uastc.ktx2 +++ b/tests/testimages/cimg5293_uastc.ktx2 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5acb6350df7268eca6c30ac0fbf2e1c4aaa1051b12e76307e0136ffe0b36d952 +oid sha256:c24422f9f5d28b09d8a2133b2c07c2d2358ec1b366e2abb92f9e05daa498c0fb size 996944 diff --git a/tests/testimages/cimg5293_uastc_zstd.ktx2 b/tests/testimages/cimg5293_uastc_zstd.ktx2 index d432d5e8ab..486ae9ea48 100644 --- a/tests/testimages/cimg5293_uastc_zstd.ktx2 +++ b/tests/testimages/cimg5293_uastc_zstd.ktx2 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f47e229b678e7fd23abd85b692980aa82b6bd4f52b5a876b3fdfba8f60bdc5ab -size 940762 +oid sha256:01001ec3af7b06f5d0f2c28a37e947b74161a69c960380152b466da1c5e8a03c +size 940652 diff --git a/tests/testimages/cubemap_goldengate_uastc_rdo4_zstd5_rd.ktx2 b/tests/testimages/cubemap_goldengate_uastc_rdo4_zstd5_rd.ktx2 index 3ee19541b5..5e7634a064 100644 --- a/tests/testimages/cubemap_goldengate_uastc_rdo4_zstd5_rd.ktx2 +++ b/tests/testimages/cubemap_goldengate_uastc_rdo4_zstd5_rd.ktx2 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5f35e6d1850ee3c80f75c95ca89a1313f93e7b99029ed1e3dc00fd85d3cfbb32 -size 4031977 +oid sha256:fbd256fb4680323e67b5c1bb7a41e06773485e75bfe672ae2d99244335a82a4c +size 4038178 diff --git a/tests/testimages/cubemap_yokohama_basis_rd.ktx2 b/tests/testimages/cubemap_yokohama_basis_rd.ktx2 index 3905550d39..24e11009c8 100644 --- a/tests/testimages/cubemap_yokohama_basis_rd.ktx2 +++ b/tests/testimages/cubemap_yokohama_basis_rd.ktx2 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a26cc4de775983b0b3ef01236d94e8114f3cafcf0feed8df20f9a1943038f67a -size 2621370 +oid sha256:5104151d9e78c4f1b1b03b1de34b6b418e872afb6dbff6d8a2219a5a19385f6b +size 2632136 diff --git a/tests/testimages/ktx_document_basis.ktx2 b/tests/testimages/ktx_document_basis.ktx2 index 0fef4d1d74..8d266d9093 100644 --- a/tests/testimages/ktx_document_basis.ktx2 +++ b/tests/testimages/ktx_document_basis.ktx2 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7ef04ca3e171eb8a54175c1e28d6facdd966d7237fe022fc75fee614f2c2a972 -size 28804 +oid sha256:bcde94857965249411f27f41dd6a5614546d6193e8848e75f9e9ee574a767b5f +size 28845 diff --git a/tests/testimages/ktx_document_uastc_rdo4_zstd5.ktx2 b/tests/testimages/ktx_document_uastc_rdo4_zstd5.ktx2 index f2f17fbc29..054618eb04 100644 --- a/tests/testimages/ktx_document_uastc_rdo4_zstd5.ktx2 +++ b/tests/testimages/ktx_document_uastc_rdo4_zstd5.ktx2 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:13111473232d69d67570bc3bb4762d6f46eaf2410a0ceb0783bb9e50c185eabc -size 63486 +oid sha256:f7616f7bbd60c2e377727ea6d5a9dc4c22bb08cc95915216f7bc1ab6023301cf +size 63166 diff --git a/tests/tests.cmake b/tests/tests.cmake index 280fc64742..9577de2306 100644 --- a/tests/tests.cmake +++ b/tests/tests.cmake @@ -31,8 +31,8 @@ add_subdirectory(transcodetests) add_subdirectory(streamtests) add_executable( unittests - unittests/unittests.cc unittests/image_unittests.cc + unittests/unittests.cc unittests/wthelper.h tests.cmake ) @@ -45,16 +45,32 @@ PRIVATE $ ${PROJECT_SOURCE_DIR}/lib ${PROJECT_SOURCE_DIR}/tools + ${PROJECT_SOURCE_DIR}/tools/imageio loadtests/common ) +target_include_directories( + unittests + SYSTEM +PRIVATE + ${PROJECT_SOURCE_DIR}/other_include +) + target_link_libraries( unittests gtest ktx + fmt::fmt ${CMAKE_THREAD_LIBS_INIT} ) +set_target_properties( + unittests + PROPERTIES + CXX_STANDARD 17 + CXX_STANDARD_REQUIRED YES +) + add_executable( texturetests texturetests/texturetests.cc unittests/wthelper.h diff --git a/tests/toktx-tests.cmake b/tests/toktx-tests.cmake index 6d8fcae8a2..ef9637539c 100644 --- a/tests/toktx-tests.cmake +++ b/tests/toktx-tests.cmake @@ -241,9 +241,9 @@ if (NOT ${CPU_ARCHITECTURE} STREQUAL "arm64" ) gencmpktx( astc_mipmap_ldr_6x6_posx astc_mipmap_ldr_6x6_posx.ktx2 ../srcimages/Yokohama3/posx.jpg "--test --encode astc --astc_blk_d 6x6 --genmipmap" "" "" ) gencmpktx( astc_mipmap_ldr_6x6_posz astc_mipmap_ldr_6x6_posz.ktx2 ../srcimages/Yokohama3/posz.jpg "--test --encode astc --astc_blk_d 6x6 --genmipmap" "" "" ) gencmpktx( astc_mipmap_ldr_6x6_posy astc_mipmap_ldr_6x6_posy.ktx2 ../srcimages/Yokohama3/posy.jpg "--test --encode astc --astc_blk_d 6x6 --genmipmap" "" "" ) + gencmpktx( astc_mipmap_ldr_6x6_kodim17_fastest astc_mipmap_ldr_6x6_kodim17_fastest.ktx2 ../srcimages/kodim17.png "--test --encode astc --astc_blk_d 6x6 --genmipmap --astc_quality fastest " "" "" ) + gencmpktx( astc_mipmap_ldr_6x6_kodim17_fast astc_mipmap_ldr_6x6_kodim17_fast.ktx2 ../srcimages/kodim17.png "--test --encode astc --astc_blk_d 6x6 --genmipmap --astc_quality fast " "" "" ) endif() -gencmpktx( astc_mipmap_ldr_6x6_kodim17_fastest astc_mipmap_ldr_6x6_kodim17_fastest.ktx2 ../srcimages/kodim17.png "--test --encode astc --astc_blk_d 6x6 --genmipmap --astc_quality fastest " "" "" ) -gencmpktx( astc_mipmap_ldr_6x6_kodim17_fast astc_mipmap_ldr_6x6_kodim17_fast.ktx2 ../srcimages/kodim17.png "--test --encode astc --astc_blk_d 6x6 --genmipmap --astc_quality fast " "" "" ) gencmpktx( astc_mipmap_ldr_6x6_kodim17_medium astc_mipmap_ldr_6x6_kodim17_medium.ktx2 ../srcimages/kodim17.png "--test --encode astc --astc_blk_d 6x6 --genmipmap --astc_quality medium " "" "" ) if (NOT ${CPU_ARCHITECTURE} STREQUAL "arm64" ) @@ -251,10 +251,10 @@ if (NOT ${CPU_ARCHITECTURE} STREQUAL "arm64" ) gencmpktx( astc_mipmap_ldr_6x5_posx astc_mipmap_ldr_6x5_posx.ktx2 ../srcimages/Yokohama3/posx.jpg "--test --encode astc --astc_blk_d 6x5 --genmipmap" "" "" ) gencmpktx( astc_mipmap_ldr_8x6_posx astc_mipmap_ldr_8x6_posx.ktx2 ../srcimages/Yokohama3/posx.jpg "--test --encode astc --astc_blk_d 8x6 --genmipmap" "" "" ) gencmpktx( astc_mipmap_ldr_10x5_posx astc_mipmap_ldr_10x5_posx.ktx2 ../srcimages/Yokohama3/posx.jpg "--test --encode astc --astc_blk_d 10x5 --genmipmap" "" "" ) + gencmpktx( astc_mipmap_ldr_8x8_posx astc_mipmap_ldr_8x8_posx.ktx2 ../srcimages/Yokohama3/posx.jpg "--test --encode astc --astc_blk_d 8x8 --genmipmap" "" "" ) + gencmpktx( astc_mipmap_ldr_12x10_posx astc_mipmap_ldr_12x10_posx.ktx2 ../srcimages/Yokohama3/posx.jpg "--test --encode astc --astc_blk_d 12x10 --genmipmap" "" "" ) + gencmpktx( astc_mipmap_ldr_12x12_posx astc_mipmap_ldr_12x12_posx.ktx2 ../srcimages/Yokohama3/posx.jpg "--test --encode astc --astc_blk_d 12x12 --genmipmap" "" "" ) endif() -gencmpktx( astc_mipmap_ldr_8x8_posx astc_mipmap_ldr_8x8_posx.ktx2 ../srcimages/Yokohama3/posx.jpg "--test --encode astc --astc_blk_d 8x8 --genmipmap" "" "" ) -gencmpktx( astc_mipmap_ldr_12x10_posx astc_mipmap_ldr_12x10_posx.ktx2 ../srcimages/Yokohama3/posx.jpg "--test --encode astc --astc_blk_d 12x10 --genmipmap" "" "" ) -gencmpktx( astc_mipmap_ldr_12x12_posx astc_mipmap_ldr_12x12_posx.ktx2 ../srcimages/Yokohama3/posx.jpg "--test --encode astc --astc_blk_d 12x12 --genmipmap" "" "" ) gencmpktx( astc_ldr_4x4_FlightHelmet_baseColor astc_ldr_4x4_FlightHelmet_baseColor.ktx2 ../srcimages/FlightHelmet_baseColor.png "--test --encode astc --astc_blk_d 4x4" "" "") gencmpktx( astc_ldr_6x5_FlightHelmet_baseColor astc_ldr_6x5_FlightHelmet_baseColor.ktx2 ../srcimages/FlightHelmet_baseColor.png "--test --encode astc --astc_blk_d 6x5" "" "") diff --git a/tests/unittests/image_unittests.cc b/tests/unittests/image_unittests.cc index 779d8d767a..12545e8268 100644 --- a/tests/unittests/image_unittests.cc +++ b/tests/unittests/image_unittests.cc @@ -24,7 +24,7 @@ #endif #include "gtest/gtest.h" -#include "toktx/image.hpp" +#include "image.hpp" namespace { diff --git a/tests/webgl/libktx-webgl/ktx_app_basis.ktx2 b/tests/webgl/libktx-webgl/ktx_app_basis.ktx2 index 1220ec9824..15205cf59c 100644 --- a/tests/webgl/libktx-webgl/ktx_app_basis.ktx2 +++ b/tests/webgl/libktx-webgl/ktx_app_basis.ktx2 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:84e8455470a36914f99adcc43bbc199001be1ce8efbf8ed768bbdda33b85079b -size 16127 +oid sha256:1a56846d7f2e8df28702475cafa739922011fecf4def2ce2644454f5e70f04a5 +size 16189 diff --git a/tests/webgl/libktx-webgl/ktx_document_uastc_rdo5.ktx2 b/tests/webgl/libktx-webgl/ktx_document_uastc_rdo5.ktx2 index 12acddb86e..935db6ce2b 100644 --- a/tests/webgl/libktx-webgl/ktx_document_uastc_rdo5.ktx2 +++ b/tests/webgl/libktx-webgl/ktx_document_uastc_rdo5.ktx2 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:77fefd3df825d76dac1f8650d2c0d9027ea76455769929b62ffb48f293a3310b +oid sha256:56ee53d62e614cbb33ff8b4b5f459bf95ab2169e404d4d2443b0433f032c0c6d size 1392288 diff --git a/tools/imageio/CMakeLists.txt b/tools/imageio/CMakeLists.txt index db6c18433c..405bb62199 100644 --- a/tools/imageio/CMakeLists.txt +++ b/tools/imageio/CMakeLists.txt @@ -15,9 +15,11 @@ set( PLUGIN_HEADERS add_library( imageio STATIC formatdesc.h + image.hpp imageinput.cc imageio.cc imageio.h + imageio_utility.h imageoutput.cc ${PROJECT_SOURCE_DIR}/lib/astc-encoder/Source/tinyexr.h ${PROJECT_SOURCE_DIR}/lib/basisu/encoder/jpgd.cpp @@ -43,11 +45,18 @@ target_include_directories( PUBLIC . PRIVATE - ${PROJECT_SOURCE_DIR}/lib/astc-encoder/Source $ +) + +target_include_directories( + imageio + SYSTEM +PUBLIC + ${PROJECT_SOURCE_DIR}/other_include +PRIVATE + ${PROJECT_SOURCE_DIR}/lib/astc-encoder/Source ${PROJECT_SOURCE_DIR}/lib/basisu ${PROJECT_SOURCE_DIR}/lib/dfdutils - ${PROJECT_SOURCE_DIR}/other_include ) target_compile_definitions( diff --git a/tools/imageio/exr.imageio/exrinput.cc b/tools/imageio/exr.imageio/exrinput.cc index bcf1309928..1cc26cb28d 100644 --- a/tools/imageio/exr.imageio/exrinput.cc +++ b/tools/imageio/exr.imageio/exrinput.cc @@ -4,17 +4,18 @@ // Copyright 2022 The Khronos Group Inc. // SPDX-License-Identifier: Apache-2.0 -// TEXR is not defined in tinyexr.h. Current GitHub tinyexr master uses -// assert. The version in astc-encoder must be old. +#include "imageio.h" + #include #include #include #include #include +// TEXR_ASSERT is not defined in tinyexr.h. Current GitHub tinyexr master +// uses assert. The version in astc-encoder must be old. #define TEXR_ASSERT(x) assert(x) #define TINYEXR_IMPLEMENTATION #include "tinyexr.h" -#include "imageio.h" #include #include "dfd.h" @@ -175,6 +176,10 @@ void ExrInput::open(ImageSpec& newspec) { newspec = spec(); } +/// @brief Read an entire image into contiguous memory performing conversions +/// to @a requestFormat. +/// +/// Supported conversions are half->[half,float,uint], float->float, and uint->uint. void ExrInput::readImage(void* outputBuffer, size_t bufferByteCount, uint32_t subimage, uint32_t miplevel, const FormatDescriptor& requestFormat) { diff --git a/tools/imageio/formatdesc.h b/tools/imageio/formatdesc.h index ba1e989879..1e5b156ae2 100644 --- a/tools/imageio/formatdesc.h +++ b/tools/imageio/formatdesc.h @@ -616,6 +616,18 @@ struct FormatDescriptor { } return maxBitLength; } + bool anyChannelBitLengthNotEqual(uint32_t bitLength) const { + for (uint32_t i = 0; i < 16; ++i) { + uint32_t channelBitLength = 0; + for (const auto& sample : samples) + if (sample.channelType == i) + channelBitLength += sample.bitLength + 1; + + if (bitLength != channelBitLength) + return true; + } + return false; + } khr_df_sample_datatype_qualifiers_e channelDataType(khr_df_model_channels_e c) const { // TODO: Fix for shared exponent case... diff --git a/tools/imageio/image.hpp b/tools/imageio/image.hpp new file mode 100644 index 0000000000..29567353bf --- /dev/null +++ b/tools/imageio/image.hpp @@ -0,0 +1,1405 @@ +// -*- tab-width: 4; -*- +// vi: set sw=2 ts=4 expandtab: + +// Copyright 2010-2020 The Khronos Group Inc. +// SPDX-License-Identifier: Apache-2.0 + +//! +//! @internal +//! @~English +//! @file image.hpp +//! +//! @brief Internal Image class +//! + +#ifndef IMAGE_HPP +#define IMAGE_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef _MSC_VER + #pragma warning(push) + #pragma warning(disable : 4201) +#endif +#include +#ifdef _MSC_VER + #pragma warning(pop) +#endif +#include "imageio_utility.h" +#include "unused.h" +#include "encoder/basisu_resampler.h" +#include "encoder/basisu_resampler_filters.h" + +// cclamp to avoid conflict in toktx.cc with clamp template defined in scApp. +template inline T cclamp(T value, T low, T high) { + return (value < low) ? low : ((value > high) ? high : value); +} +template inline T saturate(T value) { + return cclamp(value, 0, 1.0f); +} + +template inline S maximum(S a, S b) { return (a > b) ? a : b; } +template inline S minimum(S a, S b) { return (a < b) ? a : b; } + +#if defined(_MSC_VER) +#define INLINE __inline +#else +#define INLINE __inline__ +#endif + +struct ColorPrimaryTransform { + ColorPrimaryTransform() {} + + ColorPrimaryTransform(const std::vector& elements) { + for (uint32_t i = 0; i < 3; ++i) + for (uint32_t j = 0; j < 3; ++j) + matrix[i][j] = elements[i * 3 + j]; + } + + float matrix[3][3]; +}; + +// The detailed description of the TransferFunctions can be found at: +// https://registry.khronos.org/DataFormat/specs/1.3/dataformat.1.3.html#TRANSFER_CONVERSION + +struct TransferFunction { + virtual float encode(const float intensity) const = 0; + virtual float decode(const float brightness) const = 0; + virtual ~TransferFunction() {} +}; + +struct TransferFunctionLinear : public TransferFunction { + float encode(const float intensity) const override { + return intensity; + } + float decode(const float brightness) const override { + return brightness; + } +}; + +struct TransferFunctionGamma : public TransferFunction { + TransferFunctionGamma(float oeGamma) : oeGamma_{oeGamma}, eoGamma_{1.f / oeGamma} {} + + float encode(const float intensity) const override { + return saturate(powf(intensity, oeGamma_)); + } + + float decode(const float brightness) const override { + return saturate(powf(brightness, eoGamma_)); + } + +private: + const float oeGamma_; + const float eoGamma_; +}; + +struct TransferFunctionSRGB : public TransferFunction { + float encode(const float intensity) const override { + float brightness; + + if (intensity < 0.0031308f) + brightness = 12.92f * intensity; + else + brightness = 1.055f * pow(intensity, 1.0f/2.4f) - 0.055f; + + return brightness; + } + + float decode(const float brightness) const override { + float intensity; + + if (brightness < .04045f) + intensity = saturate(brightness * (1.0f/12.92f)); + else + intensity = saturate(powf((brightness + .055f) * (1.0f/1.055f), 2.4f)); + + return intensity; + } +}; + +struct TransferFunctionITU : public TransferFunction { + float encode(const float intensity) const override { + float brightness; + + if (intensity < linearCutoff_) + brightness = intensity * linearExpansion_; + else + brightness = 1.099f * pow(intensity, oeGamma_) - 0.099f; + + return brightness; + } + + float decode(const float brightness) const override { + float intensity; + + if (brightness < linearCutoff_ * linearExpansion_) + intensity = brightness / linearExpansion_; + else + intensity = pow((brightness + 0.099f) / 1.099f, eoGamma_); + + return intensity; + } + +private: + /* We're following what Netpbm does. This is their comment and code. */ + + /* Here are parameters of the gamma transfer function for the Netpbm + formats. This is ITU-R Recommendation BT.709, FKA CIE Rec 709. It is + also ITU-R Recommendation BT.601, FKA CCIR 601. + + This transfer function is linear for sample values 0 .. .018 + and an exponential for larger sample values. + The exponential is slightly stretched and translated, though, + unlike the popular pure exponential gamma transfer function. + + The standard actually defines the linear expansion as 4.500, which + means there is a discontinuity at linear intensity .018. We instead + use ~4.514 to make a continuous function. This may have been simply + a mistake when this code was written or based on an actual benefit + to having a continuous function -- The history is not clear. + + Note that the discrepancy is below the precision of a maxval 255 + image. + */ + const float eoGamma_{2.2f}; + const float oeGamma_{1.0f / eoGamma_}; + const float linearCutoff_{0.018f}; + const float linearExpansion_{(1.099f * pow(linearCutoff_, oeGamma_) - 0.099f) / linearCutoff_}; +}; + +struct TransferFunctionBT2100_PQ_EOTF : public TransferFunction { + float decode(const float brightness) const override { + float Ym1 = pow(brightness, m1_); + return pow((c1_ + c2_ * Ym1) / (1 + c3_ * Ym1), m2_); + } + + float encode(const float intensity) const override { + float Erm2 = pow(intensity, rm2_); + return std::pow(std::max(Erm2 - c1_, 0.f) / (c2_ - c3_ * Erm2), m1_); + } + +private: + const float m1_{0.1593017578125f}; + // const float rm1_{1.f / m1_}; // Commented to stop unused warning + const float m2_{78.84375f}; + const float rm2_{1.f / m2_}; + const float c1_{0.8359375f}; + const float c2_{18.8515625f}; + const float c3_{18.6875}; +}; + +// The detailed description of the ColorPrimaries can be found at: +// https://registry.khronos.org/DataFormat/specs/1.3/dataformat.1.3.html#PRIMARY_CONVERSION + +struct ColorPrimaries { + ColorPrimaries(const ColorPrimaryTransform& inToXYZ, const ColorPrimaryTransform& inFromXYZ) + : toXYZ(inToXYZ), fromXYZ(inFromXYZ) {} + + ColorPrimaryTransform transformTo(const ColorPrimaries& targetPrimaries) const { + ColorPrimaryTransform result; + for (uint32_t i = 0; i < 3; ++i) + for (uint32_t j = 0; j < 3; ++j) { + result.matrix[i][j] = 0; + for (uint32_t k = 0; k < 3; ++k) + result.matrix[i][j] += toXYZ.matrix[i][k] * targetPrimaries.fromXYZ.matrix[k][j]; + } + return result; + } + + const ColorPrimaryTransform toXYZ; + const ColorPrimaryTransform fromXYZ; +}; + + +struct ColorPrimariesBT709 : public ColorPrimaries { + ColorPrimariesBT709() : ColorPrimaries( + ColorPrimaryTransform({ + +0.412391f, +0.357584f, +0.180481f, + +0.212639f, +0.715169f, +0.072192f, + +0.019331f, +0.119195f, +0.950532f + }), + ColorPrimaryTransform({ + +3.240970f, -1.537383f, -0.498611f, + -0.969244f, +1.875968f, +0.041555f, + +0.055630f, -0.203977f, +1.056972f + }) + ) {} +}; + +struct ColorPrimariesBT601_625_EBU : public ColorPrimaries { + ColorPrimariesBT601_625_EBU() : ColorPrimaries( + ColorPrimaryTransform({ + +0.430554f, +0.341550f, +0.178352f, + +0.222004f, +0.706655f, +0.071341f, + +0.020182f, +0.129553f, +0.939322f + }), + ColorPrimaryTransform({ + +3.063361f, -1.393390f, -0.475824f, + -0.969244f, +1.875968f, +0.041555f, + +0.067861f, -0.228799f, +1.069090f + }) + ) {} +}; + +struct ColorPrimariesBT601_525_SMPTE : public ColorPrimaries { + ColorPrimariesBT601_525_SMPTE() : ColorPrimaries( + ColorPrimaryTransform({ + +0.393521f, +0.365258f, +0.191677f, + +0.212376f, +0.701060f, +0.086564f, + +0.018739f, +0.111934f, +0.958385f + }), + ColorPrimaryTransform({ + +3.506003f, -1.739791f, -0.544058f, + -1.069048f, +1.977779f, +0.035171f, + +0.056307f, -0.196976f, +1.049952f + }) + ) {} +}; + +struct ColorPrimariesBT2020 : public ColorPrimaries { + ColorPrimariesBT2020() : ColorPrimaries( + ColorPrimaryTransform({ + +0.636958f, +0.144617f, +0.168881f, + +0.262700f, +0.677998f, +0.059302f, + +0.000000f, +0.028073f, +1.060985f + }), + ColorPrimaryTransform({ + +1.716651f, -0.355671f, -0.253366f, + -0.666684f, +1.616481f, +0.015769f, + +0.017640f, -0.042771f, +0.942103f + }) + ) {} +}; + +struct ColorPrimariesCIEXYZ : public ColorPrimaries { + ColorPrimariesCIEXYZ() : ColorPrimaries( + ColorPrimaryTransform({ + 1.f, 0.f, 0.f, + 0.f, 1.f, 0.f, + 0.f, 0.f, 1.f + }), + ColorPrimaryTransform({ + 1.f, 0.f, 0.f, + 0.f, 1.f, 0.f, + 0.f, 0.f, 1.f + }) + ) {} +}; + +struct ColorPrimariesACES : public ColorPrimaries { + ColorPrimariesACES() : ColorPrimaries( + ColorPrimaryTransform({ + +0.9525523959f, 0.0000000000f, +0.0000936786f, + +0.3439664498f, +0.7281660966f, -0.0721325464f, + 0.0000000000f, 0.0000000000f, +1.0088251844f + }), + ColorPrimaryTransform({ + +1.0498110175f, 0.0000000000f, -0.0000974845f, + -0.4959030231f, +1.3733130458f, +0.0982400361f, + 0.0000000000f, 0.0000000000f, +0.9912520182f + }) + ) {} +}; + +struct ColorPrimariesACEScc : public ColorPrimaries { + ColorPrimariesACEScc() : ColorPrimaries( + ColorPrimaryTransform({ + +0.6624541811f, +0.1340042065f, +0.1561876870f, + +0.2722287168f, +0.6740817658f, +0.0536895174f, + -0.0055746495f, +0.0040607335f, +1.0103391003f + }), + ColorPrimaryTransform({ + +1.6410233797f, -0.3248032942f, -0.2464246952f, + -0.6636628587f, +1.6153315917f, +0.0167563477f, + +0.0117218943f, -0.0082844420f, +0.9883948585f + }) + ) {} +}; + +struct ColorPrimariesNTSC1953 : public ColorPrimaries { + ColorPrimariesNTSC1953() : ColorPrimaries( + ColorPrimaryTransform({ + +0.606993f, +0.173449f, +0.200571f, + +0.298967f, +0.586421f, +0.114612f, + +0.000000f, +0.066076f, +1.117469f + }), + ColorPrimaryTransform({ + +1.909675f, -0.532365f, -0.288161f, + -0.984965f, +1.999777f, -0.028317f, + +0.058241f, -0.118246f, +0.896554f + }) + ) {} +}; + +struct ColorPrimariesPAL525 : public ColorPrimaries { + ColorPrimariesPAL525() : ColorPrimaries( + ColorPrimaryTransform({ + +0.415394f, +0.354637f, +0.210677f, + +0.224181f, +0.680675f, +0.095145f, + +0.019781f, +0.108679f, +1.053387f + }), + ColorPrimaryTransform({ + +3.321392f, -1.648181f, -0.515410f, + -1.101064f, +2.037011f, +0.036225f, + +0.051228f, -0.179211f, +0.955260f + }) + ) {} +}; + +struct ColorPrimariesDisplayP3 : public ColorPrimaries { + ColorPrimariesDisplayP3() : ColorPrimaries( + ColorPrimaryTransform({ + +0.4865709486f, +0.2656676932f, +0.1982172852f, + +0.2289745641f, +0.6917385218f, +0.0792869141f, + 0.0000000000f, +0.0451133819f, +1.0439441689f + }), + ColorPrimaryTransform({ + +2.4934969119f, -0.9313836179f, -0.4027107845f, + -0.8294889696f, +1.7626640603f, +0.0236246858f, + +0.0358458302f, -0.0761723893f, +0.9568845240f + }) + ) {} +}; + +struct ColorPrimariesAdobeRGB : public ColorPrimaries { + ColorPrimariesAdobeRGB() : ColorPrimaries( + ColorPrimaryTransform({ + +0.5766690429f, +0.1855582379f, +0.1882286462f, + +0.2973449753f, +0.6273635663f, +0.0752914585f, + +0.0270313614f, +0.0706888525f, +0.9913375368f + }), + ColorPrimaryTransform({ + +2.0415879038f, -0.5650069743f, -0.3447313508f, + -0.9692436363f, +1.8759675015f, +0.0415550574f, + +0.0134442806f, -0.1183623922f, +1.0151749944f + }) + ) {} +}; + +template +class color_base { +public: + constexpr static uint32_t getComponentCount() { return componentCount; } + constexpr static uint32_t getComponentSize() { return sizeof(componentType); } + constexpr static uint32_t getPixelSize() { + return componentCount * sizeof(componentType); + } + constexpr static componentType one() { + if (std::is_floating_point_v) + return componentType{1}; + else + return std::numeric_limits::max(); + } + constexpr static float rcpOne() { + if (std::is_floating_point_v) + return 1.f; + else + return 1.f / static_cast(std::numeric_limits::max()); + } + constexpr static float halfUnit() { + if (std::is_floating_point_v) + return 0.f; + else + return 0.5f / static_cast(std::numeric_limits::max()); + } + constexpr static componentType min() { + return std::numeric_limits::min(); + } + constexpr static componentType max() { + return std::numeric_limits::max(); + } + componentType clamp(componentType value) { + return (value < min()) ? min() : ((value > max()) ? max() : value); + } +}; + +struct vec3_base { + float r; + float g; + float b; + vec3_base() : r(0.0f), g(0.0f), b(0.0f) {} + vec3_base(float _r, float _g, float _b) : r(_r), g(_g), b(_b) {} + void base_normalize() { + float len = r * r + g * g + b * b; + len = sqrtf(len); + if (len > 0.0f) + { + r /= len; + g /= len; + b /= len; + } + } + void clamp(float _a, float _b) { + r = cclamp(r, _a, _b); + g = cclamp(g, _a, _b); + b = cclamp(b, _a, _b); + } +}; + +static constexpr float gc_m[5]={0.0f, 128.0f, 32768.0f, 0.0f, 2147483648.0f}; +static constexpr uint32_t gc_s[5]={0, 255, 65535, 0, 4294967295}; + +template +struct vec3 : public vec3_base { + static constexpr uint32_t i = sizeof(componentType); + + explicit vec3(float _r) : vec3_base(_r, 0.0f, 0.0f) {} + vec3(float _r, float _g) : vec3_base(_r, _g, 0.0f) {} + vec3(float _r, float _g, float _b) : vec3_base(_r, _g, _b) {} + void normalize() { + // Zero normals in range [-1, 1] can't be normalized + if (gc_m[i] == r && gc_m[i] == g && gc_m[i] == b) { + return; + } else { + r = (float)(r / (double)gc_s[i]) * 2.0f - 1.0f; + g = (float)(g / (double)gc_s[i]) * 2.0f - 1.0f; + b = (float)(b / (double)gc_s[i]) * 2.0f - 1.0f; + clamp(-1.0f, 1.0f); + base_normalize(); + r = (std::floor((r + 1.0f) * (float)gc_s[i] * 0.5f + 0.5f)); + g = (std::floor((g + 1.0f) * (float)gc_s[i] * 0.5f + 0.5f)); + b = (std::floor((b + 1.0f) * (float)gc_s[i] * 0.5f + 0.5f)); + clamp(0.0f, (float)gc_s[i]); + } + } +}; + +template<> +struct vec3 : public vec3_base { + explicit vec3(float _r) : vec3_base(_r, 0.0f, 0.0f) {} + vec3(float _r, float _g) : vec3_base(_r, _g, 0.0f) {} + vec3(float _r, float _g, float _b) : vec3_base(_r, _g, _b) {} + void normalize(){ + base_normalize(); + } +}; + +template +class color { }; + +template +class color : public color_base { + public: + using value_type = componentType; + union { + componentType comps[4]; + + struct { + componentType r; + componentType g; + componentType b; + componentType a; + }; + }; + color() {} + color(componentType _r, componentType _g, componentType _b, + componentType _a) : r(_r), g(_g), b(_b), a(_a) {} + const componentType& operator [](unsigned int i) const { + if (i > 3) i = 3; + return comps[i]; + } + componentType& operator [](unsigned int i) { + if (i > 3) i = 3; + return comps[i]; + } + template + void set(uint32_t i, T val) { + if (i > 3) i = 3; + comps[i] = color_base::clamp((componentType)val); + } + void set(uint32_t i, componentType val) { + if (i > 3) i = 3; + comps[i] = val; + } + constexpr uint32_t comps_count() const { + return 4; + } + void normalize() { + vec3 v((float)r, (float)g, (float)b); + v.normalize(); + r = (componentType)v.r; + g = (componentType)v.g; + b = (componentType)v.b; + } + }; + +template +class color : public color_base { + public: + using value_type = componentType; + union { + componentType comps[3]; + + struct { + componentType r; + componentType g; + componentType b; + }; + }; + color() {} + color(componentType _r, componentType _g, componentType _b) : + r(_r), g(_g), b(_b) {} + color(componentType _r, componentType _g, componentType _b, componentType) : + r(_r), g(_g), b(_b) {} + const componentType& operator [](unsigned int i) const { + if (i > 2) i = 2; + return comps[i]; + } + componentType& operator [](unsigned int i) { + if (i > 2) i = 2; + return comps[i]; + } + template + void set(uint32_t i, T val) { + if (i > 2) i = 2; + comps[i] = color_base::clamp((componentType)val); + } + void set(uint32_t i, componentType val) { + if (i > 2) i = 2; + comps[i] = val; + } + constexpr uint32_t comps_count() const { + return 3; + } + void normalize() { + vec3 v((float)r, (float)g, (float)b); + v.normalize(); + r = (componentType)v.r; + g = (componentType)v.g; + b = (componentType)v.b; + } +}; + +template +class color : public color_base { + public: + using value_type = componentType; + union { + componentType comps[2]; + + struct { + componentType r; + componentType g; + }; + }; + color() {} + color(componentType _r, componentType _g) : + r(_r), g(_g) {} + color(componentType _r, componentType _g, componentType, componentType) : + r(_r), g(_g) {} + const componentType& operator [](unsigned int i) const { + if (i > 1) i = 1; + return comps[i]; + } + componentType& operator [](unsigned int i) { + if (i > 1) i = 1; + return comps[i]; + } + template + void set(uint32_t i, T val) { + if (i > 1) i = 1; + comps[i] = color_base::clamp((componentType)val); + } + void set(uint32_t i, componentType val) { + if (i > 1) i = 1; + comps[i] = val; + } + constexpr uint32_t comps_count() const { + return 2; + } + void normalize() { + vec3 v((float)r, (float)g, + (float)gc_s[sizeof(componentType)] * 0.5f); + v.normalize(); + r = (componentType)v.r; + g = (componentType)v.g; + } +}; + +template +class color : public color_base { + public: + using value_type = componentType; + union { + componentType comps[1]; + + struct { + componentType r; + }; + }; + color() {} + color(componentType _r) : + r(_r) {} + color(componentType _r, componentType, componentType, componentType) : + r(_r) {} + const componentType& operator [](unsigned int i) const { + if (i > 0) i = 0; + return comps[i]; + } + componentType& operator [](unsigned int i) { + if (i > 0) i = 0; + return comps[i]; + } + template + void set(uint32_t i, T val) { + if (i > 0) i = 0; + comps[i] = color_base::clamp((componentType)val); + } + void set(uint32_t i, componentType val) { + if (i > 0) i = 0; + comps[i] = val; + } + constexpr uint32_t comps_count() const { + return 1; + } + void normalize() { + // Normalizing single channel image doesn't make much sense + // Here I assume single channel color is (X, 0, 0, 0) + if (r != 0) + r = (componentType)gc_s[sizeof(componentType)]; + } +}; + +// Abstract base class for all Images. +class Image { + public: + class different_format : public std::runtime_error { + public: + different_format() : std::runtime_error("") { } + }; + class invalid_file : public std::runtime_error { + public: + invalid_file(std::string error) + : std::runtime_error("Invalid file: " + error) { } + }; + + virtual ~Image() { }; + + uint32_t getWidth() const { return width; } + uint32_t getHeight() const { return height; } + uint32_t getPixelCount() const { return width * height; } + khr_df_transfer_e getOetf() const { return oetf; } + void setOetf(khr_df_transfer_e noetf) { this->oetf = noetf; } + khr_df_primaries_e getPrimaries() const { return primaries; } + void setPrimaries(khr_df_primaries_e nprimaries) { + this->primaries = nprimaries; + } + + virtual operator uint8_t*() = 0; + + virtual size_t getByteCount() const = 0; + virtual uint32_t getPixelSize() const = 0; + virtual uint32_t getComponentCount() const = 0; + virtual uint32_t getComponentSize() const = 0; + virtual Image* createImage(uint32_t width, uint32_t height) = 0; + /// Should only be used if the stored image data is UNORM convertable + virtual std::vector getUNORM(uint32_t numChannels, uint32_t targetBits) const = 0; + /// Should only be used if the stored image data is UNORM convertable + virtual std::vector getUNORMPackedPadded( + uint32_t c0, uint32_t c0Pad, uint32_t c1, uint32_t c1Pad, + uint32_t c2, uint32_t c2Pad, uint32_t c3, uint32_t c3Pad) const = 0; + /// Should only be used if the stored image data is SFloat convertable + virtual std::vector getSFloat(uint32_t numChannels, uint32_t targetBits) const = 0; + /// Should only be used if the stored image data is UFloat convertable + virtual std::vector getB10G11R11() const = 0; + /// Should only be used if the stored image data is UFloat convertable + virtual std::vector getE5B9G9R9() const = 0; + /// Should only be used if the stored image data is UINT convertable + virtual std::vector getUINT(uint32_t numChannels, uint32_t targetBits) const = 0; + /// Should only be used if the stored image data is SINT convertable + virtual std::vector getSINT(uint32_t numChannels, uint32_t targetBits) const = 0; + /// Should only be used if the stored image data is UINT convertable + virtual std::vector getUINTPacked(uint32_t c0, uint32_t c1, uint32_t c2, uint32_t c3) const = 0; + /// Should only be used if the stored image data is SINT convertable + virtual std::vector getSINTPacked(uint32_t c0, uint32_t c1, uint32_t c2, uint32_t c3) const = 0; + virtual std::unique_ptr resample(uint32_t targetWidth, uint32_t targetHeight, + const char* filter, float filterScale, basisu::Resampler::Boundary_Op wrapMode) = 0; + virtual Image& yflip() = 0; + virtual Image& transformColorSpace(const TransferFunction& decode, const TransferFunction& encode, + const ColorPrimaryTransform* transformPrimaries = nullptr) = 0; + virtual Image& normalize() = 0; + virtual Image& swizzle(std::string_view swizzle) = 0; + virtual Image& copyToR(Image&, std::string_view swizzle) = 0; + virtual Image& copyToRG(Image&, std::string_view swizzle) = 0; + virtual Image& copyToRGB(Image&, std::string_view swizzle) = 0; + virtual Image& copyToRGBA(Image&, std::string_view swizzle) = 0; + + protected: + Image() : Image(0, 0) { } + Image(uint32_t w, uint32_t h) + : width(w), height(h), oetf(KHR_DF_TRANSFER_UNSPECIFIED), + primaries(KHR_DF_PRIMARIES_BT709) { } + + uint32_t width, height; // In pixels + khr_df_transfer_e oetf; + khr_df_primaries_e primaries; +}; + +// Base class for template and specializations +template +class ImageT : public Image { + friend class ImageT; + friend class ImageT; + friend class ImageT; + friend class ImageT; + public: + using Color = color; + ImageT(uint32_t w, uint32_t h) : Image(w, h) + { + size_t bytes = sizeof(Color) * w * h; + pixels = (Color*)malloc(bytes); + if (!pixels) + throw std::bad_alloc(); + + freePixels = true; + + for (uint32_t p = 0; p < w * h; ++p) + for(uint32_t c = 0; c < componentCount; ++c) + pixels[p].comps[c] = componentType{0}; + } + + ImageT(uint32_t w, uint32_t h, Color* pixels) : Image(w, h), pixels(pixels), freePixels(false) + { + } + + ~ImageT() + { + if (freePixels) + free(pixels); + } + + const Color& operator() (uint32_t x, uint32_t y) const { + assert(x < width && y < height); return pixels[x + y * width]; + } + Color& operator() (uint32_t x, uint32_t y) { + assert(x < width && y < height); return pixels[x + y * width]; + } + virtual operator uint8_t*() override { return (uint8_t*)pixels; } + + virtual size_t getByteCount() const override { + return getPixelCount() * sizeof(Color); + } + + virtual uint32_t getPixelSize() const override { + return Color::getPixelSize(); + } + virtual uint32_t getComponentCount() const override { + return Color::getComponentCount(); + } + virtual uint32_t getComponentSize() const override { + return Color::getComponentSize(); + } + + virtual Image* createImage(uint32_t w, uint32_t h) override { + ImageT* image = new ImageT(w, h); + return image; + } + + virtual std::vector getUNORM(uint32_t numChannels, uint32_t targetBits) const override { + assert(numChannels <= componentCount); + assert(targetBits == 8 || targetBits == 16 || targetBits == 32); + + const uint32_t sourceBits = sizeof(componentType) * 8; + const uint32_t targetBytes = targetBits / 8; + std::vector data(height * width * numChannels * targetBytes); + + for (uint32_t y = 0; y < height; ++y) { + for (uint32_t x = 0; x < width; ++x) { + for (uint32_t c = 0; c < numChannels; ++c) { + const auto sourceValue = c < componentCount ? pixels[y * width + x][c] : (c != 3 ? componentType{0} : Color::one()); + const auto value = imageio::convertUNORM(static_cast(sourceValue), sourceBits, targetBits); + auto* target = data.data() + (y * width * numChannels + x * numChannels + c) * targetBytes; + + if (targetBytes == 1) { + const auto outValue = static_cast(value); + std::memcpy(target, &outValue, sizeof(outValue)); + } else if (targetBytes == 2) { + const auto outValue = static_cast(value); + std::memcpy(target, &outValue, sizeof(outValue)); + } else if (targetBytes == 4) { + const auto outValue = static_cast(value); + std::memcpy(target, &outValue, sizeof(outValue)); + } + } + } + } + + return data; + } + + virtual std::vector getUNORMPackedPadded( + uint32_t c0, uint32_t c0Pad, uint32_t c1, uint32_t c1Pad, + uint32_t c2, uint32_t c2Pad, uint32_t c3, uint32_t c3Pad) const override { + assert((c0 + c0Pad + c1 + c1Pad + c2 + c2Pad + c3 + c3Pad) % 8 == 0); + const auto targetPackBytes = (c0 + c0Pad + c1 + c1Pad + c2 + c2Pad + c3 + c3Pad) / 8; + assert(targetPackBytes == 1 || targetPackBytes == 2 || targetPackBytes == 4 || targetPackBytes == 8); + const auto packC0 = c0 > 0; + const auto packC1 = c1 > 0; + const auto packC2 = c2 > 0; + const auto packC3 = c3 > 0; + const auto numChannels = (packC0 ? 1u : 0) + (packC1 ? 1u : 0) + (packC2 ? 1u : 0) + (packC3 ? 1u : 0); + assert(numChannels <= componentCount); (void) numChannels; + const uint32_t sourceBits = sizeof(componentType) * 8; + static constexpr auto hasC0 = componentCount > 0; + static constexpr auto hasC1 = componentCount > 1; + static constexpr auto hasC2 = componentCount > 2; + static constexpr auto hasC3 = componentCount > 3; + + std::vector data(height * width * targetPackBytes); + + for (uint32_t y = 0; y < height; ++y) { + for (uint32_t x = 0; x < width; ++x) { + const auto& pixel = pixels[y * width + x]; + auto* target = data.data() + (y * width + x) * targetPackBytes; + + const auto copy = [&](auto& pack) { + using PackType = std::remove_reference_t; + + if (packC0) { + const auto sourceValue = hasC0 ? pixel[0] : componentType{0}; + const auto value = imageio::convertUNORM(static_cast(sourceValue), sourceBits, c0); + pack |= static_cast(value) << (c0Pad + c1 + c1Pad + c2 + c2Pad + c3 + c3Pad); + } + if (packC1) { + const auto sourceValue = hasC1 ? pixel[1] : componentType{0}; + const auto value = imageio::convertUNORM(static_cast(sourceValue), sourceBits, c1); + pack |= static_cast(value) << (c1Pad + c2 + c2Pad + c3 + c3Pad); + } + if (packC2) { + const auto sourceValue = hasC2 ? pixel[2] : componentType{0}; + const auto value = imageio::convertUNORM(static_cast(sourceValue), sourceBits, c2); + pack |= static_cast(value) << (c2Pad + c3 + c3Pad); + } + if (packC3) { + const auto sourceValue = hasC3 ? pixel[3] : Color::one(); + const auto value = imageio::convertUNORM(static_cast(sourceValue), sourceBits, c3); + pack |= static_cast(value) << (c3Pad); + } + }; + + if (targetPackBytes == 1) { + uint8_t pack = 0; + copy(pack); + std::memcpy(target, &pack, sizeof(pack)); + } else if (targetPackBytes == 2) { + uint16_t pack = 0; + copy(pack); + std::memcpy(target, &pack, sizeof(pack)); + } else if (targetPackBytes == 4) { + uint32_t pack = 0; + copy(pack); + std::memcpy(target, &pack, sizeof(pack)); + } else if (targetPackBytes == 8) { + uint64_t pack = 0; + copy(pack); + std::memcpy(target, &pack, sizeof(pack)); + } + } + } + + return data; + } + + virtual std::vector getSFloat(uint32_t numChannels, uint32_t targetBits) const override { + assert(numChannels <= componentCount); + assert(targetBits == 16 || targetBits == 32); + + const uint32_t targetBytes = targetBits / 8; + std::vector data(height * width * numChannels * targetBytes); + + for (uint32_t y = 0; y < height; ++y) { + for (uint32_t x = 0; x < width; ++x) { + for (uint32_t c = 0; c < numChannels; ++c) { + const auto value = c < componentCount ? pixels[y * width + x][c] : (c != 3 ? componentType{0} : componentType{1}); + auto* target = data.data() + (y * width * numChannels + x * numChannels + c) * targetBytes; + + if (sizeof(componentType) == targetBytes) { + *reinterpret_cast(target) = value; + } else if (targetBytes == 2) { + const auto outValue = imageio::float_to_half(static_cast(value)); + std::memcpy(target, &outValue, targetBytes); + } else if (targetBytes == 4) { + const auto outValue = static_cast(value); + std::memcpy(target, &outValue, targetBytes); + } + } + } + } + + return data; + } + + virtual std::vector getB10G11R11() const override { + assert(3 <= componentCount); + assert(std::is_floating_point_v); + + std::vector data(height * width * 4); + + for (uint32_t y = 0; y < height; ++y) { + for (uint32_t x = 0; x < width; ++x) { + auto* target = data.data() + (y * width + x) * 4; + + const auto pixel = (*this)(x, y); + const auto r = pixel[0]; + const auto g = pixel[1]; + const auto b = pixel[2]; + const auto outValue = glm::packF2x11_1x10(glm::vec3(r, g, b)); + std::memcpy(target, &outValue, sizeof(outValue)); + } + } + + return data; + } + + virtual std::vector getE5B9G9R9() const override { + assert(3 <= componentCount); + assert(std::is_floating_point_v); + + std::vector data(height * width * 4); + + for (uint32_t y = 0; y < height; ++y) { + for (uint32_t x = 0; x < width; ++x) { + auto* target = data.data() + (y * width + x) * 4; + + const auto pixel = (*this)(x, y); + const auto r = pixel[0]; + const auto g = pixel[1]; + const auto b = pixel[2]; + const auto outValue = glm::packF3x9_E1x5(glm::vec3(r, g, b)); + std::memcpy(target, &outValue, sizeof(outValue)); + } + } + + return data; + } + + virtual std::vector getUINT(uint32_t numChannels, uint32_t targetBits) const override { + assert(numChannels <= componentCount); + assert(targetBits == 8 || targetBits == 16 || targetBits == 32 || targetBits == 64); + + const uint32_t targetBytes = targetBits / 8; + std::vector data(height * width * numChannels * targetBytes); + + for (uint32_t y = 0; y < height; ++y) { + for (uint32_t x = 0; x < width; ++x) { + for (uint32_t c = 0; c < numChannels; ++c) { + const auto value = c < componentCount ? pixels[y * width + x][c] : c != 3 ? 0 : componentType{1}; + auto* target = data.data() + (y * width * numChannels + x * numChannels + c) * targetBytes; + + if (targetBytes == 1) { + const auto outValue = static_cast(value); + std::memcpy(target, &outValue, targetBytes); + } else if (targetBytes == 2) { + const auto outValue = static_cast(value); + std::memcpy(target, &outValue, targetBytes); + } else if (targetBytes == 4) { + const auto outValue = static_cast(value); + std::memcpy(target, &outValue, targetBytes); + } else if (targetBytes == 8) { + const auto outValue = static_cast(value); + std::memcpy(target, &outValue, targetBytes); + } + } + } + } + + return data; + } + + virtual std::vector getSINT(uint32_t numChannels, uint32_t targetBits) const override { + assert(numChannels <= componentCount); + assert(targetBits == 8 || targetBits == 16 || targetBits == 32 || targetBits == 64); + + const uint32_t targetBytes = targetBits / 8; + std::vector data(height * width * numChannels * targetBytes); + + for (uint32_t y = 0; y < height; ++y) { + for (uint32_t x = 0; x < width; ++x) { + for (uint32_t c = 0; c < numChannels; ++c) { + const auto value = c < componentCount ? pixels[y * width + x][c] : c != 3 ? 0 : componentType{1}; + auto* target = data.data() + (y * width * numChannels + x * numChannels + c) * targetBytes; + + if (targetBytes == 1) { + const auto outValue = static_cast(value); + std::memcpy(target, &outValue, targetBytes); + } else if (targetBytes == 2) { + const auto outValue = static_cast(value); + std::memcpy(target, &outValue, targetBytes); + } else if (targetBytes == 4) { + const auto outValue = static_cast(value); + std::memcpy(target, &outValue, targetBytes); + } else if (targetBytes == 8) { + const auto outValue = static_cast(value); + std::memcpy(target, &outValue, targetBytes); + } + } + } + } + + return data; + } + + virtual std::vector getUINTPacked(uint32_t c0, uint32_t c1, uint32_t c2, uint32_t c3) const override { + assert(c0 + c1 + c2 + c3 == 32); + assert(c0 != 0 && c1 != 0 && c2 != 0 && c3 != 0); + assert(componentCount == 4); + + std::vector data(height * width * sizeof(uint32_t)); + for (uint32_t y = 0; y < height; ++y) { + for (uint32_t x = 0; x < width; ++x) { + const auto& pixel = pixels[y * width + x]; + auto* target = data.data() + (y * width + x) * sizeof(uint32_t); + + uint32_t pack = 0; + pack |= imageio::convertUINT(static_cast(pixel[0]), sizeof(uint32_t) * 8, c0) << (c1 + c2 + c3); + pack |= imageio::convertUINT(static_cast(pixel[1]), sizeof(uint32_t) * 8, c1) << (c2 + c3); + pack |= imageio::convertUINT(static_cast(pixel[2]), sizeof(uint32_t) * 8, c2) << c3; + pack |= imageio::convertUINT(static_cast(pixel[3]), sizeof(uint32_t) * 8, c3); + + std::memcpy(target, &pack, sizeof(pack)); + } + } + + return data; + } + + virtual std::vector getSINTPacked(uint32_t c0, uint32_t c1, uint32_t c2, uint32_t c3) const override { + assert(c0 + c1 + c2 + c3 == 32); + assert(c0 != 0 && c1 != 0 && c2 != 0 && c3 != 0); + assert(componentCount == 4); + + std::vector data(height * width * sizeof(uint32_t)); + for (uint32_t y = 0; y < height; ++y) { + for (uint32_t x = 0; x < width; ++x) { + const auto& pixel = pixels[y * width + x]; + auto* target = data.data() + (y * width + x) * sizeof(uint32_t); + + uint32_t pack = 0; + pack |= imageio::convertSINT(imageio::bit_cast(static_cast(pixel[0])), sizeof(uint32_t) * 8, c0) << (c1 + c2 + c3); + pack |= imageio::convertSINT(imageio::bit_cast(static_cast(pixel[1])), sizeof(uint32_t) * 8, c1) << (c2 + c3); + pack |= imageio::convertSINT(imageio::bit_cast(static_cast(pixel[2])), sizeof(uint32_t) * 8, c2) << c3; + pack |= imageio::convertSINT(imageio::bit_cast(static_cast(pixel[3])), sizeof(uint32_t) * 8, c3); + + std::memcpy(target, &pack, sizeof(pack)); + } + } + + return data; + } + + static void checkResamplerStatus(basisu::Resampler& resampler, const char* pFilter) { + using Status = basisu::Resampler::Status; + + switch (resampler.status()) { + case Status::STATUS_OKAY: + break; + case Status::STATUS_OUT_OF_MEMORY: + throw std::runtime_error("Resampler or Resampler::put_line out of memory."); + case Status::STATUS_BAD_FILTER_NAME: + throw std::runtime_error(fmt::format("Unknown filter: {}", pFilter)); + case Status::STATUS_SCAN_BUFFER_FULL: + throw std::runtime_error("Resampler::put_line scan buffer full."); + } + } + + virtual std::unique_ptr resample(uint32_t targetWidth, uint32_t targetHeight, + const char* filter, float filterScale, basisu::Resampler::Boundary_Op wrapMode) override { + using namespace basisu; + + auto target = std::make_unique>(targetWidth, targetHeight); + target->setOetf(oetf); + target->setPrimaries(primaries); + + const auto sourceWidth = width; + const auto sourceHeight = height; + assert(sourceWidth && sourceHeight && targetWidth && targetHeight); + + if (std::max(sourceWidth, sourceHeight) > BASISU_RESAMPLER_MAX_DIMENSION || + std::max(targetWidth, targetHeight) > BASISU_RESAMPLER_MAX_DIMENSION) { + throw std::runtime_error(fmt::format( + "Image larger than max supported size of {}", BASISU_RESAMPLER_MAX_DIMENSION)); + } + + std::array, componentCount> samples; + std::array, componentCount> resamplers; + + // Float types handled as SFloat HDR otherwise UNROM LDR is assumed + const auto isHDR = std::is_floating_point_v; + + for (uint32_t i = 0; i < componentCount; ++i) { + resamplers[i] = std::make_unique( + sourceWidth, sourceHeight, + targetWidth, targetHeight, + wrapMode, + 0.0f, isHDR ? 0.0f : 1.0f, + filter, + i == 0 ? nullptr : resamplers[0]->get_clist_x(), + i == 0 ? nullptr : resamplers[0]->get_clist_y(), + filterScale, filterScale, + 0.f, 0.f); + checkResamplerStatus(*resamplers[i], filter); + samples[i].resize(sourceWidth); + } + + const TransferFunctionSRGB tfSRGB; + const TransferFunctionLinear tfLinear; + const TransferFunction& tf = oetf == KHR_DF_TRANSFER_SRGB ? + static_cast(tfSRGB) : + static_cast(tfLinear); + + uint32_t targetY = 0; + for (uint32_t sourceY = 0; sourceY < sourceHeight; ++sourceY) { + // Put source lines into resampler(s) + for (uint32_t sourceX = 0; sourceX < sourceWidth; ++sourceX) { + const auto& sourcePixel = pixels[sourceY * sourceWidth + sourceX]; + for (uint32_t c = 0; c < componentCount; ++c) { + const float value = std::is_floating_point_v ? + sourcePixel[c] : + static_cast(sourcePixel[c]) * (1.f / static_cast(Color::one())); + + // c == 3: Alpha channel always uses tfLinear + samples[c][sourceX] = (c == 3 ? tfLinear : tf).decode(value); + } + } + + for (uint32_t c = 0; c < componentCount; ++c) + if (!resamplers[c]->put_line(&samples[c][0])) + checkResamplerStatus(*resamplers[c], filter); + + // Retrieve any output lines + while (true) { + std::array outputLine{nullptr}; + for (uint32_t c = 0; c < componentCount; ++c) + outputLine[c] = resamplers[c]->get_line(); + + if (outputLine[0] == nullptr) + break; // No new output line, break from retrieve and place in a new source line + + for (uint32_t targetX = 0; targetX < targetWidth; ++targetX) { + Color& targetPixel = target->pixels[targetY * targetWidth + targetX]; + for (uint32_t c = 0; c < componentCount; ++c) { + const auto linearValue = outputLine[c][targetX]; + + // c == 3: Alpha channel always uses tfLinear + const float outValue = (c == 3 ? tfLinear : tf).encode(linearValue); + if constexpr (std::is_floating_point_v) { + targetPixel[c] = outValue; + } else { + const auto unormValue = + std::isnan(outValue) ? componentType{0} : + outValue < 0.f ? componentType{0} : + outValue > 1.f ? Color::one() : + static_cast(outValue * static_cast(Color::one()) + 0.5f); + targetPixel[c] = unormValue; + } + } + } + + ++targetY; + } + } + + return target; + } + + virtual ImageT& yflip() override { + uint32_t rowSize = width * sizeof(Color); + // Minimize memory use by only buffering a single row. + Color* rowBuffer = new Color[width]; + + for (uint32_t sy = height-1, dy = 0; sy >= height / 2; sy--, dy++) { + Color* srcRow = &pixels[width * sy]; + Color* dstRow = &pixels[width * dy]; + + memcpy(rowBuffer, dstRow, rowSize); + memcpy(dstRow, srcRow, rowSize); + memcpy(srcRow, rowBuffer, rowSize); + } + delete[] rowBuffer; + return *this; + } + + virtual ImageT& transformColorSpace(const TransferFunction& decode, const TransferFunction& encode, + const ColorPrimaryTransform* transformPrimaries) override { + uint32_t pixelCount = getPixelCount(); + for (uint32_t i = 0; i < pixelCount; ++i) { + Color& c = pixels[i]; + // Don't transform the alpha component. + uint32_t components = cclamp(getComponentCount(), 0u, 3u); + float intensity[3]; + float brightness[3]; + + // Decode source transfer function + for (uint32_t comp = 0; comp < components; comp++) { + brightness[comp] = (float)(c[comp]) * Color::rcpOne(); + intensity[comp] = decode.decode(brightness[comp]); + } + + // If needed, transform primaries + if (transformPrimaries != nullptr) { + float origIntensity[3] = { intensity[0], intensity[1], intensity[2] }; + for (uint32_t j = 0; j < components; ++j) { + intensity[j] = 0.f; + for (uint32_t k = 0; k < components; ++k) + intensity[j] += transformPrimaries->matrix[j][k] * origIntensity[k]; + } + } + + // Encode destination transfer function + for (uint32_t comp = 0; comp < components; comp++) { + brightness[comp] = encode.encode(intensity[comp]); + // clamp(value, color::min, color::max) is required as static_cast has platform-specific behaviors + // and on certain platforms can over or underflow + c.set(comp, cclamp( + roundf(brightness[comp] * static_cast(Color::one())), + static_cast(Color::min()), + static_cast(Color::max()))); + } + } + return *this; + } + + virtual ImageT& normalize() override { + uint32_t pixelCount = getPixelCount(); + for (uint32_t i = 0; i < pixelCount; ++i) { + Color& c = pixels[i]; + c.normalize(); + } + return *this; + } + + virtual ImageT& swizzle(std::string_view swizzle) override { + assert(swizzle.size() == 4); + for (size_t i = 0; i < getPixelCount(); i++) { + Color srcPixel = pixels[i]; + for (uint32_t c = 0; c < getComponentCount(); c++) { + pixels[i].set(c, swizzlePixel(srcPixel, swizzle[c])); + } + } + return *this; + } + + template + ImageT& copyTo(DstImage& dst, std::string_view swizzle) { + assert(getComponentSize() == dst.getComponentSize()); + assert(width == dst.getWidth() && height == dst.getHeight()); + + dst.setOetf(oetf); + dst.setPrimaries(primaries); + for (size_t i = 0; i < getPixelCount(); i++) { + uint32_t c; + for (c = 0; c < dst.getComponentCount(); c++) { + if (c < getComponentCount()) + dst.pixels[i].set(c, swizzlePixel(pixels[i], swizzle[c])); + else + break; + } + for (; c < dst.getComponentCount(); c++) + if (c < 3) + dst.pixels[i].set(c, componentType{0}); + else + dst.pixels[i].set(c, Color::one()); + } + return *this; + } + + virtual ImageT& copyToR(Image& dst, std::string_view swizzle) override { return copyTo((ImageT&)dst, swizzle); } + virtual ImageT& copyToRG(Image& dst, std::string_view swizzle) override { return copyTo((ImageT&)dst, swizzle); } + virtual ImageT& copyToRGB(Image& dst, std::string_view swizzle) override { return copyTo((ImageT&)dst, swizzle); } + virtual ImageT& copyToRGBA(Image& dst, std::string_view swizzle) override { return copyTo((ImageT&)dst, swizzle); } + + protected: + componentType swizzlePixel(const Color& srcPixel, char swizzle) { + switch (swizzle) { + case 'r': + return srcPixel[0]; + case 'g': + return srcPixel[1]; + case 'b': + return srcPixel[2]; + case 'a': + return srcPixel[3]; + case '0': + return componentType{0}; + case '1': + return Color::one(); + default: + assert(false); + return componentType{0}; + } + } + + Color* pixels; + bool freePixels; +}; + +using r8color = color; +using rg8color = color; +using rgb8color = color; +using rgba8color = color; +using r16color = color; +using rg16color = color; +using rgb16color = color; +using rgba16color = color; +using r32color = color; +using rg32color = color; +using rgb32color = color; +using rgba32color = color; + +using r8scolor = color; +using rg8scolor = color; +using rgb8scolor = color; +using rgba8scolor = color; +using r16scolor = color; +using rg16scolor = color; +using rgb16scolor = color; +using rgba16scolor = color; +using r32scolor = color; +using rg32scolor = color; +using rgb32scolor = color; +using rgba32scolor = color; + +using r32fcolor = color; +using rg32fcolor = color; +using rgb32fcolor = color; +using rgba32fcolor = color; + +using r8image = ImageT; +using rg8image = ImageT; +using rgb8image = ImageT; +using rgba8image = ImageT; +using r16image = ImageT; +using rg16image = ImageT; +using rgb16image = ImageT; +using rgba16image = ImageT; +using r32image = ImageT; +using rg32image = ImageT; +using rgb32image = ImageT; +using rgba32image = ImageT; + +using r8simage = ImageT; +using rg8simage = ImageT; +using rgb8simage = ImageT; +using rgba8simage = ImageT; +using r16simage = ImageT; +using rg16simage = ImageT; +using rgb16simage = ImageT; +using rgba16simage = ImageT; +using r32simage = ImageT; +using rg32simage = ImageT; +using rgb32simage = ImageT; +using rgba32simage = ImageT; + +using r32fimage = ImageT; +using rg32fimage = ImageT; +using rgb32fimage = ImageT; +using rgba32fimage = ImageT; + +#endif /* IMAGE_HPP */ diff --git a/tools/imageio/imageinput.cc b/tools/imageio/imageinput.cc index 5273e60803..3ea5f5ecce 100644 --- a/tools/imageio/imageinput.cc +++ b/tools/imageio/imageinput.cc @@ -13,6 +13,7 @@ //! #include "imageio.h" +#include "imageio_utility.h" #include #include @@ -39,6 +40,7 @@ ImageInput::open(const _tstring& filename, ImageInput::Creator createFunction = nullptr; const _tstring* fn; const _tstring sn("stdin"); + bool doBuffer = true; if (filename.compare("-")) { // Regular file. @@ -72,10 +74,18 @@ ImageInput::open(const _tstring& filename, #if defined(_WIN32) // Set "stdin" to have binary mode. There is no way to this via cin. (void)_setmode( _fileno( stdin ), _O_BINARY ); -#endif + // Windows shells set the FILE_SYNCHRONOUS_IO_NONALERT option when + // creating pipes. Cygwin since 3.4.x does the same thing, a change + // which affects anything dependent on it, e.g. Git for Windows + // (since 2.41.0) and MSYS2. When this option is set, cin.seekg(0) + // erroneously returns success. Always buffer. + doBuffer = true; +#else // Can we seek in this cin? std::cin.seekg(0); - if (std::cin.fail()) { + doBuffer = std::cin.fail(); +#endif + if (doBuffer) { // Can't seek. Buffer stdin. This is a potentially large buffer. // Must avoid copy. If use stack variable for ss, it's streambuf // will also be on the stack and lost after this function exits @@ -149,7 +159,9 @@ ImageInput::open(const _tstring& filename, } } -// Default implementation +/// @brief Open a file for image input. +/// +/// Default implementation for derived classes. void ImageInput::open(const _tstring& filename, ImageSpec& newspec) { close(); // previously opened file. @@ -189,30 +201,57 @@ void ImageInput::open(const _tstring& filename, ImageSpec& newspec) } } -// Default implementation. -// TODO: Consider making ImageSpec param a pointer with nullptr for unknown. +/// @brief Read an entire image into contiguous memory performing conversions +/// to @a format. +/// +/// Default implementation for derived classes. Input channel values are scaled +/// from the input's channelUpper value to the max representable value of the +/// output format (unorm8 or unorm16). +/// +/// Supported conversions are +/// - bit scaling +/// - unorm8<->unorm16 void -ImageInput::readScanline(void* pBuffer, size_t bufferByteCount, +ImageInput::readScanline(void* pBufferOut, size_t bufferByteCount, uint32_t y, uint32_t z, uint32_t subimage, uint32_t miplevel, const FormatDescriptor& format) { - const FormatDescriptor* targetFormat; - if (format.isUnknown()) - targetFormat = &spec().format(); - else - targetFormat = &format; + const auto& targetFormat = format.isUnknown() ? spec().format() : format; + + const auto targetBitLength = targetFormat.largestChannelBitLength(); + const auto requestBits = std::max(imageio::bit_ceil(targetBitLength), 8u); + + if (requestBits != 8 && requestBits != 16) + throw std::runtime_error(fmt::format( + "Requested decode into {}-bit format is not supported.", + requestBits) + ); + + const bool targetL = targetFormat.samples[0].qualifierLinear; + const bool targetE = targetFormat.samples[0].qualifierExponent; + const bool targetS = targetFormat.samples[0].qualifierSigned; + const bool targetF = targetFormat.samples[0].qualifierFloat; + + // Only UNORM requests are supported here. + if (targetE || targetL || targetS || targetF) + throw std::runtime_error(fmt::format( + "Requested format conversion to {}-bit{}{}{}{} is not supported.", + requestBits, + targetL ? " Linear" : "", + targetE ? " Exponent" : "", + targetS ? " Signed" : "", + targetF ? " Float" : "") + ); seekSubimage(subimage, miplevel); - size_t imageByteCount = (targetFormat->channelBitLength() - * spec().imageChannelCount()) / 8; + size_t imageByteCount = (requestBits * spec().imageChannelCount()) / 8; if (imageByteCount < bufferByteCount) throw buffer_too_small(); uint8_t* pNativeBuffer; - // TODO: Check for unsupported conversions. Only rescaling is supported - if (targetFormat->channelBitLength() != spec().format().channelBitLength()) { + if (targetFormat.channelBitLength() != spec().format().channelBitLength()) { if (spec().format().channelBitLength() == 16) { if (nativeBuffer16.size() < spec().imageChannelCount()) nativeBuffer16.resize(spec().imageChannelCount()); @@ -225,34 +264,34 @@ ImageInput::readScanline(void* pBuffer, size_t bufferByteCount, bufferByteCount = nativeBuffer16.size() * sizeof(uint8_t); } } else { - pNativeBuffer = static_cast(pBuffer); + pNativeBuffer = static_cast(pBufferOut); } readNativeScanline(pNativeBuffer, bufferByteCount, y, z, subimage, miplevel); if (reinterpret_cast(pNativeBuffer) == nativeBuffer16.data()) { - rescale((uint8_t*)pBuffer, - static_cast(targetFormat->channelUpper()), + rescale(static_cast(pBufferOut), + static_cast(targetFormat.channelUpper()), nativeBuffer16.data(), static_cast(spec().format().channelUpper()), spec().imageChannelCount()); } else if (pNativeBuffer == nativeBuffer8.data()) { - rescale((uint16_t*)pBuffer, - static_cast(targetFormat->channelUpper()), + rescale(static_cast(pBufferOut), + static_cast(targetFormat.channelUpper()), nativeBuffer8.data(), static_cast(spec().format().channelUpper()), spec().imageChannelCount()); - } else if (targetFormat->channelUpper() != spec().format().channelUpper()) { + } else if (targetFormat.channelUpper() != spec().format().channelUpper()) { if (spec().format().channelBitLength() == 16) { - rescale(static_cast(pBuffer), - static_cast(targetFormat->channelUpper()), - static_cast(pBuffer), + rescale(static_cast(pBufferOut), + static_cast(targetFormat.channelUpper()), + static_cast(pBufferOut), static_cast(spec().format().channelUpper()), spec().imageChannelCount()); } else { - rescale(static_cast(pBuffer), - static_cast(targetFormat->channelUpper()), - static_cast(pBuffer), + rescale(static_cast(pBufferOut), + static_cast(targetFormat.channelUpper()), + static_cast(pBufferOut), static_cast(spec().format().channelUpper()), spec().imageChannelCount()); } @@ -260,7 +299,12 @@ ImageInput::readScanline(void* pBuffer, size_t bufferByteCount, } -// Default implementation +/// @brief Read an entire image into contiguous memory performing conversions +/// to @a format. +/// +/// Default implementation for derived classes. +/// +/// @sa readScanline() for support conversions. void ImageInput::readImage(void* pBuffer, size_t bufferByteCount, uint32_t subimage, uint32_t miplevel, diff --git a/tools/imageio/imageio.cc b/tools/imageio/imageio.cc index f522fd62d8..09c569875e 100644 --- a/tools/imageio/imageio.cc +++ b/tools/imageio/imageio.cc @@ -12,6 +12,8 @@ //! @brief Create plugin maps. //! +#include "imageio.h" + #include #include #include @@ -24,8 +26,6 @@ #include -#include "imageio.h" - #define PLUGENTRY(name) \ ImageInput* name##InputCreate(); \ ImageOutput* name##OutputCreate(); \ diff --git a/tools/imageio/imageio.h b/tools/imageio/imageio.h index d5d9ea0886..573f3904cf 100644 --- a/tools/imageio/imageio.h +++ b/tools/imageio/imageio.h @@ -379,36 +379,37 @@ class ImageInput { } /// Read an entire image into contiguous memory performing conversions to - /// @a targetFormat. + /// @a requestFormat. + /// + /// @TODO @a requestFormat allows callers to request almost unlimited + /// possible conversions compared to the original format. The current + /// plug-ins only provide a handful of conversions and those available + /// vary by plug-in. Plug-ins must throw an exception when an + /// unsupported conversion is requested. As a work in progress this is + /// okay but we need to rationalize all this such as + /// + /// 1. a subset of all possible conversions supported by every plug-in + /// 2. conversion-specific exceptions so caller can tell what didn't work. + /// + /// Commonly supported transformations are bit scaling and changing the + /// channel count, both adding and removing channels. See the derived + /// classes for the specific coversions supported. /// - // @TODO @a targetFormat allows callers to request almost unlimited - // possible conversions compared to the original format. The current - // plug-ins only provide a handful of conversions and those available - // vary by plug-in. As a work in progress this is okay but we need to - // rationalize all this such as - // - // 1. a subset of all possible conversions supported by every plug-in - // 2. a way to query the available conversions and specify one or more. - // - // Currently supported transformations are: - // @b Npbm can do bits/pixel rescaling, including 16- to 8-bit - // conversions. - // @b png can also do bits/pixel rescaling, including 16- to 8-bit. - // @b Jpeg can convert the number of components. This is not used by - // callers as, for historic reasons, this conversion is provided by - // the upper level Image class. virtual void readImage(void* buffer, size_t bufferByteCount, uint32_t subimage = 0, uint32_t miplevel = 0, - const FormatDescriptor& targetFormat = FormatDescriptor()); + const FormatDescriptor& requestFormat = FormatDescriptor()); - /// Read a scanline into contiguous performing conversions to - /// @a targetFormat. + /// @brief Read a scanline into contiguous memory performing conversions to + /// @a requestFormat. + /// + /// Supported conversions in the default implementation are uint->uint for + /// 8- & 16-bit values. /// - /// @sa See readImage for information about handling of targetFormat. + /// @sa See readImage for information about handling of requestFormat. virtual void readScanline(void* buffer, size_t bufferByteCount, uint32_t y, uint32_t z, uint32_t subimage, uint32_t miplevel, - const FormatDescriptor& targetFormat = FormatDescriptor()); + const FormatDescriptor& requestFormat = FormatDescriptor()); /// Read a single scanline (all channels) of native data into contiguous /// memory. virtual void readNativeScanline(void* buffer, size_t bufferByteCount, diff --git a/tools/imageio/imageio_utility.h b/tools/imageio/imageio_utility.h new file mode 100644 index 0000000000..86f9a61295 --- /dev/null +++ b/tools/imageio/imageio_utility.h @@ -0,0 +1,290 @@ +// Copyright 2022-2023 The Khronos Group Inc. +// Copyright 2022-2023 RasterGrid Kft. +// SPDX-License-Identifier: Apache-2.0 + + +#pragma once +#include +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4201) +#endif +#include +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +// ----------------------------------------------------------------------------- + +namespace imageio { + +// C++20 - std::bit_cast +template +[[nodiscard]] constexpr inline To bit_cast(const From& src) noexcept { + static_assert(sizeof(To) == sizeof(From)); + static_assert(std::is_trivially_copyable_v); + static_assert(std::is_trivially_copyable_v); + static_assert(std::is_trivially_constructible_v); + To dst; + std::memcpy(&dst, &src, sizeof(To)); + return dst; +} + +// C++20 - std::bit_ceil +template +[[nodiscard]] constexpr inline T bit_ceil(T x) noexcept { + x -= 1; + for (uint32_t i = 0; i < sizeof(x) * 8; ++i) + if (1u << i > x) + return 1u << i; + return 0; +} + +// --- Half utilities ---------------------------------------------------------- +// Based on https://gist.github.com/rygorous/eb3a019b99fdaa9c3064 + +union FP32 { + uint32_t u; + float f; + struct P { + uint32_t Mantissa : 23; + uint32_t Exponent : 8; + uint32_t Sign : 1; + } p; +}; + +union FP16 { + uint16_t u; + struct P { + uint16_t Mantissa : 10; + uint16_t Exponent : 5; + uint16_t Sign : 1; + } p; +}; + +inline float half_to_float(uint16_t value) { + FP16 h; + h.u = value; + static const FP32 magic = {113 << 23}; + static const uint32_t shifted_exp = 0x7c00 << 13; // exponent mask after shift + FP32 o; + + o.u = (h.u & 0x7fff) << 13; // exponent/mantissa bits + uint32_t exp = shifted_exp & o.u; // just the exponent + o.u += (127 - 15) << 23; // exponent adjust + + // handle exponent special cases + if (exp == shifted_exp) // Inf/NaN? + o.u += (128 - 16) << 23; // extra exp adjust + else if (exp == 0) { // Zero/Denormal? + o.u += 1 << 23; // extra exp adjust + o.f -= magic.f; // renormalize + } + + o.u |= (h.u & 0x8000) << 16; // sign bit + return o.f; +} + +inline uint16_t float_to_half(float value) { + FP32 f; + f.f = value; + FP16 o = {0}; + + // Based on ISPC reference code (with minor modifications) + if (f.p.Exponent == 0) // Signed zero/denormal (which will underflow) + o.p.Exponent = 0; + else if (f.p.Exponent == 255) { // Inf or NaN (all exponent bits set) + o.p.Exponent = 31; + o.p.Mantissa = f.p.Mantissa ? 0x200 : 0; // NaN->qNaN and Inf->Inf + } else { // Normalized number + // Exponent unbias the single, then bias the halfp + int newexp = f.p.Exponent - 127 + 15; + if (newexp >= 31) // Overflow, return signed infinity + o.p.Exponent = 31; + else if (newexp <= 0) { // Underflow + if ((14 - newexp) <= 24) { // Mantissa might be non-zero + uint32_t mant = f.p.Mantissa | 0x800000; // Hidden 1 bit + o.p.Mantissa = mant >> (14 - newexp); + if ((mant >> (13 - newexp)) & 1) // Check for rounding + o.u++; // Round, might overflow into exp bit, but this is OK + } + } else { + o.p.Exponent = newexp; + o.p.Mantissa = f.p.Mantissa >> 13; + if (f.p.Mantissa & 0x1000) // Check for rounding + o.u++; // Round, might overflow to inf, this is OK + } + } + + o.p.Sign = f.p.Sign; + return o.u; +} + +// ----------------------------------------------------------------------------- + +template +[[nodiscard]] constexpr inline T extract_bits(const void* data, uint32_t offset, uint32_t numBits) { + assert(numBits <= sizeof(T) * 8); + + const auto* source = static_cast(data); + std::array target{0}; + + for (uint32_t i = 0; i < numBits; ++i) { + const auto sourceBitIndex = offset + i; + const auto sourceByteIndex = sourceBitIndex / 8; + const auto sourceBitSubByteIndex = sourceBitIndex % 8; + const auto sourceBitSubByteMask = 1u << sourceBitSubByteIndex; + const auto sourceBitValue = (source[sourceByteIndex] & sourceBitSubByteMask) != 0; + const auto targetBitIndex = i; + const auto targetByteIndex = targetBitIndex / 8; + const auto targetBitSubByteIndex = targetBitIndex % 8; + target[targetByteIndex] |= sourceBitValue ? 1u << targetBitSubByteIndex : 0u; + } + + return bit_cast(target); +} + +[[nodiscard]] inline uint32_t convertFloatToUNORM(float value, uint32_t numBits) { + assert(numBits > 0 && numBits <= 32); + if (std::isnan(value)) + return 0; + if (value < 0.f) + return 0; + if (value > 1.f) + return (1u << numBits) - 1u; + return static_cast(value * static_cast((1u << numBits) - 1u) + 0.5f); +} + +[[nodiscard]] inline float convertSFloatToFloat(uint32_t rawBits, uint32_t numBits) { + assert(numBits == 16 || numBits == 32); + if (numBits == 16) + return half_to_float(static_cast(rawBits)); + if (numBits == 32) + return bit_cast(rawBits); + return 0; +} +[[nodiscard]] inline float convertUFloatToFloat(uint32_t rawBits, uint32_t numBits) { + assert(numBits == 10 || numBits == 11); + if (numBits == 10) + return glm::detail::packed10bitToFloat(rawBits); + else if (numBits == 11) + return glm::detail::packed11bitToFloat(rawBits); + return 0; +} +[[nodiscard]] inline float convertSIntToFloat(uint32_t rawBits, uint32_t numBits) { + assert(numBits > 0 && numBits <= 32); + const auto signBit = (rawBits & 1u << (numBits - 1)) != 0; + const auto valueBits = rawBits & ~(1u << (numBits - 1)); + const auto signedValue = static_cast(valueBits) * (signBit ? -1 : 1); + return static_cast(signedValue); +} +[[nodiscard]] inline float convertUIntToFloat(uint32_t rawBits, uint32_t numBits) { + assert(numBits > 0 && numBits <= 32); (void) numBits; + return static_cast(rawBits); +} +[[nodiscard]] inline float convertSNORMToFloat(uint32_t rawBits, uint32_t numBits) { + assert(numBits > 0 && numBits <= 32); + (void) rawBits; + (void) numBits; + assert(false && "Not yet implemented"); + return 0; +} +[[nodiscard]] inline float convertUNORMToFloat(uint32_t rawBits, uint32_t numBits) { + assert(numBits > 0 && numBits <= 32); + const auto upper = static_cast((1u << numBits) - 1u); + return static_cast(rawBits) / upper; +} +[[nodiscard]] inline uint32_t convertSFloatToUInt(uint32_t rawBits, uint32_t numBits) { + assert(numBits == 16 || numBits == 32); + if (numBits == 16) + return static_cast(half_to_float(static_cast(rawBits))); + if (numBits == 32) + return static_cast(bit_cast(rawBits)); + return 0; +} +[[nodiscard]] inline uint32_t convertUFloatToUInt(uint32_t rawBits, uint32_t numBits) { + assert(numBits == 10 || numBits == 11 || numBits == 14); + (void) rawBits; + (void) numBits; + assert(false && "Not yet implemented"); + return 0; +} +[[nodiscard]] inline uint32_t convertSIntToUInt(uint32_t rawBits, uint32_t numBits) { + assert(numBits > 0 && numBits <= 32); + (void) rawBits; + (void) numBits; + assert(false && "Not yet implemented"); + return 0; +} +[[nodiscard]] inline uint32_t convertUIntToUInt(uint32_t rawBits, uint32_t numBits) { + assert(numBits > 0 && numBits <= 32); + (void) numBits; + return rawBits; +} + +[[nodiscard]] constexpr inline uint32_t convertUNORM(uint32_t rawBits, uint32_t sourceBits, uint32_t targetBits) noexcept { + assert(sourceBits > 0 && sourceBits <= 32); + assert(targetBits > 0 && targetBits <= 32); + + rawBits &= (1u << sourceBits) - 1u; + if (targetBits == sourceBits) { + return rawBits; + } else if (targetBits >= sourceBits) { + // Upscale with "left bit replication" to fill in the least significant bits + uint64_t result = 0; + for (uint32_t i = 0; i < targetBits; i += sourceBits) + result |= static_cast(rawBits) << (targetBits - i) >> sourceBits; + + return static_cast(result); + } else { + // Downscale with rounding: Check the most significant bit that was dropped: 1 -> up, 0 -> down + const auto msDroppedBitIndex = sourceBits - targetBits - 1u; + const auto msDroppedBitValue = rawBits & (1u << msDroppedBitIndex); + if (msDroppedBitValue) + // Min stops the 'overflow' if every targetBit is saturated and we would round up + return std::min((rawBits >> (sourceBits - targetBits)) + 1u, (1u << targetBits) - 1u); + else + return rawBits >> (sourceBits - targetBits); + } +} + +[[nodiscard]] constexpr inline uint32_t convertUINT(uint32_t rawBits, uint32_t sourceBits, uint32_t targetBits) noexcept { + assert(sourceBits > 0 && sourceBits <= 32); + assert(targetBits > 0 && targetBits <= 32); + + const auto targetValueMask = targetBits == 32 ? std::numeric_limits::max() : (1u << targetBits) - 1u; + const auto sourceValueMask = sourceBits == 32 ? std::numeric_limits::max() : (1u << sourceBits) - 1u; + + rawBits &= sourceValueMask; + if (targetBits < sourceBits) + rawBits &= targetValueMask; + + return rawBits; +} + +[[nodiscard]] constexpr inline uint32_t convertSINT(uint32_t rawBits, uint32_t sourceBits, uint32_t targetBits) noexcept { + assert(sourceBits > 1 && sourceBits <= 32); + assert(targetBits > 1 && targetBits <= 32); + + const auto sourceSignBitIndex = sourceBits - 1u; + const auto sourceSignMask = 1u << sourceSignBitIndex; + const auto sign = (rawBits & sourceSignMask) != 0; + const auto sourceValueBits = sourceBits - 1u; + const auto sourceValueMask = (1u << sourceValueBits) - 1u; + const auto sourceValue = rawBits & sourceValueMask; + const auto targetSignBitIndex = targetBits - 1u; + const auto targetValueBits = targetBits - 1u; + const auto targetValueMask = (1u << targetValueBits) - 1u; + + uint32_t result = 0; + result |= (sign ? 1u : 0u) << targetSignBitIndex; + + if (targetBits < sourceBits) + result |= sourceValue & targetValueMask; + else + result |= sourceValue; + + return result; +} + +} // namespace imageio diff --git a/tools/imageio/imageoutput.cc b/tools/imageio/imageoutput.cc index 7d843dc6eb..8081ab2ad7 100644 --- a/tools/imageio/imageoutput.cc +++ b/tools/imageio/imageoutput.cc @@ -12,6 +12,8 @@ //! @brief ImageOutput class implementation //! +#include "imageio.h" + #include #include #include @@ -24,8 +26,6 @@ #include -#include "imageio.h" - std::unique_ptr ImageOutput::create(const _tstring& filename) { diff --git a/tools/imageio/jpg.imageio/jpginput.cc b/tools/imageio/jpg.imageio/jpginput.cc index 8adfcfe3f9..c08ee04f90 100644 --- a/tools/imageio/jpg.imageio/jpginput.cc +++ b/tools/imageio/jpg.imageio/jpginput.cc @@ -20,13 +20,12 @@ * @author Mark Callow. */ -#include "stdafx.h" +#include "imageio.h" #include #include #include -#include "imageio.h" #include "encoder/jpgd.h" using namespace jpgd; @@ -222,20 +221,43 @@ JpegInput::readHeader() } +/// @brief Read an image scanline into contiguous memory performing conversions +/// to @a format. +/// +/// Supported conversions are +/// - changing channel count +/// - [GREY,RGB]->[GREY,RGB,RGBA] +/// When reducing to 1 channel it calculates luma for GREY from R,G & B. +/// When increasing from 1 it makes a luminance texture, R=G=B=GREY. +/// ALPHA is set to 1.0 when converting to 4 channels. +/// 2- and 4-channel inputs are not supported. void JpegInput::readScanline(void* bufferOut, size_t bufferByteCount, uint y, uint, uint, uint, const FormatDescriptor& format) { - const FormatDescriptor* targetFormat; - - if (format.isUnknown()) - targetFormat = &spec().format(); - else - targetFormat = &format; - - if (targetFormat->channelBitLength(KHR_DF_CHANNEL_RGBSDA_R) != 8) - throw std::runtime_error("Unsupported format conversion requested."); + const auto& targetFormat = format.isUnknown() ? spec().format() : format; + const auto requestBits = targetFormat.largestChannelBitLength(); + + if (requestBits != 8) + throw std::runtime_error(fmt::format( + "Requested decode into {}-bit format is not supported.", + requestBits)); + + const bool targetL = targetFormat.samples[0].qualifierLinear; + const bool targetE = targetFormat.samples[0].qualifierExponent; + const bool targetS = targetFormat.samples[0].qualifierSigned; + const bool targetF = targetFormat.samples[0].qualifierFloat; + + // Only UNORM requests are allowed for JPEG inputs + if (targetE || targetL || targetS || targetF) + throw std::runtime_error(fmt::format( + "Requested format conversion to {}-bit{}{}{}{} is not supported.", + requestBits, + targetL ? " Linear" : "", + targetE ? " Exponent" : "", + targetS ? " Signed" : "", + targetF ? " Float" : "")); if (y >= spec().height()) y = spec().height() - 1; @@ -262,18 +284,26 @@ JpegInput::readScanline(void* bufferOut, size_t bufferByteCount, uint y, uint, ); } - uint32_t targetChannelCount = targetFormat->extended.channelCount; + const auto targetChannelCount = targetFormat.extended.channelCount; + + if (targetChannelCount == 2) + throw std::runtime_error(fmt::format( + "Requested decode into 2 channels is not supported.") + ); - // decode() returns a bufferOut of 1 (for grayscale) or 4 components. uint8_t* pDst = static_cast(bufferOut); - uint32_t imageChannelCount = spec().format().extended.channelCount; - if ((targetChannelCount == 1 && imageChannelCount == 1) - || (targetChannelCount == 4 && imageChannelCount == 4)) + uint32_t inputChannelCount = spec().format().extended.channelCount; + // decode() returns a bufferOut of 1 channel (for grayscale input) + // or 4 channels. Despite this decode() does not support 4 channel + // inputs. Nor does it support 2 channel inputs. + // TODO: Extend the following when decode supports 2- and 4-channel inputs. + if ((targetChannelCount == 1 && inputChannelCount == 1) + || (targetChannelCount == 4 && inputChannelCount == 3)) { if (bufferByteCount < scanlineByteCount) throw buffer_too_small(); memcpy(bufferOut, pScanline, scanlineByteCount); - } else if (imageChannelCount == 1) { + } else if (inputChannelCount == 1) { if (targetChannelCount == 3) { if (bufferByteCount < scanlineByteCount * 3) throw buffer_too_small(); @@ -284,7 +314,7 @@ JpegInput::readScanline(void* bufferOut, size_t bufferByteCount, uint y, uint, pDst[2] = luma; pDst += 3; } - } else { + } else { // targetChannelCount = 4 if (bufferByteCount < scanlineByteCount * 4) throw buffer_too_small(); for (uint x = 0; x < spec().width(); x++) @@ -297,7 +327,7 @@ JpegInput::readScanline(void* bufferOut, size_t bufferByteCount, uint y, uint, pDst += 4; } } - } else if (imageChannelCount == 3) { + } else if (inputChannelCount == 3) { if (targetChannelCount == 1) { if (bufferByteCount < spec().width()) throw buffer_too_small(); @@ -308,7 +338,7 @@ JpegInput::readScanline(void* bufferOut, size_t bufferByteCount, uint y, uint, int b = pScanline[x * 4 + 2]; *pDst++ = static_cast((r * YR + g * YG + b * YB + 32768) >> 16); } - } else { + } else { //targetChannelCount = 3 if (bufferByteCount < spec().width() * 3) throw buffer_too_small(); for (uint x = 0; x < spec().width(); x++) { @@ -324,6 +354,10 @@ JpegInput::readScanline(void* bufferOut, size_t bufferByteCount, uint y, uint, } +/// @brief Read an entire image into contiguous memory performing conversions +/// to @a format. +/// +/// @sa readScanline() for supported conversions void JpegInput::readImage(void* bufferOut, size_t bufferByteCount, uint subimage, uint miplevel, const FormatDescriptor& format) diff --git a/tools/imageio/npbm.imageio/npbminput.cc b/tools/imageio/npbm.imageio/npbminput.cc index 34f0bbf43e..536d43234c 100644 --- a/tools/imageio/npbm.imageio/npbminput.cc +++ b/tools/imageio/npbm.imageio/npbminput.cc @@ -415,6 +415,10 @@ void NpbmInput::parseGPHeader(filetype ftype) } +/// @brief Read an entire image into contiguous memory performing conversions +/// to @a requestFormat. +/// +/// @sa ImageInput::readScanline() for supported conversions. void NpbmInput::readImage(void* pBuffer, size_t bufferByteCount, uint32_t subimage, uint32_t miplevel, diff --git a/tools/imageio/png.imageio/pnginput.cc b/tools/imageio/png.imageio/pnginput.cc index c73c286da0..b3161d2d68 100644 --- a/tools/imageio/png.imageio/pnginput.cc +++ b/tools/imageio/png.imageio/pnginput.cc @@ -14,14 +14,14 @@ * @author Mark Callow */ -#include "stdafx.h" +#include "imageio.h" #include #include #include #include -#include "imageio.h" +#include "imageio_utility.h" #include "lodepng.h" #include #include "dfd.h" @@ -93,8 +93,8 @@ void PngInput::slurp() void PngInput::readHeader() { - // Unfortunately LoadPNG doesn't believe in stdio. The functions we - // need either read from memory or take a file name. To avoid + // Unfortunately LoadPNG doesn't believe in stdio. The functions + // we need either read from memory or take a file name. To avoid // a potentially unnecessary slurp of the whole file check the // signature ourselves. uint8_t pngsig[8] = { @@ -283,46 +283,25 @@ PngInput::readHeader() } } -// TODO Tools P5: Lift bit_ceil function into a common header where both ktx tools and imageio can access it -// C++20 - std::bit_ceil -template -[[nodiscard]] static constexpr inline T bit_ceil(T x) noexcept { - x -= 1; - for (uint32_t i = 0; i < sizeof(x) * 8; ++i) - if (1u << i > x) - return 1u << i; - return 0; -} - -// TODO Tools P5: Lift convertUNORM function into a common header where both ktx tools and imageio can access it -[[nodiscard]] constexpr inline uint32_t convertUNORM(uint32_t value, uint32_t sourceBits, uint32_t targetBits) noexcept { - assert(sourceBits != 0); - assert(targetBits != 0); - - value &= (1u << sourceBits) - 1u; - if (targetBits == sourceBits) { - return value; - } else if (targetBits >= sourceBits) { - // Upscale with "left bit replication" to fill in the least significant bits - uint64_t result = 0; - for (uint32_t i = 0; i < targetBits; i += sourceBits) - result |= static_cast(value) << (targetBits - i) >> sourceBits; - - return static_cast(result); - } else { - // Downscale with rounding: Check the most significant bit that was dropped: 1 -> up, 0 -> down - const auto msDroppedBitIndex = sourceBits - targetBits - 1u; - const auto msDroppedBitValue = value & (1u << msDroppedBitIndex); - if (msDroppedBitValue) - // Min stops the 'overflow' if every targetBit is saturated and we would round up - return std::min((value >> (sourceBits - targetBits)) + 1u, (1u << targetBits) - 1u); - else - return value >> (sourceBits - targetBits); - } -} +/// @brief Read an entire image into contiguous memory performing conversions +/// to @a format. +/// +/// Supported conversions are +/// - bit scaling +/// - unorm\<=8->[unorm8,unorm16] +/// - unorm8<->unorm16 +/// - changing channel count +/// - [GREY,GREY_ALPHA,RGB,RGBA]->[GREY,GREY_ALPHA,RGB,RGBA] +/// When reducing to 1 or 2 channels it takes the R channel for GREY. +/// When increasing from 1 or 2 channels it makes a luminance texture, +/// R=G=B=GREY. ALPHA goes to A and vice versa. If none in the source, +/// 1.0 is used. +/// +/// If the PNG file has an sBit chunk the normalized results are adjusted +/// accordingly. void -PngInput::readImage(void* bufferOut, size_t bufferOutByteCount, +PngInput::readImage(void* bufferOut, size_t bufferOutByteCount, uint32_t /*subimage*/, uint32_t /*miplevel*/, const FormatDescriptor& format) { @@ -332,11 +311,13 @@ PngInput::readImage(void* bufferOut, size_t bufferOutByteCount, const auto height = spec().height(); const auto width = spec().width(); const auto targetBitLength = targetFormat.largestChannelBitLength(); - const auto requestBits = std::max(bit_ceil(targetBitLength), 8u); + const auto requestBits = std::max(imageio::bit_ceil(targetBitLength), 8u); if (requestBits != 8 && requestBits != 16) throw std::runtime_error(fmt::format( - "PNG decode error: Requested decode into {} bit format is not supported.", requestBits)); + "PNG decode error: Requested decode into {}-bit format is not supported.", + requestBits) + ); const bool targetL = targetFormat.samples[0].qualifierLinear; const bool targetE = targetFormat.samples[0].qualifierExponent; @@ -351,7 +332,8 @@ PngInput::readImage(void* bufferOut, size_t bufferOutByteCount, targetL ? " Linear" : "", targetE ? " Exponent" : "", targetS ? " Signed" : "", - targetF ? " Float" : "")); + targetF ? " Float" : "") + ); state.info_raw.bitdepth = requestBits; state.info_raw.colortype = [&]{ @@ -366,7 +348,9 @@ PngInput::readImage(void* bufferOut, size_t bufferOutByteCount, return LCT_RGBA; } throw std::runtime_error(fmt::format( - "PNG decode error: Requested decode into {} channel is not supported.", targetFormat.channelCount())); + "PNG decode error: Requested decode into {} channels is not supported.", + targetFormat.channelCount()) + ); }(); auto lodepngError = lodepng_finish_decode( (unsigned char*)bufferOut, @@ -406,10 +390,10 @@ PngInput::readImage(void* bufferOut, size_t bufferOutByteCount, const auto index = y * width * channelCount + x * channelCount + c; if (requestBits == 8) { auto& value = *(reinterpret_cast(bufferOut) + index); - value = static_cast(convertUNORM(value >> (8 - sBits[c]), sBits[c], 8)); + value = static_cast(imageio::convertUNORM(value >> (8 - sBits[c]), sBits[c], 8)); } else { // requestBits == 16 auto& value = *(reinterpret_cast(bufferOut) + index); - value = static_cast(convertUNORM(value >> (16 - sBits[c]), sBits[c], 16)); + value = static_cast(imageio::convertUNORM(value >> (16 - sBits[c]), sBits[c], 16)); } } } diff --git a/tools/imageio/png.imageio/pngoutput.cc b/tools/imageio/png.imageio/pngoutput.cc index 2ec296a788..c0b7523859 100644 --- a/tools/imageio/png.imageio/pngoutput.cc +++ b/tools/imageio/png.imageio/pngoutput.cc @@ -14,13 +14,12 @@ * @author Mark Callow */ -#include "stdafx.h" +#include "imageio.h" #include #include #include -#include "imageio.h" #include "lodepng.h" #include #include "dfd.h" diff --git a/tools/ktx2check/ktx2check.cpp b/tools/ktx2check/ktx2check.cpp index 77d0a42a85..ea4e5ae55b 100644 --- a/tools/ktx2check/ktx2check.cpp +++ b/tools/ktx2check/ktx2check.cpp @@ -132,6 +132,9 @@ struct issue { }; #define WARNING 0x00010000 +#if defined(ERROR) // windows.h defines this and is included by ktxapp.h. + #undef ERROR +#endif #define ERROR 0x00100000 #define FATAL 0x01000000 @@ -1104,7 +1107,6 @@ ktxValidator::usage() ktxApp::usage(); } - int _tmain(int argc, _TCHAR* argv[]) { @@ -1147,17 +1149,27 @@ ktxValidator::validateFile(const _tstring& filename) // of this method. ifstream ifs; stringstream buffer; + bool doBuffer; if (filename.compare(_T("-")) == 0) { #if defined(_WIN32) /* Set "stdin" to have binary mode */ (void)_setmode( _fileno( stdin ), _O_BINARY ); -#endif + // Windows shells set the FILE_SYNCHRONOUS_IO_NONALERT option when + // creating pipes. Cygwin since 3.4.x does the same thing, a change + // which affects anything dependent on it, e.g. Git for Windows + // (since 2.41.0) and MSYS2. When this option is set, cin.seekg(0) + // erroneously returns success. Always buffer. + doBuffer = true; +#else // Can we seek in this cin? cin.seekg(0); - if (cin.fail()) { + doBuffer = cin.fail(); +#endif + if (doBuffer) { // Read entire file into a stringstream so we can seek. - buffer << std::cin.rdbuf(); + buffer << cin.rdbuf(); + buffer.seekg(0, ios::beg); isp = &buffer; } else { isp = &cin; diff --git a/tools/ktxsc/ktxsc.cpp b/tools/ktxsc/ktxsc.cpp index 3c306f020d..6538107262 100644 --- a/tools/ktxsc/ktxsc.cpp +++ b/tools/ktxsc/ktxsc.cpp @@ -4,14 +4,7 @@ // Copyright 2019-2020 Mark Callow // SPDX-License-Identifier: Apache-2.0 -#if defined(_WIN32) - // must appear before "scapp.h" for error-free mingw/gcc11 build. - // _CRT_SECURE_NO_WARNINGS must be defined before and - // so we can't rely on the definition included by "scapp.h". - #define _CRT_SECURE_NO_WARNINGS - #define WINDOWS_LEAN_AND_MEAN - #include -#endif +#include "scapp.h" #include #include @@ -22,7 +15,6 @@ #include #include "argparser.h" -#include "scapp.h" #include "version.h" #if defined(_MSC_VER) diff --git a/tools/toktx/CMakeLists.txt b/tools/toktx/CMakeLists.txt index 438a91c3eb..ddd32ac8af 100644 --- a/tools/toktx/CMakeLists.txt +++ b/tools/toktx/CMakeLists.txt @@ -2,7 +2,6 @@ # SPDX-License-Identifier: Apache-2.0 add_executable( toktx - image.hpp toktx.cc ) @@ -12,12 +11,18 @@ target_include_directories( toktx PRIVATE . - $ + $ $ $ +) + +target_include_directories( + toktx + SYSTEM +PRIVATE + $ ${PROJECT_SOURCE_DIR}/lib ${PROJECT_SOURCE_DIR}/lib/dfdutils - ${PROJECT_SOURCE_DIR}/other_include ) target_link_libraries( @@ -27,6 +32,13 @@ target_link_libraries( objUtil ) +set_target_properties( + toktx + PROPERTIES + CXX_STANDARD 17 + CXX_STANDARD_REQUIRED YES +) + target_compile_definitions( toktx PRIVATE diff --git a/tools/toktx/image.hpp b/tools/toktx/image.hpp deleted file mode 100644 index be883d9a87..0000000000 --- a/tools/toktx/image.hpp +++ /dev/null @@ -1,855 +0,0 @@ -// -*- tab-width: 4; -*- -// vi: set sw=2 ts=4 expandtab: - -// Copyright 2010-2020 The Khronos Group Inc. -// SPDX-License-Identifier: Apache-2.0 - -//! -//! @internal -//! @~English -//! @file image.hpp -//! -//! @brief Internal Image class -//! - -#ifndef IMAGE_HPP -#define IMAGE_HPP - -#include -#include -#include -#include -#include - -#include "argparser.h" -#include "unused.h" -#include "encoder/basisu_resampler.h" -#include "encoder/basisu_resampler_filters.h" - -typedef float (*OETFFunc)(float const, float const); - -// cclamp to avoid conflict in toktx.cc with clamp template defined in scApp. -template inline T cclamp(T value, T low, T high) { - return (value < low) ? low : ((value > high) ? high : value); -} -template inline T saturate(T value) { - return cclamp(value, 0, 1.0f); -} - -template inline S maximum(S a, S b) { return (a > b) ? a : b; } -template inline S minimum(S a, S b) { return (a < b) ? a : b; } - -#if defined(_MSC_VER) -#define INLINE __inline -#else -#define INLINE __inline__ -#endif - -static INLINE float -encode_bt709(float const intensity, float const) { - /* We're following what Netpbm does. This is their comment and code. */ - - /* Here are parameters of the gamma transfer function for the Netpbm - formats. This is ITU-R Recommendation BT.709, FKA CIE Rec 709. It is - also ITU-R Recommendation BT.601, FKA CCIR 601. - - This transfer function is linear for sample values 0 .. .018 - and an exponential for larger sample values. - The exponential is slightly stretched and translated, though, - unlike the popular pure exponential gamma transfer function. - - The standard actually defines the linear expansion as 4.500, which - means there is a discontinuity at linear intensity .018. We instead - use ~4.514 to make a continuous function. This may have been simply - a mistake when this code was written or based on an actual benefit - to having a continuous function -- The history is not clear. - - Note that the discrepancy is below the precision of a maxval 255 - image. - */ - float const eoGamma = 2.2f; - float const oeGamma = 1.0f / eoGamma; - float const linearCutoff = 0.018f; - float const linearExpansion = - (1.099f * pow(linearCutoff, oeGamma) - 0.099f) / linearCutoff; - - float brightness; - - if (intensity < linearCutoff) - brightness = intensity * linearExpansion; - else - brightness = 1.099f * pow(intensity, oeGamma) - 0.099f; - - return brightness; -} - -static INLINE float -decode_bt709(float const brightness, float const) -{ - float const eoGamma = 2.2f; - float const oeGamma = 1.0f / eoGamma; - float const linearCutoff = 0.018f; - float const linearExpansion = - (1.099f * pow(linearCutoff, oeGamma) - 0.099f) / linearCutoff; - - float intensity; - - if (brightness < linearCutoff * linearExpansion) - intensity = brightness / linearExpansion; - else - intensity = pow((brightness + 0.099f) / 1.099f, eoGamma); - - return intensity; -} - -// These are called from resample as well as decode, hence the default -// parameter values. -static INLINE float -encode_sRGB(float const intensity, float const unused = 1.0f) -{ - UNUSED(unused); - float brightness; - - if (intensity < 0.0031308f) - brightness = 12.92f * intensity; - else - brightness = 1.055f * pow(intensity, 1.0f/2.4f) - 0.055f; - - return brightness; -} - -static INLINE float -decode_sRGB(float const brightness, float const unused = 1.0f) -{ - UNUSED(unused); - float intensity; - - if (brightness < .04045f) - intensity = saturate(brightness * (1.0f/12.92f)); - else - intensity = saturate(powf((brightness + .055f) * (1.0f/1.055f), 2.4f)); - - return intensity; -} - -static INLINE float -// gamma must be the inverse of the exponent that was used -// when encoding to avoid a division per call. -decode_gamma(float const brightness, float const eoGamma) -{ - return saturate(powf(brightness, eoGamma)); -} - -static INLINE float -decode_linear(float const brightness, float const) -{ - return brightness; -} - -static INLINE float -encode_linear(float const intensity, float const) -{ - return intensity; -} - -template -class color_base { -public: - static uint32_t getComponentCount() { return componentCount; } - static uint32_t getComponentSize() { return sizeof(componentType); } - static uint32_t getPixelSize() { - return componentCount * sizeof(componentType); - } - static uint32_t one() { return ((1 << sizeof(componentType) * 8) - 1); } -}; - -struct vec3_base { - float r; - float g; - float b; - vec3_base() : r(0.0f), g(0.0f), b(0.0f) {} - vec3_base(float _r, float _g, float _b) : r(_r), g(_g), b(_b) {} - void base_normalize() { - float len = r * r + g * g + b * b; - len = sqrtf(len); - if (len > 0.0f) - { - r /= len; - g /= len; - b /= len; - } - } - void clamp(float _a, float _b) { - r = cclamp(r, _a, _b); - g = cclamp(g, _a, _b); - b = cclamp(b, _a, _b); - } -}; - -static constexpr float gc_m[5]={0.0f, 128.0f, 32768.0f, 0.0f, 2147483648.0f}; -static constexpr uint32_t gc_s[5]={0, 255, 65535, 0, 4294967295}; - -template -struct vec3 : public vec3_base { - static constexpr uint32_t i = sizeof(componentType); - - explicit vec3(float _r) : vec3_base(_r, 0.0f, 0.0f) {} - vec3(float _r, float _g) : vec3_base(_r, _g, 0.0f) {} - vec3(float _r, float _g, float _b) : vec3_base(_r, _g, _b) {} - void normalize() { - // Zero normals in range [-1, 1] can't be normalized - if (gc_m[i] == r && gc_m[i] == g && gc_m[i] == b) { - return; - } else { - r = (float)(r / (double)gc_s[i]) * 2.0f - 1.0f; - g = (float)(g / (double)gc_s[i]) * 2.0f - 1.0f; - b = (float)(b / (double)gc_s[i]) * 2.0f - 1.0f; - clamp(-1.0f, 1.0f); - base_normalize(); - r = (std::floor((r + 1.0f) * (float)gc_s[i] * 0.5f + 0.5f)); - g = (std::floor((g + 1.0f) * (float)gc_s[i] * 0.5f + 0.5f)); - b = (std::floor((b + 1.0f) * (float)gc_s[i] * 0.5f + 0.5f)); - clamp(0.0f, (float)gc_s[i]); - } - } -}; - -template<> -struct vec3 : public vec3_base { - explicit vec3(float _r) : vec3_base(_r, 0.0f, 0.0f) {} - vec3(float _r, float _g) : vec3_base(_r, _g, 0.0f) {} - vec3(float _r, float _g, float _b) : vec3_base(_r, _g, _b) {} - void normalize(){ - base_normalize(); - } -}; - -template -class color { }; - -template -class color : public color_base { - public: - using value_type = componentType; - union { - componentType comps[4]; - - struct { - componentType r; - componentType g; - componentType b; - componentType a; - }; - }; - color() {} - color(componentType _r, componentType _g, componentType _b, - componentType _a) : r(_r), g(_g), b(_b), a(_a) {} - componentType operator [](unsigned int i) { - if (i > 3) i = 3; - return comps[i]; - } - void set(uint32_t i, float val) { - if (i > 3) i = 3; - comps[i] = (componentType)val; - } - void set(uint32_t i, componentType val) { - if (i > 3) i = 3; - comps[i] = val; - } - constexpr uint32_t comps_count() const { - return 4; - } - void normalize() { - vec3 v((float)r, (float)g, (float)b); - v.normalize(); - r = (componentType)v.r; - g = (componentType)v.g; - b = (componentType)v.b; - } - }; - -template -class color : public color_base { - public: - using value_type = componentType; - union { - componentType comps[3]; - - struct { - componentType r; - componentType g; - componentType b; - }; - }; - color() {} - color(componentType _r, componentType _g, componentType _b) : - r(_r), g(_g), b(_b) {} - color(componentType _r, componentType _g, componentType _b, componentType) : - r(_r), g(_g), b(_b) {} - componentType operator [](unsigned int i) { - if (i > 2) i = 2; - return comps[i]; - } - void set(uint32_t i, float val) { - if (i > 2) i = 2; - comps[i] = (componentType)val; - } - void set(uint32_t i, componentType val) { - if (i > 2) i = 2; - comps[i] = val; - } - constexpr uint32_t comps_count() const { - return 3; - } - void normalize() { - vec3 v((float)r, (float)g, (float)b); - v.normalize(); - r = (componentType)v.r; - g = (componentType)v.g; - b = (componentType)v.b; - } -}; - -template -class color : public color_base { - public: - using value_type = componentType; - union { - componentType comps[2]; - - struct { - componentType r; - componentType g; - }; - }; - color() {} - color(componentType _r, componentType _g) : - r(_r), g(_g) {} - color(componentType _r, componentType _g, componentType, componentType) : - r(_r), g(_g) {} - componentType operator [](unsigned int i) { - if (i > 1) i = 1; - return comps[i]; - } - void set(uint32_t i, float val) { - if (i > 1) i = 1; - comps[i] = (componentType)val; - } - void set(uint32_t i, componentType val) { - if (i > 1) i = 1; - comps[i] = val; - } - constexpr uint32_t comps_count() const { - return 2; - } - void normalize() { - vec3 v((float)r, (float)g, - (float)gc_s[sizeof(componentType)] * 0.5f); - v.normalize(); - r = (componentType)v.r; - g = (componentType)v.g; - } -}; - -template -class color : public color_base { - public: - using value_type = componentType; - union { - componentType comps[1]; - - struct { - componentType r; - }; - }; - color() {} - color(componentType _r) : - r(_r) {} - color(componentType _r, componentType, componentType, componentType) : - r(_r) {} - componentType operator [](unsigned int i) { - if (i > 0) i = 0; - return comps[i]; - } - void set(uint32_t i, float val) { - if (i > 0) i = 0; - comps[i] = (componentType)val; - } - void set(uint32_t i, componentType val) { - if (i > 0) i = 0; - comps[i] = val; - } - constexpr uint32_t comps_count() const { - return 1; - } - void normalize() { - // Normalizing single channel image doesn't make much sense - // Here I assume single channel color is (X, 0, 0, 0) - if (r != 0) - r = (componentType)gc_s[sizeof(componentType)]; - } -}; - -// Abstract base class for all Images. -class Image { - public: - class different_format : public std::runtime_error { - public: - different_format() : std::runtime_error("") { } - }; - class invalid_file : public std::runtime_error { - public: - invalid_file(std::string error) - : std::runtime_error("Invalid file: " + error) { } - }; - - virtual ~Image() { }; - - uint32_t getWidth() const { return width; } - uint32_t getHeight() const { return height; } - uint32_t getPixelCount() const { return width * height; } - khr_df_transfer_e getOetf() const { return oetf; } - void setOetf(khr_df_transfer_e noetf) { this->oetf = noetf; } - khr_df_primaries_e getPrimaries() const { return primaries; } - void setPrimaries(khr_df_primaries_e nprimaries) { - this->primaries = nprimaries; - } - - virtual operator uint8_t*() = 0; - - virtual size_t getByteCount() const = 0; - virtual uint32_t getPixelSize() const = 0; - virtual uint32_t getComponentCount() const = 0; - virtual uint32_t getComponentSize() const = 0; - virtual Image* createImage(uint32_t width, uint32_t height) = 0; - virtual void resample(Image& dst, bool srgb = false, - const char *pFilter = "lanczos4", - float filter_scale = 1.0f, - basisu::Resampler::Boundary_Op wrapMode - = basisu::Resampler::Boundary_Op::BOUNDARY_CLAMP) = 0; - virtual Image& yflip() = 0; - virtual Image& transformOETF(OETFFunc decode, OETFFunc encode, - float gamma = 1.0f) = 0; - virtual Image& normalize() = 0; - virtual Image& swizzle(std::string& swizzle) = 0; - virtual Image& copyToR(Image&) = 0; - virtual Image& copyToRG(Image&) = 0; - virtual Image& copyToRGB(Image&) = 0; - virtual Image& copyToRGBA(Image&) = 0; - - protected: - Image() : Image(0, 0) { } - Image(uint32_t w, uint32_t h) - : width(w), height(h), oetf(KHR_DF_TRANSFER_UNSPECIFIED), - primaries(KHR_DF_PRIMARIES_BT709) { } - - uint32_t width, height; // In pixels - khr_df_transfer_e oetf; - khr_df_primaries_e primaries; -}; - -// Base class for template and specializations -template -class ImageT : public Image { - friend class ImageT; - friend class ImageT; - friend class ImageT; - friend class ImageT; - public: - using Color = color; - ImageT(uint32_t w, uint32_t h) : Image(w, h) - { - size_t bytes = sizeof(Color) * w * h; - pixels = (Color*)malloc(bytes); - if (!pixels) - throw std::bad_alloc(); - - for (uint32_t p = 0; p < w * h; ++p) - for(uint32_t c = 0; c < componentCount; ++c) - memset(&pixels[p].comps[c], 0, sizeof(componentType)); - } - - ImageT(uint32_t w, uint32_t h, Color* pixels) : Image(w, h), pixels(pixels) - { - } - - ~ImageT() - { - free(pixels); - } - - virtual const Color &operator() (uint32_t x, uint32_t y) const { - assert(x < width && y < height); return pixels[x + y * width]; - } - virtual Color &operator() (uint32_t x, uint32_t y) { - assert(x < width && y < height); return pixels[x + y * width]; - } - virtual operator uint8_t*() { return (uint8_t*)pixels; } - - virtual size_t getByteCount() const { - return getPixelCount() * sizeof(Color); - } - - virtual uint32_t getPixelSize() const { - return Color::getPixelSize(); - } - virtual uint32_t getComponentCount() const { - return Color::getComponentCount(); - } - virtual uint32_t getComponentSize() const { - return Color::getComponentSize(); - } - - virtual Image* createImage(uint32_t w, uint32_t h) { - ImageT* image = new ImageT(w, h); - return image; - } - - static void checkResamplerStatus(basisu::Resampler& resampler, - const char* pFilter) - { - using namespace basisu; - - switch (resampler.status()) { - case Resampler::Status::STATUS_OKAY: - break; - case Resampler::Status::STATUS_OUT_OF_MEMORY: - throw std::runtime_error("Resampler or Resampler::put_line out of memory."); - break; - case Resampler::Status::STATUS_BAD_FILTER_NAME: - { - std::string msg("Unknown filter: "); - msg += pFilter; - throw std::runtime_error(msg); - break; - } - case Resampler::Status::STATUS_SCAN_BUFFER_FULL: - throw std::runtime_error("Resampler::put_line scan buffer full."); - break; - } - } - - virtual void resample(Image& abstract_dst, bool srgb, const char *pFilter, - float filter_scale, - basisu::Resampler::Boundary_Op wrapMode) - { - using namespace basisu; - - ImageT& dst = static_cast(abstract_dst); - - const uint32_t src_w = width, src_h = height; - const uint32_t dst_w = dst.getWidth(), dst_h = dst.getHeight(); - assert(src_w && src_h && dst_w && dst_h); - - if (::maximum(src_w, src_h) > BASISU_RESAMPLER_MAX_DIMENSION - || ::maximum(dst_w, dst_h) > BASISU_RESAMPLER_MAX_DIMENSION) - { - std::stringstream message; - message << "Image larger than max supported size of " - << BASISU_RESAMPLER_MAX_DIMENSION; - throw std::runtime_error(message.str()); - } - - // TODO: Consider just using {decode,encode}_sRGB directly. - float srgb_to_linear_table[256]; - if (srgb) { - for (int i = 0; i < 256; ++i) - srgb_to_linear_table[i] = decode_sRGB((float)i * (1.0f/255.0f)); - } - - const int LINEAR_TO_SRGB_TABLE_SIZE = 8192; - uint8_t linear_to_srgb_table[LINEAR_TO_SRGB_TABLE_SIZE]; - - if (srgb) - { - for (int i = 0; i < LINEAR_TO_SRGB_TABLE_SIZE; ++i) - linear_to_srgb_table[i] = (uint8_t)cclamp((int)(255.0f * encode_sRGB((float)i * (1.0f / (LINEAR_TO_SRGB_TABLE_SIZE - 1))) + .5f), 0, 255); - } - - // Sadly the compiler doesn't realize that getComponentCount() is a - // constant value for each template so size the arrays to the max. - // number of components. - std::vector samples[4]; - Resampler *resamplers[4]; - - resamplers[0] = new Resampler(src_w, src_h, dst_w, dst_h, - wrapMode, - 0.0f, 1.0f, - pFilter, nullptr, nullptr, - filter_scale, filter_scale, - 0, 0); - checkResamplerStatus(*resamplers[0], pFilter); - samples[0].resize(src_w); - - for (uint32_t i = 1; i < getComponentCount(); ++i) - { - resamplers[i] = new Resampler(src_w, src_h, dst_w, dst_h, - wrapMode, - 0.0f, 1.0f, - pFilter, - resamplers[0]->get_clist_x(), - resamplers[0]->get_clist_y(), - filter_scale, filter_scale, - 0, 0); - checkResamplerStatus(*resamplers[i], pFilter); - samples[i].resize(src_w); - } - - uint32_t dst_y = 0; - - for (uint32_t src_y = 0; src_y < src_h; ++src_y) - { - //const Color *pSrc = &(this(0, src_y)); - Color* pSrc = &((*this)(0, src_y)); - - // Put source lines into resampler(s) - for (uint32_t x = 0; x < src_w; ++x) - { - for (uint32_t ci = 0; ci < getComponentCount(); ++ci) - { - const uint32_t v = (*pSrc)[ci]; - - if (!srgb || (ci == 3)) - samples[ci][x] = v * (1.0f / 255.0f); - else - samples[ci][x] = srgb_to_linear_table[v]; - } - - pSrc++; - } - - for (uint32_t ci = 0; ci < getComponentCount(); ++ci) - { - if (!resamplers[ci]->put_line(&samples[ci][0])) - { - checkResamplerStatus(*resamplers[ci], pFilter); - } - } - - // Now retrieve any output lines - for (;;) - { - uint32_t ci; - for (ci = 0; ci < getComponentCount(); ++ci) - { - const float *pOutput_samples = resamplers[ci]->get_line(); - if (!pOutput_samples) - break; - - const bool linear_flag = !srgb || (ci == 3); - - Color* pDst = &dst(0, dst_y); - - for (uint32_t x = 0; x < dst_w; x++) - { - // TODO: Add dithering - if (linear_flag) { - int j = (int)(255.0f * pOutput_samples[x] + .5f); - pDst->set(ci, (componentType)cclamp(j, 0, Color::one())); - } else { - int j = (int)((LINEAR_TO_SRGB_TABLE_SIZE - 1) * pOutput_samples[x] + .5f); - pDst->set(ci, (componentType)linear_to_srgb_table[cclamp(j, 0, LINEAR_TO_SRGB_TABLE_SIZE - 1)]); - } - - pDst++; - } - } - if (ci < getComponentCount()) - break; - - ++dst_y; - } - } - - for (uint32_t i = 0; i < getComponentCount(); ++i) - delete resamplers[i]; - } - - virtual ImageT& yflip() { - uint32_t rowSize = width * sizeof(Color); - // Minimize memory use by only buffering a single row. - Color* rowBuffer = new Color[width]; - - for (uint32_t sy = height-1, dy = 0; sy >= height / 2; sy--, dy++) { - Color* srcRow = &pixels[width * sy]; - Color* dstRow = &pixels[width * dy]; - - memcpy(rowBuffer, dstRow, rowSize); - memcpy(dstRow, srcRow, rowSize); - memcpy(srcRow, rowBuffer, rowSize); - } - delete[] rowBuffer; - return *this; - } - - // oeGamma is the exponent used when the image was encoded - // or the value to be used when re-encoding the image. - virtual ImageT& transformOETF(OETFFunc decode, OETFFunc encode, - float oeGamma = 1.0f) { - uint32_t pixelCount = getPixelCount(); - // eoGamma is the exponent for decoding the image. - float eoGamma = 1.0f / oeGamma; - for (uint32_t i = 0; i < pixelCount; ++i) { - Color& c = pixels[i]; - // Don't transform the alpha component. -------- v - for (uint32_t comp = 0; comp < getComponentCount() && comp < 3; comp++) { - float brightness = (float)(c[comp]) / Color::one(); - // gamma is only used by decode_gamma. Currently there is no - // encode_gamma. - float intensity = decode(brightness, eoGamma); - brightness = cclamp(encode(intensity, oeGamma), 0.0f, 1.0f); - c.set(comp, roundf(brightness * Color::one())); - } - } - return *this; - } - - virtual ImageT& normalize() { - uint32_t pixelCount = getPixelCount(); - for (uint32_t i = 0; i < pixelCount; ++i) { - Color& c = pixels[i]; - c.normalize(); - } - return *this; - } - - virtual ImageT& swizzle(std::string& swizzle) { - assert(swizzle.size() == 4); - for (size_t i = 0; i < getPixelCount(); i++) { - Color srcPixel = pixels[i]; - for (uint32_t c = 0; c < getComponentCount(); c++) { - switch (swizzle[c]) { - case 'r': - pixels[i].set(c, srcPixel[0]); - break; - case 'g': - pixels[i].set(c, srcPixel[1]); - break; - case 'b': - pixels[i].set(c, srcPixel[2]); - break; - case 'a': - pixels[i].set(c, srcPixel[3]); - break; - case '0': - pixels[i].set(c, (componentType)0x00); - break; - case '1': - pixels[i].set(c, (componentType)Color::one()); - break; - default: - assert(false); - } - } - } - return *this; - } - - template - ImageT& copyTo(DstImage& dst) { - assert(getComponentSize() == dst.getComponentSize()); - assert(width == dst.getWidth() && height == dst.getHeight()); - - dst.setOetf(oetf); - dst.setPrimaries(primaries); - for (size_t i = 0; i < getPixelCount(); i++) { - uint32_t c; - for (c = 0; c < dst.getComponentCount(); c++) { - if (c < getComponentCount()) - dst.pixels[i].set(c, pixels[i][c]); - else - break; - } - for (; c < dst.getComponentCount(); c++) - if (c < 3) - dst.pixels[i].set(c, (componentType)0); - else - dst.pixels[i].set(c, (componentType)Color::one()); - } - return *this; - } - - virtual ImageT& copyToR(Image& dst) { return copyTo((ImageT&)dst); } - virtual ImageT& copyToRG(Image& dst) { return copyTo((ImageT&)dst); } - virtual ImageT& copyToRGB(Image& dst){ return copyTo((ImageT&)dst); } - virtual ImageT& copyToRGBA(Image& dst) { return copyTo((ImageT&)dst); } - - protected: - Color* pixels; -}; - -using r8color = color; -using rg8color = color; -using rgb8color = color; -using rgba8color = color; -using r16color = color; -using rg16color = color; -using rgb16color = color; -using rgba16color = color; -using r32color = color; -using rg32color = color; -using rgb32color = color; -using rgba32color = color; - -class r8image : public ImageT { - public: - using MyImageT = ImageT; - r8image(uint32_t w, uint32_t h) : MyImageT(w, h) { } - r8image(uint32_t w, uint32_t h, r8color* data) - : MyImageT(w, h, data) { } -}; -class rg8image : public ImageT { - public: - using MyImageT = ImageT; - rg8image(uint32_t w, uint32_t h) : MyImageT(w, h) { } - rg8image(uint32_t w, uint32_t h, rg8color* data) - : MyImageT(w, h, data) { } -}; -class rgb8image : public ImageT { - public: - using MyImageT = ImageT; - rgb8image(uint32_t w, uint32_t h) : MyImageT(w, h) { } - rgb8image(uint32_t w, uint32_t h, rgb8color* data) - : MyImageT(w, h, data) { } -}; -class rgba8image : public ImageT { - public: - using MyImageT = ImageT; - rgba8image(uint32_t w, uint32_t h) : MyImageT(w, h) { } - rgba8image(uint32_t w, uint32_t h, rgba8color* data) - : MyImageT(w, h, data) { } -}; - -class r16image : public ImageT { - public: - using MyImageT = ImageT; - r16image(uint32_t w, uint32_t h) : MyImageT(w, h) { } - r16image(uint32_t w, uint32_t h, r16color* data) - : MyImageT(w, h, data) { } -}; -class rg16image : public ImageT { - public: - using MyImageT = ImageT; - rg16image(uint32_t w, uint32_t h) : ImageT(w, h) { } - rg16image(uint32_t w, uint32_t h, rg16color* data) - : MyImageT(w, h, data) { } -}; -class rgb16image : public ImageT { - public: - using MyImageT = ImageT; - rgb16image(uint32_t w, uint32_t h) : MyImageT(w, h) { } - rgb16image(uint32_t w, uint32_t h, rgb16color* data) - : MyImageT(w, h, data) { } -}; -class rgba16image : public ImageT { - public: - using MyImageT = ImageT; - rgba16image(uint32_t w, uint32_t h) : MyImageT(w, h) { } - rgba16image(uint32_t w, uint32_t h, rgba16color* data) - : MyImageT(w, h, data) { } -}; - -#endif /* IMAGE_HPP */ - - - diff --git a/tools/toktx/toktx.cc b/tools/toktx/toktx.cc index 383109b887..fe54db8a30 100644 --- a/tools/toktx/toktx.cc +++ b/tools/toktx/toktx.cc @@ -339,12 +339,14 @@ class toktxApp : public scApp { protected: struct targetImageSpec : public ImageSpec { - OETFFunc decodeFunc = nullptr; // To be applied to the source image! - OETFFunc encodeFunc = nullptr; + khr_df_transfer_e usedInputTransferFunction; + khr_df_primaries_e usedInputPrimaries; + std::unique_ptr srcTransferFunction{}; + std::unique_ptr dstTransferFunction{}; + std::unique_ptr srcColorPrimaries{}; + std::unique_ptr dstColorPrimaries{}; targetImageSpec& operator=(const ImageSpec& s) { *static_cast(this) = s; - encodeFunc = nullptr; - decodeFunc = nullptr; return *this; } }; @@ -352,11 +354,13 @@ class toktxApp : public scApp { virtual bool processOption(argparser& parser, int opt); void processEnvOptions(); void validateOptions(); + khr_df_primaries_e parseColorPrimaries(string& argValue); - Image* createImage(const targetImageSpec& target, ImageInput& in); - void convertImageType(Image*& pImage); - void scaleImage(Image*& pImage, ktx_uint32_t width, ktx_uint32_t height); - void genMipmap(Image*& pImage, + unique_ptr createImage(const targetImageSpec& target, ImageInput& in); + unique_ptr convertImageType(unique_ptr pImage); + unique_ptr scaleImage(unique_ptr pImage, + ktx_uint32_t width, ktx_uint32_t height); + void genMipmap(unique_ptr pImage, uint32_t layer, uint32_t faceSlice, ktxTexture* texture); @@ -391,9 +395,10 @@ class toktxApp : public scApp { int metadata; int mipmap; int two_d; - khr_df_transfer_e convert_oetf; khr_df_transfer_e assign_oetf; + khr_df_transfer_e convert_oetf; khr_df_primaries_e assign_primaries; + khr_df_primaries_e convert_primaries; int useStdin; int lower_left_maps_to_s0t0; struct mipgenOptions gmopts; @@ -428,6 +433,7 @@ class toktxApp : public scApp { convert_oetf = KHR_DF_TRANSFER_UNSPECIFIED; assign_oetf = KHR_DF_TRANSFER_UNSPECIFIED; assign_primaries = KHR_DF_PRIMARIES_MAX; + convert_primaries = KHR_DF_PRIMARIES_MAX; // As required by spec. Opposite of OpenGL {,ES}, same as // Vulkan, et al. lower_left_maps_to_s0t0 = 0; @@ -471,9 +477,10 @@ toktxApp::toktxApp() : scApp(myversion, mydefversion, options) { "scale", argparser::option::required_argument, NULL, 's' }, { "swizzle", argparser::option::required_argument, NULL, 1101}, { "target_type", argparser::option::required_argument, NULL, 1102}, - { "convert_oetf", argparser::option::required_argument, NULL, 1103}, - { "assign_oetf", argparser::option::required_argument, NULL, 1104}, + { "assign_oetf", argparser::option::required_argument, NULL, 1103}, + { "convert_oetf", argparser::option::required_argument, NULL, 1104}, { "assign_primaries", argparser::option::required_argument, NULL, 1105}, + { "convert_primaries", argparser::option::required_argument, NULL, 1106}, { "t2", argparser::option::no_argument, &options.ktx2, 1}, }; @@ -668,7 +675,6 @@ int toktxApp::main(int argc, _TCHAR *argv[]) { KTX_error_code ret; - //ktxTextureCreateInfo createInfo; ktxTexture* texture = 0; int exitCode = 0; unsigned int faceSlice, level, layer, levelCount = 1; @@ -687,7 +693,7 @@ toktxApp::main(int argc, _TCHAR *argv[]) for (it = options.infiles.begin(); it < options.infiles.end(); it++) { const _tstring& infile = *it; - Image* image = nullptr; + unique_ptr image; uint32_t subimage=0, miplevel=0; try { @@ -793,7 +799,7 @@ toktxApp::main(int argc, _TCHAR *argv[]) assert(ret == KTX_SUCCESS); if (options.genmipmap) { - genMipmap(image, layer, faceSlice, texture); + genMipmap(std::move(image), layer, faceSlice, texture); } #if IMAGE_DEBUG { @@ -806,7 +812,6 @@ toktxApp::main(int argc, _TCHAR *argv[]) texture.pData + offset); } #endif - delete image; image = nullptr; miplevel++; @@ -851,7 +856,7 @@ toktxApp::main(int argc, _TCHAR *argv[]) // been created despite its name. We want the same message // to the user hence not creating a different exception. if (image != nullptr) - delete image; + image = nullptr; exitCode = 1; goto cleanup; } catch (runtime_error& e) { @@ -981,41 +986,62 @@ toktxApp::main(int argc, _TCHAR *argv[]) return exitCode; } -Image* +unique_ptr toktxApp::createImage(const targetImageSpec& target, ImageInput& in) { const ImageSpec& inSpec = in.spec(); - - Image* image = nullptr; - if (target.format().channelBitLength() == 16) { - switch (inSpec.format().channelCount()) { + FormatDescriptor inputFormat; + unique_ptr image; + + // input plugins that support channel reduction and addition do so + // in a way which differs from the documented behaviour for --target_type + // so alway do channel adjustments in this program. + if (target.format().channelCount() != inSpec.format().channelCount()) { + // Have plugin deliver all channels. + inputFormat = inSpec.format(); + if (inSpec.format().anyChannelBitLengthNotEqual(target.format().channelBitLength())) { + // target.format() is set so all channels have same bit length. + std::vector bits; + bits.resize(1); + bits[0] = target.format().channelBitLength(); + // TODO: Consider making a function for channelBitCounts. + // Currently supported input formats all have + // numChannels = numSamples so this works but is fragile. + inputFormat.updateSampleBitCounts(bits); + } + } else { + inputFormat = target.format(); + } + //if (target.format().channelBitLength() == 16) { + if (inputFormat.channelBitLength() == 16) { + switch (inputFormat.channelCount()) { case 1: { - image = new r16image(inSpec.width(), inSpec.height()); + image = make_unique(inSpec.width(), inSpec.height()); break; } case 2: { - image = new rg16image(inSpec.width(), inSpec.height()); + image = make_unique(inSpec.width(), inSpec.height()); break; } case 3: { - image = new rgb16image(inSpec.width(), inSpec.height()); + image = make_unique(inSpec.width(), inSpec.height()); break; } case 4: { - image = new rgba16image(inSpec.width(), inSpec.height()); + image = make_unique(inSpec.width(), inSpec.height()); break; } } } else if (target.format().channelBitLength() == 8) { - switch (inSpec.format().channelCount()) { + switch (inputFormat.channelCount()) { case 1: { - image = new r8image(inSpec.width(), inSpec.height()); + image = make_unique(inSpec.width(), inSpec.height()); break; } case 2: { - image = new rg8image(inSpec.width(), inSpec.height()); + image = make_unique(inSpec.width(), inSpec.height()); break; } case 3: { - image = new rgb8image(inSpec.width(), inSpec.height()); + image = make_unique(inSpec.width(), inSpec.height()); break; } case 4: { - image = new rgba8image(inSpec.width(), inSpec.height()); + image = make_unique(inSpec.width(), inSpec.height()); break; } } @@ -1032,30 +1058,35 @@ toktxApp::createImage(const targetImageSpec& target, ImageInput& in) } in.readImage(static_cast(*image), image->getByteCount(), - 0, inSpec.format().channelCount(), target.format()); + 0/*subimage*/, 0/*miplevel*/, inputFormat); /* Sanity check. */ assert(image->getWidth() * image->getHeight() * image->getPixelSize() == image->getByteCount()); - // TODO: Convert primaries? - image->setPrimaries((khr_df_primaries_e)target.format().primaries()); - if (target.encodeFunc != nullptr) { - assert(target.decodeFunc != nullptr); - image->transformOETF(target.decodeFunc, target.encodeFunc, - inSpec.format().oeGamma()); - if (target.encodeFunc == encode_sRGB) { - image->setOetf(KHR_DF_TRANSFER_SRGB); + + + if (target.dstTransferFunction != nullptr) { + assert(target.srcTransferFunction != nullptr); + if (target.dstColorPrimaries != nullptr) { + assert(target.srcColorPrimaries != nullptr); + auto primaryTransform = target.srcColorPrimaries->transformTo(*target.dstColorPrimaries); + + // Transform OETF with primary transform + image->transformColorSpace(*target.srcTransferFunction, *target.dstTransferFunction, &primaryTransform); } else { - image->setOetf(KHR_DF_TRANSFER_LINEAR); + // Transform OETF without primary transform + image->transformColorSpace(*target.srcTransferFunction, *target.dstTransferFunction); } - } else { - image->setOetf((khr_df_transfer_e)target.format().transfer()); } + image->setPrimaries((khr_df_primaries_e)target.format().primaries()); + image->setOetf((khr_df_transfer_e)target.format().transfer()); if (options.scale != 1.0f) { - scaleImage(image, - static_cast(image->getWidth() * options.scale), - static_cast(image->getHeight() * options.scale)); + auto scaledWidth = image->getWidth() * options.scale; + auto scaledHeight = image->getHeight() * options.scale; + image = scaleImage(std::move(image), + static_cast(scaledWidth), + static_cast(scaledHeight)); } else if (options.resize && (image->getWidth() != target.width() || image->getHeight() != target.height())) @@ -1063,41 +1094,42 @@ toktxApp::createImage(const targetImageSpec& target, ImageInput& in) // --resize is not allowed with --mipmap so createImage will never be // called for other than the base level when set. This would be // incorrect otherwise. target reflects the resize value, if any. - scaleImage(image, target.width(), target.height()); + image = scaleImage(std::move(image), target.width(), target.height()); } if (options.targetType != commandOptions::eUnspecified) { - convertImageType(image); + image = convertImageType(std::move(image)); } return image; } -void -toktxApp::convertImageType(Image*& pImage) +unique_ptr +toktxApp::convertImageType(unique_ptr pImage) { // TODO: These copyTo's should be reversed. The image should have // a copy constructor for each componentCount src image. if (options.targetType != (int)pImage->getComponentCount()) { - Image* newImage = nullptr; + unique_ptr newImage; + string nullSwizzle = "rgba"; // The casts in the following copyTo* definitions only work // because, thanks to the switch, at runtime we always pass // the image type being cast to. if (pImage->getComponentSize() == 2) { switch (options.targetType) { case commandOptions::eR: - newImage = new r16image(pImage->getWidth(), pImage->getHeight()); - pImage->copyToR(*newImage); + newImage = make_unique(pImage->getWidth(), pImage->getHeight()); + pImage->copyToR(*newImage, nullSwizzle); break; case commandOptions::eRG: - newImage = new rg16image(pImage->getWidth(), pImage->getHeight()); - pImage->copyToRG(*newImage); + newImage = make_unique(pImage->getWidth(), pImage->getHeight()); + pImage->copyToRG(*newImage, nullSwizzle); break; case commandOptions::eRGB: - newImage = new rgb16image(pImage->getWidth(), pImage->getHeight()); - pImage->copyToRGB(*newImage); + newImage = make_unique(pImage->getWidth(), pImage->getHeight()); + pImage->copyToRGB(*newImage, nullSwizzle); break; case commandOptions::eRGBA: - newImage = new rgba16image(pImage->getWidth(), pImage->getHeight()); - pImage->copyToRGBA(*newImage); + newImage = make_unique(pImage->getWidth(), pImage->getHeight()); + pImage->copyToRGBA(*newImage, nullSwizzle); break; case commandOptions::eUnspecified: assert(false); @@ -1105,45 +1137,42 @@ toktxApp::convertImageType(Image*& pImage) } else { switch (options.targetType) { case commandOptions::eR: - newImage = new r8image(pImage->getWidth(), pImage->getHeight()); - pImage->copyToR(*newImage); + newImage = make_unique(pImage->getWidth(), pImage->getHeight()); + pImage->copyToR(*newImage, nullSwizzle); break; case commandOptions::eRG: - newImage = new rg8image(pImage->getWidth(), pImage->getHeight()); - pImage->copyToRG(*newImage); + newImage = make_unique(pImage->getWidth(), pImage->getHeight()); + pImage->copyToRG(*newImage, nullSwizzle); break; case commandOptions::eRGB: - newImage = new rgb8image(pImage->getWidth(), pImage->getHeight()); - pImage->copyToRGB(*newImage); + newImage = make_unique(pImage->getWidth(), pImage->getHeight()); + pImage->copyToRGB(*newImage, nullSwizzle); break; case commandOptions::eRGBA: - newImage = new rgba8image(pImage->getWidth(), pImage->getHeight()); - pImage->copyToRGBA(*newImage); + newImage = make_unique(pImage->getWidth(), pImage->getHeight()); + pImage->copyToRGBA(*newImage, nullSwizzle); break; case commandOptions::eUnspecified: assert(false); } } if (newImage) { - delete pImage; - pImage = newImage; + return newImage; } else { throw runtime_error( "Out of memory for image with new target type." ); } } + return pImage; } // TODO: This should probably be a method on Image. -void -toktxApp::scaleImage(Image*& pImage, ktx_uint32_t width, ktx_uint32_t height) +unique_ptr +toktxApp::scaleImage(unique_ptr pImage, ktx_uint32_t width, ktx_uint32_t height) { - Image* pScaledImage = pImage->createImage(width, height); - try { - pImage->resample(*pScaledImage, - pImage->getOetf() == KHR_DF_TRANSFER_SRGB, + pImage = pImage->resample(width, height, options.gmopts.filter.c_str(), options.gmopts.filterScale, basisu::Resampler::Boundary_Op::BOUNDARY_CLAMP); @@ -1155,29 +1184,23 @@ toktxApp::scaleImage(Image*& pImage, ktx_uint32_t width, ktx_uint32_t height) // latter are much more likely to occur hence choice of exception. throw cant_create_image(message.str()); } - pScaledImage->setOetf(pImage->getOetf()); - pScaledImage->setPrimaries(pImage->getPrimaries()); - delete pImage; - pImage = pScaledImage; + return pImage; } void -toktxApp::genMipmap(Image*& pImage, +toktxApp::genMipmap(unique_ptr pImage, uint32_t layer, uint32_t faceSlice, ktxTexture* texture) { + unique_ptr levelImage; for (uint32_t glevel = 1; glevel < texture->numLevels; glevel++) { - Image *levelImage = pImage->createImage( - maximum(1, pImage->getWidth() >> glevel), - maximum(1, pImage->getHeight() >> glevel)); - levelImage->setOetf(pImage->getOetf()); - levelImage->setPrimaries(pImage->getPrimaries()); + auto levelWidth = maximum(1, pImage->getWidth() >> glevel); + auto levelHeight = maximum(1, pImage->getHeight() >> glevel); try { - pImage->resample(*levelImage, - pImage->getOetf() == KHR_DF_TRANSFER_SRGB, - options.gmopts.filter.c_str(), - options.gmopts.filterScale, - options.gmopts.wrapMode); + levelImage = pImage->resample(levelWidth, levelHeight, + options.gmopts.filter.c_str(), + options.gmopts.filterScale, + options.gmopts.wrapMode); } catch (runtime_error& e) { stringstream message; message << "Image::resample() failed! " << e.what(); @@ -1195,7 +1218,6 @@ toktxApp::genMipmap(Image*& pImage, *levelImage, levelImage->getByteCount()); assert(ret == KTX_SUCCESS); - delete levelImage; } } @@ -1377,6 +1399,38 @@ toktxApp::createTexture(const targetImageSpec& target) return texture; } +static std::unique_ptr +createColorPrimaries(khr_df_primaries_e primaries) { + switch (primaries) { + case KHR_DF_PRIMARIES_BT709: + return std::make_unique(); + case KHR_DF_PRIMARIES_BT601_EBU: + return std::make_unique(); + case KHR_DF_PRIMARIES_BT601_SMPTE: + return std::make_unique(); + case KHR_DF_PRIMARIES_BT2020: + return std::make_unique(); + case KHR_DF_PRIMARIES_CIEXYZ: + return std::make_unique(); + case KHR_DF_PRIMARIES_ACES: + return std::make_unique(); + case KHR_DF_PRIMARIES_ACESCC: + return std::make_unique(); + case KHR_DF_PRIMARIES_NTSC1953: + return std::make_unique(); + case KHR_DF_PRIMARIES_PAL525: + return std::make_unique(); + case KHR_DF_PRIMARIES_DISPLAYP3: + return std::make_unique(); + case KHR_DF_PRIMARIES_ADOBERGB: + return std::make_unique(); + default: + assert(false); + // We return BT709 by default if some error happened + return std::make_unique(); + } +} + void toktxApp::determineTargetColorSpace(const ImageInput& in, targetImageSpec& target) { @@ -1389,13 +1443,34 @@ toktxApp::determineTargetColorSpace(const ImageInput& in, targetImageSpec& targe // UNSPECIFIED. const ImageSpec& spec = in.spec(); // Set Primaries + target.usedInputPrimaries = spec.format().primaries(); if (options.assign_primaries != KHR_DF_PRIMARIES_MAX) { + target.usedInputPrimaries = options.assign_primaries; target.format().setPrimaries(options.assign_primaries); } else if (spec.format().primaries() != KHR_DF_PRIMARIES_UNSPECIFIED) { target.format().setPrimaries(spec.format().primaries()); } else { + if (!in.formatName().compare("png")) { + warning("No color primaries in PNG input file \"{}\", defaulting to BT.709.", + in.filename().c_str()); + target.usedInputPrimaries = KHR_DF_PRIMARIES_BT709; + target.format().setPrimaries(KHR_DF_PRIMARIES_BT709); + } else { // Leave as unspecified. target.format().setPrimaries(spec.format().primaries()); + } + } + + if (options.convert_primaries != KHR_DF_PRIMARIES_MAX) { + if (target.usedInputPrimaries == KHR_DF_PRIMARIES_UNSPECIFIED) { + throw cant_create_image( + "Cannot convert primaries as no information about the color primaries " + "is available in the input file \"{}\". Use --assign-primaries to specify one."); + } else if (options.convert_primaries != target.usedInputPrimaries) { + target.srcColorPrimaries = createColorPrimaries(target.usedInputPrimaries); + target.dstColorPrimaries = createColorPrimaries(options.convert_primaries); + target.format().setPrimaries(options.convert_primaries); + } } // OETF / Transfer function handling in priority order: @@ -1412,23 +1487,36 @@ toktxApp::determineTargetColorSpace(const ImageInput& in, targetImageSpec& targe // 6. Convert OETF based on convert_oetf option value or as described // above. // + target.usedInputTransferFunction = KHR_DF_TRANSFER_UNSPECIFIED; if (options.assign_oetf != KHR_DF_TRANSFER_UNSPECIFIED) { target.format().setTransfer(options.assign_oetf); + target.usedInputTransferFunction = options.assign_oetf; + if (options.assign_oetf == KHR_DF_TRANSFER_SRGB) { + target.srcTransferFunction + = std::make_unique(); + } else { + assert(options.assign_oetf == KHR_DF_TRANSFER_LINEAR); + target.srcTransferFunction + = std::make_unique(); + } } else { // Set image's OETF as indicated by metadata. if (spec.format().transfer() != KHR_DF_TRANSFER_UNSPECIFIED) { target.format().setTransfer(spec.format().transfer()); + target.usedInputTransferFunction = spec.format().transfer(); switch (spec.format().transfer()) { case KHR_DF_TRANSFER_LINEAR: - target.decodeFunc = decode_linear; + target.srcTransferFunction = + make_unique(); break; case KHR_DF_TRANSFER_SRGB: - target.decodeFunc = decode_sRGB; + target.srcTransferFunction = + make_unique(); break; case KHR_DF_TRANSFER_ITU: target.format().setTransfer(KHR_DF_TRANSFER_SRGB); - target.decodeFunc = decode_bt709; - target.encodeFunc = encode_sRGB; + target.srcTransferFunction = + make_unique(); break; default: throw cant_create_image( @@ -1440,7 +1528,6 @@ toktxApp::determineTargetColorSpace(const ImageInput& in, targetImageSpec& targe "It has an ICC profile. These are not supported." " Use --assign_oetf to specify handling."); } else if (spec.format().oeGamma() >= 0.0f) { - target.decodeFunc = decode_gamma; if (spec.format().oeGamma() > .45450f && spec.format().oeGamma() < .45460f) { // N.B The previous loader matched oeGamma .45455 to the sRGB @@ -1451,16 +1538,34 @@ toktxApp::determineTargetColorSpace(const ImageInput& in, targetImageSpec& targe // This change results in 1 bit differences in the LSB of // some color values noticeable only when directly comparing // images produced before and after this change of loader. + warning("Converting gamma 2.2f to sRGB. Use --assign-oetf srgb" + " to force treating input as sRGB.", in.filename().c_str() + ); target.format().setTransfer(KHR_DF_TRANSFER_SRGB); - target.encodeFunc = encode_sRGB; + target.srcTransferFunction + = make_unique(spec.format().oeGamma()); } else if (spec.format().oeGamma() == 1.0) { target.format().setTransfer(KHR_DF_TRANSFER_LINEAR); + target.srcTransferFunction + = make_unique(); } else if (spec.format().oeGamma() == 0.0f) { if (!in.formatName().compare("png")) { - warning("Ignoring reported gamma of 0.0f in %s." - "Handling as sRGB.", in.filename().c_str()); - target.format().setTransfer(KHR_DF_TRANSFER_SRGB); - target.decodeFunc = decode_sRGB; + if (spec.format().channelBitLength() == 8) { + warning("Ignoring reported gamma of 0.0f in %s." + "Handling as sRGB.", in.filename().c_str()); + target.format().setTransfer(KHR_DF_TRANSFER_SRGB); + target.usedInputTransferFunction = KHR_DF_TRANSFER_SRGB; + target.srcTransferFunction = + make_unique(); + } else { + warning("Ignoring reported gamma of 0.0f in %s." + "Handling as linear.", in.filename().c_str()); + target.format().setTransfer(KHR_DF_TRANSFER_LINEAR); + target.usedInputTransferFunction = + KHR_DF_TRANSFER_LINEAR; + target.srcTransferFunction = + make_unique(); + } } else { throw cant_create_image("Its reported gamma is 0.0f." " Use --assign_oetf to specify handling."); @@ -1474,13 +1579,19 @@ toktxApp::determineTargetColorSpace(const ImageInput& in, targetImageSpec& targe << "Specify handling with --convert_oetf or" << " --assign_oetf."; throw cant_create_image(message.str()); + } else { + target.srcTransferFunction + = make_unique(spec.format().oeGamma()); } } } else { if (!in.formatName().compare("png")) { // Follow W3C. Treat unspecified as sRGB. target.format().setTransfer(KHR_DF_TRANSFER_SRGB); - target.decodeFunc = decode_sRGB; + target.usedInputTransferFunction = + KHR_DF_TRANSFER_SRGB; + target.srcTransferFunction = + make_unique(); } else { throw cant_create_image( "It has no color space information." @@ -1489,14 +1600,28 @@ toktxApp::determineTargetColorSpace(const ImageInput& in, targetImageSpec& targe } } - if (options.convert_oetf != KHR_DF_TRANSFER_UNSPECIFIED && - options.convert_oetf != spec.format().transfer()) { - if (options.convert_oetf == KHR_DF_TRANSFER_SRGB) { - target.encodeFunc = encode_sRGB; - target.format().setTransfer(KHR_DF_TRANSFER_SRGB); - } else { - target.encodeFunc = encode_linear; - target.format().setTransfer(KHR_DF_TRANSFER_LINEAR); + if (options.convert_oetf != KHR_DF_TRANSFER_UNSPECIFIED) { + target.format().setTransfer(options.convert_oetf); + } + + // Need to do color conversion if either the transfer functions don't match or the primaries + if (target.format().transfer() != target.usedInputTransferFunction || + target.format().primaries() != target.usedInputPrimaries) { + if (target.srcTransferFunction == nullptr) + throw cant_create_image( + "No transfer function can be determined from input file." + " Use --assign-oetf to specify one."); + + switch (target.format().transfer()) { + case KHR_DF_TRANSFER_LINEAR: + target.dstTransferFunction = std::make_unique(); + break; + case KHR_DF_TRANSFER_SRGB: + target.dstTransferFunction = std::make_unique(); + break; + default: + assert(false); + break; } } } @@ -1511,10 +1636,10 @@ toktxApp::determineTargetTypeBitLengthScale(const ImageInput& in, uint32_t bitLength = format.channelBitLength(); uint32_t maxValue; - if (format.channelBitLength() > 8 + if (format.largestChannelBitLength() > 8 && (options.etc1s || options.bopts.uastc)) { bitLength = 8; - } else if (format.channelBitLength() < 8) { + } else if (format.largestChannelBitLength() < 8) { bitLength = 8; } @@ -1523,8 +1648,7 @@ toktxApp::determineTargetTypeBitLengthScale(const ImageInput& in, // TODO: Support < 8 bit channels for non-block-compressed? - if (targetFormat.channelBitLength() - != format.channelBitLength()) { + if (bitLength != format.largestChannelBitLength()) { warning("Rescaling %d-bit image in %s to %d bits.", format.channelBitLength(), in.filename().c_str(), @@ -1547,7 +1671,7 @@ toktxApp::determineTargetTypeBitLengthScale(const ImageInput& in, } // Must be after setting of model. - if (bitLength != targetFormat.channelBitLength() + if (targetFormat.anyChannelBitLengthNotEqual(bitLength) || maxValue != targetFormat.channelUpper() || channelCount != targetFormat.channelCount()) { @@ -1778,6 +1902,48 @@ toktxApp::processEnvOptions() { } } +/* + * @brief parse a color primaries argument + */ + khr_df_primaries_e + toktxApp::parseColorPrimaries(string& argValue) { + static const std::unordered_map values{ + { "NONE", KHR_DF_PRIMARIES_UNSPECIFIED }, + { "BT709", KHR_DF_PRIMARIES_BT709 }, + { "SRGB", KHR_DF_PRIMARIES_SRGB }, + { "BT601-EBU", KHR_DF_PRIMARIES_BT601_EBU }, + { "BT601-SMPTE", KHR_DF_PRIMARIES_BT601_SMPTE }, + { "BT2020", KHR_DF_PRIMARIES_BT2020 }, + { "CIEXYZ", KHR_DF_PRIMARIES_CIEXYZ }, + { "ACES", KHR_DF_PRIMARIES_ACES }, + { "ACESCC", KHR_DF_PRIMARIES_ACESCC }, + { "NTSC1953", KHR_DF_PRIMARIES_NTSC1953 }, + { "PAL525", KHR_DF_PRIMARIES_PAL525 }, + { "DISPLAYP3", KHR_DF_PRIMARIES_DISPLAYP3 }, + { "ADOBERGB", KHR_DF_PRIMARIES_ADOBERGB }, + }; + + khr_df_primaries_e result = {}; + + if (argValue.length()) { + for_each(argValue.begin(), argValue.end(), [](char & c) { + c = (char)::toupper(c); + }); + + const auto it = values.find(argValue); + if (it != values.end()) { + result = it->second; + } else { + cerr << name + << "Invalid or unsupported transfer function specified: " + << argValue << endl; + exit(1); + } + } + + return result; + } + /* * @brief process a command line option * @@ -1890,29 +2056,24 @@ toktxApp::processOption(argparser& parser, int opt) c = (char)::tolower(c); }); if (parser.optarg.compare("linear") == 0) - options.convert_oetf = KHR_DF_TRANSFER_LINEAR; + options.assign_oetf = KHR_DF_TRANSFER_LINEAR; else if (parser.optarg.compare("srgb") == 0) - options.convert_oetf = KHR_DF_TRANSFER_SRGB; + options.assign_oetf = KHR_DF_TRANSFER_SRGB; break; case 1104: for_each(parser.optarg.begin(), parser.optarg.end(), [](char & c) { c = (char)::tolower(c); }); if (parser.optarg.compare("linear") == 0) - options.assign_oetf = KHR_DF_TRANSFER_LINEAR; + options.convert_oetf = KHR_DF_TRANSFER_LINEAR; else if (parser.optarg.compare("srgb") == 0) - options.assign_oetf = KHR_DF_TRANSFER_SRGB; + options.convert_oetf = KHR_DF_TRANSFER_SRGB; break; case 1105: - for_each(parser.optarg.begin(), parser.optarg.end(), [](char & c) { - c = (char)::tolower(c); - }); - if (parser.optarg.compare("bt709") == 0) - options.assign_primaries = KHR_DF_PRIMARIES_BT709; - else if (parser.optarg.compare("none") == 0) - options.assign_primaries = KHR_DF_PRIMARIES_UNSPECIFIED; - if (parser.optarg.compare("srgb") == 0) - options.assign_primaries = KHR_DF_PRIMARIES_SRGB; + options.assign_primaries = parseColorPrimaries(parser.optarg); + break; + case 1106: + options.convert_primaries = parseColorPrimaries(parser.optarg); break; case ':': default: diff --git a/utils/ktxapp.h b/utils/ktxapp.h index cd71eb10bb..86c6bbbfe5 100644 --- a/utils/ktxapp.h +++ b/utils/ktxapp.h @@ -6,6 +6,12 @@ #include "stdafx.h" +#if defined (_WIN32) + #define _CRT_SECURE_NO_WARNINGS + #define WINDOWS_LEAN_AND_MEAN + #include +#endif + #include #if (_MSVC_LANG >= 201703L || __cplusplus >= 201703L) #include @@ -87,8 +93,12 @@ class ktxApp { virtual int main(int argc, _TCHAR* argv[]) = 0; virtual void usage() { cerr << - " -h, --help Print this usage message and exit.\n" - " -v, --version Print the version number of this program and exit.\n"; + " -h, --help Print this usage message and exit.\n" + " -v, --version Print the version number of this program and exit.\n" +#if defined(_WIN32) && defined(DEBUG) + " --ld Launch Visual Studio deugger at start up.\n" +#endif + ; }; protected: @@ -97,8 +107,9 @@ class ktxApp { _tstring outfile; int test; int warn; + int launchDebugger; - commandOptions() : test(false), warn(1) { } + commandOptions() : test(false), warn(1), launchDebugger(0) { } }; ktxApp(std::string& version, std::string& defaultVersion, @@ -259,7 +270,7 @@ class ktxApp { listName.erase(0, relativize ? 2 : 1); FILE *lf = nullptr; -#ifdef _WIN32 +#if defined(_WIN32) _tfopen_s(&lf, listName.c_str(), "r"); #else lf = _tfopen(listName.c_str(), "r"); @@ -352,6 +363,10 @@ class ktxApp { } } } +#if defined(_WIN32) && defined(DEBUG) + if (options.launchDebugger) + launchDebugger(); +#endif } virtual bool processOption(argparser& parser, int opt) = 0; @@ -366,6 +381,47 @@ class ktxApp { cerr << endl; } +#if defined(_WIN32) && defined(DEBUG) + // For use when debugging stdin with Visual Studio which does not have a + // "wait for executable to be launched" choice in its debugger settings. + bool launchDebugger() + { + // Get System directory, typically c:\windows\system32 + std::wstring systemDir(MAX_PATH + 1, '\0'); + UINT nChars = GetSystemDirectoryW(&systemDir[0], + static_cast(systemDir.length())); + if (nChars == 0) return false; // failed to get system directory + systemDir.resize(nChars); + + // Get process ID and create the command line + DWORD pid = GetCurrentProcessId(); + std::wostringstream s; + s << systemDir << L"\\vsjitdebugger.exe -p " << pid; + std::wstring cmdLine = s.str(); + + // Start debugger process + STARTUPINFOW si; + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + + PROCESS_INFORMATION pi; + ZeroMemory(&pi, sizeof(pi)); + + if (!CreateProcessW(NULL, &cmdLine[0], NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) return false; + + // Close debugger process handles to eliminate resource leak + CloseHandle(pi.hThread); + CloseHandle(pi.hProcess); + + // Wait for the debugger to attach + while (!IsDebuggerPresent()) Sleep(100); + + // Stop execution so the debugger can take over + DebugBreak(); + return true; + } +#endif + _tstring name; _tstring& version; _tstring& defaultVersion; @@ -378,6 +434,9 @@ class ktxApp { { "help", argparser::option::no_argument, NULL, 'h' }, { "version", argparser::option::no_argument, NULL, 'v' }, { "test", argparser::option::no_argument, &options.test, 1}, +#if defined(_WIN32) && defined(DEBUG) + { "ld", argparser::option::no_argument, &options.launchDebugger, 1}, +#endif // -NSDocumentRevisionsDebugMode YES is appended to the end // of the command by Xcode when debugging and "Allow debugging when // using document Versions Browser" is checked in the scheme. It diff --git a/utils/stdafx.h b/utils/stdafx.h index 568bdeb32b..b4e19c2f52 100644 --- a/utils/stdafx.h +++ b/utils/stdafx.h @@ -5,7 +5,12 @@ #pragma once -#define _CRT_SECURE_NO_WARNINGS // For _WIN32. Must be before . +#if defined(_WIN32) + // _CRT_SECURE_NO_WARNINGS must be defined before , + // and and + #define _CRT_SECURE_NO_WARNINGS +#endif + #include #include #ifdef _WIN32