├── .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 |
--------------------------------------------------------------------------------