Initial working Windows preview

Creates previews from Basis files. Rough but creates thumbnails and preview pane views from the first images nearest mipmap.
diff --git a/contrib/previewers/win/.gitignore b/contrib/previewers/win/.gitignore
new file mode 100644
index 0000000..9fd3ebe
--- /dev/null
+++ b/contrib/previewers/win/.gitignore
@@ -0,0 +1,12 @@
+# Windows intermediate files
+bin/x86/
+bin/x64/
+
+# VS project files
+*.vcxproj.user
+.vs/
+*.VC.db
+*.VC.opendb
+*.sdf
+*.suo
+*.opensdf
diff --git a/contrib/previewers/win/README.md b/contrib/previewers/win/README.md
new file mode 100644
index 0000000..79de76a
--- /dev/null
+++ b/contrib/previewers/win/README.md
@@ -0,0 +1,7 @@
+# Windows Previewers for Basis Universal
+
+Build using Visual Studio from 2012 onwards. Enable from an Adminstrator console using `regsvr32 previewers.dll` (and remove using `regsvr32 /u previewers.dll`).
+
+![Icon and preview pane](https://raw.githubusercontent.com/cwoffenden/basis_universal/previewers/contrib/previewers/win/preview.png)
+
+Work-in-progress. Prebuilt signed version and installer coming soon. Mac version to follow.
diff --git a/contrib/previewers/win/basisthumbprovider.cpp b/contrib/previewers/win/basisthumbprovider.cpp
new file mode 100644
index 0000000..a7022a7
--- /dev/null
+++ b/contrib/previewers/win/basisthumbprovider.cpp
@@ -0,0 +1,151 @@
+#include "basisthumbprovider.h"
+
+#include <Shlwapi.h>
+
+#include "basisu_transcoder.h"
+
+#include "helpers.h"
+
+#pragma comment(lib, "Shlwapi.lib")
+
+using namespace basist;
+
+static etc1_global_selector_codebook* globalCodebook = NULL;
+
+BasisThumbProvider::BasisThumbProvider() : count(1), stream(NULL) {
+	dprintf("BasisThumbProvider ctor");
+	basisu_transcoder_init();
+	if (!globalCodebook) {
+		 globalCodebook = new etc1_global_selector_codebook(g_global_selector_cb_size, g_global_selector_cb);
+	}
+}
+
+BasisThumbProvider::~BasisThumbProvider() {
+	dprintf("BasisThumbProvider **dtor**");
+	if (stream) {
+		stream->Release();
+		stream = NULL;
+	}
+}
+
+IFACEMETHODIMP BasisThumbProvider::QueryInterface(REFIID riid, void **ppv) {
+	static const QITAB qit[] = {
+		QITABENT(BasisThumbProvider, IThumbnailProvider),
+		QITABENT(BasisThumbProvider, IInitializeWithStream),
+		{0},
+	};
+	return QISearch(this, qit, riid, ppv);
+}
+
+IFACEMETHODIMP_(ULONG) BasisThumbProvider::AddRef() {
+	return InterlockedIncrement(&count);
+}
+
+IFACEMETHODIMP_(ULONG) BasisThumbProvider::Release() {
+	LONG refs = InterlockedDecrement(&count);
+	if (refs == 0) {
+		delete this;
+	}
+	return refs;
+}
+
+IFACEMETHODIMP BasisThumbProvider::Initialize(IStream *pStream, DWORD grfMode) {
+	dprintf("BasisThumbProvider::Initialize");
+	HRESULT hr = HRESULT_FROM_WIN32(ERROR_ALREADY_INITIALIZED);
+	if (!stream) {
+		hr = pStream->QueryInterface(&stream);
+	}
+	return hr;
+}
+
+// Note: thumbnails get written here: %LocalAppData%\Microsoft\Windows\Explorer
+IFACEMETHODIMP BasisThumbProvider::GetThumbnail(UINT cx, HBITMAP *phbmp, WTS_ALPHATYPE *pdwAlpha) {
+	STATSTG stat;
+	if (stream && SUCCEEDED(stream->Stat(&stat, STATFLAG_NONAME))) {
+		if (void* data = malloc(static_cast<size_t>(stat.cbSize.LowPart))) {
+			ULONG size = 0;
+			if (SUCCEEDED(stream->Read(data, static_cast<ULONG>(stat.cbSize.LowPart), &size))) {
+				if (size == stat.cbSize.LowPart) {
+					basisu_transcoder transcoder(globalCodebook);
+					if (transcoder.validate_header(data, size)) {
+						dprintf("Requested %d bytes for %dx%d image", size, cx, cx);
+						basisu_image_info info;
+						if (transcoder.get_image_info(data, size, info, 0)) {
+							uint32_t level = 0;
+							uint32_t descW = 0, descH = 0, blocks;
+							for (uint32_t n = 0; n < info.m_total_levels; n++) {
+								if (transcoder.get_image_level_desc(data, size, 0, n, descW, descH, blocks)) {
+									dprintf("mipmap level w: %d, h: %d (blocks: %d)", descW, descH, blocks);
+									if (cx >= std::max(descW, descH)) {
+										level = n;
+										break;
+									}
+								}
+							}
+							basisu_file_info fileInfo;
+							transcoder.get_file_info(data, size, fileInfo);
+							if (transcoder.start_transcoding(data, size)) {
+								if (void* dxtBuf = malloc(basis_get_bytes_per_block(cTFBC1) * blocks)) {
+									if (transcoder.transcode_image_level(data, size, 0, level, dxtBuf, blocks, cTFBC1)) {
+										dprintf("Decoded!!!!");
+										*phbmp = dxtToBitmap(static_cast<uint8_t*>(dxtBuf), descW, descH, fileInfo.m_y_flipped);
+									}
+									delete dxtBuf;
+								}
+							}
+						}
+					}
+				}
+			}
+			free(data);
+		}
+	}
+	return (*phbmp) ? S_OK : S_FALSE;
+}
+
+//********************************** Factory *********************************/
+
+BasisThumbProviderFactory::BasisThumbProviderFactory() : count(1) {}
+
+BasisThumbProviderFactory::~BasisThumbProviderFactory() {}
+
+IFACEMETHODIMP BasisThumbProviderFactory::QueryInterface(REFIID riid, void **ppv) {
+	static const QITAB qit[] = {
+		QITABENT(BasisThumbProviderFactory, IClassFactory),
+		{0},
+	};
+	return QISearch(this, qit, riid, ppv);
+}
+
+IFACEMETHODIMP_(ULONG) BasisThumbProviderFactory::AddRef() {
+	return InterlockedIncrement(&count);
+}
+
+IFACEMETHODIMP_(ULONG) BasisThumbProviderFactory::Release() {
+	LONG refs = InterlockedDecrement(&count);
+	if (refs == 0) {
+		delete this;
+	}
+	return refs;
+}
+
+IFACEMETHODIMP BasisThumbProviderFactory::CreateInstance(IUnknown *pUnkOuter, REFIID riid, void **ppv) {
+	HRESULT hr = CLASS_E_NOAGGREGATION;
+	if (pUnkOuter == NULL) {
+		hr = E_OUTOFMEMORY;
+		if (BasisThumbProvider* provider = new (std::nothrow) BasisThumbProvider()) {
+			hr = provider->QueryInterface(riid, ppv);
+			provider->Release();
+		}
+	}
+	return hr;
+}
+
+IFACEMETHODIMP BasisThumbProviderFactory::LockServer(BOOL fLock) {
+	if (fLock) {
+		InterlockedIncrement(&count);
+	} else {
+		InterlockedDecrement(&count);
+	}
+	return S_OK;
+}
diff --git a/contrib/previewers/win/basisthumbprovider.h b/contrib/previewers/win/basisthumbprovider.h
new file mode 100644
index 0000000..e73747c
--- /dev/null
+++ b/contrib/previewers/win/basisthumbprovider.h
@@ -0,0 +1,58 @@
+#pragma once
+
+#include <Windows.h>
+#include <thumbcache.h>
+
+/**
+ * 
+ */
+class BasisThumbProvider : public IInitializeWithStream, public IThumbnailProvider
+{
+public:
+	BasisThumbProvider();
+	// IUnknown::QueryInterface()
+	IFACEMETHODIMP QueryInterface(REFIID riid, void **ppv) override;
+	// IUnknown::AddRef()
+	IFACEMETHODIMP_(ULONG) AddRef() override;
+	// IUnknown::Release()
+	IFACEMETHODIMP_(ULONG) Release() override;
+	
+	// IInitializeWithStream::Initialize()
+	IFACEMETHODIMP Initialize(IStream *pStream, DWORD grfMode) override;
+	
+	// IThumbnailProvider::GetThumbnail()
+	IFACEMETHODIMP GetThumbnail(UINT cx, HBITMAP *phbmp, WTS_ALPHATYPE *pdwAlpha) override;
+
+protected:
+	virtual ~BasisThumbProvider();
+	
+private:
+	LONG count;
+	IStream* stream;
+};
+
+/**
+ * 
+ */
+class BasisThumbProviderFactory : public IClassFactory
+{
+public:
+	BasisThumbProviderFactory();
+	// IUnknown::QueryInterface()
+	IFACEMETHODIMP QueryInterface(REFIID riid, void **ppv) override;
+	// IUnknown::AddRef()
+	IFACEMETHODIMP_(ULONG) AddRef() override;
+	// IUnknown::Release()
+	IFACEMETHODIMP_(ULONG) Release() override;
+	
+	// IClassFactory::CreateInstance()
+	IFACEMETHODIMP CreateInstance(IUnknown *pUnkOuter, REFIID riid, void **ppv) override;
+	// IClassFactory::LockServer()
+	IFACEMETHODIMP LockServer(BOOL fLock) override;
+	
+protected:
+	virtual ~BasisThumbProviderFactory();
+	
+private:
+	LONG count;
+};
diff --git a/contrib/previewers/win/helpers.cpp b/contrib/previewers/win/helpers.cpp
new file mode 100644
index 0000000..ab89279
--- /dev/null
+++ b/contrib/previewers/win/helpers.cpp
@@ -0,0 +1,170 @@
+#include "helpers.h"
+
+#include <cassert>
+#include <cstdio>
+
+//************************* See cubic/texture-dxt.cpp ************************/
+
+/**
+ * Expands an RGB565 format colour into BGR888.
+ * 
+ * \note Unlike the original code, which was RGB888, for a Windows bitmap we
+ * need this is as \e BGR.
+ * 
+ * \param[in] color RGB565 format colour
+ * \return a \e bit \e expansion of the supplied colour as 8-bit per channel (with zero alpha)
+ */
+static uint32_t rgbFrom565(unsigned const color) {
+	unsigned r = (color >> 11) & 0x1F;
+	unsigned g = (color >>  5) & 0x3F;
+	unsigned b = (color >>  0) & 0x1F;
+	return (((r << 3) | (r >> 2)) << 16)
+		 | (((g << 2) | (g >> 4)) <<  8)
+		 | (((b << 3) | (b >> 2)) <<  0);
+}
+
+/**
+ * Calculates the DXT decompressor's \e midpoint colour, where the weighting
+ * is \c 2:1 (the parameters can be exchanged to calculate both midpoints).
+ * Used by the DXT block decode.
+ * 
+ * \param[in] color0 \c endpoint colour receiving a double weighting
+ * \param[in] color1 \c endpoint colour receiving a single weighting
+ * \return blended colour (excluding alpha)
+ */
+static uint32_t midpoint21(uint32_t const color0, uint32_t const color1) {
+	return (((2 * (color0 & 0x0000FF) + (color1 & 0x0000FF)) / 3) & 0x0000FF)
+		 | (((2 * (color0 & 0x00FF00) + (color1 & 0x00FF00)) / 3) & 0x00FF00)
+		 | (((2 * (color0 & 0xFF0000) + (color1 & 0xFF0000)) / 3) & 0xFF0000);
+}
+
+/**
+ * Calculates the DXT decompressor's \e midpoint colour, where the weighting
+ * is \c 1:1. Used by the DXT block decode.
+ * 
+ * \param[in] color0 \c endpoint colour (any of the endpoints)
+ * \param[in] color1 \c endpoint colour (any of the endpoints)
+ * \return blended colour (excluding alpha)
+ */
+static uint32_t midpoint11(uint32_t const color0, uint32_t const color1) {
+	return ((((color0 & 0x0000FF) + (color1 & 0x0000FF)) / 2) & 0x0000FF)
+		 | ((((color0 & 0x00FF00) + (color1 & 0x00FF00)) / 2) & 0x00FF00)
+		 | ((((color0 & 0xFF0000) + (color1 & 0xFF0000)) / 2) & 0xFF0000);
+}
+
+/**
+ * Decodes a DXT1 block.
+ * 
+ * \note Unlike the original code, which was RGB888, for a Windows bitmap we
+ * need this is as \e BGR.
+ * 
+ * \param[in] src start of the encoded data (eight contiguous bytes)
+ * \param[in] dst destination of the block's top-left pixel
+ * \param[in] span number of pixels to advance to the next line (usually the texture width)
+ */
+static void decodeDxt1(const uint8_t* const src, uint32_t* dst, unsigned const span) {
+	assert(src && dst && span);
+	/*
+	 * Extract the two 16-bit 'endpoints'. These are little Endian, regardless
+	 * of the platform. Note that the midpoint choices are made against these
+	 * (not the platform Endian RGB/BGR versions) and that (specifically in
+	 * this implementation) DXT1 will always have solid alpha (which we bake
+	 * into the colour table). The color 'codes' are collated here into a
+	 * 32-bit value (which simplifies addressing the bits directly later).
+	 */
+#if defined(__BYTE_ORDER) && (__BYTE_ORDER == __BIG_ENDIAN)
+	unsigned const color0((src[0] <<  0) | (src[1] <<  8));
+	unsigned const color1((src[2] <<  0) | (src[3] <<  8));
+	unsigned const ccodes((src[4] <<  0) | (src[5] <<  8)
+						| (src[6] << 16) | (src[7] << 24));
+#else
+	unsigned const color0(*reinterpret_cast<const uint16_t*>(src + 0));
+	unsigned const color1(*reinterpret_cast<const uint16_t*>(src + 2));
+	unsigned const ccodes(*reinterpret_cast<const uint32_t*>(src + 4));
+#endif
+	uint32_t color[4];
+	color[0] = 0xFF000000 | rgbFrom565(color0);
+	color[1] = 0xFF000000 | rgbFrom565(color1);
+	color[2] = 0xFF000000 | ((color0 > color1)
+						  ? midpoint21(color[0], color[1])
+						  : midpoint11(color[0], color[1]));
+	color[3] = 0xFF000000 | ((color0 > color1)
+						  ? midpoint21(color[1], color[0])
+						  : 0);
+	/*
+	 * The 4x4 block is unrolled. For destinations smaller than 4x4 the pixel
+	 * overwrites here are inefficient, but the overhead isn't enough to worry
+	 * about (when compared with the GL texture upload).
+	 */
+	dst[0] = color[(ccodes >>  0) & 0x03];
+	dst[1] = color[(ccodes >>  2) & 0x03];
+	dst[2] = color[(ccodes >>  4) & 0x03];
+	dst[3] = color[(ccodes >>  6) & 0x03];
+	dst   += span;
+	dst[0] = color[(ccodes >>  8) & 0x03];
+	dst[1] = color[(ccodes >> 10) & 0x03];
+	dst[2] = color[(ccodes >> 12) & 0x03];
+	dst[3] = color[(ccodes >> 14) & 0x03];
+	dst   += span;
+	dst[0] = color[(ccodes >> 16) & 0x03];
+	dst[1] = color[(ccodes >> 18) & 0x03];
+	dst[2] = color[(ccodes >> 20) & 0x03];
+	dst[3] = color[(ccodes >> 22) & 0x03];
+	dst   += span;
+	dst[0] = color[(ccodes >> 24) & 0x03];
+	dst[1] = color[(ccodes >> 26) & 0x03];
+	dst[2] = color[(ccodes >> 28) & 0x03];
+	dst[3] = color[(ccodes >> 30) & 0x03];
+}
+
+//******************************** Public API ********************************/
+
+void dprintf(char* const fmt, ...) {
+	va_list  args;
+	char buf[256];
+	va_start(args, fmt);
+	int len = vsnprintf_s(buf, sizeof buf, fmt, args);
+	va_end  (args);
+	if (len > 0) {
+		buf[sizeof buf - 1] = 0;
+		OutputDebugStringA(buf);
+	}
+}
+
+HBITMAP dxtToBitmap(const uint8_t* src, uint32_t const imgW, uint32_t const imgH, bool const flip) {
+	assert(src && imgW && imgH);
+	/*
+	 * Creates a bitmap (a DIB) for the passed-in pixel size. Note that
+	 * negation of the height means top-down, origin upper-left, which is the
+	 * regular case.
+	 * 
+	 * TODO: 16-bit variant instead?
+	 * TODO: bitmaps with dimensions that aren't a multiple of 4
+	 */
+	BITMAPINFO bmi = {
+		sizeof(bmi.bmiHeader)
+	};
+	bmi.bmiHeader.biWidth  =          imgW;
+	bmi.bmiHeader.biHeight = (flip) ? imgH : -static_cast<int32_t>(imgH);
+	bmi.bmiHeader.biPlanes = 1;
+	bmi.bmiHeader.biBitCount = 32;
+	bmi.bmiHeader.biCompression = BI_RGB;
+	void* pixels = NULL;
+	HBITMAP hbmp = CreateDIBSection(NULL, &bmi, DIB_RGB_COLORS, &pixels, NULL, 0);
+	/*
+	 * Decode the BC1 blocks.
+	 */
+	if (hbmp && pixels) {
+		uint32_t* dst = static_cast<uint32_t*>(pixels);
+		for (unsigned y = 0; y < imgH; y += 4) {
+			uint32_t* row = dst;
+			for (unsigned x = 0; x < imgW; x += 4) {
+				decodeDxt1(src, row, imgW);
+				src += 8;
+				row += 4;
+			}
+			dst += 4 * imgW;
+		}
+	}
+	return hbmp;
+}
diff --git a/contrib/previewers/win/helpers.h b/contrib/previewers/win/helpers.h
new file mode 100644
index 0000000..1a4a955
--- /dev/null
+++ b/contrib/previewers/win/helpers.h
@@ -0,0 +1,22 @@
+#pragma once
+
+#include <Windows.h>
+
+#include <cstdint>
+
+/**
+ * Write a formatted string to the connected debugger (e.g. DebugView).
+ * 
+ * \param[in] fmt content to write in \c printf format (followed by optional arguments)
+ */
+void dprintf(char* const fmt, ...);
+
+/**
+ * Software decodes BC1 format data.
+ * 
+ * \param[in] src BC1 source blocks (the number of blocks being determined by the image dimensions)
+ * \param[in] imgW width of the decoded image
+ * \param[in] imgH height of the decoded image
+ * \return handle to a bitmap (ownership passed to the caller)
+ */
+HBITMAP dxtToBitmap(const uint8_t* src, uint32_t const imgW, uint32_t const imgH, bool const flip = false);
diff --git a/contrib/previewers/win/preview.png b/contrib/previewers/win/preview.png
new file mode 100644
index 0000000..19836cf
--- /dev/null
+++ b/contrib/previewers/win/preview.png
Binary files differ
diff --git a/contrib/previewers/win/previewers.cpp b/contrib/previewers/win/previewers.cpp
new file mode 100644
index 0000000..8274bd9
--- /dev/null
+++ b/contrib/previewers/win/previewers.cpp
@@ -0,0 +1,118 @@
+#include <Windows.h>
+#include <ShlObj.h>
+#include <tchar.h>
+
+#include <new>
+
+#include "basisthumbprovider.h"
+
+#define SHELLEX_THUMBNAIL_CLSID _T("ShellEx\\{E357FCCD-A995-4576-B01F-234630154E96}")
+#define SHELLEX_PREVIEWER_CLSID _T("ShellEx\\{8895B1C6-B41F-4C1C-A562-0D564250836F}")
+
+#define THUMBNAIL_HANDLER_TITLE _T("Basis Thumbnail Handler")
+#define THUMBNAIL_HANDLER_CLSID _T("{CD1F0EA0-283C-4D90-A41D-DEBD9207D91F}")
+
+#define PREVIEWER_HANDLER_TITLE _T("Basis Previewer Handler")
+#define PREVIEWER_HANDLER_CLSID _T("{7B5DA275-3BB6-45AE-B0EB-E50187A28F13}")
+
+#define FILE_EXTENSION _T(".basis")
+
+static const CLSID CLSID_ThumbnailHandler = {0xCD1F0EA0, 0x283C, 0x4D90, {0xA4, 0x1D, 0xDE, 0xBD, 0x92, 0x07, 0xD9, 0x1F}};
+static const CLSID CLSID_PreviewerHandler = {0x7B5DA275, 0x3BB6, 0x45AE, {0xB0, 0xEB, 0xE5, 0x01, 0x87, 0xA2, 0x8F, 0x13}};
+
+static TCHAR dllPath[MAX_PATH] = {0};
+
+static LONG dllRefs = 0;
+
+#ifndef BAIL_ON_FAIL
+#define BAIL_ON_FAIL(code) if (FAILED((hr = (code)))) return hr
+#endif
+
+static HRESULT setRegKey(HKEY root, LPTSTR key, LPTSTR val, LPTSTR data) {
+	HKEY hKey;
+	HRESULT hr = HRESULT_FROM_WIN32(RegCreateKeyEx(root, key, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, NULL, &hKey, NULL));
+	if (SUCCEEDED(hr)) {
+		hr = HRESULT_FROM_WIN32(RegSetValueEx(hKey, val, 0, REG_SZ, reinterpret_cast<LPBYTE>(data), static_cast<DWORD>(_tcslen(data) * sizeof TCHAR)));
+		RegCloseKey(hKey);
+	}
+	return hr;
+}
+
+BOOL APIENTRY DllMain(HMODULE hInstDLL, DWORD reason, LPVOID /*reserved*/) {
+	if (reason == DLL_PROCESS_ATTACH) {
+		OutputDebugString(_T("DllMain"));
+		if (GetModuleFileName(hInstDLL, dllPath, sizeof dllPath) == 0) {
+		#ifdef _DEBUG
+			OutputDebugString(_T("Failed to obtain DLL path"));
+		#endif
+		}
+		DisableThreadLibraryCalls(hInstDLL);
+	}
+	return TRUE;
+}
+
+
+STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, void **ppv) {
+	HRESULT hr = CLASS_E_CLASSNOTAVAILABLE;
+	if (IsEqualCLSID(CLSID_ThumbnailHandler, rclsid)) {
+		hr = E_OUTOFMEMORY;
+		if (IClassFactory* factory = new (std::nothrow) BasisThumbProviderFactory()) {
+			hr = factory->QueryInterface(riid, ppv);
+			factory->Release();
+		}
+	}
+	return hr;
+}
+
+void DllAddRef() {
+#ifdef _DEBUG
+	OutputDebugString(_T("DllAddRef"));
+#endif
+	InterlockedIncrement(&dllRefs);
+}
+
+void DllRelease() {
+#ifdef _DEBUG
+	OutputDebugString(_T("DllRelease"));
+#endif
+	InterlockedDecrement(&dllRefs);
+}
+
+STDAPI DllCanUnloadNow() {
+#ifdef _DEBUG
+	OutputDebugString(_T("DllCanUnloadNow"));
+#endif
+	return (dllRefs == 0) ? S_OK : S_FALSE;
+}
+
+// regsvr32 /s previewers.dll
+STDAPI DllRegisterServer() {
+	HRESULT hr = E_FAIL;
+	if (_tcslen(dllPath)) {
+		BAIL_ON_FAIL(setRegKey(HKEY_LOCAL_MACHINE, _T("Software\\Classes\\CLSID\\") THUMBNAIL_HANDLER_CLSID,                        NULL,                 THUMBNAIL_HANDLER_TITLE));
+		BAIL_ON_FAIL(setRegKey(HKEY_LOCAL_MACHINE, _T("Software\\Classes\\CLSID\\") THUMBNAIL_HANDLER_CLSID _T("\\InProcServer32"), NULL,                 dllPath));
+		BAIL_ON_FAIL(setRegKey(HKEY_LOCAL_MACHINE, _T("Software\\Classes\\CLSID\\") THUMBNAIL_HANDLER_CLSID _T("\\InProcServer32"), _T("ThreadingModel"), _T("Apartment")));
+		BAIL_ON_FAIL(setRegKey(HKEY_LOCAL_MACHINE, _T("Software\\Classes\\") FILE_EXTENSION _T("\\") SHELLEX_THUMBNAIL_CLSID,       NULL,                 THUMBNAIL_HANDLER_CLSID));
+		SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL);
+	}
+#ifdef _DEBUG
+	if (SUCCEEDED(hr)) {
+		OutputDebugString(_T("Previewer successfully registered"));
+	}
+#endif
+	return hr;
+}
+
+// regsvr32 /s /u previewers.dll
+STDAPI DllUnregisterServer() {
+	HRESULT hr = HRESULT_FROM_WIN32(RegDeleteTree(HKEY_LOCAL_MACHINE, _T("Software\\Classes\\CLSID\\") THUMBNAIL_HANDLER_CLSID));
+	if (SUCCEEDED(hr)) {
+		hr = HRESULT_FROM_WIN32(RegDeleteTree(HKEY_LOCAL_MACHINE, _T("Software\\Classes\\") FILE_EXTENSION _T("\\") SHELLEX_THUMBNAIL_CLSID));
+	}
+#ifdef _DEBUG
+	if (SUCCEEDED(hr)) {
+		OutputDebugString(_T("Previewer successfully unregistered"));
+	}
+#endif
+	return hr;
+}
diff --git a/contrib/previewers/win/previewers.def b/contrib/previewers/win/previewers.def
new file mode 100644
index 0000000..112ba88
--- /dev/null
+++ b/contrib/previewers/win/previewers.def
@@ -0,0 +1,6 @@
+EXPORTS
+	DllGetClassObject	PRIVATE
+	DllCanUnloadNow		PRIVATE
+	DllRegisterServer	PRIVATE
+	DllUnregisterServer	PRIVATE
+
diff --git a/contrib/previewers/win/previewers.sln b/contrib/previewers/win/previewers.sln
new file mode 100644
index 0000000..ec39390
--- /dev/null
+++ b/contrib/previewers/win/previewers.sln
@@ -0,0 +1,26 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 2012
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "previewers", "previewers.vcxproj", "{F3D4D2B1-20BF-44F2-B624-BE19C6911D4B}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Win32 = Debug|Win32
+		Debug|x64 = Debug|x64
+		Release|Win32 = Release|Win32
+		Release|x64 = Release|x64
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{F3D4D2B1-20BF-44F2-B624-BE19C6911D4B}.Debug|Win32.ActiveCfg = Debug|Win32
+		{F3D4D2B1-20BF-44F2-B624-BE19C6911D4B}.Debug|Win32.Build.0 = Debug|Win32
+		{F3D4D2B1-20BF-44F2-B624-BE19C6911D4B}.Debug|x64.ActiveCfg = Debug|x64
+		{F3D4D2B1-20BF-44F2-B624-BE19C6911D4B}.Debug|x64.Build.0 = Debug|x64
+		{F3D4D2B1-20BF-44F2-B624-BE19C6911D4B}.Release|Win32.ActiveCfg = Release|Win32
+		{F3D4D2B1-20BF-44F2-B624-BE19C6911D4B}.Release|Win32.Build.0 = Release|Win32
+		{F3D4D2B1-20BF-44F2-B624-BE19C6911D4B}.Release|x64.ActiveCfg = Release|x64
+		{F3D4D2B1-20BF-44F2-B624-BE19C6911D4B}.Release|x64.Build.0 = Release|x64
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+EndGlobal
diff --git a/contrib/previewers/win/previewers.vcxproj b/contrib/previewers/win/previewers.vcxproj
new file mode 100644
index 0000000..6f1d62b
--- /dev/null
+++ b/contrib/previewers/win/previewers.vcxproj
@@ -0,0 +1,188 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup Label="ProjectConfigurations">
+    <ProjectConfiguration Include="Debug|Win32">
+      <Configuration>Debug</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Debug|x64">
+      <Configuration>Debug</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|Win32">
+      <Configuration>Release</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|x64">
+      <Configuration>Release</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+  </ItemGroup>
+  <PropertyGroup Label="Globals">
+    <ProjectGuid>{F3D4D2B1-20BF-44F2-B624-BE19C6911D4B}</ProjectGuid>
+    <Keyword>Win32Proj</Keyword>
+    <RootNamespace>previewers</RootNamespace>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+    <UseDebugLibraries>true</UseDebugLibraries>
+    <PlatformToolset>$(DefaultPlatformToolset)</PlatformToolset>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+    <UseDebugLibraries>true</UseDebugLibraries>
+    <PlatformToolset>$(DefaultPlatformToolset)</PlatformToolset>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <PlatformToolset>$(DefaultPlatformToolset)</PlatformToolset>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+    <ConfigurationType>DynamicLibrary</ConfigurationType>
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <PlatformToolset>$(DefaultPlatformToolset)</PlatformToolset>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+  <ImportGroup Label="ExtensionSettings">
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <PropertyGroup Label="UserMacros" />
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <LinkIncremental>true</LinkIncremental>
+    <OutDir>$(SolutionDir)bin\$(PlatformShortName)\$(Configuration)\</OutDir>
+    <IntDir>bin\$(PlatformShortName)\$(Configuration)\</IntDir>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <LinkIncremental>true</LinkIncremental>
+    <IntDir>bin\$(PlatformShortName)\$(Configuration)\</IntDir>
+    <OutDir>$(SolutionDir)bin\$(PlatformShortName)\$(Configuration)\</OutDir>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <LinkIncremental>false</LinkIncremental>
+    <OutDir>$(SolutionDir)bin\$(PlatformShortName)\$(Configuration)\</OutDir>
+    <IntDir>bin\$(PlatformShortName)\$(Configuration)\</IntDir>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <LinkIncremental>false</LinkIncremental>
+    <IntDir>bin\$(PlatformShortName)\$(Configuration)\</IntDir>
+    <OutDir>$(SolutionDir)bin\$(PlatformShortName)\$(Configuration)\</OutDir>
+  </PropertyGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <Optimization>Disabled</Optimization>
+      <PreprocessorDefinitions>WIN32;_WIN32_WINNT=0x0601;_WINDOWS;_USRDLL;_ITERATOR_DEBUG_LEVEL=1;BASISD_SUPPORT_DXT5A=0;BASISD_SUPPORT_PVRTC1=0;BASISD_SUPPORT_BC7=0;BASISD_SUPPORT_ETC2_EAC_A8=0;_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <SDLCheck>true</SDLCheck>
+      <ExceptionHandling>false</ExceptionHandling>
+      <AdditionalIncludeDirectories>$(ProjectDir)..\..\..\transcoder\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <RuntimeTypeInfo>false</RuntimeTypeInfo>
+      <MinimalRebuild>false</MinimalRebuild>
+    </ClCompile>
+    <Link>
+      <SubSystem>Windows</SubSystem>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <ModuleDefinitionFile>previewers.def</ModuleDefinitionFile>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <Optimization>Disabled</Optimization>
+      <PreprocessorDefinitions>WIN32;_WIN32_WINNT=0x0601;_WINDOWS;_USRDLL;_ITERATOR_DEBUG_LEVEL=1;BASISD_SUPPORT_DXT5A=0;BASISD_SUPPORT_PVRTC1=0;BASISD_SUPPORT_BC7=0;BASISD_SUPPORT_ETC2_EAC_A8=0;_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <SDLCheck>true</SDLCheck>
+      <ExceptionHandling>false</ExceptionHandling>
+      <AdditionalIncludeDirectories>$(ProjectDir)..\..\..\transcoder\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <RuntimeTypeInfo>false</RuntimeTypeInfo>
+      <MinimalRebuild>false</MinimalRebuild>
+    </ClCompile>
+    <Link>
+      <SubSystem>Windows</SubSystem>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <ModuleDefinitionFile>previewers.def</ModuleDefinitionFile>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <Optimization>Full</Optimization>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+      <PreprocessorDefinitions>WIN32;_WIN32_WINNT=0x0601;_WINDOWS;_USRDLL;_ITERATOR_DEBUG_LEVEL=0;BASISD_SUPPORT_DXT5A=0;BASISD_SUPPORT_PVRTC1=0;BASISD_SUPPORT_BC7=0;BASISD_SUPPORT_ETC2_EAC_A8=0;NDEBUG;WIN32_LEAN_AND_MEAN;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <ExceptionHandling>false</ExceptionHandling>
+      <AdditionalIncludeDirectories>$(ProjectDir)..\..\..\transcoder\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <RuntimeTypeInfo>false</RuntimeTypeInfo>
+    </ClCompile>
+    <Link>
+      <SubSystem>Windows</SubSystem>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <OptimizeReferences>true</OptimizeReferences>
+      <ModuleDefinitionFile>previewers.def</ModuleDefinitionFile>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <Optimization>Full</Optimization>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+      <PreprocessorDefinitions>WIN32;_WIN32_WINNT=0x0601;_WINDOWS;_USRDLL;_ITERATOR_DEBUG_LEVEL=0;BASISD_SUPPORT_DXT5A=0;BASISD_SUPPORT_PVRTC1=0;BASISD_SUPPORT_BC7=0;BASISD_SUPPORT_ETC2_EAC_A8=0;NDEBUG;WIN32_LEAN_AND_MEAN;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <ExceptionHandling>false</ExceptionHandling>
+      <AdditionalIncludeDirectories>$(ProjectDir)..\..\..\transcoder\;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <RuntimeTypeInfo>false</RuntimeTypeInfo>
+    </ClCompile>
+    <Link>
+      <SubSystem>Windows</SubSystem>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <OptimizeReferences>true</OptimizeReferences>
+      <ModuleDefinitionFile>previewers.def</ModuleDefinitionFile>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemGroup>
+    <ClInclude Include="..\..\..\transcoder\basisu_transcoder.h" />
+    <ClInclude Include="basisthumbprovider.h" />
+    <ClInclude Include="helpers.h" />
+  </ItemGroup>
+  <ItemGroup>
+    <ClCompile Include="..\..\..\transcoder\basisu_transcoder.cpp" />
+    <ClCompile Include="basisthumbprovider.cpp" />
+    <ClCompile Include="helpers.cpp" />
+    <ClCompile Include="previewers.cpp">
+      <CompileAsManaged Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">false</CompileAsManaged>
+      <CompileAsManaged Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">false</CompileAsManaged>
+      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+      </PrecompiledHeader>
+      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+      </PrecompiledHeader>
+      <CompileAsManaged Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">false</CompileAsManaged>
+      <CompileAsManaged Condition="'$(Configuration)|$(Platform)'=='Release|x64'">false</CompileAsManaged>
+      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+      </PrecompiledHeader>
+      <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+      </PrecompiledHeader>
+    </ClCompile>
+  </ItemGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+  <ImportGroup Label="ExtensionTargets">
+  </ImportGroup>
+</Project>
\ No newline at end of file
diff --git a/contrib/previewers/win/previewers.vcxproj.filters b/contrib/previewers/win/previewers.vcxproj.filters
new file mode 100644
index 0000000..e2ac404
--- /dev/null
+++ b/contrib/previewers/win/previewers.vcxproj.filters
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup>
+    <Filter Include="inc">
+      <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
+      <Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions>
+    </Filter>
+    <Filter Include="src">
+      <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
+      <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
+    </Filter>
+    <Filter Include="src\transcoder">
+      <UniqueIdentifier>{2d6d195a-ebe1-467a-a713-8abb5d133ce6}</UniqueIdentifier>
+    </Filter>
+    <Filter Include="inc\transcoder">
+      <UniqueIdentifier>{724b1ae2-a9f5-4706-b406-26d4660d72e0}</UniqueIdentifier>
+    </Filter>
+  </ItemGroup>
+  <ItemGroup>
+    <ClInclude Include="basisthumbprovider.h">
+      <Filter>inc</Filter>
+    </ClInclude>
+    <ClInclude Include="..\..\..\transcoder\basisu_transcoder.h">
+      <Filter>inc\transcoder</Filter>
+    </ClInclude>
+    <ClInclude Include="helpers.h">
+      <Filter>inc</Filter>
+    </ClInclude>
+  </ItemGroup>
+  <ItemGroup>
+    <ClCompile Include="basisthumbprovider.cpp">
+      <Filter>src</Filter>
+    </ClCompile>
+    <ClCompile Include="previewers.cpp">
+      <Filter>src</Filter>
+    </ClCompile>
+    <ClCompile Include="..\..\..\transcoder\basisu_transcoder.cpp">
+      <Filter>src\transcoder</Filter>
+    </ClCompile>
+    <ClCompile Include="helpers.cpp">
+      <Filter>src</Filter>
+    </ClCompile>
+  </ItemGroup>
+</Project>
\ No newline at end of file