├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── README.md ├── build_version.cpp.in ├── build_version.h ├── cmake ├── GetGitRevisionDescription.cmake └── GetGitRevisionDescription.cmake.in ├── img2ktx.cpp ├── img2ktx.sln ├── ispc_texcomp.dll ├── ispc_texcomp.exp ├── ispc_texcomp.h ├── ispc_texcomp.lib ├── license.txt └── third_party └── CMakeLists.txt /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.spv 3 | 4 | # Visual Studio detritus 5 | Debug/ 6 | Release/ 7 | *.obj 8 | *.o 9 | *.htm 10 | *.manifest* 11 | *.pch 12 | *.dep 13 | *.idb 14 | *.pdb 15 | *.ncb 16 | *.suo 17 | *.user 18 | *.ilk 19 | *.exe 20 | *.opensdf 21 | *.sdf 22 | *.tlog 23 | *.log 24 | *_manifest.rc 25 | *.lastbuildstate 26 | *.unsuccessfulbuild 27 | *.ipch 28 | *.cache 29 | 30 | CMakeLists.txt.user* 31 | build 32 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "third_party/stb"] 2 | path = third_party/stb 3 | url = https://github.com/nothings/stb 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | 3 | project(img2ktx) 4 | 5 | set(CMAKE_MSVC_RUNTIME_LIBRARY MultiThreaded$<$:Debug>) # requires CMake 3.15 6 | 7 | add_executable(img2ktx "") 8 | target_sources(img2ktx PRIVATE ${CMAKE_CURRENT_LIST_DIR}/img2ktx.cpp) 9 | 10 | if(${MSVC}) 11 | set_property(GLOBAL PROPERTY USE_FOLDERS ON) 12 | target_compile_options(img2ktx PRIVATE -W4 -EHsc -wd4996) 13 | elseif(${UNIX}) 14 | set_property(DIRECTORY APPEND PROPERTY COMPILE_OPTIONS -w) 15 | target_link_libraries(img2ktx PRIVATE m) 16 | endif() 17 | 18 | # Update build_version.h whenever current commit changes 19 | # c/o https://github.com/rpavlik/cmake-modules/blob/master/GetGitRevisionDescription.cmake 20 | # c/o https://stackoverflow.com/a/4318642 21 | list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake/) 22 | include(GetGitRevisionDescription) 23 | git_describe(GIT_DESCRIBE_RESULT --tags --always) 24 | configure_file(${CMAKE_CURRENT_LIST_DIR}/build_version.cpp.in ${CMAKE_CURRENT_BINARY_DIR}/build_version.cpp @ONLY) 25 | target_sources(img2ktx PRIVATE 26 | ${CMAKE_CURRENT_BINARY_DIR}/build_version.cpp 27 | ${CMAKE_CURRENT_LIST_DIR}/build_version.h 28 | ) 29 | 30 | find_library(IMG2KTX_ISPC_TEXCOMP_LIB 31 | ispc_texcomp 32 | PATHS ${CMAKE_CURRENT_SOURCE_DIR} # Finds the pre-build Windows binary as a last resort 33 | DOC "the ispc_texcomp library to link against" 34 | ) 35 | target_link_libraries(img2ktx PRIVATE ${IMG2KTX_ISPC_TEXCOMP_LIB}) 36 | 37 | add_subdirectory(third_party) 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | img2ktx 2 | ======= 3 | 4 | img2ktx is a simple command-line utility. 5 | 6 | It currently runs on Windows, Linux and MacOS. 7 | 8 | It loads images with [stb_image](https://github.com/nothings/stb). Supported formats include 9 | JPEG, PNG, BMP, TGA, GIF, etc. 10 | 11 | It optionally generates mipmap chains with [stb_image_resize](http://github.com/nothings/stb). 12 | 13 | It compresses the mipmaps to BC1, BC3, BC7, ETC1, or ASTC with Intel's 14 | [ISPC Texture Compressor](https://github.com/GameTechDev/ISPCTextureCompressor). It can also 15 | output uncompressed 32-bit RGBA images. Only the Windows ispc_texcomp library is included in 16 | the repo; users on other platforms must provide their own. 17 | 18 | It writes the compressed images to a [KTX](https://www.khronos.org/opengles/sdk/tools/KTX/) file. 19 | If more than one image is provided with identical dimensions, the output KTX file can be either a 20 | 2D texture array or a cubemap. 21 | 22 | Compile 23 | ------- 24 | img2ktx uses submodules for its dependencies. After cloning the repository, be sure to fetch 25 | these dependencies: 26 | ``` 27 | $ git submodule update --init 28 | ``` 29 | 30 | Then use [CMake](https://cmake.org) 3.15+ to generate a project file for your platform. 31 | 32 | Binaries 33 | -------- 34 | Download pre-built binaries from the [Releases](https://github.com/cdwfs/img2ktx/releases) page. 35 | 36 | 37 | TODO 38 | ---- 39 | img2ktx may eventually do the following things as well (but no promises): 40 | 41 | - Output [DDS](https://msdn.microsoft.com/en-us/library/windows/desktop/bb943991(v=vs.85).aspx) files, 42 | because inevitably somebody is going to ask for it. 43 | - Convert animated GIFs directly into array textures, because what 3D graphics application isn't 44 | improved by animated GIFs? 45 | -------------------------------------------------------------------------------- /build_version.cpp.in: -------------------------------------------------------------------------------- 1 | const char* img2ktx_build_version = "@GIT_DESCRIBE_RESULT@"; -------------------------------------------------------------------------------- /build_version.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | extern const char* img2ktx_build_version; -------------------------------------------------------------------------------- /cmake/GetGitRevisionDescription.cmake: -------------------------------------------------------------------------------- 1 | # - Returns a version string from Git 2 | # 3 | # These functions force a re-configure on each git commit so that you can 4 | # trust the values of the variables in your build system. 5 | # 6 | # get_git_head_revision( [ ...]) 7 | # 8 | # Returns the refspec and sha hash of the current head revision 9 | # 10 | # git_describe( [ ...]) 11 | # 12 | # Returns the results of git describe on the source tree, and adjusting 13 | # the output so that it tests false if an error occurs. 14 | # 15 | # git_get_exact_tag( [ ...]) 16 | # 17 | # Returns the results of git describe --exact-match on the source tree, 18 | # and adjusting the output so that it tests false if there was no exact 19 | # matching tag. 20 | # 21 | # git_local_changes() 22 | # 23 | # Returns either "CLEAN" or "DIRTY" with respect to uncommitted changes. 24 | # Uses the return code of "git diff-index --quiet HEAD --". 25 | # Does not regard untracked files. 26 | # 27 | # Requires CMake 2.6 or newer (uses the 'function' command) 28 | # 29 | # Original Author: 30 | # 2009-2010 Ryan Pavlik 31 | # http://academic.cleardefinition.com 32 | # Iowa State University HCI Graduate Program/VRAC 33 | # 34 | # Copyright Iowa State University 2009-2010. 35 | # Distributed under the Boost Software License, Version 1.0. 36 | # (See accompanying file LICENSE_1_0.txt or copy at 37 | # http://www.boost.org/LICENSE_1_0.txt) 38 | 39 | if(__get_git_revision_description) 40 | return() 41 | endif() 42 | set(__get_git_revision_description YES) 43 | 44 | # We must run the following at "include" time, not at function call time, 45 | # to find the path to this module rather than the path to a calling list file 46 | get_filename_component(_gitdescmoddir ${CMAKE_CURRENT_LIST_FILE} PATH) 47 | 48 | function(get_git_head_revision _refspecvar _hashvar) 49 | set(GIT_PARENT_DIR "${CMAKE_CURRENT_SOURCE_DIR}") 50 | set(GIT_DIR "${GIT_PARENT_DIR}/.git") 51 | while(NOT EXISTS "${GIT_DIR}") # .git dir not found, search parent directories 52 | set(GIT_PREVIOUS_PARENT "${GIT_PARENT_DIR}") 53 | get_filename_component(GIT_PARENT_DIR ${GIT_PARENT_DIR} PATH) 54 | if(GIT_PARENT_DIR STREQUAL GIT_PREVIOUS_PARENT) 55 | # We have reached the root directory, we are not in git 56 | set(${_refspecvar} "GITDIR-NOTFOUND" PARENT_SCOPE) 57 | set(${_hashvar} "GITDIR-NOTFOUND" PARENT_SCOPE) 58 | return() 59 | endif() 60 | set(GIT_DIR "${GIT_PARENT_DIR}/.git") 61 | endwhile() 62 | # check if this is a submodule 63 | if(NOT IS_DIRECTORY ${GIT_DIR}) 64 | file(READ ${GIT_DIR} submodule) 65 | string(REGEX REPLACE "gitdir: (.*)\n$" "\\1" GIT_DIR_RELATIVE ${submodule}) 66 | get_filename_component(SUBMODULE_DIR ${GIT_DIR} PATH) 67 | get_filename_component(GIT_DIR ${SUBMODULE_DIR}/${GIT_DIR_RELATIVE} ABSOLUTE) 68 | endif() 69 | if(NOT IS_DIRECTORY "${GIT_DIR}") 70 | file(READ ${GIT_DIR} worktree) 71 | string(REGEX REPLACE "gitdir: (.*)worktrees(.*)\n$" "\\1" GIT_DIR ${worktree}) 72 | endif() 73 | set(GIT_DATA "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/git-data") 74 | if(NOT EXISTS "${GIT_DATA}") 75 | file(MAKE_DIRECTORY "${GIT_DATA}") 76 | endif() 77 | 78 | if(NOT EXISTS "${GIT_DIR}/HEAD") 79 | return() 80 | endif() 81 | set(HEAD_FILE "${GIT_DATA}/HEAD") 82 | configure_file("${GIT_DIR}/HEAD" "${HEAD_FILE}" COPYONLY) 83 | 84 | configure_file("${_gitdescmoddir}/GetGitRevisionDescription.cmake.in" 85 | "${GIT_DATA}/grabRef.cmake" 86 | @ONLY) 87 | include("${GIT_DATA}/grabRef.cmake") 88 | 89 | set(${_refspecvar} "${HEAD_REF}" PARENT_SCOPE) 90 | set(${_hashvar} "${HEAD_HASH}" PARENT_SCOPE) 91 | endfunction() 92 | 93 | function(git_describe _var) 94 | if(NOT GIT_FOUND) 95 | find_package(Git QUIET) 96 | endif() 97 | get_git_head_revision(refspec hash) 98 | if(NOT GIT_FOUND) 99 | set(${_var} "GIT-NOTFOUND" PARENT_SCOPE) 100 | return() 101 | endif() 102 | if(NOT hash) 103 | set(${_var} "HEAD-HASH-NOTFOUND" PARENT_SCOPE) 104 | return() 105 | endif() 106 | 107 | # TODO sanitize 108 | #if((${ARGN}" MATCHES "&&") OR 109 | # (ARGN MATCHES "||") OR 110 | # (ARGN MATCHES "\\;")) 111 | # message("Please report the following error to the project!") 112 | # message(FATAL_ERROR "Looks like someone's doing something nefarious with git_describe! Passed arguments ${ARGN}") 113 | #endif() 114 | 115 | #message(STATUS "Arguments to execute_process: ${ARGN}") 116 | 117 | execute_process(COMMAND 118 | "${GIT_EXECUTABLE}" 119 | describe 120 | ${hash} 121 | ${ARGN} 122 | WORKING_DIRECTORY 123 | "${CMAKE_CURRENT_SOURCE_DIR}" 124 | RESULT_VARIABLE 125 | res 126 | OUTPUT_VARIABLE 127 | out 128 | ERROR_QUIET 129 | OUTPUT_STRIP_TRAILING_WHITESPACE) 130 | if(NOT res EQUAL 0) 131 | set(out "${out}-${res}-NOTFOUND") 132 | endif() 133 | 134 | set(${_var} "${out}" PARENT_SCOPE) 135 | endfunction() 136 | 137 | function(git_get_exact_tag _var) 138 | git_describe(out --exact-match ${ARGN}) 139 | set(${_var} "${out}" PARENT_SCOPE) 140 | endfunction() 141 | 142 | function(git_local_changes _var) 143 | if(NOT GIT_FOUND) 144 | find_package(Git QUIET) 145 | endif() 146 | get_git_head_revision(refspec hash) 147 | if(NOT GIT_FOUND) 148 | set(${_var} "GIT-NOTFOUND" PARENT_SCOPE) 149 | return() 150 | endif() 151 | if(NOT hash) 152 | set(${_var} "HEAD-HASH-NOTFOUND" PARENT_SCOPE) 153 | return() 154 | endif() 155 | 156 | execute_process(COMMAND 157 | "${GIT_EXECUTABLE}" 158 | diff-index --quiet HEAD -- 159 | WORKING_DIRECTORY 160 | "${CMAKE_CURRENT_SOURCE_DIR}" 161 | RESULT_VARIABLE 162 | res 163 | OUTPUT_VARIABLE 164 | out 165 | ERROR_QUIET 166 | OUTPUT_STRIP_TRAILING_WHITESPACE) 167 | if(res EQUAL 0) 168 | set(${_var} "CLEAN" PARENT_SCOPE) 169 | else() 170 | set(${_var} "DIRTY" PARENT_SCOPE) 171 | endif() 172 | endfunction() 173 | -------------------------------------------------------------------------------- /cmake/GetGitRevisionDescription.cmake.in: -------------------------------------------------------------------------------- 1 | # 2 | # Internal file for GetGitRevisionDescription.cmake 3 | # 4 | # Requires CMake 2.6 or newer (uses the 'function' command) 5 | # 6 | # Original Author: 7 | # 2009-2010 Ryan Pavlik 8 | # http://academic.cleardefinition.com 9 | # Iowa State University HCI Graduate Program/VRAC 10 | # 11 | # Copyright Iowa State University 2009-2010. 12 | # Distributed under the Boost Software License, Version 1.0. 13 | # (See accompanying file LICENSE_1_0.txt or copy at 14 | # http://www.boost.org/LICENSE_1_0.txt) 15 | 16 | set(HEAD_HASH) 17 | 18 | file(READ "@HEAD_FILE@" HEAD_CONTENTS LIMIT 1024) 19 | 20 | string(STRIP "${HEAD_CONTENTS}" HEAD_CONTENTS) 21 | if(HEAD_CONTENTS MATCHES "ref") 22 | # named branch 23 | string(REPLACE "ref: " "" HEAD_REF "${HEAD_CONTENTS}") 24 | if(EXISTS "@GIT_DIR@/${HEAD_REF}") 25 | configure_file("@GIT_DIR@/${HEAD_REF}" "@GIT_DATA@/head-ref" COPYONLY) 26 | else() 27 | configure_file("@GIT_DIR@/packed-refs" "@GIT_DATA@/packed-refs" COPYONLY) 28 | file(READ "@GIT_DATA@/packed-refs" PACKED_REFS) 29 | if(${PACKED_REFS} MATCHES "([0-9a-z]*) ${HEAD_REF}") 30 | set(HEAD_HASH "${CMAKE_MATCH_1}") 31 | endif() 32 | endif() 33 | else() 34 | # detached HEAD 35 | configure_file("@GIT_DIR@/HEAD" "@GIT_DATA@/head-ref" COPYONLY) 36 | endif() 37 | 38 | if(NOT HEAD_HASH) 39 | file(READ "@GIT_DATA@/head-ref" HEAD_HASH LIMIT 1024) 40 | string(STRIP "${HEAD_HASH}" HEAD_HASH) 41 | endif() 42 | -------------------------------------------------------------------------------- /img2ktx.cpp: -------------------------------------------------------------------------------- 1 | #include "build_version.h" 2 | 3 | #include "ispc_texcomp.h" 4 | 5 | #pragma warning(push,3) 6 | #define STB_IMAGE_IMPLEMENTATION 7 | #include 8 | 9 | #pragma warning(disable:4702) // unreachable code 10 | #define STB_IMAGE_RESIZE_IMPLEMENTATION 11 | #define STBI_MALLOC(sz) malloc(sz) 12 | #define STBI_REALLOC(p,newsz) realloc(p,newsz) 13 | #define STBI_FREE(p) free(p) 14 | #include 15 | #pragma warning(pop) 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | enum { 24 | // For glFormat 25 | IMG2KTX_GL_RED = 0x1903, 26 | IMG2KTX_GL_RG = 0x8227, 27 | IMG2KTX_GL_RGB = 0x1907, 28 | IMG2KTX_GL_RGBA = 0x1908, 29 | 30 | // For glType 31 | IMG2KTX_GL_UNSIGNED_BYTE = 0x1401, 32 | 33 | // For glInternalFormat 34 | IMG2KTX_GL_RGBA8 = 0x8058, 35 | IMG2KTX_GL_COMPRESSED_RGB_S3TC_DXT1_EXT = 0x83F0, // BC1 (no alpha) 36 | IMG2KTX_GL_COMPRESSED_RGBA_S3TC_DXT1_EXT = 0x83F1, // BC1 (alpha) 37 | IMG2KTX_GL_COMPRESSED_RGBA_S3TC_DXT5_EXT = 0x83F3, // BC3 38 | IMG2KTX_GL_COMPRESSED_RGBA_BPTC_UNORM_ARB = 0x8E8C, // BC7 39 | IMG2KTX_GL_COMPRESSED_RGBA_ASTC_4x4_KHR = 0x93B0, 40 | IMG2KTX_GL_COMPRESSED_RGBA_ASTC_5x4_KHR = 0x93B1, 41 | IMG2KTX_GL_COMPRESSED_RGBA_ASTC_5x5_KHR = 0x93B2, 42 | IMG2KTX_GL_COMPRESSED_RGBA_ASTC_6x5_KHR = 0x93B3, 43 | IMG2KTX_GL_COMPRESSED_RGBA_ASTC_6x6_KHR = 0x93B4, 44 | IMG2KTX_GL_COMPRESSED_RGBA_ASTC_8x5_KHR = 0x93B5, 45 | IMG2KTX_GL_COMPRESSED_RGBA_ASTC_8x6_KHR = 0x93B6, 46 | IMG2KTX_GL_COMPRESSED_RGBA_ASTC_8x8_KHR = 0x93B7, 47 | IMG2KTX_GL_COMPRESSED_RGBA_ASTC_10x5_KHR = 0x93B8, 48 | IMG2KTX_GL_COMPRESSED_RGBA_ASTC_10x6_KHR = 0x93B9, 49 | IMG2KTX_GL_COMPRESSED_RGBA_ASTC_10x8_KHR = 0x93BA, 50 | IMG2KTX_GL_COMPRESSED_RGBA_ASTC_10x10_KHR = 0x93BB, 51 | IMG2KTX_GL_COMPRESSED_RGBA_ASTC_12x10_KHR = 0x93BC, 52 | IMG2KTX_GL_COMPRESSED_RGBA_ASTC_12x12_KHR = 0x93BD, 53 | IMG2KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR = 0x93D0, 54 | IMG2KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR = 0x93D1, 55 | IMG2KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR = 0x93D2, 56 | IMG2KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR = 0x93D3, 57 | IMG2KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR = 0x93D4, 58 | IMG2KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR = 0x93D5, 59 | IMG2KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR = 0x93D6, 60 | IMG2KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR = 0x93D7, 61 | IMG2KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR = 0x93D8, 62 | IMG2KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR = 0x93D9, 63 | IMG2KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR = 0x93DA, 64 | IMG2KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR = 0x93DB, 65 | IMG2KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR = 0x93DC, 66 | IMG2KTX_GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR = 0x93DD, 67 | }; 68 | 69 | struct GlFormatInfo { 70 | const char *name; 71 | uint32_t internal_format; 72 | uint32_t base_format; 73 | uint32_t gl_format; // channel count, effectively (RGB, RGBA, etc). For compressed formats, format=0. 74 | uint32_t gl_type; // type of each channel. For compressed formats, type=0. 75 | uint32_t gl_type_size; // size in bytes of gl_type for endianness conversion. for compressed types, size=1. 76 | uint32_t block_dim_x; 77 | uint32_t block_dim_y; 78 | uint32_t block_bytes; 79 | }; 80 | const GlFormatInfo g_formats[] = { 81 | { "RGBA", IMG2KTX_GL_RGBA8, IMG2KTX_GL_RGBA, IMG2KTX_GL_RGBA, IMG2KTX_GL_UNSIGNED_BYTE, 1, 1, 1, 4 }, 82 | { "BC1", IMG2KTX_GL_COMPRESSED_RGB_S3TC_DXT1_EXT, IMG2KTX_GL_RGB, 0, 0, 1, 4, 4, 8 }, 83 | { "BC1a", IMG2KTX_GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, IMG2KTX_GL_RGBA, 0, 0, 1, 4, 4, 16 }, 84 | { "BC3", IMG2KTX_GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, IMG2KTX_GL_RGBA, 0, 0, 1, 4, 4, 16 }, 85 | { "BC7", IMG2KTX_GL_COMPRESSED_RGBA_BPTC_UNORM_ARB, IMG2KTX_GL_RGBA, 0, 0, 1, 4, 4, 16 }, 86 | { "ASTC4x4", IMG2KTX_GL_COMPRESSED_RGBA_ASTC_4x4_KHR, IMG2KTX_GL_RGBA, 0, 0, 1, 4, 4, 16 }, 87 | { "ASTC5x4", IMG2KTX_GL_COMPRESSED_RGBA_ASTC_5x4_KHR, IMG2KTX_GL_RGBA, 0, 0, 1, 5, 4, 16 }, 88 | { "ASTC5x5", IMG2KTX_GL_COMPRESSED_RGBA_ASTC_5x5_KHR, IMG2KTX_GL_RGBA, 0, 0, 1, 5, 5, 16 }, 89 | { "ASTC6x5", IMG2KTX_GL_COMPRESSED_RGBA_ASTC_6x5_KHR, IMG2KTX_GL_RGBA, 0, 0, 1, 6, 5, 16 }, 90 | { "ASTC6x6", IMG2KTX_GL_COMPRESSED_RGBA_ASTC_6x6_KHR, IMG2KTX_GL_RGBA, 0, 0, 1, 6, 6, 16 }, 91 | { "ASTC8x5", IMG2KTX_GL_COMPRESSED_RGBA_ASTC_8x5_KHR, IMG2KTX_GL_RGBA, 0, 0, 1, 8, 5, 16 }, 92 | { "ASTC8x6", IMG2KTX_GL_COMPRESSED_RGBA_ASTC_8x6_KHR, IMG2KTX_GL_RGBA, 0, 0, 1, 8, 6, 16 }, 93 | { "ASTC8x8", IMG2KTX_GL_COMPRESSED_RGBA_ASTC_8x8_KHR, IMG2KTX_GL_RGBA, 0, 0, 1, 8, 8, 16 }, 94 | }; 95 | const size_t g_format_count = sizeof(g_formats) / sizeof(g_formats[0]); 96 | 97 | struct MipLevel { 98 | std::vector bytes; 99 | }; 100 | struct ImagePixels { 101 | stbi_uc* packed; // base mip level only, tightly packed 102 | std::vector input_mips; // padded 103 | std::vector output_mips; 104 | }; 105 | 106 | 107 | struct KtxHeader { 108 | uint8_t identifier[12]; 109 | uint32_t endianness; 110 | uint32_t glType; 111 | uint32_t glTypeSize; 112 | uint32_t glFormat; 113 | uint32_t glInternalFormat; 114 | uint32_t glBaseInternalFormat; 115 | uint32_t pixelWidth; 116 | uint32_t pixelHeight; 117 | uint32_t pixelDepth; 118 | uint32_t numberOfArrayElements; 119 | uint32_t numberOfFaces; 120 | uint32_t numberOfMipmapLevels; 121 | uint32_t bytesOfKeyValueData; 122 | }; 123 | 124 | void PrintVersion() { 125 | fprintf(stdout, "img2ktx %s\n", img2ktx_build_version); 126 | } 127 | 128 | void PrintUsage(char *argv[]) { 129 | PrintVersion(); 130 | fprintf(stdout, "Usage: %s [options] [input]\n", argv[0]); 131 | fprintf(stdout, R"options(options: 132 | -o [out.ktx] Output file [required] 133 | -f [format] Output format [required] 134 | -r [width height] Resize input to width x height before conversion. 135 | Provided dimensions must both be >= 1. 136 | -m Enable mipmap generation 137 | -c Enable cubemap output. Each set of six input images will be 138 | treated as one cubemap. Face order is +X -X +Y -Y +Z -Z. 139 | -q Enable quiet mode (suppress non-error console output) 140 | -h Displays this help message 141 | -v Displays version information\)options"); 142 | fprintf(stdout, "formats:\n "); 143 | for(int i=0; i input_filenames; 153 | const char *output_filename = nullptr; 154 | const char *output_format_name = nullptr; 155 | bool generate_mipmaps = false; 156 | bool output_as_cubemap = false; 157 | bool quiet_mode = false; 158 | bool base_resize_enable = false; 159 | int base_resize_width = 0, base_resize_height = 0; 160 | for(int a = 1; a < argc; ++a) { 161 | if (strcmp("-o", argv[a]) == 0 && a+1 < argc) { 162 | output_filename = argv[++a]; 163 | } else if (strcmp("-f", argv[a]) == 0 && a+1 < argc) { 164 | output_format_name = argv[++a]; 165 | } else if (strcmp("-r", argv[a]) == 0 && a+2 < argc) { 166 | base_resize_enable = true; 167 | base_resize_width = (int)strtol(argv[++a], nullptr, 10); 168 | base_resize_height = (int)strtol(argv[++a], nullptr, 10); 169 | } else if (strcmp("-m", argv[a]) == 0) { 170 | generate_mipmaps = true; 171 | } else if (strcmp("-c", argv[a]) == 0) { 172 | output_as_cubemap = true; 173 | } else if (strcmp("-q", argv[a]) == 0) { 174 | quiet_mode = true; 175 | } else if (strcmp("-h", argv[a]) == 0) { 176 | PrintUsage(argv); 177 | return 0; 178 | } 179 | else if (strcmp("-v", argv[a]) == 0) { 180 | PrintVersion(); 181 | return 0; 182 | } else { 183 | // All remaining params are input filenames 184 | input_filenames.insert(input_filenames.end(), argv+a, argv+argc); 185 | break; 186 | } 187 | } 188 | if (!output_filename || !output_format_name || input_filenames.empty()) { 189 | PrintUsage(argv); 190 | return -1; 191 | } 192 | if (output_as_cubemap && (input_filenames.size() % 6) != 0) { 193 | fprintf(stderr, "Error: when generating cubemaps, six images are required per cube.\n"); 194 | return -1; 195 | } 196 | if (base_resize_enable && (base_resize_width < 1 || base_resize_height < 1)) { 197 | fprintf(stderr, "Error: resize width (%d) and height (%d) must both be >= 1.\n", 198 | base_resize_width, base_resize_height); 199 | return -1; 200 | } 201 | 202 | // Look up the output format info 203 | const GlFormatInfo *format_info = NULL; 204 | for(int f = 0; f < g_format_count; ++f) { 205 | if (strcmp(g_formats[f].name, output_format_name) == 0) { 206 | format_info = g_formats + f; 207 | break; 208 | } 209 | } 210 | if (format_info == NULL) { 211 | fprintf(stderr, "Error: format %s not supported\n", output_format_name); 212 | return 1; 213 | } 214 | const int bytes_per_block = format_info->block_bytes; 215 | const int block_dim_x = format_info->block_dim_x; 216 | const int block_dim_y = format_info->block_dim_y; 217 | 218 | // Load the input file(s) 219 | int base_width = 0, base_height = 0; 220 | int input_components = 4; // ispc_texcomp requires 32-bit RGBA input 221 | int original_components = 0; 222 | std::vector images(input_filenames.size()); 223 | images[0].packed = stbi_load(input_filenames[0], &base_width, 224 | &base_height, &original_components, input_components); 225 | if (!images[0].packed) { 226 | fprintf(stderr, "Error loading input '%s'\n", input_filenames[0]); 227 | return 2; 228 | } 229 | if (output_as_cubemap && base_width != base_height) { 230 | fprintf(stderr, "Error: when generating cubemaps, input width/height must be equal.\n"); 231 | fprintf(stderr, " %s: %d x %d\n", input_filenames[0], base_width, base_height); 232 | return 4; 233 | } 234 | qprintf("Loaded %s -- width=%d height=%d comp=%d\n", 235 | input_filenames[0], base_width, base_height, 236 | original_components); 237 | // Subsequent files must match dimensions of the first 238 | for(size_t i = 1; i < input_filenames.size(); ++i) { 239 | int bw = 0, bh = 0, oc = 0; 240 | images[i].packed = stbi_load(input_filenames[i], &bw, &bh, &oc, input_components); 241 | if (!images[i].packed) { 242 | fprintf(stderr, "Error loading input '%s'\n", input_filenames[i]); 243 | return 2; 244 | } 245 | if (bw != base_width || bh != base_height) { 246 | fprintf(stderr, "Error: input image dimensions do not match.\n"); 247 | fprintf(stderr, " %s: %d x %d\n", input_filenames[0], base_width, base_height); 248 | fprintf(stderr, " %s: %d x %d\n", input_filenames[i], bw, bh); 249 | return 3; 250 | } 251 | qprintf("Loaded %s -- width=%d height=%d comp=%d\n", 252 | input_filenames[i], bw, bh, oc); 253 | } 254 | // Optionally, resize the input images 255 | if (base_resize_enable) { 256 | for(int layer = 0; layer < (int)images.size(); ++layer) { 257 | auto& img = images[layer]; 258 | stbi_uc* resized_packed = (stbi_uc*)STBI_MALLOC(base_resize_width * base_resize_height * input_components); 259 | stbir_resize_uint8( 260 | img.packed, base_width, base_height, base_width * input_components, 261 | resized_packed, base_resize_width, base_resize_height, base_resize_width * input_components, 262 | input_components); 263 | stbi_image_free(img.packed); 264 | img.packed = resized_packed; 265 | } 266 | qprintf("Resized inputs (old: width=%d height=%d, new: width=%d height=%d\n", 267 | base_width, base_height, base_resize_width, base_resize_height); 268 | base_width = base_resize_width; 269 | base_height = base_resize_height; 270 | } 271 | 272 | // Determine mip chain properties 273 | int mip_levels = 1; 274 | if (generate_mipmaps) { 275 | int mip_w = base_width, mip_h = base_height; 276 | while (mip_w > 1 || mip_h > 1) { 277 | mip_levels += 1; 278 | mip_w = std::max(1, mip_w / 2); 279 | mip_h = std::max(1, mip_h / 2); 280 | } 281 | } 282 | std::vector output_mip_sizes(mip_levels); 283 | 284 | // Generate the input mipmap chain(s). At every level, the input 285 | // width and height must be padded up to a multiple of the output 286 | // block dimensions. 287 | for(int layer = 0; layer < (int)images.size(); ++layer) { 288 | int mip_width = base_width; 289 | int mip_height = base_height; 290 | int mip_pitch_x = ((mip_width + block_dim_x - 1) / block_dim_x) * block_dim_x; 291 | int mip_pitch_y = ((mip_height + block_dim_y - 1) / block_dim_y) * block_dim_y; 292 | 293 | auto& img = images[layer]; 294 | img.input_mips.resize(mip_levels); 295 | img.output_mips.resize(mip_levels); 296 | // Populate padded input mip level 0 297 | img.input_mips[0].bytes.resize(mip_pitch_x * mip_pitch_y * input_components); 298 | // memset(img.input_mips[0].bytes.data(), 0, mip_pitch_x * mip_pitch_y * input_components); 299 | for(int y=0; yblock_dim_x, format_info->block_dim_y); 367 | } else if (original_components == 4) { 368 | GetProfile_astc_alpha_fast(&enc_settings, 369 | format_info->block_dim_x, format_info->block_dim_y); 370 | } 371 | CompressBlocksASTC(&input_surface, img.output_mips[mip].bytes.data(), 372 | &enc_settings); 373 | } 374 | mip_width = std::max(1, mip_width / 2); 375 | mip_height = std::max(1, mip_height / 2); 376 | } 377 | } 378 | 379 | // Output to KTX 380 | KtxHeader header = {}; 381 | const uint8_t ktx_magic_id[12] = { 382 | 0xAB, 0x4B, 0x54, 0x58, 383 | 0x20, 0x31, 0x31, 0xBB, 384 | 0x0D, 0x0A, 0x1A, 0x0A 385 | }; 386 | memcpy(header.identifier, ktx_magic_id, 12); 387 | header.endianness = 0x04030201; 388 | header.glType = format_info->gl_type; 389 | header.glTypeSize = format_info->gl_type_size; 390 | header.glFormat = format_info->gl_format; 391 | header.glInternalFormat = format_info->internal_format; 392 | header.glBaseInternalFormat = format_info->base_format; 393 | header.pixelWidth = base_width; 394 | header.pixelHeight = base_height; 395 | header.pixelDepth = 0; // must be 0 for 2D/cubemap textures 396 | uint32_t real_array_element_count = (uint32_t)(images.size() / (output_as_cubemap ? 6 : 1)); 397 | // KTX spec says this field must be 0 for non-array textures 398 | header.numberOfArrayElements = (real_array_element_count > 1) ? real_array_element_count : 0; 399 | header.numberOfFaces = output_as_cubemap ? 6 : 1; 400 | header.numberOfMipmapLevels = mip_levels; 401 | header.bytesOfKeyValueData = 0; 402 | FILE *output_file = fopen(output_filename, "wb"); 403 | if (!output_file) { 404 | fprintf(stderr, "Error opening output '%s'\n", output_filename); 405 | return 3; 406 | } 407 | fwrite(&header, 1, sizeof(KtxHeader), output_file); 408 | uint32_t zero = 0; 409 | for(int mip=0; mip 35 | 36 | struct rgba_surface 37 | { 38 | uint8_t* ptr; 39 | int32_t width; 40 | int32_t height; 41 | int32_t stride; // in bytes 42 | }; 43 | 44 | struct bc7_enc_settings 45 | { 46 | bool mode_selection[4]; 47 | int refineIterations[8]; 48 | 49 | bool skip_mode2; 50 | int fastSkipTreshold_mode1; 51 | int fastSkipTreshold_mode3; 52 | int fastSkipTreshold_mode7; 53 | 54 | int mode45_channel0; 55 | int refineIterations_channel; 56 | 57 | int channels; 58 | }; 59 | 60 | struct bc6h_enc_settings 61 | { 62 | bool slow_mode; 63 | bool fast_mode; 64 | int refineIterations_1p; 65 | int refineIterations_2p; 66 | int fastSkipTreshold; 67 | }; 68 | 69 | struct etc_enc_settings 70 | { 71 | int fastSkipTreshold; 72 | }; 73 | 74 | struct astc_enc_settings 75 | { 76 | int block_width; 77 | int block_height; 78 | int channels; 79 | 80 | int fastSkipTreshold; 81 | int refineIterations; 82 | }; 83 | 84 | // profiles for RGB data (alpha channel will be ignored) 85 | extern "C" void GetProfile_ultrafast(bc7_enc_settings* settings); 86 | extern "C" void GetProfile_veryfast(bc7_enc_settings* settings); 87 | extern "C" void GetProfile_fast(bc7_enc_settings* settings); 88 | extern "C" void GetProfile_basic(bc7_enc_settings* settings); 89 | extern "C" void GetProfile_slow(bc7_enc_settings* settings); 90 | 91 | // profiles for RGBA inputs 92 | extern "C" void GetProfile_alpha_ultrafast(bc7_enc_settings* settings); 93 | extern "C" void GetProfile_alpha_veryfast(bc7_enc_settings* settings); 94 | extern "C" void GetProfile_alpha_fast(bc7_enc_settings* settings); 95 | extern "C" void GetProfile_alpha_basic(bc7_enc_settings* settings); 96 | extern "C" void GetProfile_alpha_slow(bc7_enc_settings* settings); 97 | 98 | // profiles for BC6H (RGB HDR) 99 | extern "C" void GetProfile_bc6h_veryfast(bc6h_enc_settings* settings); 100 | extern "C" void GetProfile_bc6h_fast(bc6h_enc_settings* settings); 101 | extern "C" void GetProfile_bc6h_basic(bc6h_enc_settings* settings); 102 | extern "C" void GetProfile_bc6h_slow(bc6h_enc_settings* settings); 103 | extern "C" void GetProfile_bc6h_veryslow(bc6h_enc_settings* settings); 104 | 105 | // profiles for ETC 106 | extern "C" void GetProfile_etc_slow(etc_enc_settings* settings); 107 | 108 | // profiles for ASTC 109 | extern "C" void GetProfile_astc_fast(astc_enc_settings* settings, int block_width, int block_height); 110 | extern "C" void GetProfile_astc_alpha_fast(astc_enc_settings* settings, int block_width, int block_height); 111 | extern "C" void GetProfile_astc_alpha_slow(astc_enc_settings* settings, int block_width, int block_height); 112 | 113 | // helper function to replicate border pixels for the desired block sizes (bpp = 32 or 64) 114 | extern "C" void ReplicateBorders(rgba_surface* dst_slice, const rgba_surface* src_tex, int x, int y, int bpp); 115 | 116 | /* 117 | Notes: 118 | - input width and height need to be a multiple of block size 119 | - LDR input is 32 bit/pixel (sRGB), HDR is 64 bit/pixel (half float) 120 | - dst buffer must be allocated with enough space for the compressed texture: 121 | 4 bytes/block for BC1/ETC1, 8 bytes/block for BC3/BC6H/BC7/ASTC 122 | the blocks are stored in raster scan order (natural CPU texture layout) 123 | - you can use GetProfile_* functions to select various speed/quality tradeoffs. 124 | - the RGB profiles are slightly faster as they ignore the alpha channel 125 | */ 126 | 127 | extern "C" void CompressBlocksBC1(const rgba_surface* src, uint8_t* dst); 128 | extern "C" void CompressBlocksBC3(const rgba_surface* src, uint8_t* dst); 129 | extern "C" void CompressBlocksBC6H(const rgba_surface* src, uint8_t* dst, bc6h_enc_settings* settings); 130 | extern "C" void CompressBlocksBC7(const rgba_surface* src, uint8_t* dst, bc7_enc_settings* settings); 131 | extern "C" void CompressBlocksETC1(const rgba_surface* src, uint8_t* dst, etc_enc_settings* settings); 132 | extern "C" void CompressBlocksASTC(const rgba_surface* src, uint8_t* dst, astc_enc_settings* settings); 133 | -------------------------------------------------------------------------------- /ispc_texcomp.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdwfs/img2ktx/1607ea408125f1d4c091111169709b9d4e1ede01/ispc_texcomp.lib -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | Copyright (c) 2017 Intel 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | The above copyright notice and this permission notice shall be included in all 10 | copies or substantial portions of the Software. 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 15 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 17 | SOFTWARE. 18 | -------------------------------------------------------------------------------- /third_party/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Suppress all warnings from external projects. 2 | if(${MSVC}) 3 | set_property(DIRECTORY APPEND PROPERTY COMPILE_OPTIONS /W0) 4 | elseif(${UNIX}) 5 | set_property(DIRECTORY APPEND PROPERTY COMPILE_OPTIONS -w) 6 | endif() 7 | 8 | # stb 9 | target_include_directories(img2ktx PRIVATE ${CMAKE_CURRENT_LIST_DIR}/stb) 10 | target_sources(img2ktx PRIVATE 11 | ${CMAKE_CURRENT_LIST_DIR}/stb/stb_image.h 12 | ${CMAKE_CURRENT_LIST_DIR}/stb/stb_image_resize.h 13 | ) 14 | --------------------------------------------------------------------------------