<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>craftablescience::blog</title><link>https://craftablescience.info/blog/</link><description>Recent content on craftablescience::blog</description><generator>Hugo -- gohugo.io</generator><language>en</language><copyright>CC0</copyright><lastBuildDate>Wed, 04 Dec 2024 00:00:00 +0000</lastBuildDate><atom:link href="https://craftablescience.info/blog/index.xml" rel="self" type="application/rss+xml"/><item><title>Tutorial: Custom Thumbnails (Almost) Everywhere</title><link>https://craftablescience.info/blog/posts/tutorial_thumbnailer/</link><pubDate>Wed, 04 Dec 2024 00:00:00 +0000</pubDate><guid>https://craftablescience.info/blog/posts/tutorial_thumbnailer/</guid><description>Background / Definitions I work with the Source engine a lot. I recently made a C++ library for working with Source engine textures, and I&amp;rsquo;m reasonably proud of it. While I was working on it, a friend of mine said they&amp;rsquo;d like to use it to make thumbnails for these texture files, so they&amp;rsquo;d be much easier to work with in a file browser. I said &amp;ldquo;hey that sounds really fun&amp;rdquo; and proceeded to steal their idea and make it myself (with permission).</description><content>&lt;h2 id="background--definitions">Background / Definitions&lt;/h2>
&lt;p>I work with the Source engine a lot. I recently made a C++ library for working with Source engine textures, and I&amp;rsquo;m
reasonably proud of it. While I was working on it, a friend of mine said they&amp;rsquo;d like to use it to make thumbnails for
these texture files, so they&amp;rsquo;d be much easier to work with in a file browser. I said &amp;ldquo;hey that sounds really fun&amp;rdquo; and
proceeded to steal their idea and make it myself (with permission). This is how that went down.&lt;/p>
&lt;p>For simplicity, I use the terms &amp;ldquo;thumbnail creator&amp;rdquo;, &amp;ldquo;thumbnail generator&amp;rdquo;, &amp;ldquo;thumbnail provider&amp;rdquo;, and &amp;ldquo;thumbnailer&amp;rdquo;
interchangeably to mean a program or library that handles generating thumbnails.&lt;/p>
&lt;h2 id="thumbnailer-types">Thumbnailer Types&lt;/h2>
&lt;p>Before getting into the process of making thumbnailers, there are two things you should know. There are two main methods
of handling thumbnail generation, both with upsides and downsides.&lt;/p>
&lt;ol>
&lt;li>Run a command (usually calling a custom program) which creates the thumbnail image on disk.
&lt;ul>
&lt;li>Pros:
&lt;ul>
&lt;li>Easy to program.&lt;/li>
&lt;li>Can easily use shell scripts or other scripting languages to generate the thumbnail.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Cons:
&lt;ul>
&lt;li>Time to generate and display a thumbnail is dependent on how fast your storage medium is.&lt;/li>
&lt;li>The thumbnail must be read by a separate process to be displayed.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Hook into a library which exposes a function to get the raw thumbnail data.
&lt;ul>
&lt;li>Pros:
&lt;ul>
&lt;li>More control is given to the calling process over what to do with the data. Should it be written to disk?
Perhaps, but maybe for some file types we don&amp;rsquo;t want to cache the thumbnail because it can be generated very
quickly.&lt;/li>
&lt;li>Time to generate and display a thumbnail is not dependent on how fast your storage medium is.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>Cons:
&lt;ul>
&lt;li>More difficult to program. The library will likely depend on OS-specific code, and there&amp;rsquo;s usually some degree of
boilerplate that must be added.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;p>Linux thumbnailers use the former method. KDE&amp;rsquo;s thumbnail plugins, Windows thumbnail generators, and macOS thumbnail
plugins use the latter method.&lt;/p>
&lt;h3 id="linux-generic">Linux (Generic)&lt;/h3>
&lt;p>I started here, at the easiest end of the spectrum. Doing some basic research, I found the &lt;code>tumbler&lt;/code> service, as well as
most Linux file browsers, read thumbnailer entries stored in &lt;code>$PREFIX/share/thumbnailers&lt;/code> (where &lt;code>$PREFIX&lt;/code> is usually
&lt;code>/usr&lt;/code>) to figure out how to generate thumbnails. Thumbnail entries are simple key-value files that look very similar to
desktop entries. They begin with the text &lt;code>[Thumbnailer Entry]&lt;/code>, and support three keys.&lt;/p>
&lt;ul>
&lt;li>&lt;code>TryExec&lt;/code>: optional, the program to check the existence of before executing the command to create the thumbnail.&lt;/li>
&lt;li>&lt;code>Exec&lt;/code>: required, the command that will result in the thumbnail&amp;rsquo;s creation if successful.&lt;/li>
&lt;li>&lt;code>MimeType&lt;/code>: required, a semicolon-separated and terminated list of MIME types to generate thumbnails for.&lt;/li>
&lt;/ul>
&lt;p>The command put in for the &lt;code>Exec&lt;/code> key supports certain substitutions.&lt;/p>
&lt;ul>
&lt;li>&lt;code>%u&lt;/code>: the URI pointing to the input file.&lt;/li>
&lt;li>&lt;code>%i&lt;/code>: the path to the input file.&lt;/li>
&lt;li>&lt;code>%o&lt;/code>: the path to the output file.&lt;/li>
&lt;li>&lt;code>%s&lt;/code>: the maximum desired size of the thumbnail for either dimension, in pixels. This will probably be a power of two
between 128 and 4096, inclusive.&lt;/li>
&lt;li>&lt;code>%%&lt;/code>: the escaped percent character.&lt;/li>
&lt;/ul>
&lt;p>For the &lt;code>MimeType&lt;/code> field I needed to create a custom MIME type entry, since VTF files are not part of any universally
recognized standard. This would also come in handy for the KDE thumbnailer.
&lt;a href="https://specifications.freedesktop.org/shared-mime-info-spec">The MIME type entry standard can be found here.&lt;/a> Mine was
very simple and looked like this.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="859613724" type="checkbox" />
&lt;label for="859613724">
&lt;span class="collapsable-code__language">xml&lt;/span>
&lt;span class="collapsable-code__title">vtf-thumbnailer.xml&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-xml" >&lt;code>
&amp;lt;?xml version=&amp;#34;1.0&amp;#34; encoding=&amp;#34;UTF-8&amp;#34;?&amp;gt;
&amp;lt;mime-info xmlns=&amp;#34;http://www.freedesktop.org/standards/shared-mime-info&amp;#34;&amp;gt;
&amp;lt;mime-type type=&amp;#34;image/x-vtf&amp;#34;&amp;gt;
&amp;lt;comment&amp;gt;Valve Texture Format File&amp;lt;/comment&amp;gt;
&amp;lt;acronym&amp;gt;VTF&amp;lt;/acronym&amp;gt;
&amp;lt;expanded-acronym&amp;gt;Valve Texture Format&amp;lt;/expanded-acronym&amp;gt;
&amp;lt;glob-deleteall/&amp;gt;
&amp;lt;glob pattern=&amp;#34;*.vtf&amp;#34;/&amp;gt;
&amp;lt;glob pattern=&amp;#34;*.VTF&amp;#34;/&amp;gt;
&amp;lt;/mime-type&amp;gt;
&amp;lt;/mime-info&amp;gt;
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>Thus, my complete thumbnailer entry looked like this.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="829657134" type="checkbox" />
&lt;label for="829657134">
&lt;span class="collapsable-code__language">toml&lt;/span>
&lt;span class="collapsable-code__title">vtf-thumbnailer.thumbnailer&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-toml" >&lt;code>
[Thumbnailer Entry]
TryExec=/opt/vtf-thumbnailer/vtf-thumbnailer
Exec=/opt/vtf-thumbnailer/vtf-thumbnailer -i %i -o %o -s %s
MimeType=image/x-vtf;
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>(Yes, I install my software in &lt;code>/opt&lt;/code>. No particular reason why, it&amp;rsquo;s just how I learned to do it.)&lt;/p>
&lt;p>The program uses these values to create the thumbnail. Thanks to the
&lt;a href="https://specifications.freedesktop.org/thumbnail-spec">FreeDesktop thumbnail specification&lt;/a>, we can assume the output
image from a given thumbnailer will be an 8-bit PNG. The specification also says you should set some extra metadata in
the PNG, but this is only required if you&amp;rsquo;d like the thumbnails to update when their original files&amp;rsquo; contents change.
These fields are listed in the aforementioned FreeDesktop specification, and are reproduced here for convenience.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Key&lt;/th>
&lt;th>Description&lt;/th>
&lt;th>Useful?&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>Thumb::URI&lt;/code>&lt;/td>
&lt;td>The URI of the original file.&lt;/td>
&lt;td>Used to find the original file.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>Thumb::MTime&lt;/code>&lt;/td>
&lt;td>The modification time of the original file.&lt;/td>
&lt;td>Used to invalidate cache entries when the original file&amp;rsquo;s modification time exceeds the cache entry.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>Thumb::Size&lt;/code>&lt;/td>
&lt;td>The size in bytes of the original file.&lt;/td>
&lt;td>Can help in verifying the original file has or hasn&amp;rsquo;t changed, but mostly pointless.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>Thumb::Mimetype&lt;/code>&lt;/td>
&lt;td>The file MIME type.&lt;/td>
&lt;td>Mostly pointless.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>Description&lt;/code>&lt;/td>
&lt;td>A description of the thumbnail for accessibility purposes.&lt;/td>
&lt;td>Unknown how you&amp;rsquo;d generate this automatically.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>Software&lt;/code>&lt;/td>
&lt;td>The name of the thumbnailer that generated the thumbnail.&lt;/td>
&lt;td>Pointless.&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>I ignored this metadata step because I was using &lt;code>stb_image_write&lt;/code> to save the PNG, and it doesn&amp;rsquo;t support setting PNG
metadata fields. It didn&amp;rsquo;t really impact the thumbnailer&amp;rsquo;s functionality, so I didn&amp;rsquo;t want to go to the extra trouble.&lt;/p>
&lt;hr>
&lt;p>Three files are written when installing this application. No further installation steps are required beyond writing
these files, although a reboot might be necessary to get everything working.&lt;/p>
&lt;ul>
&lt;li>&lt;code>/opt/vtf-thumbnailer/vtf-thumbnailer&lt;/code>&lt;/li>
&lt;li>&lt;code>/usr/share/mime/packages/vtf-thumbnailer.xml&lt;/code>&lt;/li>
&lt;li>&lt;code>/usr/share/thumbnailers/vtf-thumbnailer.thumbnailer&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>Linux&amp;rsquo;s thumbnailer system is exceedingly simple and very pleasant to work with in general, even if it is a bit slow.&lt;/p>
&lt;h3 id="linux-kde">Linux (KDE)&lt;/h3>
&lt;p>As I finished up work on the thumbnailer, I noticed it was not working in Dolphin. I installed Thunar, observed it
working there (which was cool), and then realized Dolphin (or really KDE in general) has a completely different
framework for serving thumbnails. This is where things start to get painful.&lt;/p>
&lt;p>Dolphin uses KDE-specific plugins, which in turn depend on Qt. This is the basic C++ boilerplate, extracted from the KDE
developers&amp;rsquo; sample thumbnailer plugin repository.&lt;/p>
&lt;p>
&lt;div class="collapsable-code">
&lt;input id="398614257" type="checkbox" />
&lt;label for="398614257">
&lt;span class="collapsable-code__language">C++&lt;/span>
&lt;span class="collapsable-code__title">kde.h&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-cpp" >&lt;code>
#include &amp;lt;KIO/ThumbnailCreator&amp;gt;
#include &amp;lt;QImage&amp;gt;
class VTFThumbCreator : public KIO::ThumbnailCreator {
public:
using ThumbnailCreator::ThumbnailCreator;
KIO::ThumbnailResult create(const KIO::ThumbnailRequest&amp;amp; request) override;
};
&lt;/code>&lt;/pre>
&lt;/div>
&lt;div class="collapsable-code">
&lt;input id="124387596" type="checkbox" />
&lt;label for="124387596">
&lt;span class="collapsable-code__language">C++&lt;/span>
&lt;span class="collapsable-code__title">kde.cpp&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-cpp" >&lt;code>
#include &amp;#34;kde.h&amp;#34;
#include &amp;lt;KPluginFactory&amp;gt;
K_PLUGIN_CLASS_WITH_JSON(VTFThumbCreator, &amp;#34;plugin.json&amp;#34;)
KIO::ThumbnailResult VTFThumbCreator::create(const KIO::ThumbnailRequest&amp;amp; request) {
// return KIO::ThumbnailResult::pass(image) with image being a valid QImage
// instance on success
return KIO::ThumbnailResult::fail();
}
#include &amp;lt;kde.moc&amp;gt;
&lt;/code>&lt;/pre>
&lt;/div>
&lt;/p>
&lt;p>All KDE library plugins have a &lt;code>plugin.json&lt;/code> file which gets packed into the binary. In this case it holds
important metadata such as whether to cache generated thumbnails, MIME types to generate thumbnails for, and the plugin
name.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="638457192" type="checkbox" />
&lt;label for="638457192">
&lt;span class="collapsable-code__language">json&lt;/span>
&lt;span class="collapsable-code__title">plugin.json&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-json" >&lt;code>
{
&amp;#34;CacheThumbnail&amp;#34;: false,
&amp;#34;KPlugin&amp;#34;: {
&amp;#34;MimeTypes&amp;#34;: [&amp;#34;image/x-vtf&amp;#34;],
&amp;#34;Name&amp;#34;: &amp;#34;Valve Texture Format Files (VTF Thumbnailer)&amp;#34;
},
&amp;#34;MimeType&amp;#34;: &amp;#34;image/x-vtf;&amp;#34;
}
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>The plugin name will be read by Dolphin and appear as the name of the entry in the previews settings. (And by the way,
new plugins must be manually enabled there. If your plugin doesn&amp;rsquo;t work, make sure it&amp;rsquo;s both showing up in this menu and
that it&amp;rsquo;s enabled.)&lt;/p>
&lt;p>Two more files are necessary for thumbnailer plugins, the XML metadata file and the desktop entry. The metadata file is
meant for something like a storefront, where it shows the author, version, description of the plugin, and so on.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="752368419" type="checkbox" />
&lt;label for="752368419">
&lt;span class="collapsable-code__language">xml&lt;/span>
&lt;span class="collapsable-code__title">info.craftablescience.vtf-thumbnailer.metainfo.xml&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-xml" >&lt;code>
&amp;lt;?xml version=&amp;#34;1.0&amp;#34; encoding=&amp;#34;utf-8&amp;#34;?&amp;gt;
&amp;lt;component type=&amp;#34;addon&amp;#34;&amp;gt;
&amp;lt;id&amp;gt;info.craftablescience.vtf-thumbnailer&amp;lt;/id&amp;gt;
&amp;lt;metadata_license&amp;gt;MIT&amp;lt;/metadata_license&amp;gt;
&amp;lt;project_license&amp;gt;MIT&amp;lt;/project_license&amp;gt;
&amp;lt;extends&amp;gt;org.kde.dolphin.desktop&amp;lt;/extends&amp;gt;
&amp;lt;extends&amp;gt;org.kde.konqueror.desktop&amp;lt;/extends&amp;gt;
&amp;lt;extends&amp;gt;org.kde.krusader.desktop&amp;lt;/extends&amp;gt;
&amp;lt;extends&amp;gt;org.kde.gwenview.desktop&amp;lt;/extends&amp;gt;
&amp;lt;name&amp;gt;VTF Thumbnailer&amp;lt;/name&amp;gt;
&amp;lt;summary&amp;gt;Valve Texture Format thumbnail generator&amp;lt;/summary&amp;gt;
&amp;lt;description&amp;gt;
&amp;lt;p&amp;gt;This plugin allow KDE software to display thumbnails for Valve Texture Format files.&amp;lt;/p&amp;gt;
&amp;lt;/description&amp;gt;
&amp;lt;url type=&amp;#34;homepage&amp;#34;&amp;gt;https://github.com/craftablescience/vtf-thumbnailer&amp;lt;/url&amp;gt;
&amp;lt;url type=&amp;#34;bugtracker&amp;#34;&amp;gt;https://github.com/craftablescience/vtf-thumbnailer/issues&amp;lt;/url&amp;gt;
&amp;lt;project_group&amp;gt;craftablescience&amp;lt;/project_group&amp;gt;
&amp;lt;categories&amp;gt;
&amp;lt;category&amp;gt;Graphics&amp;lt;/category&amp;gt;
&amp;lt;/categories&amp;gt;
&amp;lt;icon type=&amp;#34;stock&amp;#34;&amp;gt;application-postscript&amp;lt;/icon&amp;gt;
&amp;lt;/component&amp;gt;
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>The desktop entry is used to identify the plugin as a thumbnailer. One important thing to note is when
&lt;code>ThumbnailerVersion&lt;/code> is incremented after an update, all cached thumbnails created with this thumbnailer will be
regenerated if caching is enabled. (Yes, most of these keys are duplicated from the plugin JSON. I don&amp;rsquo;t exactly know
why this file needs to exist.)&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="851243697" type="checkbox" />
&lt;label for="851243697">
&lt;span class="collapsable-code__language">toml&lt;/span>
&lt;span class="collapsable-code__title">vtf-thumbnailer.desktop&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-toml" >&lt;code>
[Desktop Entry]
Type=Service
Name=Valve Texture Format Files (VTF Thumbnailer)
X-KDE-ServiceTypes=ThumbCreator
MimeType=image/x-vtf;
X-KDE-Library=vtf-thumbnailer
CacheThumbnail=false
ThumbnailerVersion=1
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>Possibly the most important file that is needed is the CMake buildscript. CMake is essentially required here, as you
need to use the KDE CMake utilities to get the correct installation paths for the various files, find/link to KDE
Framework libraries, and create the plugin target. Unfortunately it&amp;rsquo;s a lot of CMake. As with pretty much everything
else, I copied most of it from other thumbnailers, making modifications where necessary to allow it to compile for
both KDE v5 and v6 depending on the given value of &lt;code>QT_MAJOR_VERSION&lt;/code>.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="524137698" type="checkbox" checked />
&lt;label for="524137698">
&lt;span class="collapsable-code__language">cmake&lt;/span>
&lt;span class="collapsable-code__title">CMakeLists.txt&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-cmake" >&lt;code>
set(QT_MIN_VERSION &amp;#34;5.15.2&amp;#34;)
set(KF_MIN_VERSION &amp;#34;5.92.0&amp;#34;)
set(KDE_COMPILERSETTINGS_LEVEL &amp;#34;5.82&amp;#34;)
find_package(ECM ${KF_MIN_VERSION} REQUIRED NO_MODULE)
set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH})
include(ECMOptionalAddSubdirectory)
include(KDEInstallDirs${QT_MAJOR_VERSION})
include(KDECMakeSettings)
include(KDECompilerSettings NO_POLICY_SCOPE)
include(FeatureSummary)
include(ECMDeprecationSettings)
find_package(Qt${QT_MAJOR_VERSION} ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS Gui)
find_package(KF${QT_MAJOR_VERSION} ${KF_MIN_VERSION} REQUIRED COMPONENTS KIO)
# Plugin
ecm_set_disabled_deprecation_versions(QT 5.15.2 KF 5.100.0)
set(BUILD_SHARED_LIBS ON)
kcoreaddons_add_plugin(vtf-thumbnailer INSTALL_NAMESPACE &amp;#34;kf${QT_MAJOR_VERSION}/vtf-thumbnailer&amp;#34;)
target_sources(vtf-thumbnailer PRIVATE
&amp;#34;${CMAKE_CURRENT_SOURCE_DIR}/src/common.cpp&amp;#34;
&amp;#34;${CMAKE_CURRENT_SOURCE_DIR}/src/common.h&amp;#34;
&amp;#34;${CMAKE_CURRENT_SOURCE_DIR}/src/kde.cpp&amp;#34;
&amp;#34;${CMAKE_CURRENT_SOURCE_DIR}/src/kde.h&amp;#34;)
target_link_libraries(vtf-thumbnailer PUBLIC
KF${QT_MAJOR_VERSION}::KIOGui
Qt::Gui)
install(TARGETS vtf-thumbnailer
DESTINATION ${KDE_INSTALL_PLUGINDIR})
# Desktop
if(QT_MAJOR_VERSION STREQUAL &amp;#34;6&amp;#34;)
set(KDE_INSTALL_KSERVICESDIR &amp;#34;${KDE_INSTALL_DATADIR}/kio&amp;#34;)
endif()
install(FILES &amp;#34;${CMAKE_CURRENT_SOURCE_DIR}/kde/vtf-thumbnailer.desktop&amp;#34;
DESTINATION ${KDE_INSTALL_KSERVICESDIR})
# Metadata
install(FILES &amp;#34;${CMAKE_CURRENT_SOURCE_DIR}/kde/info.craftablescience.vtf-thumbnailer.metainfo.xml&amp;#34;
DESTINATION ${KDE_INSTALL_METAINFODIR})
# MIME type info
install(FILES &amp;#34;${CMAKE_CURRENT_SOURCE_DIR}/linux/vtf-thumbnailer.xml&amp;#34;
DESTINATION ${KDE_INSTALL_MIMEDIR}
RENAME &amp;#34;vtf-thumbnailer-kde${QT_MAJOR_VERSION}.xml&amp;#34;)
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>The version of Qt that you compile against matters. If you want your thumbnails to work on KDE Plasma v5, compile
against Qt v5. Similarly, for KDE Plasma v6 compile against Qt v6. Qt guarantees compatibility for basic objects like
&lt;code>QImage&lt;/code> for the entire duration of major versions, so use earlier minor versions of Qt to maximize compatibility. It&amp;rsquo;s
expected that you will ship multiple versions of your thumbnailer built for different major KDE versions (or simply
target the latest version and call it a day, but keep in mind as of 2024 Debian still hasn&amp;rsquo;t updated to KDE v6).
Unfortunately, I couldn&amp;rsquo;t figure out how to compile the plugin for KDE v6 in GitHub Actions, so I only created a release
for KDE v5.&lt;/p>
&lt;p>Finally, to actually compile this code you need some packages installed. On Debian-based distros the packages are called
&lt;code>extra-cmake-modules&lt;/code> and &lt;code>libkf5kio-dev&lt;/code>, accessible through &lt;code>apt&lt;/code>. This took me far longer than I&amp;rsquo;d like to admit to
figure out, as most of the official thumbnailers don&amp;rsquo;t specify what packages they need to build. I found a listing of
KDE&amp;rsquo;s framework packages on a forum post and slowly pared it down until these two were left.&lt;/p>
&lt;hr>
&lt;p>Five files are written when installing this plugin. For KDE v5, the paths look like this. The computer may need a reboot
after installation. I haven&amp;rsquo;t yet figured out if that&amp;rsquo;s completely necessary, but it doesn&amp;rsquo;t hurt.&lt;/p>
&lt;ul>
&lt;li>&lt;code>/usr/lib/plugins/vtf-thumbnailer.so&lt;/code>&lt;/li>
&lt;li>&lt;code>/usr/lib/plugins/kf5/vtf-thumbnailer/vtf-thumbnailer.so&lt;/code> (I still have not figured out why the library gets
installed to two separate locations!)&lt;/li>
&lt;li>&lt;code>/usr/share/mime/packages/vtf-thumbnailer-kde5.xml&lt;/code>&lt;/li>
&lt;li>&lt;code>/usr/share/metainfo/info.craftablescience.vtf-thumbnailer.metainfo.xml&lt;/code>&lt;/li>
&lt;li>&lt;code>/usr/share/kservices5/vtf-thumbnailer.desktop&lt;/code>&lt;/li>
&lt;/ul>
&lt;h3 id="windows-vista-onward">Windows (Vista onward)&lt;/h3>
&lt;p>Hell. It&amp;rsquo;s hell here. After finishing the KDE thumbnail plugin it took about 18 hours more work to figure out the
Windows thumbnail provider, and that&amp;rsquo;s while referencing several examples. Maybe I&amp;rsquo;m stupid, but I prefer to think
Windows is a pile of convoluted garbage that&amp;rsquo;s been left in the sun too long. I made small tweaks and changes to this
code for &lt;em>ages&lt;/em>, and the reason it wasn&amp;rsquo;t working, for presumably several hours, was that I wasn&amp;rsquo;t exporting all the
necessary symbols. But the thing is, some symbols here cannot be exported from C++ with a &lt;code>__declspec&lt;/code> declaration,
because it would be redefining symbols from some random Windows header. Making a &lt;code>.def&lt;/code> file to control symbol
visibility is &lt;em>required&lt;/em>.&lt;/p>
&lt;p>Anyway, Windows is interesting. The thumbnail provider returns an &lt;code>HBITMAP&lt;/code>, very much like the KDE plugin&amp;rsquo;s &lt;code>QImage&lt;/code>
but infinitely worse. Essentially it&amp;rsquo;s the same style of code as the KDE plugin but with worse types and infinitely
more boilerplate. The metadata files are eschewed for the registry (of course), so registering the thumbnail
provider is fairly trivial at least. This is what the code ended up looking like.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="453296178" type="checkbox" />
&lt;label for="453296178">
&lt;span class="collapsable-code__language">C++&lt;/span>
&lt;span class="collapsable-code__title">Windows Thumbnail Provider BS - Includes&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-cpp" >&lt;code>
#include &amp;lt;cstddef&amp;gt;
#include &amp;lt;vector&amp;gt;
#define WIN32_LEAN_AND_MEAN
#include &amp;lt;Windows.h&amp;gt;
#include &amp;lt;initguid.h&amp;gt;
#include &amp;lt;ShlObj_core.h&amp;gt;
#include &amp;lt;Shlwapi.h&amp;gt;
#include &amp;lt;strsafe.h&amp;gt;
#include &amp;lt;thumbcache.h&amp;gt;
#include &amp;lt;wrl.h&amp;gt;
#define VTF_THUMBNAILER_CLSID_STR L&amp;#34;{8b206795-0606-40ca-9eac-1d049c7ff3be}&amp;#34;
DEFINE_GUID(VTF_THUMBNAILER_CLSID, 0x8b206795, 0x0606, 0x40ca, 0x9e, 0xac, 0x1d, 0x04, 0x9c, 0x7f, 0xf3, 0xbe);
#define GLOBAL(ret) extern &amp;#34;C&amp;#34; [[maybe_unused]] ret __stdcall
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>To start, we set up the GUID for the thumbnail provider. I used an online generator, since it has to be unique. Next
comes the thumbnail provider class.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="613872459" type="checkbox" checked />
&lt;label for="613872459">
&lt;span class="collapsable-code__language">C++&lt;/span>
&lt;span class="collapsable-code__title">Windows Thumbnail Provider BS - Thumbnail Provider&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-cpp" >&lt;code>
class VTFThumbnailProvider : public IThumbnailProvider, public IInitializeWithStream {
public:
VTFThumbnailProvider()
: refCount(1)
, stream(nullptr) {}
~VTFThumbnailProvider() {
if (this-&amp;gt;stream) {
this-&amp;gt;stream-&amp;gt;Release();
this-&amp;gt;stream = nullptr;
}
}
STDMETHOD(QueryInterface)(REFIID riid, void** ppv) override {
static const QITAB qit[] = {
QITABENT(VTFThumbnailProvider, IThumbnailProvider),
QITABENT(VTFThumbnailProvider, IInitializeWithStream),
{nullptr},
};
return QISearch(this, qit, riid, ppv);
}
STDMETHOD_(ULONG, AddRef)() override {
return InterlockedIncrement(&amp;amp;this-&amp;gt;refCount);
}
STDMETHOD_(ULONG, Release)() override {
const ULONG rc = InterlockedDecrement(&amp;amp;this-&amp;gt;refCount);
if (rc == 0) {
delete this;
return 0;
}
return rc;
}
STDMETHOD(Initialize)(IStream* stream_, DWORD) override {
if (!stream_) {
return E_POINTER;
}
this-&amp;gt;stream = stream_;
this-&amp;gt;stream-&amp;gt;AddRef();
return S_OK;
}
STDMETHOD(GetThumbnail)(UINT cx, HBITMAP* phbmp, WTS_ALPHATYPE* pdwAlpha) override {
if (!phbmp || !pdwAlpha) {
return E_POINTER;
}
if (!this-&amp;gt;stream) {
return E_UNEXPECTED;
}
STATSTG stat;
HRESULT hr = this-&amp;gt;stream-&amp;gt;Stat(&amp;amp;stat, STATFLAG_NONAME);
if (FAILED(hr)) {
return hr;
}
ULONG bytesRead = 0;
std::vector&amp;lt;std::byte&amp;gt; data(stat.cbSize.QuadPart);
hr = this-&amp;gt;stream-&amp;gt;Read(data.data(), static_cast&amp;lt;ULONG&amp;gt;(stat.cbSize.QuadPart), &amp;amp;bytesRead);
if (FAILED(hr) || bytesRead != stat.cbSize.QuadPart) {
return E_FAIL;
}
// The file&amp;#39;s data has now been read to the data vector, process it
// Return E_UNEXPECTED if the file is broken
// Past this point data is now storing BGRA8888 (yes, BGRA8888)
// thumbnail image data
BITMAPINFO bmi = {};
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = static_cast&amp;lt;LONG&amp;gt;(width);
bmi.bmiHeader.biHeight = -static_cast&amp;lt;LONG&amp;gt;(height);
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RGB;
void* pBits = nullptr;
HDC hdc = GetDC(nullptr);
HBITMAP hBitmap = CreateDIBSection(hdc, &amp;amp;bmi, DIB_RGB_COLORS, &amp;amp;pBits, nullptr, 0);
ReleaseDC(nullptr, hdc);
if (!hBitmap) {
return E_OUTOFMEMORY;
}
std::memcpy(pBits, data.data(), data.size());
*phbmp = hBitmap;
*pdwAlpha = WTSAT_ARGB;
return S_OK;
}
private:
ULONG refCount;
IStream* stream;
};
GLOBAL(HRESULT) VTFThumbnailProvider_CreateInstance(REFIID riid, void** ppv) {
auto* pNew = new(std::nothrow) VTFThumbnailProvider;
HRESULT hr = pNew ? S_OK : E_OUTOFMEMORY;
if (SUCCEEDED(hr)) {
hr = pNew-&amp;gt;QueryInterface(riid, ppv);
pNew-&amp;gt;Release();
}
return hr;
}
typedef HRESULT(*PFNCREATEINSTANCE)(REFIID, void**);
struct CLASS_OBJECT_INIT {
const CLSID* pClsid;
PFNCREATEINSTANCE pfnCreate;
};
const CLASS_OBJECT_INIT c_rgClassObjectInit[] = {{&amp;amp;VTF_THUMBNAILER_CLSID, VTFThumbnailProvider_CreateInstance}};
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>The boilerplate isn&amp;rsquo;t nasty yet, but working with &lt;code>HBITMAP&lt;/code> is frustrating. For some reason it wants to read the pixel
order in reverse, so &lt;code>BGRA8888&lt;/code> gets turned into &lt;code>ARGB8888&lt;/code>. There are a few formats the bitmap can store pixel data in,
but for the sake of simplicity I used &lt;code>BGRA8888&lt;/code> everywhere, even for opaque textures. There&amp;rsquo;s not too much interesting
going on here so let&amp;rsquo;s move on to the factory.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="732518694" type="checkbox" checked />
&lt;label for="732518694">
&lt;span class="collapsable-code__language">C++&lt;/span>
&lt;span class="collapsable-code__title">Windows Thumbnail Provider BS - Thumbnail Provider Factory&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-cpp" >&lt;code>
HINSTANCE g_hInst = nullptr;
ULONG g_cRefModule = 0;
GLOBAL(BOOL) DllMain(HINSTANCE hInstance, DWORD dwReason, void*) {
if (dwReason == DLL_PROCESS_ATTACH) {
g_hInst = hInstance;
DisableThreadLibraryCalls(hInstance);
}
return TRUE;
}
GLOBAL(void) DllAddRef() {
InterlockedIncrement(&amp;amp;g_cRefModule);
}
GLOBAL(void) DllRelease() {
InterlockedDecrement(&amp;amp;g_cRefModule);
}
GLOBAL(HRESULT) DllCanUnloadNow() {
return !g_cRefModule ? S_OK : S_FALSE;
}
class VTFThumbnailProviderFactory : public IClassFactory {
public:
static HRESULT CreateInstance(REFCLSID clsid, const CLASS_OBJECT_INIT* pClassObjectInits, size_t cClassObjectInits, REFIID riid, void** ppv) {
*ppv = nullptr;
HRESULT hr = CLASS_E_CLASSNOTAVAILABLE;
for (size_t i = 0; i &amp;lt; cClassObjectInits; i&amp;#43;&amp;#43;) {
if (clsid == *pClassObjectInits[i].pClsid) {
IClassFactory *pClassFactory = new(std::nothrow) VTFThumbnailProviderFactory(pClassObjectInits[i].pfnCreate);
hr = pClassFactory ? S_OK : E_OUTOFMEMORY;
if (SUCCEEDED(hr)) {
hr = pClassFactory-&amp;gt;QueryInterface(riid, ppv);
pClassFactory-&amp;gt;Release();
}
break;
}
}
return hr;
}
explicit VTFThumbnailProviderFactory(PFNCREATEINSTANCE pfnCreate_)
: refCount(1)
, pfnCreate(pfnCreate_) {
::DllAddRef();
}
IFACEMETHODIMP QueryInterface(REFIID riid, void** ppv) override {
static const QITAB qit[] = {
QITABENT(VTFThumbnailProviderFactory, IClassFactory),
{nullptr},
};
return QISearch(this, qit, riid, ppv);
}
IFACEMETHODIMP_(ULONG) AddRef() override {
return InterlockedIncrement(&amp;amp;this-&amp;gt;refCount);
}
IFACEMETHODIMP_(ULONG) Release() override {
const ULONG rc = InterlockedDecrement(&amp;amp;this-&amp;gt;refCount);
if (!rc) {
delete this;
}
return rc;
}
IFACEMETHODIMP CreateInstance(IUnknown* pUnkOuter, REFIID riid, void** ppv) override {
return pUnkOuter ? CLASS_E_NOAGGREGATION : this-&amp;gt;pfnCreate(riid, ppv);
}
IFACEMETHODIMP LockServer(BOOL fLock) override {
if (fLock) {
::DllAddRef();
} else {
::DllRelease();
}
return S_OK;
}
private:
~VTFThumbnailProviderFactory() {
::DllRelease();
}
ULONG refCount;
PFNCREATEINSTANCE pfnCreate;
};
GLOBAL(HRESULT) DllGetClassObject(REFCLSID clsid, REFIID riid, void** ppv) {
return VTFThumbnailProviderFactory::CreateInstance(clsid, c_rgClassObjectInit, ARRAYSIZE(c_rgClassObjectInit), riid, ppv);
}
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>All of that code is boilerplate to create an instance of the thumbnail provider. The following code is more interesting.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="976132845" type="checkbox" checked />
&lt;label for="976132845">
&lt;span class="collapsable-code__language">C++&lt;/span>
&lt;span class="collapsable-code__title">Windows Thumbnail Provider BS - Thumbnail Provider Registry&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-cpp" >&lt;code>
namespace {
HRESULT SetHKCRRegistryKey(LPCWSTR subKey, LPCWSTR valueName, LPCWSTR data) {
HKEY hKey;
LONG result = RegCreateKeyExW(HKEY_CLASSES_ROOT, subKey, 0, nullptr, REG_OPTION_NON_VOLATILE, KEY_WRITE, nullptr, &amp;amp;hKey, nullptr);
if (result != ERROR_SUCCESS) {
return HRESULT_FROM_WIN32(result);
}
result = RegSetValueExW(hKey, valueName, 0, REG_SZ, (const BYTE*) data, (lstrlenW(data) &amp;#43; 1) * sizeof(WCHAR));
RegCloseKey(hKey);
return HRESULT_FROM_WIN32(result);
}
HRESULT DeleteHKCRRegistryKey(LPCWSTR subKey) {
LONG result = SHDeleteKeyW(HKEY_CLASSES_ROOT, subKey);
return HRESULT_FROM_WIN32(result == ERROR_FILE_NOT_FOUND ? ERROR_SUCCESS : result);
}
} // namespace
GLOBAL(HRESULT) DllNotifyShell() {
SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, nullptr, nullptr);
return S_OK;
}
GLOBAL(HRESULT) DllRegisterServer() {
wchar_t modulePath[MAX_PATH];
if (!GetModuleFileNameW(g_hInst, modulePath, MAX_PATH)) {
return HRESULT_FROM_WIN32(GetLastError());
}
HRESULT hr;
hr = ::SetHKCRRegistryKey(L&amp;#34;CLSID\\&amp;#34; VTF_THUMBNAILER_CLSID_STR, nullptr, L&amp;#34;Valve Texture Format Files (VTF Thumbnailer)&amp;#34;);
if (FAILED(hr)) {
return hr;
}
hr = ::SetHKCRRegistryKey(L&amp;#34;CLSID\\&amp;#34; VTF_THUMBNAILER_CLSID_STR &amp;#34;\\InProcServer32&amp;#34;, nullptr, modulePath);
if (FAILED(hr)) {
return hr;
}
hr = ::SetHKCRRegistryKey(L&amp;#34;CLSID\\&amp;#34; VTF_THUMBNAILER_CLSID_STR &amp;#34;\\InProcServer32&amp;#34;, L&amp;#34;ThreadingModel&amp;#34;, L&amp;#34;Apartment&amp;#34;);
if (FAILED(hr)) {
return hr;
}
hr = ::SetHKCRRegistryKey(L&amp;#34;.vtf\\ShellEx\\{e357fccd-a995-4576-b01f-234630154e96}&amp;#34;, nullptr, VTF_THUMBNAILER_CLSID_STR);
if (SUCCEEDED(hr)) {
::DllNotifyShell();
}
return hr;
}
GLOBAL(HRESULT) DllUnregisterServer() {
HRESULT hr;
hr = ::DeleteHKCRRegistryKey(L&amp;#34;CLSID\\&amp;#34; VTF_THUMBNAILER_CLSID_STR);
if (FAILED(hr)) {
return hr;
}
return ::DeleteHKCRRegistryKey(L&amp;#34;.vtf\\ShellEx\\{e357fccd-a995-4576-b01f-234630154e96}&amp;#34;);
}
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>The &lt;code>DllRegisterServer&lt;/code> and &lt;code>DllUnregisterServer&lt;/code> functions are called in the installer after the files are copied into
place through &lt;code>regsvr32.exe&lt;/code>. Passing a path to a DLL to this program will call the &lt;code>DllRegisterServer&lt;/code> function in the
DLL, which in this case modifies the necessary registry values, lets the file browser know there&amp;rsquo;s a new thumbnail
provider, and exits. Running the same command with the &lt;code>/u&lt;/code> flag will call the &lt;code>DllUnregisterServer&lt;/code> function in the DLL
instead.&lt;/p>
&lt;p>As for the specific registry keys, each one resides under &lt;code>HKEY_CLASSES_ROOT&lt;/code>. The &lt;code>CLSID\&amp;lt;GUID&amp;gt;&lt;/code> value is the thumbnail
provider name. The value for &lt;code>CLSID\&amp;lt;GUID&amp;gt;\InProcServer32&lt;/code> is the path to the DLL. The value for
&lt;code>CLSID\&amp;lt;GUID&amp;gt;\InProcServer32\ThreadingModel&lt;/code> should always be &lt;code>Apartment&lt;/code> according to the scant Windows docs on
thumbnail providers and every example of one I&amp;rsquo;ve found. Finally, the value for
&lt;code>&amp;lt;file extension&amp;gt;\ShellEx\{e357fccd-a995-4576-b01f-234630154e96}&lt;/code> (yes, you need that exact GUID) should be your own
thumbnail provider&amp;rsquo;s GUID. These are all Windows needs to find the DLL, load it correctly, and use it on the files you
want thumbnails for.&lt;/p>
&lt;p>Oh right and here&amp;rsquo;s the stupid &lt;code>.def&lt;/code> file, because the Windows API is just the worst.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="685397241" type="checkbox" />
&lt;label for="685397241">
&lt;span class="collapsable-code__language">def&lt;/span>
&lt;span class="collapsable-code__title">Windows Thumbnail Provider BS - Exports&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-def" >&lt;code>
LIBRARY &amp;#34;vtf-thumbnailer&amp;#34;
EXPORTS
VTFThumbnailProvider_CreateInstance PRIVATE
DllMain PRIVATE
DllAddRef PRIVATE
DllRelease PRIVATE
DllCanUnloadNow PRIVATE
DllGetClassObject PRIVATE
DllNotifyShell PRIVATE
DllRegisterServer PRIVATE
DllUnregisterServer PRIVATE
&lt;/code>&lt;/pre>
&lt;/div>
&lt;hr>
&lt;p>One DLL is written when installing this application. It should start working immediately without rebooting.&lt;/p>
&lt;ul>
&lt;li>&lt;code>C:\Program Files\vtf-thumbnailer\vtf-thumbnailer.dll&lt;/code>&lt;/li>
&lt;/ul>
&lt;h2 id="wrap-up">Wrap-Up&lt;/h2>
&lt;p>At this point I was tired of thumbnailers. I packaged the product and shipped a release, built from the code hosted on
&lt;a href="https://github.com/craftablescience/vtf-thumbnailer">this GitHub repository&lt;/a>, and haven&amp;rsquo;t looked back until now. I hope
this post helps and/or inspires you to make a thumbnailer of your own, it&amp;rsquo;s really not that difficult if you have a
decent reference and quite rewarding in the end!&lt;/p></content></item><item><title>Format Deep Dive: VTF (Valve Texture Format)</title><link>https://craftablescience.info/blog/posts/format_deep_dive_vtf/</link><pubDate>Tue, 26 Nov 2024 00:00:00 +0000</pubDate><guid>https://craftablescience.info/blog/posts/format_deep_dive_vtf/</guid><description>Background / Definitions At this point I&amp;rsquo;m going to assume you are a smart cookie and know what textures are, and why engines need to use custom file formats to store them.
(tl;dr it&amp;rsquo;s more efficient than loading more accessible formats like PNG, some types of image data cannot be stored in accessible formats like PNG, engines frequently need to assign textures metadata which cannot be done in most accessible formats like PNG)</description><content>&lt;h2 id="background--definitions">Background / Definitions&lt;/h2>
&lt;p>At this point I&amp;rsquo;m going to assume you are a smart cookie and know what textures are, and why engines need to use custom
file formats to store them.&lt;/p>
&lt;p>(tl;dr it&amp;rsquo;s more efficient than loading more accessible formats like PNG, some types of image data cannot be stored in
accessible formats like PNG, engines frequently need to assign textures metadata which cannot be done in most accessible
formats like PNG)&lt;/p>
&lt;p>From this point forward, when I say &amp;ldquo;image&amp;rdquo;, &amp;ldquo;image data&amp;rdquo;, or &amp;ldquo;texture data&amp;rdquo; I&amp;rsquo;m referring to a two-dimensional array of
pixels, and not a file like PNG or VTF.&lt;/p>
&lt;p>Finally, I&amp;rsquo;d like to note that I have no experience with the VTF format for console versions of Source, so this post
will strictly focus on the VTF formats found on PC. Console VTFs seem to be very similar, and I might make a separate
blog post about them in the future when I have more experience working with them.&lt;/p>
&lt;p>Anyway now that that&amp;rsquo;s out of the way, let&amp;rsquo;s get into this format.&lt;/p>
&lt;h2 id="dds">DDS&lt;/h2>
&lt;p>Since the first iteration of VTF is based on the DDS file format, let&amp;rsquo;s go over that first, and then we can see what
Valve took inspiration from. Skip past this section if you already know what DDS looks like or don&amp;rsquo;t particularly care.
The &lt;a href="https://learn.microsoft.com/en-us/windows/win32/direct3ddds/dds-header">DDS header specification&lt;/a> looks like this:&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="152394768" type="checkbox" />
&lt;label for="152394768">
&lt;span class="collapsable-code__language">C++&lt;/span>
&lt;span class="collapsable-code__title">DDS Header&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-cpp" >&lt;code>
struct DDS_Header {
uint32_t signature;
uint32_t headerSize;
uint32_t flags;
uint32_t height;
uint32_t width;
uint32_t pitchOrLinearSize;
uint32_t depth;
uint32_t mipMapCount;
uint32_t _unused0[11];
uint32_t format;
uint32_t caps1;
uint32_t caps2;
uint32_t _unused1[3];
};
&lt;/code>&lt;/pre>
&lt;/div>
&lt;div style="display: flex; flex-wrap: wrap; margin-bottom: calc(var(--line-height) * -1.2)">
&lt;section style="flex: 1; padding-bottom: 0">
&lt;p style="margin-top: 0">DDS files are fairly straightforward when storing basic textures.&lt;/p>
&lt;ul>
&lt;li>&lt;code>signature&lt;/code> is always the fourCC code &lt;code>&amp;quot;DDS\0&amp;quot;&lt;/code>.&lt;/li>
&lt;li>&lt;code>headerSize&lt;/code> is always 124 bytes.&lt;/li>
&lt;li>&lt;code>flags&lt;/code> is poorly named, essentially stores a flag for every field of the header with valid data.&lt;/li>
&lt;li>&lt;code>height&lt;/code> and &lt;code>width&lt;/code> store the dimensions of the base image (the first mip level).&lt;/li>
&lt;li>&lt;code>pitchOrLinearSize&lt;/code> stores the pitch (number of bytes per scan line) in uncompressed textures, or the size in bytes of
the first mip level in compressed textures.&lt;/li>
&lt;li>&lt;code>depth&lt;/code> stores the size of the image in the third dimension (number of layers of a volume texture), if the texture is
not a volume texture it may not be set to a valid value.&lt;/li>
&lt;li>&lt;code>mipMapCount&lt;/code> is self-explanatory, the number of mipmaps stored in the file. Mipmaps are images shrunken by a power of
two from the image that came before it.&lt;/li>
&lt;li>&lt;code>format&lt;/code> is the format of the image data stored in the file.&lt;/li>
&lt;li>&lt;code>caps1&lt;/code> and &lt;code>caps2&lt;/code> together define the type of the stored texture (volume texture? cubemap texture?).&lt;/li>
&lt;/ul>
&lt;p>Immediately following this header is the image data. The entire file looks something like this image.&lt;/p>
&lt;/section>
&lt;img src="fig_dds.webp" alt="A diagram of the structure of a DDS file. The header is at the beginning of the file, and immediately following it are each mip level of the texture, going from largest to smallest." class="left" style="margin-top: 0; margin-left: 16px; object-fit: contain" />
&lt;/div>
&lt;h2 id="vtf">VTF&lt;/h2>
&lt;p>DDS is a nice generic format, but its generic-ness made it unsuited for Valve&amp;rsquo;s purposes. Valve needs to store several
things in their textures that the stock DDS header doesn&amp;rsquo;t make room for, e.g. a reflectivity vector for faster
radiosity calculations.&lt;/p>
&lt;h3 id="early-vtf-v70-72">Early VTF (v7.0-7.2)&lt;/h3>
&lt;p>VTF may have gone through several internal revisions before the first format we know of publicly, since the major
version (yes, major, there are two version integers) has always been set to 7. The minor version is what gets updated
when the format changes, and it starts at 0. This is what the first public iteration of VTF&amp;rsquo;s header looks like.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="176835924" type="checkbox" />
&lt;label for="176835924">
&lt;span class="collapsable-code__language">C++&lt;/span>
&lt;span class="collapsable-code__title">VTF Header&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-cpp" >&lt;code>
#pragma pack(push, 16)
struct VTF_Header_70 {
uint32_t signature;
uint32_t majorVersion;
uint32_t minorVersion;
uint32_t headerSize;
uint16_t width;
uint16_t height;
uint32_t flags;
uint16_t frameCount;
uint16_t startFrame;
uint32_t _padding0;
float reflectivity[3];
uint32_t _padding1;
float bumpMapScale;
uint32_t format;
uint8_t mipCount;
uint32_t thumbnailFormat;
uint8_t thumbnailWidth;
uint8_t thumbnailHeight;
};
// Bitwise identical
struct VTF_Header_71 : public VTF_Header_70 {};
#pragma pack(pop)
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>It doesn&amp;rsquo;t take a genius to figure out Valve copied Microsoft&amp;rsquo;s homework.
Some of the fields are reordered, but in general the contents of the header flow the same direction.
Note that this struct is 16-byte aligned, which is why the reflectivity vector has that weird padding around it.&lt;/p>
&lt;ul>
&lt;li>&lt;code>signature&lt;/code> is always the fourCC code &lt;code>&amp;quot;VTF\0&amp;quot;&lt;/code>.&lt;/li>
&lt;li>&lt;code>majorVersion&lt;/code> is always 7.&lt;/li>
&lt;li>&lt;code>minorVersion&lt;/code> ranges from 0 to 6.&lt;/li>
&lt;li>&lt;code>headerSize&lt;/code> is the same as the DDS &lt;code>headerSize&lt;/code>, except this header is smaller since it doesn&amp;rsquo;t have tons of reserved
space.&lt;/li>
&lt;li>&lt;code>width&lt;/code> and &lt;code>height&lt;/code> is the same as in DDS, the width and height of the base mip.&lt;/li>
&lt;li>&lt;code>flags&lt;/code> is actually different than DDS, and is more what you&amp;rsquo;d expect from a field named flags. It stores flags. See
&lt;a href="https://craftablescience.info/blog/blog/posts/format_deep_dive_vtf/#appendix-a-more-information-on-flags">Appendix A&lt;/a> for more information on supported flags.&lt;/li>
&lt;li>&lt;code>frameCount&lt;/code> is the number of &amp;ldquo;frames&amp;rdquo;, and &lt;code>startFrame&lt;/code> is the first frame in the animation sequence. Again, more on
this later!&lt;/li>
&lt;li>&lt;code>reflectivity&lt;/code> stores the overall reflectivity of the texture, to be used in radiosity calculations. Valve&amp;rsquo;s method of
calculating it is apparently adding all the pixels&amp;rsquo; RGB values together, then dividing by the number of pixels. The
reflectivity is stored in RGB order.&lt;/li>
&lt;li>&lt;code>bumpMapScale&lt;/code> controls the intensity of the bump map, if this texture is a bump map. It can be ignored otherwise.&lt;/li>
&lt;li>&lt;code>format&lt;/code> is the same as in DDS, the format of the image data stored in the file. See &lt;a href="https://craftablescience.info/blog/blog/posts/format_deep_dive_vtf/#appendix-b-supported-image-formats">Appendix B&lt;/a>
for more information on supported format types.&lt;/li>
&lt;li>&lt;code>mipCount&lt;/code> is renamed from &lt;code>mipMapCount&lt;/code> in DDS.&lt;/li>
&lt;li>&lt;code>thumbnailFormat&lt;/code>, &lt;code>thumbnailWidth&lt;/code>, and &lt;code>thumbnailHeight&lt;/code> are for the VTF thumbnail. More on this later.&lt;/li>
&lt;/ul>
&lt;p>Thumbnail data is stored immediately after the header, followed by the image data.&lt;/p>
&lt;p>The thumbnail is a low-resolution copy of the base image, which the engine uses in a couple places when it needs to know
general information about the brightness of the texture. It needs to know this information in 2D space, so using the
reflectivity vector doesn&amp;rsquo;t work. The thumbnail format should always be treated as &lt;code>DXT1&lt;/code> even if the format field is
different, because there are a few official VTFs that have the wrong format in this field, and every VTF creation tool
will create the thumbnail with the &lt;code>DXT1&lt;/code> format. For VTFs created using Valve&amp;rsquo;s own tooling, the size of the thumbnail
is always observed to be 16x16, as long as the image is square. If the image is not square, the thumbnail size will be
proportional to the image dimensions, with the largest dimension being 16 (so if the image is 2048x1024, the thumbnail
will be 16x8). This behavior is not a hard requirement, and thumbnails that have a different aspect ratio from their
source image will still work. Custom sized thumbnails should also work, but there&amp;rsquo;s no need to go higher, or deviate
from what Valve&amp;rsquo;s official tooling produces.&lt;/p>
&lt;figure class="center" >
&lt;img src="fig_thumb.webp" alt="A diagram of the base image being used to create the thumbnail and the reflectivity vector." />
&lt;figcaption class="center" >The base image is used to calculate both the thumbnail and the reflectivity vector.&lt;/figcaption>
&lt;/figure>
&lt;p>The &lt;code>flags&lt;/code> field controls several things, although it&amp;rsquo;s also used by &lt;code>vtex&lt;/code>, Valve&amp;rsquo;s texture compiler, to store data
about the VTF while it&amp;rsquo;s being created. Some flags only have meaning in &lt;code>vtex&lt;/code>, and some flags only have meaning in the
engine. The list of flags seems to change slightly every VTF version, but one important flag is the cubemap flag
(&lt;code>2^14&lt;/code>). When this flag is enabled, the VTF has either 6 or 7 faces, the first 6 forming a cubemap and the 7th either
being garbage data or a spheremap when present. When the cubemap flag is present VTF v7.0 has 6 faces, while v7.1 has 7
faces. Unfortunately, Valve has broken their own rules in regards to the presence of the spheremap in official VTFs in
the past, so the only way to know for sure if the spheremap is or isn&amp;rsquo;t present is to check the size of the VTF&amp;rsquo;s image
data. Fortunately, if the VTF is outside the version range v7.1-7.4 (inclusive), we know for sure the spheremap cannot
exist.&lt;/p>
&lt;p>The &lt;code>frameCount&lt;/code> field controls the number of frames the VTF stores. Frames are used for animated textures, and the
frame currently rendered by the game is controlled by the material using the texture. The &lt;code>startFrame&lt;/code> is the default
value for the current frame (not very complicated).&lt;/p>
&lt;p>VTF v7.0-7.1 can&amp;rsquo;t store volumetric textures, despite the fact that DDS can. They might not have cared to implement
support here, since volumetric textures aren&amp;rsquo;t used anywhere in Valve-published Source engine games (or really anywhere
else as far as I can tell). VTF v7.2 adds support for volumetric textures (the only change to the format). At this point
in the timeline we&amp;rsquo;re post-Half-Life 2 release, so they might have been doing internal experiments that would
necessitate this change.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="285317496" type="checkbox" />
&lt;label for="285317496">
&lt;span class="collapsable-code__language">C++&lt;/span>
&lt;span class="collapsable-code__title">VTF Header with Depth&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-cpp" >&lt;code>
#pragma pack(push, 16)
// All the fields from v7.1 verbatim, plus...
struct VTF_Header_72 : public VTF_Header_71 {
uint16_t depth;
};
#pragma pack(pop)
&lt;/code>&lt;/pre>
&lt;/div>
&lt;div style="display: flex; flex-wrap: wrap; margin-bottom: calc(var(--line-height) * -1.2)">
&lt;section style="flex: 1; margin-top: calc(var(--line-height) * -1.2)">
&lt;p>Now that volumetric textures are in play, we can properly visualize how image data is ordered.&lt;/p>
&lt;!-- 864px is hardcoded as the maximum content width, divide by 2 -->
&lt;div style="max-width: 432px">
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-json" data-lang="json">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">&amp;#34;mipmaps&amp;#34;&lt;/span> &lt;span style="color:#75715e">// 0 to mipCount
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;frames&amp;#34;&lt;/span> &lt;span style="color:#75715e">// 0 to frameCount
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;faces&amp;#34;&lt;/span> &lt;span style="color:#75715e">// 0 to number of faces
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> { &lt;span style="color:#75715e">// (either 1, 6, or 7)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#f92672">&amp;#34;slices&amp;#34;&lt;/span> &lt;span style="color:#75715e">// 0 to depth
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#960050;background-color:#1e0010">image&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">data&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/div>
&lt;p>One interesting thing to note is that mipmaps are stored in reverse compared to DDS, going &lt;strong>smallest to largest&lt;/strong>. I
don&amp;rsquo;t know for sure, but my theory is they made this change so less of the file would need to be loaded when loading
smaller mip levels from disk. I also don&amp;rsquo;t think the order would matter too much these days.&lt;/p>
&lt;p>Another important consideration is that 3D cubemap textures (cubemaps with a nonzero value for depth) are technically
possible to create, but are treated as invalid by the engine. Thus, the face count and the image depth cannot be greater
than 1 simultaneously.&lt;/p>
&lt;/section>
&lt;img src="fig_vtf.webp" alt="A diagram of the structure of a VTF file. The header is at the beginning of the file. Immediately following it is the thumbnail, followed by each mip level of the texture, going from smallest to largest" class="left" style="margin-top: 0; margin-left: 16px; object-fit: contain" />
&lt;/div>
&lt;h3 id="modern-vtf-v73-75">Modern VTF (v7.3-7.5)&lt;/h3>
&lt;p>VTF v7.3 is where Valve starts to pull away from copying DDS, and attempt to make it a nicer container for more kinds of
texture metadata by introducing the resource system.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="395127846" type="checkbox" />
&lt;label for="395127846">
&lt;span class="collapsable-code__language">C++&lt;/span>
&lt;span class="collapsable-code__title">VTF Header with Resources&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-cpp" >&lt;code>
#pragma pack(push, 16)
// All the fields from v7.2 verbatim, plus...
struct VTF_Header_73 : public VTF_Header_72 {
uint8_t _padding1[3];
uint32_t resourceCount;
uint8_t _padding2[8];
};
// Bitwise identical
struct VTF_Header_74 : public VTF_Header_73 {};
// Bitwise identical
struct VTF_Header_75 : public VTF_Header_74 {};
#pragma pack(pop)
struct VTF_Resource {
uint8_t type[3];
uint8_t flags;
uint32_t data;
};
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>Resources are stored after the VTF header in the form of the given struct. Each resource entry counts toward the overall
&lt;code>headerSize&lt;/code> as well. The type is a three character code used to identify the resource. There is currently only one
resource flag (&lt;code>2^2&lt;/code>), controlling the location of the resource&amp;rsquo;s data. If this flag is &lt;em>not&lt;/em> present, the resource data
field holds an absolute offset to the data in the VTF file. If the flag &lt;em>is&lt;/em> present, the resource data field holds the
resource&amp;rsquo;s data. This is more efficient for resources that only need to store four bytes of data or less. On non-console
platforms the maximum amount of resources in a VTF is 32, but there quite literally are not enough resource types to
ever hit this maximum.&lt;/p>
&lt;p>The only required resource in a VTF file is the image data. Thumbnails are technically not necessary to load the VTF,
although they&amp;rsquo;re highly recommended to always include.&lt;/p>
&lt;p>The thumbnail and image data are now both considered &amp;ldquo;legacy&amp;rdquo; resources, appearing in the resource entry list but
working exactly as they did before. The only difference is their beginning position in the file, which is now controlled
by the resource data field, storing the offset to the thumbnail or image data respectively. The non-legacy resources, if
not stored in the header, store a &lt;code>uint32_t&lt;/code> just before their data, holding the length of the resource data (including
this integer).&lt;/p>
&lt;p>Note that due to overzealous optimization, resource entries in the header need to be sorted from lowest numeric resource
ID to highest numeric resource ID. I found this out the hard way, and you may not even run into any issues writing
unsorted resource entries until a VTF starts to break out of nowhere. Resource entries can be written in any order for
VTFs used by the Strata Source engine branch.&lt;/p>
&lt;p>This is the full list of VTF resources as of VTF v7.5, sorted lowest to highest.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Resource Name&lt;/th>
&lt;th style="text-align:center">Resource ID&lt;/th>
&lt;th style="text-align:center">Stored in Header?&lt;/th>
&lt;th>Description of Data&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Thumbnail&lt;/td>
&lt;td style="text-align:center">&lt;code>&amp;quot;\x01\0\0&amp;quot;&lt;/code> (1)&lt;/td>
&lt;td style="text-align:center">No&lt;/td>
&lt;td>&amp;ldquo;Legacy&amp;rdquo; resource, holds thumbnail data.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Particle Sheet&lt;/td>
&lt;td style="text-align:center">&lt;code>&amp;quot;\x10\0\0&amp;quot;&lt;/code> (16)&lt;/td>
&lt;td style="text-align:center">No&lt;/td>
&lt;td>If the VTF was created using a particle sheet, this is the particle sheet it used (an SHT file). For the sake of focusing on VTF I will save this format for a possible future post.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Image Data&lt;/td>
&lt;td style="text-align:center">&lt;code>&amp;quot;\x30\0\0&amp;quot;&lt;/code> (48)&lt;/td>
&lt;td style="text-align:center">No&lt;/td>
&lt;td>&amp;ldquo;Legacy&amp;rdquo; resource, holds image data. Required in all VTFs from v7.3+.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>CRC&lt;/td>
&lt;td style="text-align:center">&lt;code>&amp;quot;CRC&amp;quot;&lt;/code> (4411971)&lt;/td>
&lt;td style="text-align:center">Yes&lt;/td>
&lt;td>Holds the CRC32 checksum of the &lt;em>input file&lt;/em> that &lt;code>vtex&lt;/code> used to create the VTF, not the VTF itself. Unused by the engine.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Texture LOD Control Info&lt;/td>
&lt;td style="text-align:center">&lt;code>&amp;quot;LOD&amp;quot;&lt;/code> (4476748)&lt;/td>
&lt;td style="text-align:center">Yes&lt;/td>
&lt;td>Holds two &lt;code>uint8_t&lt;/code> integers, the last two bytes of the data field are unused. Controls the highest loaded mipmap when texture detail is set to &amp;ldquo;High&amp;rdquo;. For example, if U and V are 11, the highest loaded mipmap on &amp;ldquo;High&amp;rdquo; will be 2048x2048 (&lt;code>2^11&lt;/code>), and on &amp;ldquo;Very High&amp;rdquo; the highest loaded mipmap will be 4096x4096 (&lt;code>2^(11+1)&lt;/code>). There is very little point to this resource&amp;rsquo;s existence in my opinion.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>KeyValues Data&lt;/td>
&lt;td style="text-align:center">&lt;code>&amp;quot;KVD&amp;quot;&lt;/code> (4478539)&lt;/td>
&lt;td style="text-align:center">No&lt;/td>
&lt;td>Holds a string which is &lt;em>not&lt;/em> null-terminated, specified to be valid KeyValues 1 data. Unused by the engine, is used in third-party tooling to add various information like the texture author and creation date.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Extra Texture Flags&lt;/td>
&lt;td style="text-align:center">&lt;code>&amp;quot;TSO&amp;quot;&lt;/code> (5198676)&lt;/td>
&lt;td style="text-align:center">Yes&lt;/td>
&lt;td>Stores extra texture flags, which can be used in mods for game-specific purposes. Unused by the engine in official Valve games, unknown if it&amp;rsquo;s used in other games. I have never encountered a VTF in the wild with this resource.&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>As for differences between VTF v7.3, v7.4, and v7.5, v7.3 and v7.4 are functionally identical. As previously stated
spheremaps were deprecated in v7.4 and removed in v7.5. v7.5 is unsupported in most Source engine branches before Alien
Swarm, but since it&amp;rsquo;s nearly identical to v7.4, changing the version in the header and adding a dummy spheremap if the
texture is a cubemap is enough to convince the engine to load it.&lt;/p>
&lt;h3 id="strata-source-vtf-v76">Strata Source VTF (v7.6)&lt;/h3>
&lt;p>Strata Source, a community-maintained fork of the engine, has made some additions to the format to support CPU
compression (usually in tandem with GPU compression), resulting in a new version of the format. VTF v7.6&amp;rsquo;s header is
identical to VTF v7.5.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="597342681" type="checkbox" />
&lt;label for="597342681">
&lt;span class="collapsable-code__language">C++&lt;/span>
&lt;span class="collapsable-code__title">VTF Header with Compression&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-cpp" >&lt;code>
#pragma pack(push, 16)
// Bitwise identical
struct VTF_Header_76 : public VTF_Header_75 {};
#pragma pack(pop)
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>Where v7.6 differs is in the new &lt;code>&amp;quot;AXC&amp;quot;&lt;/code> resource, which is optional and sandwiched between the &lt;code>&amp;quot;CRC&amp;quot;&lt;/code> and &lt;code>&amp;quot;LOD&amp;quot;&lt;/code>
resources in the resource entry list.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Resource Name&lt;/th>
&lt;th style="text-align:center">Resource ID&lt;/th>
&lt;th style="text-align:center">Stored in Header?&lt;/th>
&lt;th>Description of Data&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Auxiliary Compression Info&lt;/td>
&lt;td style="text-align:center">&lt;code>&amp;quot;AXC&amp;quot;&lt;/code> (4413505)&lt;/td>
&lt;td style="text-align:center">No&lt;/td>
&lt;td>Stores the compression type and strength, as well as the compressed sizes of each individual image. See the description below.&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>If the &lt;code>&amp;quot;AXC&amp;quot;&lt;/code> resource is present, the image data is compressed. The original implementation of the resource looks like
this.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="619287435" type="checkbox" />
&lt;label for="619287435">
&lt;span class="collapsable-code__language">C++&lt;/span>
&lt;span class="collapsable-code__title">First Public &lt;code>&amp;quot;AXC&amp;quot;&lt;/code> Resource Iteration&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-cpp" >&lt;code>
struct AXC_V1 {
// Excluding the size integer present at the beginning of every
// non-legacy resource&amp;#39;s data when not stored in the header
uint32_t compressionStrength;
uint32_t compressedSizes[mipCount * frameCount * faceCount];
};
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>This version of the &lt;code>&amp;quot;AXC&amp;quot;&lt;/code> resource always uses the &lt;a href="https://en.wikipedia.org/wiki/Deflate">Deflate&lt;/a> compression method. The compression
strength is unnecessary at runtime, and stores the level of compression used when creating the texture. For Deflate this
value can be between -1 and 9, inclusive. If the compression strength is 0, the rest of the resource is ignored, since 0
means no compression took place.&lt;/p>
&lt;p>The compressed sizes store the size in bytes of every image that the VTF holds. This is typically calculated from the
image dimensions and format, but compression size is non-deterministic so it must be stored. The compressed size layout
is as follows. Note that if the texture is a volumetric texture, the entire 3D texture at the given mip, frame, and face
level is compressed in one block.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-json" data-lang="json">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">&amp;#34;mipmaps&amp;#34;&lt;/span> &lt;span style="color:#75715e">// 0 - mipCount
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;frames&amp;#34;&lt;/span> &lt;span style="color:#75715e">// 0 - frameCount
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;faces&amp;#34;&lt;/span> &lt;span style="color:#75715e">// 0 - number of faces
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> { &lt;span style="color:#75715e">// (either 1, 6, or 7)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">compressed&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">image&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">data&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">length&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Given this information, it is possible to access the compressed size of an image at a given mip, frame, and face level
by using the following formula.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="496723815" type="checkbox" />
&lt;label for="496723815">
&lt;span class="collapsable-code__language">C++&lt;/span>
&lt;span class="collapsable-code__title">Compressed Image Size Formula&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-cpp" >&lt;code>
uint32_t compressedSizeAt(AXC_Old axc, uint8_t mip, uint16_t frame, uint8_t face) {
return axc.compressedSizes[
(mipCount - mip - 1) * frameCount * faceCount &amp;#43;
frame * faceCount &amp;#43;
face
];
}
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>When a VTF is compressed, the position of an image at a given mip, frame, and face level relative to the beginning of
the image data resource can be found by summing the sizes of all the compressed images that came before it. The image
data is tightly packed (if it wasn&amp;rsquo;t compression would be rather pointless).&lt;/p>
&lt;hr>
&lt;p>A few years after the introduction of VTF v7.6, I updated it to support &lt;a href="https://en.wikipedia.org/wiki/Zstd">Zstd&lt;/a> compression in addition to Deflate.
From my testing Zstd doesn&amp;rsquo;t have a significant advantage in compression size, but it does have a significant advantage
in decompression speed. The new &lt;code>&amp;quot;AXC&amp;quot;&lt;/code> resource looks like this.&lt;/p>
&lt;div class="collapsable-code">
&lt;input id="284135976" type="checkbox" />
&lt;label for="284135976">
&lt;span class="collapsable-code__language">C++&lt;/span>
&lt;span class="collapsable-code__title">Latest Public &lt;code>&amp;quot;AXC&amp;quot;&lt;/code> Resource Iteration&lt;/span>
&lt;span class="collapsable-code__toggle" data-label-expand="△" data-label-collapse="▽">&lt;/span>
&lt;/label>
&lt;pre class="language-cpp" >&lt;code>
struct AXC_V2 {
// Excluding the size integer present at the beginning of every
// non-legacy resource&amp;#39;s data when not stored in the header
uint16_t compressionStrength;
uint16_t compressionMethod;
uint32_t compressedSizes[mipCount * frameCount * faceCount];
};
&lt;/code>&lt;/pre>
&lt;/div>
&lt;p>By splitting the compression strength into two shorts, since compression strength was only ever used to store a value
between -1 and 9 inclusive, and VTF is stored in little endian, we can take advantage of the empty space to store the
compression method in a backwards-compatible way. There are three accepted value ranges for &lt;code>compressionMethod&lt;/code>.&lt;/p>
&lt;ul>
&lt;li>&amp;lt;=0: Identifies the first iteration of AXC, using Deflate compression.&lt;/li>
&lt;li>8: The latest iteration of AXC, using Deflate compression.&lt;/li>
&lt;li>93: The latest iteration of AXC, using Zstd compression.&lt;/li>
&lt;/ul>
&lt;p>Note that since the compression strength value is useless at runtime, third-party tooling expecting the old &lt;code>&amp;quot;AXC&amp;quot;&lt;/code>
resource will continue to work with new Deflate-compressed VTFs. Programs expecting the new &lt;code>&amp;quot;AXC&amp;quot;&lt;/code> resource should also
load the old AXC resource correctly assuming they handle &lt;code>compressionMethod&lt;/code> correctly. For these reasons the resource
is not versioned, as making a separate version would actually do more harm than good.&lt;/p>
&lt;p>Although &lt;code>minizip-ng&lt;/code> is not used in the engine or in any third-party tooling I&amp;rsquo;ve seen, &lt;code>compressionMethod&lt;/code> copies its
defines for compression method types. The door is left open for other compression methods, although there&amp;rsquo;s not much of
a point in adding new ones.&lt;/p>
&lt;h2 id="unspoken-requirements--things-to-know">Unspoken Requirements / Things to Know&lt;/h2>
&lt;p>A few VTF requirements aren&amp;rsquo;t immediately obvious, and sometimes requirements that people will tell you about don&amp;rsquo;t
actually exist. Let&amp;rsquo;s go over all of them.&lt;/p>
&lt;h4 id="texture-dimensions">Texture Dimensions&lt;/h4>
&lt;p>Some say a VTF&amp;rsquo;s image dimensions must not stray from the path of the powers of two. They are utter fools, too cowardly
to taste the sweet forbidden fruit of non-PO2 dimensions. Non-PO2 sized textures may not have worked well in the past,
but in modern times on modern hardware they are fine, and don&amp;rsquo;t seem to cause any issues in Source from my testing. Use
non-PO2 textures sparingly, but don&amp;rsquo;t be afraid of them if they&amp;rsquo;re convenient. (Side note, the dimension for a mip level
below an image that has a dimension that doesn&amp;rsquo;t divide evenly by 2 will be the result of the division plus one (rounded
up). For example, if an image has dimensions 111x64, the mip level below that image is expected to have dimensions
56x32.)&lt;/p>
&lt;p>That being said, if a VTF is using a compressed format such as &lt;code>DXTn&lt;/code>, &lt;code>BCn&lt;/code>, or &lt;code>ATIxN&lt;/code> its dimensions must be a
&lt;em>multiple&lt;/em> of 4. Compressed formats require 4x4 pixel blocks throughout the image. This is also why mip levels lower
than 4x4 sometimes look very weird for compressed formats.&lt;/p>
&lt;h4 id="spheremaps">Spheremaps&lt;/h4>
&lt;p>Spheremaps are required for VTF cubemaps with a version between v7.1-7.4 inclusive, but they are never used by the
engine. If you are making a program to create cubemap VTFs feel free to leave the spheremap blank, or insert a doodle.&lt;/p>
&lt;h4 id="flags">Flags&lt;/h4>
&lt;ul>
&lt;li>When creating a VTF with no mipmaps, the &lt;code>NOMIP&lt;/code> and &lt;code>NOLOD&lt;/code> flags should be applied.&lt;/li>
&lt;li>When creating a VTF with a format that supports transparency, either the &lt;code>ONEBITALPHA&lt;/code> flag or the &lt;code>EIGHTBITALPHA&lt;/code>
flag must be applied. (Yes, the &lt;code>EIGHTBITALPHA&lt;/code> flag actually means multi-bit alpha. It&amp;rsquo;s a bad name.)&lt;/li>
&lt;li>When creating a cubemap VTF, the &lt;code>ENVMAP&lt;/code> flag should be applied.&lt;/li>
&lt;/ul>
&lt;p>See &lt;a href="https://craftablescience.info/blog/blog/posts/format_deep_dive_vtf/#appendix-a-more-information-on-flags">Appendix A&lt;/a> for more information on flags.&lt;/p>
&lt;h2 id="appendix-a-more-information-on-flags">Appendix A: More Information on Flags&lt;/h2>
&lt;p>Most VTF flags are either internal to &lt;code>vtex&lt;/code> or internal to the engine. There are a few important ones though.
This list covers all the flags the engine uses that are valid to set in a VTF.&lt;/p>
&lt;h3 id="v70">v7.0+&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Flag&lt;/th>
&lt;th style="text-align:center">Value&lt;/th>
&lt;th>Description&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>POINTSAMPLE&lt;/code>&lt;/td>
&lt;td style="text-align:center">&lt;code>2^0&lt;/code>&lt;/td>
&lt;td>Disable texture filtering when sampling from the texture. Largely obsolete on modern Source branches (CS:GO and up) now that materials can control this.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>TRILINEAR&lt;/code>&lt;/td>
&lt;td style="text-align:center">&lt;code>2^1&lt;/code>&lt;/td>
&lt;td>Always use trilinear filtering when sampling from the texture.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>CLAMPS&lt;/code>&lt;/td>
&lt;td style="text-align:center">&lt;code>2^2&lt;/code>&lt;/td>
&lt;td>Do not wrap on the X axis.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>CLAMPT&lt;/code>&lt;/td>
&lt;td style="text-align:center">&lt;code>2^3&lt;/code>&lt;/td>
&lt;td>Do not wrap on the Y axis.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>ANISOTROPIC&lt;/code>&lt;/td>
&lt;td style="text-align:center">&lt;code>2^4&lt;/code>&lt;/td>
&lt;td>Always use anisotropic filtering when sampling from the texture.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>NORMAL&lt;/code>&lt;/td>
&lt;td style="text-align:center">&lt;code>2^7&lt;/code>&lt;/td>
&lt;td>Texture is a normal map.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>NOMIP&lt;/code>&lt;/td>
&lt;td style="text-align:center">&lt;code>2^8&lt;/code>&lt;/td>
&lt;td>Texture has no mipmaps. Should be present on every texture with 1 mip level.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>NOLOD&lt;/code>&lt;/td>
&lt;td style="text-align:center">&lt;code>2^9&lt;/code>&lt;/td>
&lt;td>Texture has no LOD (it does not use lower mip levels than the base). This flag is utterly useless and should be present when &lt;code>NOMIP&lt;/code> is present.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>MINMIP&lt;/code>&lt;/td>
&lt;td style="text-align:center">&lt;code>2^10&lt;/code>&lt;/td>
&lt;td>&lt;strong>Renamed in v7.3.&lt;/strong> Allows the game to load mip levels with dimensions below 32x32. Should only be used for uncompressed formats, and may introduce performance regressions if used frequently.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>PROCEDURAL&lt;/code>&lt;/td>
&lt;td style="text-align:center">&lt;code>2^11&lt;/code>&lt;/td>
&lt;td>Texture was either created in code, or will be modified in code.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>ONEBITALPHA&lt;/code>&lt;/td>
&lt;td style="text-align:center">&lt;code>2^12&lt;/code>&lt;/td>
&lt;td>Should be set for all VTFs storing texture data with one bit alpha.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>EIGHTBITALPHA&lt;/code>&lt;/td>
&lt;td style="text-align:center">&lt;code>2^13&lt;/code>&lt;/td>
&lt;td>Should be set for all VTFs storing texture data with more than one bit of alpha (ignore the name).&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>ENVMAP&lt;/code>&lt;/td>
&lt;td style="text-align:center">&lt;code>2^14&lt;/code>&lt;/td>
&lt;td>Set if the VTF is storing a cubemap.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>NO_DEBUG_OVERRIDE&lt;/code>&lt;/td>
&lt;td style="text-align:center">&lt;code>2^17&lt;/code>&lt;/td>
&lt;td>Texture cannot be modified when debugging graphics features are enabled.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>SINGLE_COPY&lt;/code>&lt;/td>
&lt;td style="text-align:center">&lt;code>2^18&lt;/code>&lt;/td>
&lt;td>Keep only one copy of the texture in memory(?). Unused in later versions of the engine.&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="v72">v7.2+&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Flag&lt;/th>
&lt;th style="text-align:center">Value&lt;/th>
&lt;th>Description&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>NO_DEPTH_BUFFER&lt;/code>&lt;/td>
&lt;td style="text-align:center">&lt;code>2^23&lt;/code>&lt;/td>
&lt;td>Unknown.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>CLAMPU&lt;/code>&lt;/td>
&lt;td style="text-align:center">&lt;code>2^25&lt;/code>&lt;/td>
&lt;td>Do not wrap on the Z axis.&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="v73">v7.3+&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Flag&lt;/th>
&lt;th style="text-align:center">Value&lt;/th>
&lt;th>Description&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>ALL_MIPS&lt;/code>&lt;/td>
&lt;td style="text-align:center">&lt;code>2^10&lt;/code>&lt;/td>
&lt;td>&lt;strong>Unused in v7.5+.&lt;/strong> Renamed from &lt;code>MINMIP&lt;/code>.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>VERTEXTEXTURE&lt;/code>&lt;/td>
&lt;td style="text-align:center">&lt;code>2^26&lt;/code>&lt;/td>
&lt;td>Texture is a vertex texture for &lt;code>VertexLitGeneric&lt;/code> materials.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>SSBUMP&lt;/code>&lt;/td>
&lt;td style="text-align:center">&lt;code>2^27&lt;/code>&lt;/td>
&lt;td>Texture is an ssbump.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>BORDER&lt;/code>&lt;/td>
&lt;td style="text-align:center">&lt;code>2^29&lt;/code>&lt;/td>
&lt;td>Clamp to the texture border color on all axes.&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="v74">v7.4+&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Flag&lt;/th>
&lt;th style="text-align:center">Value&lt;/th>
&lt;th>Description&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>SRGB&lt;/code>&lt;/td>
&lt;td style="text-align:center">&lt;code>2^6&lt;/code>&lt;/td>
&lt;td>&lt;strong>Replaced in v7.5.&lt;/strong> Texture uses the sRGB color space.&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="v74-tf2-based-branches">v7.4 (TF2-based branches)&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Flag&lt;/th>
&lt;th style="text-align:center">Value&lt;/th>
&lt;th>Description&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>STAGING_MEMORY&lt;/code>&lt;/td>
&lt;td style="text-align:center">&lt;code>2^19&lt;/code>&lt;/td>
&lt;td>Unknown.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>IMMEDIATE_CLEANUP&lt;/code>&lt;/td>
&lt;td style="text-align:center">&lt;code>2^20&lt;/code>&lt;/td>
&lt;td>Unknown.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>IGNORE_PICMIP&lt;/code>&lt;/td>
&lt;td style="text-align:center">&lt;code>2^21&lt;/code>&lt;/td>
&lt;td>Ignore &lt;code>mat_picmip&lt;/code> and do not change the maximum mip level.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>STREAMABLE_COARSE&lt;/code>&lt;/td>
&lt;td style="text-align:center">&lt;code>2^30&lt;/code>&lt;/td>
&lt;td>Enables &amp;ldquo;coarse&amp;rdquo; texture streaming.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>STREAMABLE_FINE&lt;/code>&lt;/td>
&lt;td style="text-align:center">&lt;code>2^31&lt;/code>&lt;/td>
&lt;td>Enables &amp;ldquo;fine&amp;rdquo; texture streaming.&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="v75">v7.5+&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Flag&lt;/th>
&lt;th style="text-align:center">Value&lt;/th>
&lt;th>Description&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>PWL_CORRECTED&lt;/code>&lt;/td>
&lt;td style="text-align:center">&lt;code>2^6&lt;/code>&lt;/td>
&lt;td>Texture uses PWL correction (Xbox 360 sRGB approximation).&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>SRGB&lt;/code>&lt;/td>
&lt;td style="text-align:center">&lt;code>2^19&lt;/code>&lt;/td>
&lt;td>Same as the &lt;code>SRGB&lt;/code> flag from v7.4, just in a different position.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>DEFAULT_POOL&lt;/code>&lt;/td>
&lt;td style="text-align:center">&lt;code>2^20&lt;/code>&lt;/td>
&lt;td>Unknown.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>MOST_MIPS&lt;/code>&lt;/td>
&lt;td style="text-align:center">&lt;code>2^28&lt;/code>&lt;/td>
&lt;td>The inverse of &lt;code>ALL_MIPS&lt;/code>, which is now unused. When enabled, mips below 32x32 will &lt;em>not&lt;/em> be loaded.&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="v75-csgo-based-branches">v7.5+ (CS:GO-based branches)&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Flag&lt;/th>
&lt;th style="text-align:center">Value&lt;/th>
&lt;th>Description&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>COMBINED&lt;/code>&lt;/td>
&lt;td style="text-align:center">&lt;code>2^21&lt;/code>&lt;/td>
&lt;td>VTF file is embedded inside a &amp;ldquo;combined&amp;rdquo; model. Do not set manually.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>ASYNC_DOWNLOAD&lt;/code>&lt;/td>
&lt;td style="text-align:center">&lt;code>2^22&lt;/code>&lt;/td>
&lt;td>Unknown.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>SKIP_INITIAL_DOWNLOAD&lt;/code>&lt;/td>
&lt;td style="text-align:center">&lt;code>2^24&lt;/code>&lt;/td>
&lt;td>Unknown.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>YCOCG&lt;/code>&lt;/td>
&lt;td style="text-align:center">&lt;code>2^30&lt;/code>&lt;/td>
&lt;td>Unknown.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>ASYNC_SKIP_INITIAL_LOW_RES&lt;/code>&lt;/td>
&lt;td style="text-align:center">&lt;code>2^31&lt;/code>&lt;/td>
&lt;td>Unknown.&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h2 id="appendix-b-supported-image-formats">Appendix B: Supported Image Formats&lt;/h2>
&lt;p>Unfortunately the list of supported formats is not tied to a VTF version, rather it is tied to the the branch of the
Source engine you&amp;rsquo;re using. Fortunately the list of formats common to all branches is fairly extensive. Check
&lt;a href="https://developer.valvesoftware.com/wiki/VTF_(Valve_Texture_Format)">the Valve Developer Wiki&lt;/a> for a listing with more format-specific information.&lt;/p>
&lt;p>SDK2013 is a bit of a dead end in regards to formats, every engine branch created after Alien Swarm should support Alien
Swarm&amp;rsquo;s extra formats in theory (replacing SDK2013&amp;rsquo;s formats).&lt;/p>
&lt;p>This listing also does not include console-specific formats.&lt;/p>
&lt;h3 id="all-branches">All Branches&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Format&lt;/th>
&lt;th style="text-align:center">ID&lt;/th>
&lt;th>Extra Information&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>RGBA8888&lt;/code>&lt;/td>
&lt;td style="text-align:center">0&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>ABGR8888&lt;/code>&lt;/td>
&lt;td style="text-align:center">1&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>RGB888&lt;/code>&lt;/td>
&lt;td style="text-align:center">2&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>BGR888&lt;/code>&lt;/td>
&lt;td style="text-align:center">3&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>RGB565&lt;/code>&lt;/td>
&lt;td style="text-align:center">4&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>I8&lt;/code>&lt;/td>
&lt;td style="text-align:center">5&lt;/td>
&lt;td>Greyscale, luminance&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>IA88&lt;/code>&lt;/td>
&lt;td style="text-align:center">6&lt;/td>
&lt;td>Greyscale, luminance with alpha&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>P8&lt;/code>&lt;/td>
&lt;td style="text-align:center">7&lt;/td>
&lt;td>Uses a palette. Unclear how a palette would be specified, likely a holdover from GoldSrc. I haven&amp;rsquo;t observed this format in any official or unofficial VTFs.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>A8&lt;/code>&lt;/td>
&lt;td style="text-align:center">8&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>RGB888_BLUESCREEN&lt;/code>&lt;/td>
&lt;td style="text-align:center">9&lt;/td>
&lt;td>Identical to &lt;code>RGB888&lt;/code>, except when the color is &lt;code>0x0000FF&lt;/code> the color is interpreted as transparent instead.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>BGR888_BLUESCREEN&lt;/code>&lt;/td>
&lt;td style="text-align:center">10&lt;/td>
&lt;td>Identical to &lt;code>BGR888&lt;/code>, except when the color is &lt;code>0xFF0000&lt;/code> the color is interpreted as transparent instead.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>ARGB8888&lt;/code>&lt;/td>
&lt;td style="text-align:center">11&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>BGRA8888&lt;/code>&lt;/td>
&lt;td style="text-align:center">12&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>DXT1&lt;/code>&lt;/td>
&lt;td style="text-align:center">13&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>DXT3&lt;/code>&lt;/td>
&lt;td style="text-align:center">14&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>DXT5&lt;/code>&lt;/td>
&lt;td style="text-align:center">15&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>BGRX8888&lt;/code>&lt;/td>
&lt;td style="text-align:center">16&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>BGR565&lt;/code>&lt;/td>
&lt;td style="text-align:center">17&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>BGRX5551&lt;/code>&lt;/td>
&lt;td style="text-align:center">18&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>BGRA4444&lt;/code>&lt;/td>
&lt;td style="text-align:center">19&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>DXT1_ONE_BIT_ALPHA&lt;/code>&lt;/td>
&lt;td style="text-align:center">20&lt;/td>
&lt;td>This format does not function in-game. Use &lt;code>DXT1&lt;/code> with either alpha flag checked (preferably &lt;code>ONEBITALPHA&lt;/code>) instead.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>BGRA5551&lt;/code>&lt;/td>
&lt;td style="text-align:center">21&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>UV88&lt;/code>&lt;/td>
&lt;td style="text-align:center">22&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>UVWQ8888&lt;/code>&lt;/td>
&lt;td style="text-align:center">23&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>RGBA16161616F&lt;/code>&lt;/td>
&lt;td style="text-align:center">24&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>RGBA16161616&lt;/code>&lt;/td>
&lt;td style="text-align:center">25&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>UVLX8888&lt;/code>&lt;/td>
&lt;td style="text-align:center">26&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>R32F&lt;/code>&lt;/td>
&lt;td style="text-align:center">27&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>RGB323232F&lt;/code>&lt;/td>
&lt;td style="text-align:center">28&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>RGBA32323232F&lt;/code>&lt;/td>
&lt;td style="text-align:center">29&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="extra-sdk2013-formats">Extra SDK2013 Formats&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Format&lt;/th>
&lt;th style="text-align:center">ID&lt;/th>
&lt;th>Extra Information&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>EMPTY&lt;/code>&lt;/td>
&lt;td style="text-align:center">36&lt;/td>
&lt;td>Should not be used in VTFs, specifies a pixel size of 0 bytes.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>ATI2N&lt;/code>&lt;/td>
&lt;td style="text-align:center">37&lt;/td>
&lt;td>Broken in all Source engine branches except Strata Source.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>ATI1N&lt;/code>&lt;/td>
&lt;td style="text-align:center">38&lt;/td>
&lt;td>Broken in all Source engine branches except Strata Source.&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="extra-alien-swarm-and-beyond-formats">Extra Alien Swarm (and beyond) Formats&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Format&lt;/th>
&lt;th style="text-align:center">ID&lt;/th>
&lt;th>Extra Information&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>RG1616F&lt;/code>&lt;/td>
&lt;td style="text-align:center">30&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>RG3232F&lt;/code>&lt;/td>
&lt;td style="text-align:center">31&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>RGBX8888&lt;/code>&lt;/td>
&lt;td style="text-align:center">32&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>EMPTY&lt;/code>&lt;/td>
&lt;td style="text-align:center">33&lt;/td>
&lt;td>Should not be used in VTFs, specifies a pixel size of 0 bytes.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>ATI2N&lt;/code>&lt;/td>
&lt;td style="text-align:center">34&lt;/td>
&lt;td>Broken in all Source engine branches except Strata Source.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>ATI1N&lt;/code>&lt;/td>
&lt;td style="text-align:center">35&lt;/td>
&lt;td>Broken in all Source engine branches except Strata Source.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>RGBA1010102&lt;/code>&lt;/td>
&lt;td style="text-align:center">36&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>BGRA1010102&lt;/code>&lt;/td>
&lt;td style="text-align:center">37&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>R16F&lt;/code>&lt;/td>
&lt;td style="text-align:center">38&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="extra-strata-source-formats">Extra Strata Source Formats&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Format&lt;/th>
&lt;th style="text-align:center">ID&lt;/th>
&lt;th>Extra Information&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;code>R8&lt;/code>&lt;/td>
&lt;td style="text-align:center">69&lt;/td>
&lt;td>Identical to &lt;code>I8&lt;/code>, except only using the red channel instead of greyscale.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>BC7&lt;/code>&lt;/td>
&lt;td style="text-align:center">70&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;code>BC6H&lt;/code>&lt;/td>
&lt;td style="text-align:center">71&lt;/td>
&lt;td>Specifically the signed half-float variant.&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table></content></item></channel></rss>