├── .github └── FUNDING.yml ├── .gitignore ├── CHANGELOG.md ├── CHANGELOG.md.meta ├── LICENSE ├── LICENSE.meta ├── Plugins.meta ├── Plugins ├── FreeImage.meta └── FreeImage │ ├── Linux.meta │ ├── Linux │ ├── FreeImage.so │ └── FreeImage.so.meta │ ├── Windows.meta │ └── Windows │ ├── x64.meta │ ├── x64 │ ├── FreeImage.dll │ └── FreeImage.dll.meta │ ├── x86.meta │ └── x86 │ ├── FreeImage.dll │ └── FreeImage.dll.meta ├── README.md ├── README.md.meta ├── Runtime.meta ├── Runtime ├── AsyncImageLoader.Runtime.asmdef ├── AsyncImageLoader.Runtime.asmdef.meta ├── AsyncImageLoader.cs ├── AsyncImageLoader.cs.meta ├── AsyncImageLoader.meta ├── AsyncImageLoader │ ├── FreeImage.cs │ ├── FreeImage.cs.meta │ ├── MipmapJobs.cs │ ├── MipmapJobs.cs.meta │ ├── Pixels.cs │ ├── Pixels.cs.meta │ ├── TransferImageJob.cs │ └── TransferImageJob.cs.meta ├── ImageImporter.cs └── ImageImporter.cs.meta ├── Tests.meta ├── Tests ├── AsyncImageLoader.Tests.asmdef ├── AsyncImageLoader.Tests.asmdef.meta ├── TestMipmap.cs └── TestMipmap.cs.meta ├── package.json └── package.json.meta /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | patreon: Looooong 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # This .gitignore file should be placed at the root of your Unity project directory 2 | # 3 | # Get latest from https://github.com/github/gitignore/blob/master/Unity.gitignore 4 | # 5 | /[Ll]ibrary/ 6 | /[Tt]emp/ 7 | /[Oo]bj/ 8 | /[Bb]uild/ 9 | /[Bb]uilds/ 10 | /[Ll]ogs/ 11 | /[Mm]emoryCaptures/ 12 | 13 | # Asset meta data should only be ignored when the corresponding asset is also ignored 14 | !/[Aa]ssets/**/*.meta 15 | 16 | # Uncomment this line if you wish to ignore the asset store tools plugin 17 | # /[Aa]ssets/AssetStoreTools* 18 | 19 | # Autogenerated Jetbrains Rider plugin 20 | [Aa]ssets/Plugins/Editor/JetBrains* 21 | 22 | # Visual Studio cache directory 23 | .vs/ 24 | 25 | # Gradle cache directory 26 | .gradle/ 27 | 28 | # Autogenerated VS/MD/Consulo solution and project files 29 | ExportedObj/ 30 | .consulo/ 31 | *.csproj 32 | *.unityproj 33 | *.sln 34 | *.suo 35 | *.tmp 36 | *.user 37 | *.userprefs 38 | *.pidb 39 | *.booproj 40 | *.svd 41 | *.pdb 42 | *.mdb 43 | *.opendb 44 | *.VC.db 45 | 46 | # Unity3D generated meta files 47 | *.pidb.meta 48 | *.pdb.meta 49 | *.mdb.meta 50 | 51 | # Unity3D generated file on crash reports 52 | sysinfo.txt 53 | 54 | # Builds 55 | *.apk 56 | *.unitypackage 57 | 58 | # Crashlytics generated file 59 | crashlytics-build.properties 60 | 61 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [Unreleased] 2 | 3 | ## [0.1.3] - 2022-12-19 4 | ### Enhancement 5 | - Add support for non-bitmap image type. 6 | 7 | ### Fixed 8 | - Fix accuracy loss when generating mipmap (#7). 9 | 10 | ## [0.1.2] - 2022-02-17 11 | ### Fixed 12 | - Fix Burst complain about two containers maybe aliasing in FilterMipmapJob. 13 | - Use `Texture2D.Reinitialize` instead of `Texture2D.Resize` from Unity 2021.1 onward. 14 | 15 | ## [0.1.1] - 2021-07-30 16 | ### Added 17 | - Add `CHANGELOG.md`. 18 | 19 | ### Changed 20 | - Refactor Burst jobs. 21 | 22 | ### Fixed 23 | - Fix typo in `README.md`. 24 | - Fix performance issue when importing PNG without alpha channel. 25 | - Fix out of bound access in mipmap generation job. 26 | 27 | ## [0.1.0] - 2021-07-28 28 | ### Added 29 | - Implement `AsyncImageLoader`. 30 | 31 | [Unreleased]: https://github.com/Looooong/UnityAsyncImageLoader/compare/v0.1.3...HEAD 32 | [0.1.3]: https://github.com/Looooong/UnityAsyncImageLoader/releases/tag/v0.1.3 33 | [0.1.2]: https://github.com/Looooong/UnityAsyncImageLoader/releases/tag/v0.1.2 34 | [0.1.1]: https://github.com/Looooong/UnityAsyncImageLoader/releases/tag/v0.1.1 35 | [0.1.0]: https://github.com/Looooong/UnityAsyncImageLoader/releases/tag/v0.1.0 36 | -------------------------------------------------------------------------------- /CHANGELOG.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b1863da24c0801c4f8bbc8c28f803e98 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Nguyễn Đức Long 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSE.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e6b8e95e8b1589d42aca7919c9884646 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Plugins.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: aa2fdfffb4819034d81715a9b71fc01a 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Plugins/FreeImage.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: fc196c69148b4e9469d637c28ffb3b61 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Plugins/FreeImage/Linux.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9fba023c1c2aeae4a84c56879bb346bd 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Plugins/FreeImage/Linux/FreeImage.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Looooong/UnityAsyncImageLoader/a7594faf216b93ec4380300f2b70801fa505381e/Plugins/FreeImage/Linux/FreeImage.so -------------------------------------------------------------------------------- /Plugins/FreeImage/Linux/FreeImage.so.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 59157823033ab914ebfa6a3754cec0ec 3 | PluginImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | iconMap: {} 7 | executionOrder: {} 8 | defineConstraints: [] 9 | isPreloaded: 0 10 | isOverridable: 0 11 | isExplicitlyReferenced: 0 12 | validateReferences: 1 13 | platformData: 14 | - first: 15 | Any: 16 | second: 17 | enabled: 1 18 | settings: {} 19 | - first: 20 | Editor: Editor 21 | second: 22 | enabled: 0 23 | settings: 24 | DefaultValueInitialized: true 25 | userData: 26 | assetBundleName: 27 | assetBundleVariant: 28 | -------------------------------------------------------------------------------- /Plugins/FreeImage/Windows.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ac8d563fa7a79e24e98d465b7f8449d8 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Plugins/FreeImage/Windows/x64.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: fd2a1e5de3758dd47921aaaea3a335f2 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Plugins/FreeImage/Windows/x64/FreeImage.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Looooong/UnityAsyncImageLoader/a7594faf216b93ec4380300f2b70801fa505381e/Plugins/FreeImage/Windows/x64/FreeImage.dll -------------------------------------------------------------------------------- /Plugins/FreeImage/Windows/x64/FreeImage.dll.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ecd4d93a865915546bf8f1d9b5b39691 3 | PluginImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | iconMap: {} 7 | executionOrder: {} 8 | defineConstraints: [] 9 | isPreloaded: 0 10 | isOverridable: 0 11 | isExplicitlyReferenced: 0 12 | validateReferences: 1 13 | platformData: 14 | - first: 15 | : Any 16 | second: 17 | enabled: 0 18 | settings: 19 | Exclude Editor: 0 20 | Exclude Linux64: 0 21 | Exclude OSXUniversal: 0 22 | Exclude Win: 1 23 | Exclude Win64: 0 24 | - first: 25 | Any: 26 | second: 27 | enabled: 1 28 | settings: {} 29 | - first: 30 | Editor: Editor 31 | second: 32 | enabled: 1 33 | settings: 34 | CPU: x86_64 35 | DefaultValueInitialized: true 36 | OS: Windows 37 | - first: 38 | Standalone: Linux64 39 | second: 40 | enabled: 1 41 | settings: 42 | CPU: AnyCPU 43 | - first: 44 | Standalone: OSXUniversal 45 | second: 46 | enabled: 1 47 | settings: 48 | CPU: x86_64 49 | - first: 50 | Standalone: Win 51 | second: 52 | enabled: 0 53 | settings: 54 | CPU: None 55 | - first: 56 | Standalone: Win64 57 | second: 58 | enabled: 1 59 | settings: 60 | CPU: x86_64 61 | userData: 62 | assetBundleName: 63 | assetBundleVariant: 64 | -------------------------------------------------------------------------------- /Plugins/FreeImage/Windows/x86.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c29e20b65d98d2949b985026a20bd8c8 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Plugins/FreeImage/Windows/x86/FreeImage.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Looooong/UnityAsyncImageLoader/a7594faf216b93ec4380300f2b70801fa505381e/Plugins/FreeImage/Windows/x86/FreeImage.dll -------------------------------------------------------------------------------- /Plugins/FreeImage/Windows/x86/FreeImage.dll.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d56618cdbfc606d40b9fd84327701433 3 | PluginImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | iconMap: {} 7 | executionOrder: {} 8 | defineConstraints: [] 9 | isPreloaded: 0 10 | isOverridable: 0 11 | isExplicitlyReferenced: 0 12 | validateReferences: 1 13 | platformData: 14 | - first: 15 | : Any 16 | second: 17 | enabled: 0 18 | settings: 19 | Exclude Editor: 0 20 | Exclude Linux64: 0 21 | Exclude OSXUniversal: 0 22 | Exclude Win: 0 23 | Exclude Win64: 1 24 | - first: 25 | Any: 26 | second: 27 | enabled: 1 28 | settings: {} 29 | - first: 30 | Editor: Editor 31 | second: 32 | enabled: 1 33 | settings: 34 | CPU: x86 35 | DefaultValueInitialized: true 36 | OS: Windows 37 | - first: 38 | Standalone: Linux64 39 | second: 40 | enabled: 1 41 | settings: 42 | CPU: AnyCPU 43 | - first: 44 | Standalone: OSXUniversal 45 | second: 46 | enabled: 1 47 | settings: 48 | CPU: x86_64 49 | - first: 50 | Standalone: Win 51 | second: 52 | enabled: 1 53 | settings: 54 | CPU: x86 55 | - first: 56 | Standalone: Win64 57 | second: 58 | enabled: 0 59 | settings: 60 | CPU: None 61 | userData: 62 | assetBundleName: 63 | assetBundleVariant: 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Unity Asynchronous Image Loader 2 | 3 | [`ImageConversion.LoadImage`](https://docs.unity3d.com/ScriptReference/ImageConversion.LoadImage.html) and `Texture2D.LoadImage` are slow when loading large images (greater than 2K) at runtime. They blocks the Unity main thread when loading the image for a duration between a hundred milliseconds and even a few seconds. This is a dealbreaker for those games and applications that want to load those images programmatically at runtime. 4 | 5 | This package aims to offload image loading, image decoding and mipmap generation to other threads. It creates smoother gameplay and reduces lag spike on the Unity main thread when loading large images. 6 | 7 | This package uses [FreeImage](https://freeimage.sourceforge.io/). which is the same library used by Unity to process image data. 8 | 9 | ## Unity Version 10 | 11 | This package is developed in Unity 2019.1. It may work on Unity 2020 and Unity 2021. 12 | 13 | ## Installation 14 | 15 | The package can be installed using the Git URL `https://github.com/Looooong/UnityAsyncImageLoader.git` by following [Installing from a Git URL](https://docs.unity3d.com/Manual/upm-ui-giturl.html) instructions. 16 | 17 | ## Dependencies 18 | 19 | + Unity Burst 20 | + Unity Mathematics 21 | 22 | ## Usage 23 | 24 | ### Loader Settings 25 | 26 | ```cs 27 | /// Settings used by the image loader. 28 | public struct LoaderSettings { 29 | /// Create linear texture. Only applicable to methods that create new Texture2D. Defaults to false. 30 | public bool linear; 31 | /// Texture data won't be readable on the CPU after loading. Defaults to false. 32 | public bool markNonReadable; 33 | /// Whether or not to generate mipmaps. Defaults to true. 34 | public bool generateMipmap; 35 | /// Automatically calculate the number of mipmap levels. Defaults to true. Only applicable to methods that create new Texture2D. 36 | public bool autoMipmapCount; 37 | /// Mipmap count, including the base level. Must be greater than 1. Only applicable to methods that create new Texture2D. 38 | public int mipmapCount; 39 | /// Used to explicitly specify the image format. Defaults to FIF_UNKNOWN, which the image format will be automatically determined. 40 | public FreeImage.Format format; 41 | /// Whether or not to log exception caught by this method. Defaults to true. 42 | public bool logException; 43 | 44 | public static LoaderSettings Default => new LoaderSettings { 45 | linear = false, 46 | markNonReadable = false, 47 | generateMipmap = true, 48 | autoMipmapCount = true, 49 | format = FreeImage.Format.FIF_UNKNOWN, 50 | logException = true, 51 | }; 52 | } 53 | ``` 54 | 55 | ### Load Image Asynchronously 56 | 57 | ```cs 58 | var imageData = File.ReadAllBytes(); 59 | var texture = new Texture2D(1, 1); 60 | var loaderSettings = AsyncImageLoader.LoaderSettings.Default; 61 | var success = false; 62 | 63 | // ===================================== 64 | // Load image data into existing texture 65 | // ===================================== 66 | 67 | // Use the default LoaderSettings 68 | success = await AsyncImageLoader.LoadImageAsync(texture, imageData); 69 | 70 | // Similar to ImageConversion.LoadImage 71 | // Mark texture as unreadable after reading. 72 | success = await AsyncImageLoader.LoadImageAsync(texture, imageData, true); 73 | 74 | // Use a custom LoaderSettings 75 | success = await AsyncImageLoader.LoadImageAsync(texture, imageData, loaderSettings); 76 | 77 | // ================================== 78 | // Create new texture from image data 79 | // ================================== 80 | 81 | // Use the default LoaderSettings 82 | texture = await AsyncImageLoader.CreateFromImageAsync(imageData); 83 | 84 | // Use a custom LoaderSettings 85 | texture = await AsyncImageLoader.CreateFromImageAsync(imageData, loaderSettings); 86 | ``` 87 | 88 | ### Load Image Synchronously 89 | 90 | The synchronous variants are the same as the asynchronous counterparts but without `Async` suffix in theirs name. They are useful for debugging and profiling within a frame. 91 | 92 | ```cs 93 | var imageData = File.ReadAllBytes(); 94 | var texture = new Texture2D(1, 1); 95 | var loaderSettings = AsyncImageLoader.LoaderSettings.Default; 96 | var success = false; 97 | 98 | // ===================================== 99 | // Load image data into existing texture 100 | // ===================================== 101 | 102 | // Use the default LoaderSettings 103 | success = AsyncImageLoader.LoadImage(texture, imageData); 104 | 105 | // Similar to ImageConversion.LoadImage 106 | // Mark texture as unreadable after reading. 107 | success = AsyncImageLoader.LoadImage(texture, imageData, true); 108 | 109 | // Use a custom LoaderSettings 110 | success = AsyncImageLoader.LoadImage(texture, imageData, loaderSettings); 111 | 112 | // ================================== 113 | // Create new texture from image data 114 | // ================================== 115 | 116 | // Use the default LoaderSettings 117 | texture = AsyncImageLoader.CreateFromImage(imageData); 118 | 119 | // Use a custom LoaderSettings 120 | texture = AsyncImageLoader.CreateFromImage(imageData, loaderSettings); 121 | ``` 122 | 123 | ## After Loading 124 | 125 | ### Texture Format 126 | 127 | If the image has alpha channel, the format will be `RGBA32`, otherwise, it will be `RGB24`. 128 | 129 | ### Mipmap Count 130 | 131 | If the `LoadImage` and `LoadImageAsync` variants are used with `generateMipmap` set to `true`, the mipmap count is set to the maximum possible number for a particular texture. If you want to control the number of mipmap, you can use the `CreateFromImage` and `CreateFromImageAsync` instead. 132 | 133 | ### Mipmap Data 134 | 135 | The mipmaps are generated using box filtering with 2x2 kernel. The final result won't be the same as Unity's counterpart when using texture import in the editor. 136 | 137 | ## Troubleshooting 138 | 139 | ### There is still lag spike when loading large images 140 | 141 | After `AsyncImageLoader` method finishes executing, the image data are still transfering to the GPU. Therefore, any object, like material or UI, that wants to use the texture afterward will have to wait for the texture to finish uploading and thus block the Unity main thread. 142 | 143 | There is no easy way to detect if the texture has finished uploading its data. The workarounds are: 144 | + Wait for a second or more before using the texture. 145 | + **(Not tested)** Use [`AsyncGPUReadback`](https://docs.unity3d.com/ScriptReference/Rendering.AsyncGPUReadback.html) to request a single pixel from the texture. It will wait for the texture to finish uploading before downloading that single pixel. Then the request callback can be used to notify the Unity main thread about the texture upload completion. 146 | 147 | ## Acknowledgement 148 | 149 | This package is inspired by Matias Lavik's [`unity-async-textureimport`](https://codeberg.org/matiaslavik/unity-async-textureimport). 150 | -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: dabc0ed9659c10d4aafebc4a7676b6fe 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Runtime.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0f996c2e79fb99d48a47b298b77c702f 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/AsyncImageLoader.Runtime.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "AsyncImageLoader.Runtime", 3 | "references": [ 4 | "Unity.Burst", 5 | "Unity.Mathematics" 6 | ], 7 | "includePlatforms": [], 8 | "excludePlatforms": [], 9 | "allowUnsafeCode": true, 10 | "overrideReferences": false, 11 | "precompiledReferences": [], 12 | "autoReferenced": true, 13 | "defineConstraints": [], 14 | "versionDefines": [], 15 | "noEngineReferences": false 16 | } -------------------------------------------------------------------------------- /Runtime/AsyncImageLoader.Runtime.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e89de4e5a0e0c0b47a8506136f6d550d 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Runtime/AsyncImageLoader.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using UnityEngine; 3 | 4 | public static partial class AsyncImageLoader { 5 | /// Load image synchronously. This variant uses the default LoaderSettings. 6 | /// True if the data can be loaded, false otherwise. 7 | public static bool LoadImage(Texture2D texture, byte[] data) { 8 | return LoadImage(texture, data, LoaderSettings.Default); 9 | } 10 | 11 | /// Load image synchronously. This variant is similar to ImageConversion.LoadImage. 12 | /// True if the data can be loaded, false otherwise. 13 | public static bool LoadImage(Texture2D texture, byte[] data, bool markNonReadable) { 14 | var loaderSettings = LoaderSettings.Default; 15 | loaderSettings.markNonReadable = markNonReadable; 16 | return LoadImage(texture, data, loaderSettings); 17 | } 18 | 19 | /// Load image synchronously. This variant accepts a LoaderSettings. Useful for debugging and profiling. 20 | /// True if the data can be loaded, false otherwise. 21 | public static bool LoadImage(Texture2D texture, byte[] data, LoaderSettings loaderSettings) { 22 | try { 23 | if (data == null || data.Length == 0) throw new System.Exception("Input data is null or empty."); 24 | 25 | using (var importer = new ImageImporter(data, loaderSettings)) { 26 | importer.LoadIntoTexture(texture); 27 | } 28 | 29 | return true; 30 | } catch (System.Exception e) { 31 | if (loaderSettings.logException) Debug.LogException(e); 32 | 33 | return false; 34 | } 35 | } 36 | 37 | /// Load image asynchronously. This variant uses the default LoaderSettings. 38 | /// True if the data can be loaded, false otherwise. 39 | public static Task LoadImageAsync(Texture2D texture, byte[] data) { 40 | return LoadImageAsync(texture, data, LoaderSettings.Default); 41 | } 42 | 43 | /// Load image asynchronously. This variant is similar to ImageConversion.LoadImage. 44 | /// True if the data can be loaded, false otherwise. 45 | public static Task LoadImageAsync(Texture2D texture, byte[] data, bool markNonReadable) { 46 | var loaderSettings = LoaderSettings.Default; 47 | loaderSettings.markNonReadable = markNonReadable; 48 | return LoadImageAsync(texture, data, loaderSettings); 49 | } 50 | 51 | /// Load image asynchronously. This variant accepts a LoaderSettings. 52 | /// True if the data can be loaded, false otherwise. 53 | public static async Task LoadImageAsync(Texture2D texture, byte[] data, LoaderSettings loaderSettings) { 54 | try { 55 | if (data == null || data.Length == 0) throw new System.Exception("Input data is null or empty."); 56 | 57 | using (var importer = await Task.Run(() => new ImageImporter(data, loaderSettings))) { 58 | await importer.LoadIntoTextureAsync(texture); 59 | } 60 | 61 | return true; 62 | } catch (System.Exception e) { 63 | if (loaderSettings.logException) Debug.LogException(e); 64 | 65 | return false; 66 | } 67 | } 68 | 69 | /// Create Texture2D from image data synchronously. This variant uses a default LoaderSettings. Linear and mipmap count can be specified. 70 | /// Texture2D object if the data can be loaded, null otherwise. 71 | public static Texture2D CreateFromImage(byte[] data) { 72 | return CreateFromImage(data, LoaderSettings.Default); 73 | } 74 | 75 | 76 | /// Create Texture2D from image data synchronously. This variant accepts a LoaderSettings. Linear and mipmap count can be specified. 77 | /// Texture2D object if the data can be loaded, null otherwise. 78 | public static Texture2D CreateFromImage(byte[] data, LoaderSettings loaderSettings) { 79 | try { 80 | if (data == null || data.Length == 0) throw new System.Exception("Input data is null or empty."); 81 | 82 | using (var importer = new ImageImporter(data, loaderSettings)) { 83 | return importer.CreateNewTexture(); 84 | } 85 | } catch (System.Exception e) { 86 | if (loaderSettings.logException) Debug.LogException(e); 87 | 88 | return null; 89 | } 90 | } 91 | 92 | /// Create Texture2D from image data asynchronously. This variant uses a default LoaderSettings. Linear and mipmap count can be specified. 93 | /// Texture2D object if the data can be loaded, null otherwise. 94 | public static Task CreateFromImageAsync(byte[] data) { 95 | return CreateFromImageAsync(data, LoaderSettings.Default); 96 | } 97 | 98 | /// Create Texture2D from image data asynchronously. This variant accepts a LoaderSettings. Linear and mipmap count can be specified. 99 | /// Texture2D object if the data can be loaded, null otherwise. 100 | public static async Task CreateFromImageAsync(byte[] data, LoaderSettings loaderSettings) { 101 | try { 102 | if (data == null || data.Length == 0) throw new System.Exception("Input data is null or empty."); 103 | 104 | using (var importer = await Task.Run(() => new ImageImporter(data, loaderSettings))) { 105 | return await importer.CreateNewTextureAsync(); 106 | } 107 | } catch (System.Exception e) { 108 | if (loaderSettings.logException) Debug.LogException(e); 109 | 110 | return null; 111 | } 112 | } 113 | 114 | /// Settings used by the image loader. 115 | public struct LoaderSettings { 116 | /// Create linear texture. Only applicable to methods that create new Texture2D. Defaults to false. 117 | public bool linear; 118 | /// Texture data won't be readable on the CPU after loading. Defaults to false. 119 | public bool markNonReadable; 120 | /// Whether or not to generate mipmaps. Defaults to true. 121 | public bool generateMipmap; 122 | /// Automatically calculate the number of mipmap levels. Defaults to true. Only applicable to methods that create new Texture2D. 123 | public bool autoMipmapCount; 124 | /// Mipmap count, including the base level. Must be greater than 1. Only applicable to methods that create new Texture2D. 125 | public int mipmapCount; 126 | /// Used to explicitly specify the image format. Defaults to FIF_UNKNOWN, which the image format will be automatically determined. 127 | public FreeImage.Format format; 128 | /// Whether or not to log exception caught by this method. Defaults to true. 129 | public bool logException; 130 | 131 | public static LoaderSettings Default => new LoaderSettings { 132 | linear = false, 133 | markNonReadable = false, 134 | generateMipmap = true, 135 | autoMipmapCount = true, 136 | format = FreeImage.Format.FIF_UNKNOWN, 137 | logException = true, 138 | }; 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /Runtime/AsyncImageLoader.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2cac9b8ce6e98bc43abd31a087b3380e 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/AsyncImageLoader.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1efe1ffc13d67ac4c9049c1c02b03b58 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/AsyncImageLoader/FreeImage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | public static partial class AsyncImageLoader { 5 | public static class FreeImage { 6 | public enum Format { 7 | FIF_UNKNOWN = -1, 8 | FIF_BMP = 0, 9 | FIF_ICO = 1, 10 | FIF_JPEG = 2, 11 | FIF_JNG = 3, 12 | FIF_KOALA = 4, 13 | FIF_LBM = 5, 14 | FIF_IFF = FIF_LBM, 15 | FIF_MNG = 6, 16 | FIF_PBM = 7, 17 | FIF_PBMRAW = 8, 18 | FIF_PCD = 9, 19 | FIF_PCX = 10, 20 | FIF_PGM = 11, 21 | FIF_PGMRAW = 12, 22 | FIF_PNG = 13, 23 | FIF_PPM = 14, 24 | FIF_PPMRAW = 15, 25 | FIF_RAS = 16, 26 | FIF_TARGA = 17, 27 | FIF_TIFF = 18, 28 | FIF_WBMP = 19, 29 | FIF_PSD = 20, 30 | FIF_CUT = 21, 31 | FIF_XBM = 22, 32 | FIF_XPM = 23, 33 | FIF_DDS = 24, 34 | FIF_GIF = 25, 35 | FIF_HDR = 26, 36 | FIF_FAXG3 = 27, 37 | FIF_SGI = 28, 38 | FIF_EXR = 29, 39 | FIF_J2K = 30, 40 | FIF_JP2 = 31, 41 | FIF_PFM = 32, 42 | FIF_PICT = 33, 43 | FIF_RAW = 34, 44 | FIF_WEBP = 35, 45 | FIF_JXR = 36 46 | } 47 | 48 | internal enum Type { 49 | /// 50 | /// unknown type 51 | /// 52 | FIT_UNKNOWN = 0, 53 | /// 54 | /// standard image : 1-, 4-, 8-, 16-, 24-, 32-bit 55 | /// 56 | FIT_BITMAP = 1, 57 | /// 58 | /// array of unsigned short : unsigned 16-bit 59 | /// 60 | FIT_UINT16 = 2, 61 | /// 62 | /// array of short : signed 16-bit 63 | /// 64 | FIT_INT16 = 3, 65 | /// 66 | /// array of unsigned long : unsigned 32-bit 67 | /// 68 | FIT_UINT32 = 4, 69 | /// 70 | /// array of long : signed 32-bit 71 | /// 72 | FIT_INT32 = 5, 73 | /// 74 | /// array of float : 32-bit IEEE floating point 75 | /// 76 | FIT_FLOAT = 6, 77 | /// 78 | /// array of double : 64-bit IEEE floating point 79 | /// 80 | FIT_DOUBLE = 7, 81 | /// 82 | /// array of FICOMPLEX : 2 x 64-bit IEEE floating point 83 | /// 84 | FIT_COMPLEX = 8, 85 | /// 86 | /// 48-bit RGB image : 3 x 16-bit 87 | /// 88 | FIT_RGB16 = 9, 89 | /// 90 | /// 64-bit RGBA image : 4 x 16-bit 91 | /// 92 | FIT_RGBA16 = 10, 93 | /// 94 | /// 96-bit RGB float image : 3 x 32-bit IEEE floating point 95 | /// 96 | FIT_RGBF = 11, 97 | /// 98 | /// 128-bit RGBA float image : 4 x 32-bit IEEE floating point 99 | /// 100 | FIT_RGBAF = 12 101 | } 102 | 103 | [System.Flags] 104 | internal enum LoadFlags { 105 | /// 106 | /// Default option for all types. 107 | /// 108 | DEFAULT = 0, 109 | /// 110 | /// Load the image as a 256 color image with ununsed palette entries, if it's 16 or 2 color. 111 | /// 112 | GIF_LOAD256 = 1, 113 | /// 114 | /// 'Play' the GIF to generate each frame (as 32bpp) instead of returning raw frame data when loading. 115 | /// 116 | GIF_PLAYBACK = 2, 117 | /// 118 | /// Convert to 32bpp and create an alpha channel from the AND-mask when loading. 119 | /// 120 | ICO_MAKEALPHA = 1, 121 | /// 122 | /// Load the file as fast as possible, sacrificing some quality. 123 | /// 124 | JPEG_FAST = 0x0001, 125 | /// 126 | /// Load the file with the best quality, sacrificing some speed. 127 | /// 128 | JPEG_ACCURATE = 0x0002, 129 | /// 130 | /// Load separated CMYK "as is" (use | to combine with other load flags). 131 | /// 132 | JPEG_CMYK = 0x0004, 133 | /// 134 | /// Load and rotate according to Exif 'Orientation' tag if available. 135 | /// 136 | JPEG_EXIFROTATE = 0x0008, 137 | /// 138 | /// Load the bitmap sized 768 x 512. 139 | /// 140 | PCD_BASE = 1, 141 | /// 142 | /// Load the bitmap sized 384 x 256. 143 | /// 144 | PCD_BASEDIV4 = 2, 145 | /// 146 | /// Load the bitmap sized 192 x 128. 147 | /// 148 | PCD_BASEDIV16 = 3, 149 | /// 150 | /// Avoid gamma correction. 151 | /// 152 | PNG_IGNOREGAMMA = 1, 153 | /// 154 | /// If set the loader converts RGB555 and ARGB8888 -> RGB888. 155 | /// 156 | TARGA_LOAD_RGB888 = 1, 157 | /// 158 | /// Reads tags for separated CMYK. 159 | /// 160 | TIFF_CMYK = 0x0001, 161 | /// 162 | /// Tries to load the JPEG preview image, embedded in 163 | /// Exif Metadata or load the image as RGB 24-bit if no 164 | /// preview image is available. 165 | /// 166 | RAW_PREVIEW = 0x1, 167 | /// 168 | /// Loads the image as RGB 24-bit. 169 | /// 170 | RAW_DISPLAY = 0x2, 171 | } 172 | 173 | const string FreeImageLibrary = "FreeImage"; 174 | 175 | [DllImport(FreeImageLibrary, EntryPoint = "FreeImage_IsLittleEndian")] 176 | internal static extern bool IsLittleEndian(); 177 | 178 | [DllImport(FreeImageLibrary, EntryPoint = "FreeImage_GetFileTypeFromMemory")] 179 | internal static extern Format GetFileTypeFromMemory(IntPtr memory, int size); 180 | 181 | [DllImport(FreeImageLibrary, EntryPoint = "FreeImage_OpenMemory")] 182 | internal static extern IntPtr OpenMemory(IntPtr data, uint size_in_bytes); 183 | 184 | [DllImport(FreeImageLibrary, EntryPoint = "FreeImage_CloseMemory")] 185 | internal static extern IntPtr CloseMemory(IntPtr data); 186 | 187 | [DllImport(FreeImageLibrary, EntryPoint = "FreeImage_LoadFromMemory")] 188 | internal static extern IntPtr LoadFromMemory(Format format, IntPtr stream, int flags); 189 | 190 | [DllImport(FreeImageLibrary, EntryPoint = "FreeImage_Unload")] 191 | internal static extern void Unload(IntPtr dib); 192 | 193 | [DllImport(FreeImageLibrary, EntryPoint = "FreeImage_ConvertToRawBits")] 194 | internal static extern void ConvertToRawBits(IntPtr bits, IntPtr dib, int pitch, uint bpp, uint red_mask, uint green_mask, uint blue_mask, bool topdown); 195 | 196 | [DllImport(FreeImageLibrary, EntryPoint = "FreeImage_GetWidth")] 197 | internal static extern uint GetWidth(IntPtr handle); 198 | 199 | [DllImport(FreeImageLibrary, EntryPoint = "FreeImage_GetHeight")] 200 | internal static extern uint GetHeight(IntPtr handle); 201 | 202 | [DllImport(FreeImageLibrary, EntryPoint = "FreeImage_GetImageType")] 203 | internal static extern Type GetImageType(IntPtr dib); 204 | 205 | [DllImport(FreeImageLibrary, EntryPoint = "FreeImage_GetBPP")] 206 | internal static extern int GetBPP(IntPtr dib); 207 | 208 | [DllImport(FreeImageLibrary, EntryPoint = "FreeImage_GetBits")] 209 | internal static extern IntPtr GetBits(IntPtr dib); 210 | 211 | [DllImport(FreeImageLibrary, EntryPoint = "FreeImage_GetLine")] 212 | internal static extern int GetLine(IntPtr dib); 213 | 214 | [DllImport(FreeImageLibrary, EntryPoint = "FreeImage_GetPitch")] 215 | internal static extern int GetPitch(IntPtr dib); 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /Runtime/AsyncImageLoader/FreeImage.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 20c06f5494a5fdf41876fc4f9db8386e 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/AsyncImageLoader/MipmapJobs.cs: -------------------------------------------------------------------------------- 1 | using Unity.Burst; 2 | using Unity.Collections; 3 | using Unity.Jobs; 4 | using Unity.Mathematics; 5 | using static Unity.Mathematics.math; 6 | 7 | public partial class AsyncImageLoader { 8 | interface IMipmapJob : IJobParallelFor where TChannel : struct { 9 | int2 InputSize { get; set; } 10 | int2 OutputSize { get; set; } 11 | NativeSlice InputChannel { get; set; } 12 | NativeSlice OutputChannel { get; set; } 13 | } 14 | 15 | [BurstCompile(CompileSynchronously = true)] 16 | struct ByteChannelMipmapJob : IMipmapJob { 17 | public int2 InputSize { get; set; } 18 | public int2 OutputSize { get; set; } 19 | 20 | public NativeSlice InputChannel { get => _inputChannel; set => _inputChannel = value; } 21 | public NativeSlice OutputChannel { get => _outputChannel; set => _outputChannel = value; } 22 | 23 | [ReadOnly] NativeSlice _inputChannel; 24 | [WriteOnly] NativeSlice _outputChannel; 25 | 26 | public void Execute(int outputIndex) { 27 | var outputPosition = int2( 28 | outputIndex % OutputSize.x, 29 | outputIndex / OutputSize.x 30 | ); 31 | var offset = int2(0); 32 | var total = 0u; 33 | 34 | for (offset.y = 0; offset.y < 2; offset.y++) { 35 | for (offset.x = 0; offset.x < 2; offset.x++) { 36 | var inputPosition = min(mad(2, outputPosition, offset), InputSize - 1); 37 | var inputIndex = mad(InputSize.x, inputPosition.y, inputPosition.x); 38 | total += _inputChannel[inputIndex]; 39 | } 40 | } 41 | 42 | _outputChannel[outputIndex] = (byte)(total >> 2); 43 | } 44 | } 45 | 46 | [BurstCompile(CompileSynchronously = true)] 47 | struct ShortChannelMipmapJob : IMipmapJob { 48 | public int2 InputSize { get; set; } 49 | public int2 OutputSize { get; set; } 50 | 51 | public NativeSlice InputChannel { get => _inputChannel; set => _inputChannel = value; } 52 | public NativeSlice OutputChannel { get => _outputChannel; set => _outputChannel = value; } 53 | 54 | [ReadOnly] NativeSlice _inputChannel; 55 | [WriteOnly] NativeSlice _outputChannel; 56 | 57 | public void Execute(int outputIndex) { 58 | var outputPosition = int2( 59 | outputIndex % OutputSize.x, 60 | outputIndex / OutputSize.x 61 | ); 62 | var offset = int2(0); 63 | var total = 0; 64 | 65 | for (offset.y = 0; offset.y < 2; offset.y++) { 66 | for (offset.x = 0; offset.x < 2; offset.x++) { 67 | var inputPosition = min(mad(2, outputPosition, offset), InputSize - 1); 68 | var inputIndex = mad(InputSize.x, inputPosition.y, inputPosition.x); 69 | total += _inputChannel[inputIndex]; 70 | } 71 | } 72 | 73 | _outputChannel[outputIndex] = (short)(total >> 2); 74 | } 75 | } 76 | 77 | [BurstCompile(CompileSynchronously = true)] 78 | struct FloatChannelMipmapJob : IMipmapJob { 79 | public int2 InputSize { get; set; } 80 | public int2 OutputSize { get; set; } 81 | 82 | public NativeSlice InputChannel { get => _inputChannel; set => _inputChannel = value; } 83 | public NativeSlice OutputChannel { get => _outputChannel; set => _outputChannel = value; } 84 | 85 | [ReadOnly] NativeSlice _inputChannel; 86 | [WriteOnly] NativeSlice _outputChannel; 87 | 88 | public void Execute(int outputIndex) { 89 | var outputPosition = int2( 90 | outputIndex % OutputSize.x, 91 | outputIndex / OutputSize.x 92 | ); 93 | var offset = int2(0); 94 | var total = 0f; 95 | 96 | for (offset.y = 0; offset.y < 2; offset.y++) { 97 | for (offset.x = 0; offset.x < 2; offset.x++) { 98 | var inputPosition = min(mad(2, outputPosition, offset), InputSize - 1); 99 | var inputIndex = mad(InputSize.x, inputPosition.y, inputPosition.x); 100 | total += _inputChannel[inputIndex]; 101 | } 102 | } 103 | 104 | _outputChannel[outputIndex] = .25f * total; 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /Runtime/AsyncImageLoader/MipmapJobs.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 89c830d6b0686564a845e1e517c46f58 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/AsyncImageLoader/Pixels.cs: -------------------------------------------------------------------------------- 1 | public partial class AsyncImageLoader { 2 | interface IPixel where TChannel : struct { 3 | static int ChannelCount() => throw new System.NotImplementedException(); 4 | static int ChannelByteCount() => throw new System.NotImplementedException(); 5 | } 6 | 7 | struct R16Pixel : IPixel { 8 | public static int ChannelCount() => 1; 9 | public static int ChannelByteCount() => sizeof(short); 10 | 11 | public short r; 12 | } 13 | 14 | struct RFloatPixel : IPixel { 15 | public static int ChannelCount() => 1; 16 | public static int ChannelByteCount() => sizeof(float); 17 | 18 | public float r; 19 | } 20 | 21 | struct RGB24Pixel : IPixel { 22 | public static int ChannelCount() => 3; 23 | public static int ChannelByteCount() => sizeof(byte); 24 | 25 | public byte r, g, b; 26 | } 27 | 28 | struct RGB48Pixel : IPixel { 29 | public static int ChannelCount() => 3; 30 | public static int ChannelByteCount() => sizeof(short); 31 | 32 | public short r, g, b; 33 | } 34 | 35 | struct RGBA32Pixel : IPixel { 36 | public static int ChannelCount() => 4; 37 | public static int ChannelByteCount() => sizeof(byte); 38 | 39 | public byte r, g, b, a; 40 | } 41 | 42 | struct RGBA64Pixel : IPixel { 43 | public static int ChannelCount() => 4; 44 | public static int ChannelByteCount() => sizeof(short); 45 | 46 | public short r, g, b, a; 47 | } 48 | 49 | struct RGBAFloatPixel : IPixel { 50 | public static int ChannelCount() => 4; 51 | public static int ChannelByteCount() => sizeof(float); 52 | 53 | public float r, g, b, a; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Runtime/AsyncImageLoader/Pixels.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: bbdfab977a6f00c409b972806fcef1ed 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/AsyncImageLoader/TransferImageJob.cs: -------------------------------------------------------------------------------- 1 | using Unity.Burst; 2 | using Unity.Collections; 3 | using Unity.Collections.LowLevel.Unsafe; 4 | using Unity.Jobs; 5 | 6 | public partial class AsyncImageLoader { 7 | [BurstCompile(CompileSynchronously = true)] 8 | struct TransferImageToTextureJob : IJobParallelFor { 9 | public int bytesPerLine; 10 | public int bytesPerScanline; 11 | 12 | [NativeDisableUnsafePtrRestriction] 13 | public System.IntPtr bitsPtr; 14 | 15 | [WriteOnly] public NativeSlice textureData; 16 | 17 | public unsafe void Execute(int rowIndex) { 18 | UnsafeUtility.MemCpy( 19 | (byte*)textureData.GetUnsafePtr() + rowIndex * bytesPerLine, 20 | (bitsPtr + rowIndex * bytesPerScanline).ToPointer(), 21 | bytesPerLine 22 | ); 23 | } 24 | } 25 | 26 | [BurstCompile(CompileSynchronously = true)] 27 | struct TransferBGR24ImageToRGB24TextureJob : IJobParallelFor { 28 | public int bytesPerScanline; 29 | public int width; 30 | 31 | [NativeDisableUnsafePtrRestriction] 32 | public System.IntPtr bitsPtr; 33 | 34 | [WriteOnly] public NativeSlice textureData; 35 | 36 | public unsafe void Execute(int rowIndex) { 37 | var rowData = NativeSliceUnsafeUtility.ConvertExistingDataToNativeSlice( 38 | (RGB24Pixel*)textureData.GetUnsafePtr() + rowIndex * width, sizeof(RGB24Pixel), width 39 | ); 40 | var rowBits = NativeSliceUnsafeUtility.ConvertExistingDataToNativeSlice( 41 | (bitsPtr + rowIndex * bytesPerScanline).ToPointer(), sizeof(RGB24Pixel), width 42 | ); 43 | 44 | rowData.SliceWithStride(0).CopyFrom(rowBits.SliceWithStride(2)); 45 | rowData.SliceWithStride(1).CopyFrom(rowBits.SliceWithStride(1)); 46 | rowData.SliceWithStride(2).CopyFrom(rowBits.SliceWithStride(0)); 47 | } 48 | } 49 | 50 | [BurstCompile(CompileSynchronously = true)] 51 | struct TransferBGRA32ImageToRGBA32TextureJob : IJobParallelFor { 52 | public int bytesPerScanline; 53 | public int width; 54 | 55 | [NativeDisableUnsafePtrRestriction] 56 | public System.IntPtr bitsPtr; 57 | 58 | [WriteOnly] public NativeSlice textureData; 59 | 60 | public unsafe void Execute(int rowIndex) { 61 | var rowData = NativeSliceUnsafeUtility.ConvertExistingDataToNativeSlice( 62 | (RGBA32Pixel*)textureData.GetUnsafePtr() + rowIndex * width, sizeof(RGBA32Pixel), width 63 | ); 64 | var rowBits = NativeSliceUnsafeUtility.ConvertExistingDataToNativeSlice( 65 | (bitsPtr + rowIndex * bytesPerScanline).ToPointer(), sizeof(RGBA32Pixel), width 66 | ); 67 | 68 | rowData.SliceWithStride(0).CopyFrom(rowBits.SliceWithStride(2)); 69 | rowData.SliceWithStride(1).CopyFrom(rowBits.SliceWithStride(1)); 70 | rowData.SliceWithStride(2).CopyFrom(rowBits.SliceWithStride(0)); 71 | rowData.SliceWithStride(3).CopyFrom(rowBits.SliceWithStride(3)); 72 | } 73 | } 74 | 75 | [BurstCompile(CompileSynchronously = true)] 76 | struct TransferRGBFloatImageToRGBAFloatTextureJob : IJobParallelFor { 77 | public int bytesPerScanline; 78 | public int width; 79 | 80 | [NativeDisableUnsafePtrRestriction] 81 | public System.IntPtr bitsPtr; 82 | 83 | [WriteOnly] public NativeSlice textureData; 84 | 85 | public unsafe void Execute(int rowIndex) { 86 | var rowData = NativeSliceUnsafeUtility.ConvertExistingDataToNativeSlice( 87 | (RGBAFloatPixel*)textureData.GetUnsafePtr() + rowIndex * width, sizeof(RGBAFloatPixel), width 88 | ); 89 | var rowAlphaData = rowData.SliceWithStride(3 * sizeof(float)); 90 | 91 | UnsafeUtility.MemCpyStride( 92 | rowData.GetUnsafePtr(), rowData.Stride, 93 | (bitsPtr + rowIndex * bytesPerScanline).ToPointer(), 3 * sizeof(float), 94 | 3 * sizeof(float), width 95 | ); 96 | 97 | for (var i = 0; i < rowAlphaData.Length; i++) rowAlphaData[i] = 1f; 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /Runtime/AsyncImageLoader/TransferImageJob.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6110f3cee45f27a4ebcb586486fc06a1 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/ImageImporter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using System.Threading.Tasks; 4 | using Unity.Collections; 5 | using Unity.Mathematics; 6 | using Unity.Jobs; 7 | using Unity.Profiling; 8 | using UnityEngine; 9 | using static Unity.Mathematics.math; 10 | 11 | public static partial class AsyncImageLoader { 12 | static readonly int MaxTextureSize = SystemInfo.maxTextureSize; 13 | static readonly int MaxMipmapLevel = (int)Mathf.Log(SystemInfo.maxTextureSize, 2f) + 1; 14 | 15 | class ImageImporter : IDisposable { 16 | static readonly ProfilerMarker ConstructorMarker = new ProfilerMarker("ImageImporter.Constructor"); 17 | static readonly ProfilerMarker CreateNewTextureMarker = new ProfilerMarker("ImageImporter.CreateNewTexture"); 18 | static readonly ProfilerMarker LoadIntoTextureMarker = new ProfilerMarker("ImageImporter.LoadIntoTexture"); 19 | 20 | LoaderSettings _loaderSettings; 21 | 22 | IntPtr _bitmap; 23 | int _width; 24 | int _height; 25 | TextureFormat _textureFormat; 26 | int _bytesPerPixel; 27 | int _imageBitsPerPixel; 28 | FreeImage.Type _imageType; 29 | 30 | public ImageImporter(byte[] imageData, LoaderSettings loaderSettings) { 31 | using (ConstructorMarker.Auto()) { 32 | _loaderSettings = loaderSettings; 33 | _bitmap = IntPtr.Zero; 34 | 35 | IntPtr memoryStream = IntPtr.Zero; 36 | 37 | try { 38 | memoryStream = FreeImage.OpenMemory( 39 | Marshal.UnsafeAddrOfPinnedArrayElement(imageData, 0), 40 | (uint)imageData.Length 41 | ); 42 | 43 | if (_loaderSettings.format == FreeImage.Format.FIF_UNKNOWN) { 44 | _loaderSettings.format = FreeImage.GetFileTypeFromMemory(memoryStream, imageData.Length); 45 | } 46 | 47 | if (_loaderSettings.format == FreeImage.Format.FIF_UNKNOWN) { 48 | throw new Exception("Cannot automatically determine the image format. Consider explicitly specifying image format."); 49 | } 50 | 51 | _bitmap = FreeImage.LoadFromMemory(_loaderSettings.format, memoryStream, (int)FreeImage.LoadFlags.JPEG_ACCURATE); 52 | _width = (int)FreeImage.GetWidth(_bitmap); 53 | _height = (int)FreeImage.GetHeight(_bitmap); 54 | _imageBitsPerPixel = FreeImage.GetBPP(_bitmap); 55 | _imageType = FreeImage.GetImageType(_bitmap); 56 | 57 | if (_width > MaxTextureSize || _height > MaxTextureSize) { 58 | Dispose(); 59 | throw new Exception("Texture size exceed maximum dimension supported by Unity."); 60 | } 61 | 62 | DetermineTextureFormat(); 63 | } finally { 64 | if (memoryStream != IntPtr.Zero) FreeImage.CloseMemory(memoryStream); 65 | } 66 | } 67 | } 68 | 69 | public void Dispose() { 70 | if (_bitmap != IntPtr.Zero) { 71 | FreeImage.Unload(_bitmap); 72 | _bitmap = IntPtr.Zero; 73 | } 74 | } 75 | 76 | public Texture2D CreateTexture() { 77 | var mipmapCount = GetMipmapCount(); 78 | return new Texture2D(_width, _height, _textureFormat, mipmapCount, _loaderSettings.linear); 79 | } 80 | 81 | public void ReinitializeTexture(Texture2D texture) { 82 | if ((texture.width, texture.height, texture.format, texture.mipmapCount > 1) != (_width, _height, _textureFormat, _loaderSettings.generateMipmap)) { 83 | #if UNITY_2021_1_OR_NEWER 84 | texture.Reinitialize(_width, _height, _textureFormat, _loaderSettings.generateMipmap); 85 | #else 86 | texture.Resize(_width, _height, _textureFormat, _loaderSettings.generateMipmap); 87 | #endif 88 | } 89 | } 90 | 91 | public Texture2D CreateNewTexture() { 92 | using (CreateNewTextureMarker.Auto()) { 93 | var texture = CreateTexture(); 94 | var dependency = ProcessImage(texture); 95 | dependency.Complete(); 96 | texture.Apply(false, _loaderSettings.markNonReadable); 97 | return texture; 98 | } 99 | } 100 | 101 | public async Task CreateNewTextureAsync() { 102 | var texture = CreateTexture(); 103 | var dependency = ProcessImage(texture); 104 | while (!dependency.IsCompleted) await Task.Yield(); 105 | dependency.Complete(); 106 | texture.Apply(false, _loaderSettings.markNonReadable); 107 | return texture; 108 | } 109 | 110 | public void LoadIntoTexture(Texture2D texture) { 111 | using (LoadIntoTextureMarker.Auto()) { 112 | ReinitializeTexture(texture); 113 | var dependency = ProcessImage(texture); 114 | dependency.Complete(); 115 | texture.Apply(false, _loaderSettings.markNonReadable); 116 | } 117 | } 118 | 119 | public async Task LoadIntoTextureAsync(Texture2D texture) { 120 | ReinitializeTexture(texture); 121 | var dependency = ProcessImage(texture); 122 | while (!dependency.IsCompleted) await Task.Yield(); 123 | dependency.Complete(); 124 | texture.Apply(false, _loaderSettings.markNonReadable); 125 | } 126 | 127 | int GetMipmapCount() { 128 | if (!_loaderSettings.generateMipmap) return 1; 129 | 130 | var maxDimension = Mathf.Max(_width, _height); 131 | var mipmapCount = Mathf.FloorToInt(Mathf.Log(maxDimension, 2f)) + 1; 132 | mipmapCount = Mathf.Clamp(mipmapCount, 2, MaxMipmapLevel); 133 | 134 | if (!_loaderSettings.autoMipmapCount) { 135 | mipmapCount = Mathf.Clamp(_loaderSettings.mipmapCount, 2, mipmapCount); 136 | } 137 | 138 | return mipmapCount; 139 | } 140 | 141 | int2 GetMipmapSize(int mipmapLevel) { 142 | // Base level 143 | if (mipmapLevel == 0) { 144 | return int2(_width, _height); 145 | } else { 146 | var mipmapFactor = Mathf.Pow(2f, -mipmapLevel); 147 | var mipmapWidth = Mathf.Max(1, Mathf.FloorToInt(mipmapFactor * _width)); 148 | var mipmapHeight = Mathf.Max(1, Mathf.FloorToInt(mipmapFactor * _height)); 149 | return int2(mipmapWidth, mipmapHeight); 150 | } 151 | } 152 | 153 | void DetermineTextureFormat() { 154 | switch (_imageType) { 155 | case FreeImage.Type.FIT_BITMAP: 156 | switch (_imageBitsPerPixel) { 157 | case 24: 158 | _textureFormat = TextureFormat.RGB24; 159 | _bytesPerPixel = 3; 160 | break; 161 | case 32: 162 | _textureFormat = TextureFormat.RGBA32; 163 | _bytesPerPixel = 4; 164 | break; 165 | default: 166 | throw new Exception($"Bitmap bitdepth not supported: {_imageBitsPerPixel}"); 167 | } 168 | break; 169 | case FreeImage.Type.FIT_INT16: 170 | case FreeImage.Type.FIT_UINT16: 171 | _textureFormat = TextureFormat.R16; 172 | _bytesPerPixel = 2; 173 | break; 174 | case FreeImage.Type.FIT_FLOAT: 175 | _textureFormat = TextureFormat.RFloat; 176 | _bytesPerPixel = 4; 177 | break; 178 | case FreeImage.Type.FIT_RGB16: 179 | _textureFormat = TextureFormat.RGB48; 180 | _bytesPerPixel = 6; 181 | break; 182 | case FreeImage.Type.FIT_RGBA16: 183 | _textureFormat = TextureFormat.RGBA64; 184 | _bytesPerPixel = 8; 185 | break; 186 | case FreeImage.Type.FIT_RGBF: 187 | case FreeImage.Type.FIT_RGBAF: 188 | _textureFormat = TextureFormat.RGBAFloat; 189 | _bytesPerPixel = 16; 190 | break; 191 | default: 192 | throw new Exception( 193 | $"Image type not supported: {_imageType}. Please submit a new GitHub issue if you want this type to be supported." 194 | ); 195 | } 196 | } 197 | 198 | public JobHandle ProcessImage(Texture2D texture) { 199 | JobHandle dependency = default; 200 | dependency = ScheduleLoadTextureDataJob(texture, dependency); 201 | dependency = ScheduleMipmapJobForEachLevels(texture, dependency); 202 | return dependency; 203 | } 204 | 205 | public unsafe JobHandle ScheduleLoadTextureDataJob(Texture2D texture, JobHandle dependency = default) { 206 | var textureData = GetTextureData(texture, 0); 207 | var bitsPtr = FreeImage.GetBits(_bitmap); 208 | var bytesPerLine = FreeImage.GetLine(_bitmap); 209 | var bytesPerScanline = FreeImage.GetPitch(_bitmap); 210 | 211 | if (FreeImage.IsLittleEndian() && _imageType == FreeImage.Type.FIT_BITMAP) { 212 | switch (_imageBitsPerPixel) { 213 | case 24: 214 | var transferBGR24ImageToRGB24TextureJob = new TransferBGR24ImageToRGB24TextureJob { 215 | bytesPerScanline = bytesPerScanline, 216 | width = _width, 217 | bitsPtr = bitsPtr, 218 | textureData = textureData, 219 | }; 220 | 221 | return transferBGR24ImageToRGB24TextureJob.Schedule(_height, 1, dependency); 222 | case 32: 223 | var transferBGRA32ImageToRGBA32TextureJob = new TransferBGRA32ImageToRGBA32TextureJob { 224 | bytesPerScanline = bytesPerScanline, 225 | width = _width, 226 | bitsPtr = bitsPtr, 227 | textureData = textureData, 228 | }; 229 | 230 | return transferBGRA32ImageToRGBA32TextureJob.Schedule(_height, 1, dependency); 231 | default: 232 | throw new Exception($"Bitmap bitdepth not supported: {_imageBitsPerPixel}"); 233 | } 234 | } else if (_imageType == FreeImage.Type.FIT_RGBF) { 235 | var transferRGBFloatImageToRGBAFloatTextureJob = new TransferRGBFloatImageToRGBAFloatTextureJob { 236 | bytesPerScanline = bytesPerScanline, 237 | width = _width, 238 | bitsPtr = bitsPtr, 239 | textureData = textureData, 240 | }; 241 | 242 | return transferRGBFloatImageToRGBAFloatTextureJob.Schedule(_height, 1, dependency); 243 | } else { 244 | var transferImageToTextureJob = new TransferImageToTextureJob { 245 | bytesPerLine = bytesPerLine, 246 | bytesPerScanline = bytesPerScanline, 247 | bitsPtr = bitsPtr, 248 | textureData = textureData, 249 | }; 250 | 251 | return transferImageToTextureJob.Schedule(_height, 1, dependency); 252 | } 253 | } 254 | 255 | public JobHandle ScheduleMipmapJobForEachLevels(Texture2D texture, JobHandle dependency = default) { 256 | var mipmapSize = GetMipmapSize(0); 257 | var mipmapSlice = GetTextureData(texture, 0); 258 | 259 | for (int mipmapLevel = 1; mipmapLevel < texture.mipmapCount; mipmapLevel++) { 260 | var nextMipmapSize = GetMipmapSize(mipmapLevel); 261 | var nextMipmapSlice = GetTextureData(texture, mipmapLevel); 262 | 263 | dependency = _textureFormat switch { 264 | TextureFormat.R16 => ScheduleMipmapJobForEachChannels( 265 | R16Pixel.ChannelCount(), R16Pixel.ChannelByteCount(), 266 | mipmapSlice, mipmapSize, nextMipmapSlice, nextMipmapSize, 267 | dependency 268 | ), 269 | TextureFormat.RFloat => ScheduleMipmapJobForEachChannels( 270 | RFloatPixel.ChannelCount(), RFloatPixel.ChannelByteCount(), 271 | mipmapSlice, mipmapSize, nextMipmapSlice, nextMipmapSize, 272 | dependency 273 | ), 274 | TextureFormat.RGB24 => ScheduleMipmapJobForEachChannels( 275 | RGB24Pixel.ChannelCount(), RGB24Pixel.ChannelByteCount(), 276 | mipmapSlice, mipmapSize, nextMipmapSlice, nextMipmapSize, 277 | dependency 278 | ), 279 | TextureFormat.RGB48 => ScheduleMipmapJobForEachChannels( 280 | RGB48Pixel.ChannelCount(), RGB48Pixel.ChannelByteCount(), 281 | mipmapSlice, mipmapSize, nextMipmapSlice, nextMipmapSize, 282 | dependency 283 | ), 284 | TextureFormat.RGBA32 => ScheduleMipmapJobForEachChannels( 285 | RGBA32Pixel.ChannelCount(), RGBA32Pixel.ChannelByteCount(), 286 | mipmapSlice, mipmapSize, nextMipmapSlice, nextMipmapSize, 287 | dependency 288 | ), 289 | TextureFormat.RGBA64 => ScheduleMipmapJobForEachChannels( 290 | RGBA64Pixel.ChannelCount(), RGBA64Pixel.ChannelByteCount(), 291 | mipmapSlice, mipmapSize, nextMipmapSlice, nextMipmapSize, 292 | dependency 293 | ), 294 | TextureFormat.RGBAFloat => ScheduleMipmapJobForEachChannels( 295 | RGBAFloatPixel.ChannelCount(), RGBAFloatPixel.ChannelByteCount(), 296 | mipmapSlice, mipmapSize, nextMipmapSlice, nextMipmapSize, 297 | dependency 298 | ), 299 | _ => throw new NotSupportedException($"Texture format not supported: {_textureFormat}"), 300 | }; 301 | 302 | mipmapSize = nextMipmapSize; 303 | mipmapSlice = nextMipmapSlice; 304 | } 305 | 306 | return dependency; 307 | } 308 | 309 | JobHandle ScheduleMipmapJobForEachChannels( 310 | int channelCount, int channelByteCount, 311 | NativeSlice inputSlice, int2 inputSize, 312 | NativeSlice outputSlice, int2 outputSize, 313 | JobHandle dependency 314 | ) 315 | where TChannel : struct 316 | where TPixel : struct, IPixel 317 | where TMipmapJob : struct, IMipmapJob { 318 | var minSize = min(outputSize.x, outputSize.y); 319 | var inputPixelSlice = inputSlice.SliceConvert(); 320 | var outputPixelSlice = outputSlice.SliceConvert(); 321 | 322 | for (var channel = 0; channel < channelCount; channel++) { 323 | var mipmapJob = new TMipmapJob { 324 | InputSize = inputSize, 325 | OutputSize = outputSize, 326 | InputChannel = inputPixelSlice.SliceWithStride(channel * channelByteCount), 327 | OutputChannel = outputPixelSlice.SliceWithStride(channel * channelByteCount), 328 | }; 329 | 330 | dependency = mipmapJob.Schedule(outputPixelSlice.Length, minSize, dependency); 331 | } 332 | 333 | return dependency; 334 | } 335 | 336 | NativeSlice GetTextureData(Texture2D texture, int mipmapLevel) { 337 | #if UNITY_2020_1_OR_NEWER 338 | return texture.GetPixelData(mipmapLevel); 339 | #endif 340 | 341 | #pragma warning disable CS0162 342 | var rawTextureData = texture.GetRawTextureData(); 343 | var mipmapByteSize = 0; 344 | var mipmapOffset = 0; 345 | 346 | for (var i = 0; i <= mipmapLevel; i++) { 347 | mipmapOffset += mipmapByteSize; 348 | var mipmapSize = GetMipmapSize(i); 349 | mipmapByteSize = _bytesPerPixel * mipmapSize.x * mipmapSize.y; 350 | } 351 | 352 | return new NativeSlice(rawTextureData, mipmapOffset, mipmapByteSize); 353 | #pragma warning restore CS0162 354 | } 355 | } 356 | } 357 | -------------------------------------------------------------------------------- /Runtime/ImageImporter.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b352ab25b5b53304388ad118ff4a30e8 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Tests.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: bdf713343597b22499344366b1790541 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Tests/AsyncImageLoader.Tests.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "AsyncImageLoader.Tests", 3 | "references": [ 4 | "AsyncImageLoader.Runtime" 5 | ], 6 | "optionalUnityReferences": [ 7 | "TestAssemblies" 8 | ], 9 | "includePlatforms": [], 10 | "excludePlatforms": [] 11 | } -------------------------------------------------------------------------------- /Tests/AsyncImageLoader.Tests.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 316cdc28f768375408bda7d02ac9eb80 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Tests/TestMipmap.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using UnityEngine; 3 | 4 | public class TestMipmap { 5 | [Test] 6 | public void TestPNGImage() { 7 | var data = new byte[] { 8 | 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 9 | 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x08, 0x06, 0x00, 0x00, 0x00, 0x72, 0xB6, 0x0D, 10 | 0x24, 0x00, 0x00, 0x00, 0x1B, 0x49, 0x44, 0x41, 0x54, 0x08, 0xD7, 0x63, 0x60, 0xF8, 0xCF, 0xD0, 11 | 0xC0, 0xC0, 0xF0, 0xDF, 0x81, 0xE1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x33, 0x30, 0xEC, 0x07, 0x00, 12 | 0x43, 0x18, 0x08, 0x79, 0xEB, 0xE3, 0x55, 0xB4, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 13 | 0xAE, 0x42, 0x60, 0x82 14 | }; 15 | 16 | var texture = AsyncImageLoader.CreateFromImage(data); 17 | var mipmapLevel0 = texture.GetPixelData(0); 18 | var mipmapLevel1 = texture.GetPixelData(1); 19 | 20 | Assert.That(texture.format, Is.EqualTo(TextureFormat.RGBA32)); 21 | Assert.That(texture.width, Is.EqualTo(2)); 22 | Assert.That(texture.height, Is.EqualTo(2)); 23 | Assert.That(texture.mipmapCount, Is.EqualTo(2)); 24 | 25 | Assert.That(mipmapLevel0.Length, Is.EqualTo(4)); 26 | Assert.That(mipmapLevel0, Is.EquivalentTo(new int[] { 27 | unchecked((int)0xFF_FF_FF_FF), 28 | unchecked((int)0xBF_00_00_FF), 29 | unchecked((int)0x80_00_FF_00), 30 | unchecked((int)0x40_FF_00_00), 31 | })); 32 | 33 | Assert.That(mipmapLevel1.Length, Is.EqualTo(1)); 34 | Assert.That(mipmapLevel1, Is.EquivalentTo(new int[] { 35 | unchecked((int)0x9F_7F_7F_7F), 36 | })); 37 | } 38 | 39 | [Test] 40 | public void TestJPEGImage() { 41 | var data = new byte[] { 42 | 0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46, 0x00, 0x01, 0x01, 0x01, 0x01, 0x2C, 43 | 0x01, 0x2C, 0x00, 0x00, 0xFF, 0xDB, 0x00, 0x43, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 44 | 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 45 | 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 46 | 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 47 | 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0xFF, 0xDB, 0x00, 0x43, 0x01, 0x01, 0x01, 48 | 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 49 | 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 50 | 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 51 | 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0xFF, 0xC9, 52 | 0x00, 0x11, 0x08, 0x00, 0x02, 0x00, 0x02, 0x03, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 53 | 0x01, 0xFF, 0xCC, 0x00, 0x0A, 0x00, 0x10, 0x10, 0x05, 0x01, 0x10, 0x11, 0x05, 0xFF, 0xDA, 0x00, 54 | 0x0C, 0x03, 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, 0x00, 0x3F, 0x00, 0xFF, 0x00, 0xED, 0x83, 0x1F, 55 | 0x33, 0xBE, 0xF8, 0x94, 0x15, 0x70, 0xB2, 0xBB, 0x79, 0x32, 0xF4, 0x33, 0x76, 0x95, 0xE5, 0x72, 56 | 0x12, 0x80, 0xC7, 0xBB, 0xD4, 0xFB, 0xA3, 0x62, 0x4D, 0xF7, 0x60, 0x15, 0xAF, 0x52, 0x24, 0xCD, 57 | 0xD2, 0xBF, 0xC1, 0x1C, 0xF3, 0x9A, 0x08, 0x87, 0xEE, 0x86, 0xB1, 0xD7, 0xE2, 0xC8, 0x9D, 0x24, 58 | 0x55, 0xC1, 0x6D, 0xFF, 0x00, 0x4F, 0x49, 0x19, 0x7B, 0x42, 0xB3, 0x28, 0xB9, 0xB6, 0x05, 0x0E, 59 | 0x5F, 0xCF, 0x84, 0x92, 0x6C, 0xB5, 0x54, 0x89, 0xBF, 0xFE, 0xE0, 0x0F, 0x33, 0x5E, 0x63, 0xB3, 60 | 0x89, 0xEB, 0x11, 0x5A, 0x95, 0xD5, 0x09, 0x39, 0x2C, 0xB6, 0x04, 0x72, 0x3D, 0xF4, 0x9B, 0xB4, 61 | 0x58, 0x49, 0x64, 0x46, 0x1D, 0x65, 0x60, 0x14, 0xBD, 0x04, 0x5C, 0xA4, 0xF0, 0x82, 0xAF, 0x10, 62 | 0xC8, 0xFD, 0x60, 0x8E, 0x9C, 0x55, 0x8C, 0xA4, 0xFF, 0x00, 0x3C, 0xAE, 0x16, 0xBB, 0x53, 0xC2, 63 | 0x59, 0x82, 0xDD, 0xAD, 0x3F, 0x7D, 0x68, 0x04, 0x6D, 0x7E, 0xD3, 0x6B, 0xA9, 0xA2, 0x01, 0x7F, 64 | 0x6E, 0xA9, 0xBE, 0xE9, 0x3E, 0xB6, 0x24, 0x9E, 0xAB, 0xAD, 0xD8, 0xE7, 0x60, 0x64, 0x73, 0xC3, 65 | 0x8F, 0xDC, 0xDC, 0xC2, 0x2F, 0x35, 0xF1, 0xEB, 0x2F, 0xA2, 0x75, 0x61, 0x01, 0xDF, 0x11, 0xF4, 66 | 0xD0, 0xC1, 0x62, 0x5A, 0x28, 0x22, 0xEC, 0x05, 0x2A, 0x41, 0x14, 0xC2, 0xA0, 0x4D, 0xB7, 0x9F, 67 | 0x3C, 0xF5, 0xD8, 0x90, 0xF1, 0x6F, 0x87, 0x09, 0xBE, 0xA9, 0xFD, 0x44, 0xBD, 0x59, 0x0C, 0x8B, 68 | 0x03, 0xF8, 0xB1, 0x0E, 0x07, 0x73, 0x3C, 0x82, 0xDA, 0x09, 0x83, 0x35, 0x03, 0x90, 0x3E, 0xBC, 69 | 0x65, 0x4B, 0x44, 0xAF, 0x2C, 0x80, 0xFF, 0xD9 70 | }; 71 | 72 | var texture = AsyncImageLoader.CreateFromImage(data); 73 | var mipmapLevel0 = texture.GetPixelData(0); 74 | var mipmapLevel1 = texture.GetPixelData(1); 75 | 76 | Assert.That(texture.format, Is.EqualTo(TextureFormat.RGB24)); 77 | Assert.That(texture.width, Is.EqualTo(2)); 78 | Assert.That(texture.height, Is.EqualTo(2)); 79 | Assert.That(texture.mipmapCount, Is.EqualTo(2)); 80 | 81 | Assert.That(mipmapLevel0.Length, Is.EqualTo(3 * 4)); 82 | Assert.That(mipmapLevel0, Is.EquivalentTo(new byte[] { 83 | 0xFF, 0xFF, 0xFF, 84 | 0xFE, 0x00, 0x00, 85 | 0x00, 0xFF, 0x01, 86 | 0x00, 0x00, 0xFE 87 | })); 88 | 89 | Assert.That(mipmapLevel1.Length, Is.EqualTo(3)); 90 | Assert.That(mipmapLevel1, Is.EquivalentTo(new byte[] { 91 | 0x7F, 0x7F, 0x7F, 92 | })); 93 | } 94 | 95 | [Test] 96 | public void TestEXRImage() { 97 | var data = new byte[] { 98 | 0x76, 0x2F, 0x31, 0x01, 0x02, 0x00, 0x00, 0x00, 0x63, 0x68, 0x61, 0x6E, 0x6E, 0x65, 0x6C, 0x73, 99 | 0x00, 0x63, 0x68, 0x6C, 0x69, 0x73, 0x74, 0x00, 0x49, 0x00, 0x00, 0x00, 0x41, 0x00, 0x02, 0x00, 100 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x42, 0x00, 101 | 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 102 | 0x47, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 103 | 0x00, 0x00, 0x52, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 104 | 0x01, 0x00, 0x00, 0x00, 0x00, 0x63, 0x68, 0x72, 0x6F, 0x6D, 0x61, 0x74, 0x69, 0x63, 0x69, 0x74, 105 | 0x69, 0x65, 0x73, 0x00, 0x63, 0x68, 0x72, 0x6F, 0x6D, 0x61, 0x74, 0x69, 0x63, 0x69, 0x74, 0x69, 106 | 0x65, 0x73, 0x00, 0x20, 0x00, 0x00, 0x00, 0xF4, 0xD6, 0x23, 0x3F, 0x17, 0xF7, 0xA8, 0x3E, 0x19, 107 | 0x9A, 0x99, 0x3E, 0xD2, 0x99, 0x19, 0x3F, 0x23, 0x9A, 0x19, 0x3E, 0xA1, 0xBF, 0x75, 0x3D, 0x37, 108 | 0x1A, 0xA0, 0x3E, 0xB0, 0x72, 0xA8, 0x3E, 0x63, 0x6F, 0x6D, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 109 | 0x6F, 0x6E, 0x00, 0x63, 0x6F, 0x6D, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6F, 0x6E, 0x00, 0x01, 110 | 0x00, 0x00, 0x00, 0x03, 0x64, 0x61, 0x74, 0x61, 0x57, 0x69, 0x6E, 0x64, 0x6F, 0x77, 0x00, 0x62, 111 | 0x6F, 0x78, 0x32, 0x69, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 112 | 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64, 0x69, 0x73, 0x70, 0x6C, 0x61, 0x79, 113 | 0x57, 0x69, 0x6E, 0x64, 0x6F, 0x77, 0x00, 0x62, 0x6F, 0x78, 0x32, 0x69, 0x00, 0x10, 0x00, 0x00, 114 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 115 | 0x00, 0x6C, 0x69, 0x6E, 0x65, 0x4F, 0x72, 0x64, 0x65, 0x72, 0x00, 0x6C, 0x69, 0x6E, 0x65, 0x4F, 116 | 0x72, 0x64, 0x65, 0x72, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x70, 0x69, 0x78, 0x65, 0x6C, 0x41, 117 | 0x73, 0x70, 0x65, 0x63, 0x74, 0x52, 0x61, 0x74, 0x69, 0x6F, 0x00, 0x66, 0x6C, 0x6F, 0x61, 0x74, 118 | 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3F, 0x73, 0x63, 0x72, 0x65, 0x65, 0x6E, 0x57, 119 | 0x69, 0x6E, 0x64, 0x6F, 0x77, 0x43, 0x65, 0x6E, 0x74, 0x65, 0x72, 0x00, 0x76, 0x32, 0x66, 0x00, 120 | 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x73, 0x63, 0x72, 0x65, 121 | 0x65, 0x6E, 0x57, 0x69, 0x6E, 0x64, 0x6F, 0x77, 0x57, 0x69, 0x64, 0x74, 0x68, 0x00, 0x66, 0x6C, 122 | 0x6F, 0x61, 0x74, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3F, 0x00, 0x95, 0x01, 0x00, 123 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x00, 0x00, 0x78, 0x9C, 0x63, 124 | 0x68, 0x68, 0x60, 0x60, 0x80, 0x60, 0x08, 0x60, 0x60, 0x38, 0xE0, 0x00, 0x13, 0x01, 0xB1, 0xF7, 125 | 0x3B, 0xEE, 0x73, 0x6A, 0x68, 0xD8, 0xE7, 0xB4, 0xDF, 0x11, 0x22, 0xBF, 0xDF, 0x11, 0x04, 0x41, 126 | 0x34, 0x98, 0x0D, 0x00, 0xDC, 0x52, 0x19, 0xC0, 0x0B, 0x64, 0xFD, 0x4E, 0xE0, 0x01, 0x7B, 0x61, 127 | 0x1A, 0xC0 128 | }; 129 | 130 | var texture = AsyncImageLoader.CreateFromImage(data); 131 | var mipmapLevel0 = texture.GetPixelData(0); 132 | var mipmapLevel1 = texture.GetPixelData(1); 133 | 134 | Assert.That(texture.format, Is.EqualTo(TextureFormat.RGBAFloat)); 135 | Assert.That(texture.width, Is.EqualTo(2)); 136 | Assert.That(texture.height, Is.EqualTo(2)); 137 | Assert.That(texture.mipmapCount, Is.EqualTo(2)); 138 | 139 | Assert.That(mipmapLevel0.Length, Is.EqualTo(4 * 4)); 140 | Assert.That(mipmapLevel0, Is.EquivalentTo(new float[] { 141 | 1.0f, 1.0f, 1.0f, 1.0f, 142 | .75f, .00f, .00f, .75f, 143 | .50f, .00f, .50f, .00f, 144 | .25f, .25f, .00f, .00f, 145 | })); 146 | 147 | Assert.That(mipmapLevel1.Length, Is.EqualTo(4)); 148 | Assert.That(mipmapLevel1, Is.EquivalentTo(new float[] { 149 | .625f, .3125f, .375f, .4375f, 150 | })); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /Tests/TestMipmap.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e07eea1cdce31d24bafe21cecae2c7a7 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.looooong.asyncimageloader", 3 | "version": "0.1.3", 4 | "displayName": "Asynchronous Image Loader", 5 | "unity": "2019.1", 6 | "keywords": [ 7 | "image", 8 | "texture", 9 | "async", 10 | "asynchronous", 11 | "loader", 12 | "importer" 13 | ], 14 | "author": { 15 | "name": "Nguyễn Đức Long", 16 | "email": "duclong120995@gmail.com" 17 | }, 18 | "description": "An asynchronous version of ImageConversion.LoadImage.", 19 | "dependencies": { 20 | "com.unity.burst": "1.5.4", 21 | "com.unity.mathematics": "1.2.1" 22 | } 23 | } -------------------------------------------------------------------------------- /package.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 89dbd45cab1d7154cbda757474e29d80 3 | PackageManifestImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | --------------------------------------------------------------------------------