diff --git a/Wabbajack.ImageHashing.Test/ImageLoadingTests.cs b/Wabbajack.ImageHashing.Test/ImageLoadingTests.cs
index 849f7b10..39ce4dd6 100644
--- a/Wabbajack.ImageHashing.Test/ImageLoadingTests.cs
+++ b/Wabbajack.ImageHashing.Test/ImageLoadingTests.cs
@@ -9,50 +9,59 @@ namespace Wabbajack.ImageHashing.Test
         [Fact]
         public async Task CanLoadAndCompareDDSImages()
         {
-            var file1 = DDSImage.FromFile(AbsolutePath.EntryPoint.Combine("Resources", "test-dxt5.dds"));
-            var hash1 = file1.PerceptionHash();
+            var hash1 = await ImageState.GetPHash(AbsolutePath.EntryPoint.Combine("Resources", "test-dxt5.dds"));
+            var state1 = await ImageState.GetState(AbsolutePath.EntryPoint.Combine("Resources", "test-dxt5.dds"));
+            
+            Assert.Equal(512, state1.Width);
+            Assert.Equal(512, state1.Height);
+            Assert.Equal(DXGI_FORMAT.BC3_UNORM, state1.Format);
+
+            var hash2 = await ImageState.GetPHash(AbsolutePath.EntryPoint.Combine("Resources", "test-dxt5.dds"));
+
+            // From old embedded hashing method, we want to make sure the hashing algorithm hasn't changed so much that
+            // we've broken the old caches
+            var hash3 = PHash.FromBase64("cns+/2xel0ulcwCXeTlVW2x5aGtwaGl9glpthWZkb2ducnF0c2lvgQ==");
             
-            var file2 = DDSImage.FromFile(AbsolutePath.EntryPoint.Combine("Resources", "test-dxt5.dds"));
-            var hash2 = file2.PerceptionHash();
             
             Assert.Equal(1, hash1.Similarity(hash2));
+            Assert.True(hash1.Similarity(hash3) > 0.99f);
         }
         
         [Fact]
         public async Task CanLoadAndCompareResizedImage()
         {
-            var file1 = DDSImage.FromFile(AbsolutePath.EntryPoint.Combine("Resources", "test-dxt5.dds"));
-            var hash1 = file1.PerceptionHash();
+            var hash1 = await ImageState.GetPHash(AbsolutePath.EntryPoint.Combine("Resources", "test-dxt5.dds"));
             
-            var file2 = DDSImage.FromFile(AbsolutePath.EntryPoint.Combine("Resources", "test-dxt5-small-bc7.dds"));
-            var hash2 = file2.PerceptionHash();
+            var hash2 = await ImageState.GetPHash(AbsolutePath.EntryPoint.Combine("Resources", "test-dxt5-small-bc7.dds"));
             
-            Assert.Equal(0.956666886806488, hash1.Similarity(hash2));
+            var state2 = await ImageState.GetState(AbsolutePath.EntryPoint.Combine("Resources", "test-dxt5-small-bc7.dds"));
+            
+            Assert.Equal(64, state2.Width);
+            Assert.Equal(64, state2.Height);
+            Assert.Equal(DXGI_FORMAT.BC7_UNORM_SRGB, state2.Format);
+            
+            Assert.Equal(0.8811911940574646, hash1.Similarity(hash2));
         }
         
                 
         [Fact]
         public async Task CanLoadAndCompareResizedVFlipImage()
         {
-            var file1 = DDSImage.FromFile(AbsolutePath.EntryPoint.Combine("Resources", "test-dxt5.dds"));
-            var hash1 = file1.PerceptionHash();
+            var hash1 = await ImageState.GetPHash(AbsolutePath.EntryPoint.Combine("Resources", "test-dxt5.dds"));
             
-            var file2 = DDSImage.FromFile(AbsolutePath.EntryPoint.Combine("Resources", "test-dxt5-small-bc7-vflip.dds"));
-            var hash2 = file2.PerceptionHash();
+            var hash2 = await ImageState.GetPHash(AbsolutePath.EntryPoint.Combine("Resources", "test-dxt5-small-bc7-vflip.dds"));
             
-            Assert.Equal(0.2465425431728363, hash1.Similarity(hash2));
+            Assert.Equal(0.19484494626522064, hash1.Similarity(hash2));
         }
         
         [Fact]
         public async Task CanLoadAndCompareRecompressedImage()
         {
-            var file1 = DDSImage.FromFile(AbsolutePath.EntryPoint.Combine("Resources", "test-dxt5.dds"));
-            var hash1 = file1.PerceptionHash();
+            var hash1 = await ImageState.GetPHash(AbsolutePath.EntryPoint.Combine("Resources", "test-dxt5.dds"));
 
-            var file2 = DDSImage.FromFile(AbsolutePath.EntryPoint.Combine("Resources", "test-dxt5-recompressed.dds"));
-            var hash2 = file2.PerceptionHash();
+            var hash2 = await ImageState.GetPHash(AbsolutePath.EntryPoint.Combine("Resources", "test-dxt5-recompressed.dds"));
 
-            Assert.Equal(0.9999724626541138, hash1.Similarity(hash2));
+            Assert.Equal(0.9298737645149231, hash1.Similarity(hash2));
         }
     }
 }
diff --git a/Wabbajack.ImageHashing/DDSImage.cs b/Wabbajack.ImageHashing/DDSImage.cs
deleted file mode 100644
index 18b45620..00000000
--- a/Wabbajack.ImageHashing/DDSImage.cs
+++ /dev/null
@@ -1,215 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Numerics;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-using System.Threading.Tasks;
-using DirectXTexNet;
-using Shipwreck.Phash;
-using Shipwreck.Phash.Imaging;
-using Wabbajack.Common;
-
-namespace Wabbajack.ImageHashing
-{
-    public class DDSImage : IDisposable
-    {
-        private static Lazy<DX11Device> DX11Device = new(() => new DX11Device());
-        private DDSImage(ScratchImage img, TexMetadata metadata, Extension ext)
-        {
-            _image = img;
-            _metaData = metadata;
-            _extension = ext;
-        }
-
-        private static Extension DDSExtension = new(".dds");
-        private static Extension TGAExtension = new(".tga");
-        private ScratchImage _image;
-        private TexMetadata _metaData;
-
-        public static DDSImage FromFile(AbsolutePath file)
-        {
-            if (file.Extension != DDSExtension)
-                throw new Exception("File does not end in DDS");
-
-            var img = TexHelper.Instance.LoadFromDDSFile(file.ToString(), DDS_FLAGS.NONE);
-            
-            return new DDSImage(img, img.GetMetadata(), new Extension(".dds"));
-        }
-
-        public static DDSImage FromDDSMemory(byte[] data)
-        {
-            unsafe
-            {
-                fixed (byte* ptr = data)
-                {
-                    var img = TexHelper.Instance.LoadFromDDSMemory((IntPtr)ptr, data.Length, DDS_FLAGS.NONE);
-                    return new DDSImage(img, img.GetMetadata(), new Extension(".dds"));
-                }
-            }
-        }
-        
-        public static DDSImage FromTGAMemory(byte[] data)
-        {
-            unsafe
-            {
-                fixed (byte* ptr = data)
-                {
-                    var img = TexHelper.Instance.LoadFromTGAMemory((IntPtr)ptr, data.Length);
-                    return new DDSImage(img, img.GetMetadata(), new Extension(".tga"));
-                }
-            }
-        }
-        
-        
-        public static async Task<DDSImage> FromStream(Stream stream, IPath arg1Name)
-        {
-            var data = await stream.ReadAllAsync();
-            if (arg1Name.FileName.Extension == DDSExtension)
-                return FromDDSMemory(data);
-            if (arg1Name.FileName.Extension == TGAExtension)
-                return FromTGAMemory(data);
-            throw new NotImplementedException("Only DDS and TGA files supported");
-        }
-
-        public void Dispose()
-        {
-            if (!_image.IsDisposed) 
-                _image.Dispose();
-        }
-
-        public int Width => _metaData.Width;
-        public int Height => _metaData.Height;
-
-        // Destructively resize a Image
-        public void ResizeRecompressAndSave(int width, int height, DXGI_FORMAT newFormat, AbsolutePath dest)
-        {
-            ScratchImage? resized = default;
-            try
-            {
-                // First we resize the image, so that changes due to image scaling matter less in the final hash
-                if (CompressedTypes.Contains(_metaData.Format))
-                {
-                    using var decompressed = _image.Decompress(DXGI_FORMAT.UNKNOWN);
-                    resized = decompressed.Resize(width, height, TEX_FILTER_FLAGS.DEFAULT);
-                }
-                else
-                {
-                    resized = _image.Resize(width, height, TEX_FILTER_FLAGS.DEFAULT);
-                }
-
-                
-                if (CompressedTypes.Contains(newFormat))
-                {
-                    var old = resized;
-                    resized = DX11Device.Value.Compress(resized, newFormat, TEX_COMPRESS_FLAGS.BC7_QUICK, 0.5f);
-                    old.Dispose();
-                }
-
-                if (dest.Extension == new Extension(".dds"))
-                {
-                    resized.SaveToDDSFile(DDS_FLAGS.NONE, dest.ToString());
-                }
-            }
-            finally
-            {
-                resized?.Dispose();
-            }
-        }
-
-        private static HashSet<DXGI_FORMAT> CompressedTypes = new HashSet<DXGI_FORMAT>()
-        {
-            DXGI_FORMAT.BC1_TYPELESS,
-            DXGI_FORMAT.BC1_UNORM,
-            DXGI_FORMAT.BC1_UNORM_SRGB,
-            DXGI_FORMAT.BC2_TYPELESS,
-            DXGI_FORMAT.BC2_UNORM,
-            DXGI_FORMAT.BC2_UNORM_SRGB,
-            DXGI_FORMAT.BC3_TYPELESS,
-            DXGI_FORMAT.BC3_UNORM,
-            DXGI_FORMAT.BC3_UNORM_SRGB,
-            DXGI_FORMAT.BC4_TYPELESS,
-            DXGI_FORMAT.BC4_UNORM,
-            DXGI_FORMAT.BC4_SNORM,
-            DXGI_FORMAT.BC5_TYPELESS,
-            DXGI_FORMAT.BC5_UNORM,
-            DXGI_FORMAT.BC5_SNORM,
-            DXGI_FORMAT.BC6H_TYPELESS,
-            DXGI_FORMAT.BC6H_UF16,
-            DXGI_FORMAT.BC6H_SF16,
-            DXGI_FORMAT.BC7_TYPELESS,
-            DXGI_FORMAT.BC7_UNORM,
-            DXGI_FORMAT.BC7_UNORM_SRGB,
-        };
-
-        private Extension _extension;
-
-        public ImageState ImageState()
-        {
-            return new()
-            {
-                Width = _metaData.Width,
-                Height = _metaData.Height,
-                Format = _metaData.Format,
-                PerceptualHash = PerceptionHash()
-            };
-        }
-
-        public PHash PerceptionHash()
-        {
-            ScratchImage? resized = default;
-            try
-            {
-                // First we resize the image, so that changes due to image scaling matter less in the final hash
-                if (CompressedTypes.Contains(_metaData.Format))
-                {
-                    using var decompressed = _image.Decompress(DXGI_FORMAT.UNKNOWN);
-                    resized = decompressed.Resize(512, 512, TEX_FILTER_FLAGS.DEFAULT);
-                }
-                else
-                {
-                    resized = _image.Resize(512, 512, TEX_FILTER_FLAGS.DEFAULT);
-                }
-                
-                var image = new byte[512 * 512];
-
-                unsafe void EvaluatePixels(IntPtr pixels, IntPtr width, IntPtr line)
-                {
-                    float* ptr = (float*)pixels.ToPointer();
-
-                    int widthV = width.ToInt32();
-                    if (widthV != 512) return;
-
-                    var y = line.ToInt32();
-
-                    for (int i = 0; i < widthV; i++)
-                    {
-                        var r = ptr[0] * 0.229f;
-                        var g = ptr[1] * 0.587f;
-                        var b = ptr[2] * 0.114f;
-
-                        var combined = (r + g + b) * 255.0f;
-
-                        image[(y * widthV) + i] = (byte)combined;
-                        ptr += 4;
-                    }
-
-                }
-
-                resized.EvaluateImage(EvaluatePixels);
-
-                var digest = ImagePhash.ComputeDigest(new ByteImage(512, 512, image));
-                return PHash.FromDigest(digest);
-            }
-            finally
-            {
-                resized?.Dispose();
-            }
-        }
-
-        public void ResizeRecompressAndSave(ImageState state, AbsolutePath dest)
-        {
-            ResizeRecompressAndSave(state.Width, state.Height, state.Format, dest);
-        }
-    }
-}
diff --git a/Wabbajack.ImageHashing/DX11Device.cs b/Wabbajack.ImageHashing/DX11Device.cs
deleted file mode 100644
index c27af5ae..00000000
--- a/Wabbajack.ImageHashing/DX11Device.cs
+++ /dev/null
@@ -1,75 +0,0 @@
-using System;
-using System.Runtime.InteropServices;
-using DirectXTexNet;
-using Silk.NET.Core.Native;
-using Silk.NET.Direct3D11;
-
-namespace Wabbajack.ImageHashing
-{
-    public unsafe class DX11Device
-    {
-        private ID3D11Device* _device;
-
-        public DX11Device()
-        {
-            unsafe
-            {
-                var dxgi = Silk.NET.DXGI.DXGI.GetApi();
-                var dx11 = Silk.NET.Direct3D11.D3D11.GetApi();
-
-                D3DFeatureLevel[] levels =
-                {
-                    //D3DFeatureLevel.D3DFeatureLevel100, D3DFeatureLevel.D3DFeatureLevel101,
-                    D3DFeatureLevel.D3DFeatureLevel110
-                };
-                uint createDeviceFlags = 0;
-
-                var adapterIdx = 0;
-
-                D3DFeatureLevel fl;
-                ID3D11Device* device;
-                fixed (D3DFeatureLevel* lvls = levels)
-                {
-                    var hr = dx11.CreateDevice(null, D3DDriverType.D3DDriverTypeHardware, IntPtr.Zero,
-                        createDeviceFlags, lvls,
-                        (uint)levels.Length,
-                        Silk.NET.Direct3D11.D3D11.SdkVersion, &device, &fl, null);
-
-                    if (FAILED(hr))
-                    {
-                        _device = null;
-                        return;
-                    }
-
-                    _device = device;
-                }
-            }
-        }
-
-        public ScratchImage Compress(ScratchImage input, DXGI_FORMAT format, TEX_COMPRESS_FLAGS compress,
-            float threshold)
-        {
-            lock (this)
-            {
-                if (_device != null)
-                {
-                    try
-                    {
-                        return input.Compress((IntPtr)_device, format, compress, threshold);
-                    }
-                    catch (COMException _)
-                    {
-                        _device->Release();
-                        _device = null;
-                    }
-                }
-                return input.Compress(format, compress, threshold);
-            }
-        }
-
-        private static bool FAILED(int x)
-        {
-            return x != 0;
-        }
-    }
-}
diff --git a/Wabbajack.ImageHashing/DXGI_FORMAT.cs b/Wabbajack.ImageHashing/DXGI_FORMAT.cs
new file mode 100644
index 00000000..d3d8b0ac
--- /dev/null
+++ b/Wabbajack.ImageHashing/DXGI_FORMAT.cs
@@ -0,0 +1,125 @@
+namespace Wabbajack.ImageHashing
+{
+  public enum DXGI_FORMAT
+  {
+    UNKNOWN = 0,
+    R32G32B32A32_TYPELESS = 1,
+    R32G32B32A32_FLOAT = 2,
+    R32G32B32A32_UINT = 3,
+    R32G32B32A32_SINT = 4,
+    R32G32B32_TYPELESS = 5,
+    R32G32B32_FLOAT = 6,
+    R32G32B32_UINT = 7,
+    R32G32B32_SINT = 8,
+    R16G16B16A16_TYPELESS = 9,
+    R16G16B16A16_FLOAT = 10, // 0x0000000A
+    R16G16B16A16_UNORM = 11, // 0x0000000B
+    R16G16B16A16_UINT = 12, // 0x0000000C
+    R16G16B16A16_SNORM = 13, // 0x0000000D
+    R16G16B16A16_SINT = 14, // 0x0000000E
+    R32G32_TYPELESS = 15, // 0x0000000F
+    R32G32_FLOAT = 16, // 0x00000010
+    R32G32_UINT = 17, // 0x00000011
+    R32G32_SINT = 18, // 0x00000012
+    R32G8X24_TYPELESS = 19, // 0x00000013
+    D32_FLOAT_S8X24_UINT = 20, // 0x00000014
+    R32_FLOAT_X8X24_TYPELESS = 21, // 0x00000015
+    X32_TYPELESS_G8X24_UINT = 22, // 0x00000016
+    R10G10B10A2_TYPELESS = 23, // 0x00000017
+    R10G10B10A2_UNORM = 24, // 0x00000018
+    R10G10B10A2_UINT = 25, // 0x00000019
+    R11G11B10_FLOAT = 26, // 0x0000001A
+    R8G8B8A8_TYPELESS = 27, // 0x0000001B
+    R8G8B8A8_UNORM = 28, // 0x0000001C
+    R8G8B8A8_UNORM_SRGB = 29, // 0x0000001D
+    R8G8B8A8_UINT = 30, // 0x0000001E
+    R8G8B8A8_SNORM = 31, // 0x0000001F
+    R8G8B8A8_SINT = 32, // 0x00000020
+    R16G16_TYPELESS = 33, // 0x00000021
+    R16G16_FLOAT = 34, // 0x00000022
+    R16G16_UNORM = 35, // 0x00000023
+    R16G16_UINT = 36, // 0x00000024
+    R16G16_SNORM = 37, // 0x00000025
+    R16G16_SINT = 38, // 0x00000026
+    R32_TYPELESS = 39, // 0x00000027
+    D32_FLOAT = 40, // 0x00000028
+    R32_FLOAT = 41, // 0x00000029
+    R32_UINT = 42, // 0x0000002A
+    R32_SINT = 43, // 0x0000002B
+    R24G8_TYPELESS = 44, // 0x0000002C
+    D24_UNORM_S8_UINT = 45, // 0x0000002D
+    R24_UNORM_X8_TYPELESS = 46, // 0x0000002E
+    X24_TYPELESS_G8_UINT = 47, // 0x0000002F
+    R8G8_TYPELESS = 48, // 0x00000030
+    R8G8_UNORM = 49, // 0x00000031
+    R8G8_UINT = 50, // 0x00000032
+    R8G8_SNORM = 51, // 0x00000033
+    R8G8_SINT = 52, // 0x00000034
+    R16_TYPELESS = 53, // 0x00000035
+    R16_FLOAT = 54, // 0x00000036
+    D16_UNORM = 55, // 0x00000037
+    R16_UNORM = 56, // 0x00000038
+    R16_UINT = 57, // 0x00000039
+    R16_SNORM = 58, // 0x0000003A
+    R16_SINT = 59, // 0x0000003B
+    R8_TYPELESS = 60, // 0x0000003C
+    R8_UNORM = 61, // 0x0000003D
+    R8_UINT = 62, // 0x0000003E
+    R8_SNORM = 63, // 0x0000003F
+    R8_SINT = 64, // 0x00000040
+    A8_UNORM = 65, // 0x00000041
+    R1_UNORM = 66, // 0x00000042
+    R9G9B9E5_SHAREDEXP = 67, // 0x00000043
+    R8G8_B8G8_UNORM = 68, // 0x00000044
+    G8R8_G8B8_UNORM = 69, // 0x00000045
+    BC1_TYPELESS = 70, // 0x00000046
+    BC1_UNORM = 71, // 0x00000047
+    BC1_UNORM_SRGB = 72, // 0x00000048
+    BC2_TYPELESS = 73, // 0x00000049
+    BC2_UNORM = 74, // 0x0000004A
+    BC2_UNORM_SRGB = 75, // 0x0000004B
+    BC3_TYPELESS = 76, // 0x0000004C
+    BC3_UNORM = 77, // 0x0000004D
+    BC3_UNORM_SRGB = 78, // 0x0000004E
+    BC4_TYPELESS = 79, // 0x0000004F
+    BC4_UNORM = 80, // 0x00000050
+    BC4_SNORM = 81, // 0x00000051
+    BC5_TYPELESS = 82, // 0x00000052
+    BC5_UNORM = 83, // 0x00000053
+    BC5_SNORM = 84, // 0x00000054
+    B5G6R5_UNORM = 85, // 0x00000055
+    B5G5R5A1_UNORM = 86, // 0x00000056
+    B8G8R8A8_UNORM = 87, // 0x00000057
+    B8G8R8X8_UNORM = 88, // 0x00000058
+    R10G10B10_XR_BIAS_A2_UNORM = 89, // 0x00000059
+    B8G8R8A8_TYPELESS = 90, // 0x0000005A
+    B8G8R8A8_UNORM_SRGB = 91, // 0x0000005B
+    B8G8R8X8_TYPELESS = 92, // 0x0000005C
+    B8G8R8X8_UNORM_SRGB = 93, // 0x0000005D
+    BC6H_TYPELESS = 94, // 0x0000005E
+    BC6H_UF16 = 95, // 0x0000005F
+    BC6H_SF16 = 96, // 0x00000060
+    BC7_TYPELESS = 97, // 0x00000061
+    BC7_UNORM = 98, // 0x00000062
+    BC7_UNORM_SRGB = 99, // 0x00000063
+    AYUV = 100, // 0x00000064
+    Y410 = 101, // 0x00000065
+    Y416 = 102, // 0x00000066
+    NV12 = 103, // 0x00000067
+    P010 = 104, // 0x00000068
+    P016 = 105, // 0x00000069
+    OPAQUE_420 = 106, // 0x0000006A
+    YUY2 = 107, // 0x0000006B
+    Y210 = 108, // 0x0000006C
+    Y216 = 109, // 0x0000006D
+    NV11 = 110, // 0x0000006E
+    AI44 = 111, // 0x0000006F
+    IA44 = 112, // 0x00000070
+    P8 = 113, // 0x00000071
+    A8P8 = 114, // 0x00000072
+    B4G4R4A4_UNORM = 115, // 0x00000073
+    P208 = 130, // 0x00000082
+    V208 = 131, // 0x00000083
+    V408 = 132, // 0x00000084
+  }
+}
diff --git a/Wabbajack.ImageHashing/IImage.cs b/Wabbajack.ImageHashing/IImage.cs
deleted file mode 100644
index 6973124a..00000000
--- a/Wabbajack.ImageHashing/IImage.cs
+++ /dev/null
@@ -1,6 +0,0 @@
-using System;
-
-namespace Wabbajack.ImageHashing
-{
-
-}
diff --git a/Wabbajack.ImageHashing/ImageState.cs b/Wabbajack.ImageHashing/ImageState.cs
index 6b31ec78..6d003a47 100644
--- a/Wabbajack.ImageHashing/ImageState.cs
+++ b/Wabbajack.ImageHashing/ImageState.cs
@@ -1,9 +1,15 @@
 using System;
+using System.Collections.Generic;
+using System.Drawing;
 using System.IO;
+using System.Linq;
+using System.Reactive.Linq;
+using System.Reactive.Subjects;
 using System.Threading.Tasks;
-using DirectXTexNet;
+using Shipwreck.Phash;
 using Wabbajack.Common;
 using Wabbajack.Common.Serialization.Json;
+using Shipwreck.Phash.Bitmaps;
 
 namespace Wabbajack.ImageHashing
 {
@@ -36,36 +42,68 @@ namespace Wabbajack.ImageHashing
 
         public static async Task<ImageState?> FromImageStream(Stream stream, Extension ext, bool takeStreamOwnership = true)
         {
-            var ms = new MemoryStream();
-            await stream.CopyToAsync(ms);
-            if (takeStreamOwnership) await stream.DisposeAsync();
+            await using var tf = new TempFile(ext);
+            await tf.Path.WriteAllAsync(stream, takeStreamOwnership);
+            return await GetState(tf.Path);
+        }
 
-            DDSImage? img = default;
-            try
-            {
-                if (ext == new Extension(".dds"))
-                    img = DDSImage.FromDDSMemory(ms.GetBuffer());
-                else if (ext == new Extension(".tga"))
-                {
-                    img = DDSImage.FromTGAMemory(ms.GetBuffer());
-                }
-                else
-                {
-                    throw new NotImplementedException("Only DDS and TGA files supported by PHash");
-                }
+        private static readonly Extension PNGExtension = new(".png");
+        public static async Task<PHash> GetPHash(AbsolutePath path)
+        {
+            await using var tmp = await TempFolder.Create();
+            await ConvertImage(path, tmp.Dir, 512, 512, DXGI_FORMAT.R8G8B8A8_UNORM, PNGExtension);
+            
+            using var img = (Bitmap)Image.FromFile(path.FileName.RelativeTo(tmp.Dir).ReplaceExtension(PNGExtension).ToString());
+            return PHash.FromDigest(ImagePhash.ComputeDigest(img.ToLuminanceImage()));
+        }
 
-                return img.ImageState();
+        public static async Task ConvertImage(AbsolutePath from, AbsolutePath toFolder, int w, int h, DXGI_FORMAT format, Extension fileFormat)
+        {
+            // User isn't renaming the file, so we don't have to create a temporary folder
+            var ph = new ProcessHelper
+            {
+                Path = @"Tools\texconv.exe".RelativeTo(AbsolutePath.EntryPoint),
+                Arguments = new object[] {from, "-ft", fileFormat.ToString()[1..], "-f", format, "-o", toFolder, "-w", w, "-h", h, "-if", "CUBIC", "-singleproc"},
+                ThrowOnNonZeroExitCode = true
+            }; 
+            await ph.Start();
 
-            }
-            catch (Exception ex)
+        }
+
+        public static async Task ConvertImage(Stream from, ImageState state, Extension ext, AbsolutePath to)
+        {
+            await using var tmpFile = await TempFolder.Create();
+            var inFile = to.FileName.RelativeTo(tmpFile.Dir).WithExtension(ext);
+            await inFile.WriteAllAsync(from);
+            await ConvertImage(inFile, to.Parent, state.Width, state.Height, state.Format, ext);
+        }
+
+        public static async Task<ImageState> GetState(AbsolutePath path)
+        {
+            var ph = new ProcessHelper
             {
-                Utils.Log($"Unable to read image state (this is fine)");
-                return null;
-            }
-            finally
+                Path = @"Tools\texdiag.exe".RelativeTo(AbsolutePath.EntryPoint), Arguments = new object[] {"info", path, "-nologo"},
+                ThrowOnNonZeroExitCode = true
+            };
+            var lines = new List<string>();
+            using var _ = ph.Output.Where(p => p.Type == ProcessHelper.StreamType.Output)
+                .Select(p => p.Line)
+                .Where(p => p.Contains(" = "))
+                .Subscribe(l => lines.Add(l));
+            await ph.Start();
+            var data = lines.Select(l =>
             {
-                img?.Dispose();
-            }
+                var split = l.Split(" = ");
+                return (split[0].Trim(), split[1].Trim());
+            }).ToDictionary(p => p.Item1, p => p.Item2);
+            
+            return new ImageState
+            {
+                Width = int.Parse(data["width"]),
+                Height = int.Parse(data["height"]),
+                Format = Enum.Parse<DXGI_FORMAT>(data["format"]),
+                PerceptualHash = await GetPHash(path)
+            };
         }
     }
 }
diff --git a/Wabbajack.ImageHashing/PHash.cs b/Wabbajack.ImageHashing/PHash.cs
index a5f17c7b..f844a24d 100644
--- a/Wabbajack.ImageHashing/PHash.cs
+++ b/Wabbajack.ImageHashing/PHash.cs
@@ -83,42 +83,6 @@ namespace Wabbajack.ImageHashing
             h <<= 8;
             return (int)h;
         }
-
-        public static async Task<PHash> FromStream(Stream stream, Extension ext, bool takeStreamOwnership = true)
-        {
-            try
-            {
-                var ms = new MemoryStream();
-                await stream.CopyToAsync(ms);
-                if (takeStreamOwnership) await stream.DisposeAsync();
-
-                DDSImage img;
-                if (ext == new Extension(".dds"))
-                    img = DDSImage.FromDDSMemory(ms.GetBuffer());
-                else if (ext == new Extension(".tga"))
-                {
-                    img = DDSImage.FromTGAMemory(ms.GetBuffer());
-                }
-                else
-                {
-                    throw new NotImplementedException("Only DDS and TGA files supported by PHash");
-                }
-
-                return img.PerceptionHash();
-            }
-            catch (Exception ex)
-            {
-                Utils.Log($"Error getting PHASH {ex}");
-                return default;
-            }
-        }
-
-        public static async Task<PHash> FromFile(AbsolutePath path)
-        {
-            await using var s = await path.OpenRead();
-            return await FromStream(s, path.Extension);
-
-        }
     }
     
             
diff --git a/Wabbajack.ImageHashing/Tools/texconv.exe b/Wabbajack.ImageHashing/Tools/texconv.exe
new file mode 100644
index 00000000..9228e6a2
Binary files /dev/null and b/Wabbajack.ImageHashing/Tools/texconv.exe differ
diff --git a/Wabbajack.ImageHashing/Tools/texdiag.exe b/Wabbajack.ImageHashing/Tools/texdiag.exe
new file mode 100644
index 00000000..f443a5aa
Binary files /dev/null and b/Wabbajack.ImageHashing/Tools/texdiag.exe differ
diff --git a/Wabbajack.ImageHashing/Wabbajack.ImageHashing.csproj b/Wabbajack.ImageHashing/Wabbajack.ImageHashing.csproj
index c53d1461..82537375 100644
--- a/Wabbajack.ImageHashing/Wabbajack.ImageHashing.csproj
+++ b/Wabbajack.ImageHashing/Wabbajack.ImageHashing.csproj
@@ -9,12 +9,27 @@
     <ItemGroup>
       <PackageReference Include="DirectXTexNet" Version="1.0.2" />
       <PackageReference Include="Shipwreck.Phash" Version="0.5.0" />
+      <PackageReference Include="Shipwreck.Phash.Bitmaps" Version="0.5.0" />
       <PackageReference Include="Silk.NET.Direct3D11" Version="2.6.0" />
       <PackageReference Include="Silk.NET.DXGI" Version="2.6.0" />
+      <PackageReference Include="System.Drawing.Common" Version="5.0.2" />
     </ItemGroup>
 
     <ItemGroup>
       <ProjectReference Include="..\Wabbajack.Common\Wabbajack.Common.csproj" />
     </ItemGroup>
 
+    <ItemGroup>
+      <Folder Include="Tools" />
+    </ItemGroup>
+
+    <ItemGroup>
+      <None Update="Tools\texconv.exe">
+        <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+      </None>
+      <None Update="Tools\texdiag.exe">
+        <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+      </None>
+    </ItemGroup>
+
 </Project>
diff --git a/Wabbajack.Lib/AInstaller.cs b/Wabbajack.Lib/AInstaller.cs
index c6db2e01..424092eb 100644
--- a/Wabbajack.Lib/AInstaller.cs
+++ b/Wabbajack.Lib/AInstaller.cs
@@ -157,9 +157,7 @@ namespace Wabbajack.Lib
                         case TransformedTexture tt:
                         {
                             await using var s = await sf.GetStream();
-                            using var img = await DDSImage.FromStream(s, vf.Name);
-                            
-                            img.ResizeRecompressAndSave(tt.ImageState, directive.Directive.To.RelativeTo(OutputFolder));
+                            await ImageState.ConvertImage(s, tt.ImageState, tt.To.Extension, directive.Directive.To.RelativeTo(OutputFolder));
 
                         }
                             break;
diff --git a/Wabbajack.Test/SanityTests.cs b/Wabbajack.Test/SanityTests.cs
index af00249f..0f5945ff 100644
--- a/Wabbajack.Test/SanityTests.cs
+++ b/Wabbajack.Test/SanityTests.cs
@@ -8,13 +8,10 @@ using Wabbajack.Common;
 using Wabbajack.ImageHashing;
 using Wabbajack.Lib;
 using Wabbajack.Lib.CompilationSteps;
-using Wabbajack.Lib.CompilationSteps.CompilationErrors;
 using Xunit;
 using Xunit.Abstractions;
 using Xunit.Sdk;
-using DXGI_FORMAT = DirectXTexNet.DXGI_FORMAT;
-using File = Alphaleonis.Win32.Filesystem.File;
-using Path = Alphaleonis.Win32.Filesystem.Path;
+using DXGI_FORMAT = Wabbajack.ImageHashing.DXGI_FORMAT;
 
 namespace Wabbajack.Test
 {
@@ -388,17 +385,17 @@ namespace Wabbajack.Test
             }
 
             {
-                using var originalDDS = DDSImage.FromFile(nativeFile);
-                originalDDS.ResizeRecompressAndSave(originalDDS.Width, originalDDS.Height, DXGI_FORMAT.BC7_UNORM, recompressedFile);
-                originalDDS.ResizeRecompressAndSave(128, 128, DXGI_FORMAT.BC7_UNORM, resizedFile);
+                var originalDDS = await ImageState.GetState(nativeFile);
+                await ImageState.ConvertImage(nativeFile, recompressedFile.Parent, originalDDS.Width, originalDDS.Height, DXGI_FORMAT.BC7_UNORM, recompressedFile.Extension);
+                await ImageState.ConvertImage(nativeFile, resizedFile.Parent, 128, 128, DXGI_FORMAT.BC7_UNORM, resizedFile.Extension);
             }
 
             await utils.Configure();
             
             await CompileAndInstall(profile, true);
             await utils.VerifyInstalledFile(mod, @"native\whitestagbody.dds");
-            Assert.True(0.99f <=(await PHash.FromFile(recompressedFile)).Similarity(await PHash.FromFile(utils.InstalledPath(mod, @"recompressed\whitestagbody.dds"))));
-            Assert.True(0.98f <=(await PHash.FromFile(resizedFile)).Similarity(await PHash.FromFile(utils.InstalledPath(mod, @"resized\whitestagbody.dds"))));
+            Assert.True(0.99f <=(await ImageState.GetState(recompressedFile)).PerceptualHash.Similarity(await ImageState.GetPHash(utils.InstalledPath(mod, @"recompressed\whitestagbody.dds"))));
+            Assert.True(0.98f <=(await ImageState.GetState(resizedFile)).PerceptualHash.Similarity(await ImageState.GetPHash(utils.InstalledPath(mod, @"resized\whitestagbody.dds"))));
         }
         
         [Fact]