using System; using System.Collections; using System.Collections.Generic; using System.Text; using RSG; using Unity.UIWidgets.async; using Unity.UIWidgets.foundation; using Unity.UIWidgets.ui; using UnityEngine; using UnityEngine.Networking; namespace Unity.UIWidgets.painting { public class ImageConfiguration : IEquatable { public ImageConfiguration( AssetBundle bundle = null, double? devicePixelRatio = null, Locale locale = null, Size size = null, RuntimePlatform? platform = null ) { this.bundle = bundle; this.devicePixelRatio = devicePixelRatio; this.locale = locale; this.size = size; this.platform = platform; } public ImageConfiguration copyWith( AssetBundle bundle = null, double? devicePixelRatio = null, Locale locale = null, Size size = null, RuntimePlatform? platform = null ) { return new ImageConfiguration( bundle: bundle ? bundle : this.bundle, devicePixelRatio: devicePixelRatio ?? this.devicePixelRatio, locale: locale ?? this.locale, size: size ?? this.size, platform: platform ?? this.platform ); } public readonly AssetBundle bundle; public readonly double? devicePixelRatio; public readonly Locale locale; public readonly Size size; public readonly RuntimePlatform? platform; public static readonly ImageConfiguration empty = new ImageConfiguration(); public bool Equals(ImageConfiguration other) { if (ReferenceEquals(null, other)) { return false; } if (ReferenceEquals(this, other)) { return true; } return Equals(this.bundle, other.bundle) && this.devicePixelRatio.Equals(other.devicePixelRatio) && Equals(this.locale, other.locale) && Equals(this.size, other.size) && this.platform == other.platform; } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } if (ReferenceEquals(this, obj)) { return true; } if (obj.GetType() != this.GetType()) { return false; } return this.Equals((ImageConfiguration) obj); } public override int GetHashCode() { unchecked { var hashCode = (this.bundle != null ? this.bundle.GetHashCode() : 0); hashCode = (hashCode * 397) ^ this.devicePixelRatio.GetHashCode(); hashCode = (hashCode * 397) ^ (this.locale != null ? this.locale.GetHashCode() : 0); hashCode = (hashCode * 397) ^ (this.size != null ? this.size.GetHashCode() : 0); hashCode = (hashCode * 397) ^ this.platform.GetHashCode(); return hashCode; } } public static bool operator ==(ImageConfiguration left, ImageConfiguration right) { return Equals(left, right); } public static bool operator !=(ImageConfiguration left, ImageConfiguration right) { return !Equals(left, right); } public override string ToString() { var result = new StringBuilder(); result.Append("ImageConfiguration("); bool hasArguments = false; if (this.bundle != null) { if (hasArguments) { result.Append(", "); } result.Append($"bundle: {this.bundle}"); hasArguments = true; } if (this.devicePixelRatio != null) { if (hasArguments) { result.Append(", "); } result.Append($"devicePixelRatio: {this.devicePixelRatio:F1}"); hasArguments = true; } if (this.locale != null) { if (hasArguments) { result.Append(", "); } result.Append($"locale: {this.locale}"); hasArguments = true; } if (this.size != null) { if (hasArguments) { result.Append(", "); } result.Append($"size: {this.size}"); hasArguments = true; } if (this.platform != null) { if (hasArguments) { result.Append(", "); } result.Append($"platform: {this.platform}"); hasArguments = true; } result.Append(")"); return result.ToString(); } } public abstract class ImageProvider { public abstract ImageStream resolve(ImageConfiguration configuration); } public abstract class ImageProvider : ImageProvider { public override ImageStream resolve(ImageConfiguration configuration) { D.assert(configuration != null); ImageStream stream = new ImageStream(); T obtainedKey = default; this.obtainKey(configuration).Then(key => { obtainedKey = key; stream.setCompleter(PaintingBinding.instance.imageCache.putIfAbsent(key, () => this.load(key))); }).Catch(ex => { UIWidgetsError.reportError(new UIWidgetsErrorDetails( exception: ex, library: "services library", context: "while resolving an image", silent: true, informationCollector: information => { information.AppendLine($"Image provider: {this}"); information.AppendLine($"Image configuration: {configuration}"); if (obtainedKey != null) { information.AppendLine($"Image key: {obtainedKey}"); } } )); }); return stream; } public IPromise evict(ImageCache cache = null, ImageConfiguration configuration = null) { configuration = configuration ?? ImageConfiguration.empty; cache = cache ?? PaintingBinding.instance.imageCache; return this.obtainKey(configuration).Then(key => cache.evict(key)); } protected abstract ImageStreamCompleter load(T key); protected abstract IPromise obtainKey(ImageConfiguration configuration); } public class AssetBundleImageKey : IEquatable { public AssetBundleImageKey( AssetBundle bundle, string name, double scale ) { D.assert(name != null); D.assert(scale >= 0.0); this.bundle = bundle; this.name = name; this.scale = scale; } public readonly AssetBundle bundle; public readonly string name; public readonly double scale; public bool Equals(AssetBundleImageKey other) { if (ReferenceEquals(null, other)) { return false; } if (ReferenceEquals(this, other)) { return true; } return Equals(this.bundle, other.bundle) && string.Equals(this.name, other.name) && this.scale.Equals(other.scale); } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } if (ReferenceEquals(this, obj)) { return true; } if (obj.GetType() != this.GetType()) { return false; } return this.Equals((AssetBundleImageKey) obj); } public override int GetHashCode() { unchecked { var hashCode = (this.bundle != null ? this.bundle.GetHashCode() : 0); hashCode = (hashCode * 397) ^ (this.name != null ? this.name.GetHashCode() : 0); hashCode = (hashCode * 397) ^ this.scale.GetHashCode(); return hashCode; } } public static bool operator ==(AssetBundleImageKey left, AssetBundleImageKey right) { return Equals(left, right); } public static bool operator !=(AssetBundleImageKey left, AssetBundleImageKey right) { return !Equals(left, right); } public override string ToString() { return $"{this.GetType()}(bundle: {this.bundle}, name: \"{this.name}\", scale: {this.scale})"; } } public abstract class AssetBundleImageProvider : ImageProvider { protected AssetBundleImageProvider() { } protected override ImageStreamCompleter load(AssetBundleImageKey key) { return new MultiFrameImageStreamCompleter( codec: this._loadAsync(key), scale: key.scale, informationCollector: information => { information.AppendLine($"Image provider: {this}"); information.Append($"Image key: {key}"); } ); } IPromise _loadAsync(AssetBundleImageKey key) { var coroutine = Window.instance.startCoroutine(this._loadAssetAsync(key)); return coroutine.promise.Then(result => { if (result == null) { if (key.bundle == null) { throw new Exception($"Unable to find asset \"{key.name}\" from Resources folder"); } throw new Exception($"Unable to find asset \"{key.name}\" from asset bundle \"{key.bundle}\""); } if (result is Texture2D texture) { return CodecUtils.getCodec(new Image(texture, isAsset: true, bundle: key.bundle)); } else if (result is TextAsset text) { var bytes = text.bytes; if (key.bundle == null) { Resources.UnloadAsset(text); } else { key.bundle.Unload(text); } return CodecUtils.getCodec(bytes); } else { throw new Exception($"Unknown type for asset \"{key.name}\": \"{result.GetType()}\""); } }); } IEnumerator _loadAssetAsync(AssetBundleImageKey key) { if (key.bundle == null) { ResourceRequest request = Resources.LoadAsync(key.name); yield return request; yield return request.asset; } else { AssetBundleRequest request = key.bundle.LoadAssetAsync(key.name); yield return request; yield return request.asset; } } } public class NetworkImage : ImageProvider, IEquatable { public NetworkImage(string url, double scale = 1.0, IDictionary headers = null) { D.assert(url != null); this.url = url; this.scale = scale; this.headers = headers; } public readonly string url; public readonly double scale; public readonly IDictionary headers; protected override IPromise obtainKey(ImageConfiguration configuration) { return Promise.Resolved(this); } protected override ImageStreamCompleter load(NetworkImage key) { return new MultiFrameImageStreamCompleter( codec: this._loadAsync(key), scale: key.scale, informationCollector: information => { information.AppendLine($"Image provider: {this}"); information.Append($"Image key: {key}"); } ); } IPromise _loadAsync(NetworkImage key) { var coroutine = Window.instance.startCoroutine(this._loadBytes(key)); return coroutine.promise.Then(obj => { if (obj is byte[] bytes) { return CodecUtils.getCodec(bytes); } return CodecUtils.getCodec(new Image((Texture2D) obj)); }); } IEnumerator _loadBytes(NetworkImage key) { D.assert(key == this); var uri = new Uri(key.url); if (uri.LocalPath.EndsWith(".gif")) { using (var www = UnityWebRequest.Get(uri)) { if (this.headers != null) { foreach (var header in this.headers) { www.SetRequestHeader(header.Key, header.Value); } } yield return www.SendWebRequest(); if (www.isNetworkError || www.isHttpError) { throw new Exception($"Failed to load from url \"{uri}\": {www.error}"); } var data = www.downloadHandler.data; yield return data; } yield break; } using (var www = UnityWebRequestTexture.GetTexture(uri)) { if (this.headers != null) { foreach (var header in this.headers) { www.SetRequestHeader(header.Key, header.Value); } } yield return www.SendWebRequest(); if (www.isNetworkError || www.isHttpError) { throw new Exception($"Failed to load from url \"{uri}\": {www.error}"); } var data = ((DownloadHandlerTexture) www.downloadHandler).texture; yield return data; } } public bool Equals(NetworkImage other) { if (ReferenceEquals(null, other)) { return false; } if (ReferenceEquals(this, other)) { return true; } return string.Equals(this.url, other.url) && this.scale.Equals(other.scale); } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } if (ReferenceEquals(this, obj)) { return true; } if (obj.GetType() != this.GetType()) { return false; } return this.Equals((NetworkImage) obj); } public override int GetHashCode() { unchecked { return ((this.url != null ? this.url.GetHashCode() : 0) * 397) ^ this.scale.GetHashCode(); } } public static bool operator ==(NetworkImage left, NetworkImage right) { return Equals(left, right); } public static bool operator !=(NetworkImage left, NetworkImage right) { return !Equals(left, right); } public override string ToString() { return $"runtimeType(\"{this.url}\", scale: {this.scale})"; } } public class FileImage : ImageProvider, IEquatable { public FileImage(string file, double scale = 1.0) { D.assert(file != null); this.file = file; this.scale = scale; } public readonly string file; public readonly double scale; protected override IPromise obtainKey(ImageConfiguration configuration) { return Promise.Resolved(this); } protected override ImageStreamCompleter load(FileImage key) { return new MultiFrameImageStreamCompleter(this._loadAsync(key), scale: key.scale, informationCollector: information => { information.AppendLine($"Path: {this.file}"); }); } IPromise _loadAsync(FileImage key) { var coroutine = Window.instance.startCoroutine(this._loadBytes(key)); return coroutine.promise.Then(obj => { if (obj is byte[] bytes) { return CodecUtils.getCodec(bytes); } return CodecUtils.getCodec(new Image((Texture2D) obj)); }); } IEnumerator _loadBytes(FileImage key) { D.assert(key == this); var uri = "file://" + key.file; if (uri.EndsWith(".gif")) { using (var www = UnityWebRequest.Get(uri)) { yield return www.SendWebRequest(); if (www.isNetworkError || www.isHttpError) { throw new Exception($"Failed to get file \"{uri}\": {www.error}"); } var data = www.downloadHandler.data; yield return data; } yield break; } using (var www = UnityWebRequestTexture.GetTexture(uri)) { yield return www.SendWebRequest(); if (www.isNetworkError || www.isHttpError) { throw new Exception($"Failed to get file \"{uri}\": {www.error}"); } var data = ((DownloadHandlerTexture) www.downloadHandler).texture; yield return data; } } public bool Equals(FileImage other) { if (ReferenceEquals(null, other)) { return false; } if (ReferenceEquals(this, other)) { return true; } return string.Equals(this.file, other.file) && this.scale.Equals(other.scale); } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } if (ReferenceEquals(this, obj)) { return true; } if (obj.GetType() != this.GetType()) { return false; } return this.Equals((FileImage) obj); } public override int GetHashCode() { unchecked { return ((this.file != null ? this.file.GetHashCode() : 0) * 397) ^ this.scale.GetHashCode(); } } public static bool operator ==(FileImage left, FileImage right) { return Equals(left, right); } public static bool operator !=(FileImage left, FileImage right) { return !Equals(left, right); } public override string ToString() { return $"{this.GetType()}(\"{this.file}\", scale: {this.scale})"; } } public class MemoryImage : ImageProvider, IEquatable { public MemoryImage(byte[] bytes, double scale = 1.0) { D.assert(bytes != null); this.bytes = bytes; this.scale = scale; } public readonly byte[] bytes; public readonly double scale; protected override IPromise obtainKey(ImageConfiguration configuration) { return Promise.Resolved(this); } protected override ImageStreamCompleter load(MemoryImage key) { return new MultiFrameImageStreamCompleter( this._loadAsync(key), scale: key.scale); } IPromise _loadAsync(MemoryImage key) { D.assert(key == this); return CodecUtils.getCodec(this.bytes); } public bool Equals(MemoryImage other) { if (ReferenceEquals(null, other)) { return false; } if (ReferenceEquals(this, other)) { return true; } return Equals(this.bytes, other.bytes) && this.scale.Equals(other.scale); } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } if (ReferenceEquals(this, obj)) { return true; } if (obj.GetType() != this.GetType()) { return false; } return this.Equals((MemoryImage) obj); } public override int GetHashCode() { unchecked { return ((this.bytes != null ? this.bytes.GetHashCode() : 0) * 397) ^ this.scale.GetHashCode(); } } public static bool operator ==(MemoryImage left, MemoryImage right) { return Equals(left, right); } public static bool operator !=(MemoryImage left, MemoryImage right) { return !Equals(left, right); } public override string ToString() { return $"{this.GetType()}({Diagnostics.describeIdentity(this.bytes)}), scale: {this.scale}"; } } public class ExactAssetImage : AssetBundleImageProvider, IEquatable { public ExactAssetImage( string assetName, double scale = 1.0, AssetBundle bundle = null ) { D.assert(assetName != null); this.assetName = assetName; this.scale = scale; this.bundle = bundle; } public readonly string assetName; public readonly double scale; public readonly AssetBundle bundle; protected override IPromise obtainKey(ImageConfiguration configuration) { return Promise.Resolved(new AssetBundleImageKey( bundle: this.bundle ? this.bundle : configuration.bundle, name: this.assetName, scale: this.scale )); } public bool Equals(ExactAssetImage other) { if (ReferenceEquals(null, other)) { return false; } if (ReferenceEquals(this, other)) { return true; } return string.Equals(this.assetName, other.assetName) && this.scale.Equals(other.scale) && Equals(this.bundle, other.bundle); } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } if (ReferenceEquals(this, obj)) { return true; } if (obj.GetType() != this.GetType()) { return false; } return this.Equals((ExactAssetImage) obj); } public override int GetHashCode() { unchecked { var hashCode = (this.assetName != null ? this.assetName.GetHashCode() : 0); hashCode = (hashCode * 397) ^ this.scale.GetHashCode(); hashCode = (hashCode * 397) ^ (this.bundle != null ? this.bundle.GetHashCode() : 0); return hashCode; } } public static bool operator ==(ExactAssetImage left, ExactAssetImage right) { return Equals(left, right); } public static bool operator !=(ExactAssetImage left, ExactAssetImage right) { return !Equals(left, right); } public override string ToString() { return $"{this.GetType()}(name: \"{this.assetName}\", scale: {this.scale}, bundle: {this.bundle})"; } } }