您最多选择25个主题
主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
1086 行
36 KiB
1086 行
36 KiB
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Text;
|
|
using Unity.UIWidgets.async2;
|
|
using Unity.UIWidgets.engine2;
|
|
using Unity.UIWidgets.foundation;
|
|
using Unity.UIWidgets.ui;
|
|
using UnityEngine;
|
|
using UnityEngine.Networking;
|
|
using Codec = Unity.UIWidgets.ui.Codec;
|
|
using Locale = Unity.UIWidgets.ui.Locale;
|
|
using Object = UnityEngine.Object;
|
|
using Path = System.IO.Path;
|
|
using TextDirection = Unity.UIWidgets.ui.TextDirection;
|
|
|
|
namespace Unity.UIWidgets.painting {
|
|
public static partial class painting_ {
|
|
internal delegate void _KeyAndErrorHandlerCallback<T>(T key, Action<Exception> handleError);
|
|
|
|
internal delegate Future _AsyncKeyErrorHandler<T>(T key, Exception exception);
|
|
}
|
|
|
|
public class ImageConfiguration : IEquatable<ImageConfiguration> {
|
|
public ImageConfiguration(
|
|
AssetBundle bundle = null,
|
|
float? 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,
|
|
float? 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 float? devicePixelRatio;
|
|
|
|
public readonly Locale locale;
|
|
|
|
public readonly TextDirection textDirection;
|
|
|
|
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(bundle, other.bundle) && devicePixelRatio.Equals(other.devicePixelRatio) &&
|
|
Equals(locale, other.locale) && Equals(size, other.size) &&
|
|
platform == other.platform;
|
|
}
|
|
|
|
public override bool Equals(object obj) {
|
|
if (ReferenceEquals(null, obj)) {
|
|
return false;
|
|
}
|
|
|
|
if (ReferenceEquals(this, obj)) {
|
|
return true;
|
|
}
|
|
|
|
if (obj.GetType() != GetType()) {
|
|
return false;
|
|
}
|
|
|
|
return Equals((ImageConfiguration) obj);
|
|
}
|
|
|
|
public override int GetHashCode() {
|
|
unchecked {
|
|
var hashCode = (bundle != null ? bundle.GetHashCode() : 0);
|
|
hashCode = (hashCode * 397) ^ devicePixelRatio.GetHashCode();
|
|
hashCode = (hashCode * 397) ^ (locale != null ? locale.GetHashCode() : 0);
|
|
hashCode = (hashCode * 397) ^ (size != null ? size.GetHashCode() : 0);
|
|
hashCode = (hashCode * 397) ^ 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 (bundle != null) {
|
|
if (hasArguments) {
|
|
result.Append(", ");
|
|
}
|
|
|
|
result.Append($"bundle: {bundle}");
|
|
hasArguments = true;
|
|
}
|
|
|
|
if (devicePixelRatio != null) {
|
|
if (hasArguments) {
|
|
result.Append(", ");
|
|
}
|
|
|
|
result.Append($"devicePixelRatio: {devicePixelRatio:F1}");
|
|
hasArguments = true;
|
|
}
|
|
|
|
if (locale != null) {
|
|
if (hasArguments) {
|
|
result.Append(", ");
|
|
}
|
|
|
|
result.Append($"locale: {locale}");
|
|
hasArguments = true;
|
|
}
|
|
|
|
if (size != null) {
|
|
if (hasArguments) {
|
|
result.Append(", ");
|
|
}
|
|
|
|
result.Append($"size: {size}");
|
|
hasArguments = true;
|
|
}
|
|
|
|
if (platform != null) {
|
|
if (hasArguments) {
|
|
result.Append(", ");
|
|
}
|
|
|
|
result.Append($"platform: {platform}");
|
|
hasArguments = true;
|
|
}
|
|
|
|
result.Append(")");
|
|
return result.ToString();
|
|
}
|
|
}
|
|
|
|
public delegate Future<Codec> DecoderCallback(byte[] bytes, int? cacheWidth = 0, int? cacheHeight = 0);
|
|
|
|
public abstract class ImageProvider {
|
|
public abstract ImageStream resolve(ImageConfiguration configuration);
|
|
|
|
public static bool operator ==(ImageProvider left, ImageProvider right) {
|
|
return Equals(left, right);
|
|
}
|
|
|
|
public static bool operator !=(ImageProvider left, ImageProvider right) {
|
|
return !Equals(left, right);
|
|
}
|
|
}
|
|
|
|
public abstract class ImageProvider<T> : ImageProvider {
|
|
public override ImageStream resolve(ImageConfiguration configuration) {
|
|
D.assert(configuration != null);
|
|
|
|
ImageStream stream = new ImageStream();
|
|
_createErrorHandlerAndKey(
|
|
configuration,
|
|
(T successKey, Action<Exception> errorHandler) => {
|
|
resolveStreamForKey(configuration, stream, successKey, (Exception e) => errorHandler(e));
|
|
},
|
|
(T key, Exception exception) => {
|
|
Timer.run(() => {
|
|
_ErrorImageCompleter imageCompleter = new _ErrorImageCompleter();
|
|
stream.setCompleter(imageCompleter);
|
|
InformationCollector collector = null;
|
|
D.assert(() => {
|
|
IEnumerable<DiagnosticsNode> infoCollector() {
|
|
yield return new DiagnosticsProperty<ImageProvider>("Image provider", this);
|
|
yield return new DiagnosticsProperty<ImageConfiguration>("Image configuration",
|
|
configuration);
|
|
yield return new DiagnosticsProperty<T>("Image key", key, defaultValue: null);
|
|
}
|
|
|
|
collector = infoCollector;
|
|
return true;
|
|
});
|
|
imageCompleter.setError(
|
|
exception: exception,
|
|
stack: exception.StackTrace,
|
|
context: new ErrorDescription("while resolving an image"),
|
|
silent: true, // could be a network error or whatnot
|
|
informationCollector: collector
|
|
);
|
|
return null;
|
|
});
|
|
return null;
|
|
}
|
|
);
|
|
|
|
return stream;
|
|
}
|
|
|
|
public ImageStream createStream(ImageConfiguration configuration) {
|
|
return new ImageStream();
|
|
}
|
|
|
|
public virtual void resolveStreamForKey(ImageConfiguration configuration, ImageStream stream, T key,
|
|
ImageErrorListener handleError) {
|
|
if (stream.completer != null) {
|
|
ImageStreamCompleter completerEdge = PaintingBinding.instance.imageCache.putIfAbsent(
|
|
key,
|
|
() => stream.completer,
|
|
onError: handleError
|
|
);
|
|
D.assert(Equals(completerEdge, stream.completer));
|
|
return;
|
|
}
|
|
|
|
ImageStreamCompleter completer = PaintingBinding.instance.imageCache.putIfAbsent(
|
|
key,
|
|
() => load(key, ui_.instantiateImageCodec),
|
|
onError: handleError
|
|
);
|
|
if (completer != null) {
|
|
stream.setCompleter(completer);
|
|
}
|
|
}
|
|
|
|
public Future<bool> evict(ImageCache cache = null, ImageConfiguration configuration = null) {
|
|
configuration = configuration ?? ImageConfiguration.empty;
|
|
cache = cache ?? PaintingBinding.instance.imageCache;
|
|
|
|
return obtainKey(configuration).then(key => cache.evict(key)).to<bool>();
|
|
}
|
|
|
|
public abstract ImageStreamCompleter load(T assetBundleImageKey, DecoderCallback decode);
|
|
|
|
public abstract Future<T> obtainKey(ImageConfiguration configuration);
|
|
|
|
Future<ImageCacheStatus> obtainCacheStatus(
|
|
ImageConfiguration configuration,
|
|
ImageErrorListener handleError = null
|
|
) {
|
|
D.assert(configuration != null);
|
|
Completer completer = Completer.create();
|
|
_createErrorHandlerAndKey(
|
|
configuration,
|
|
(T key, Action<Exception> innerHandleError) => {
|
|
completer.complete(FutureOr.value(PaintingBinding.instance.imageCache.statusForKey(key)));
|
|
},
|
|
(T key, Exception exception) => {
|
|
if (handleError != null) {
|
|
handleError(exception);
|
|
}
|
|
else {
|
|
InformationCollector collector = null;
|
|
D.assert(() => {
|
|
IEnumerable<DiagnosticsNode> infoCollector() {
|
|
yield return new DiagnosticsProperty<ImageProvider>("Image provider", this);
|
|
yield return new DiagnosticsProperty<ImageConfiguration>("Image configuration",
|
|
configuration);
|
|
yield return new DiagnosticsProperty<T>("Image key", key, defaultValue: null);
|
|
}
|
|
|
|
collector = infoCollector;
|
|
return true;
|
|
});
|
|
UIWidgetsError.onError(new UIWidgetsErrorDetails(
|
|
context: new ErrorDescription("while checking the cache location of an image"),
|
|
informationCollector: collector,
|
|
exception: exception
|
|
));
|
|
completer.complete();
|
|
}
|
|
|
|
return Future.value();
|
|
}
|
|
);
|
|
return completer.future.to<ImageCacheStatus>();
|
|
}
|
|
|
|
private void _createErrorHandlerAndKey(
|
|
ImageConfiguration configuration,
|
|
painting_._KeyAndErrorHandlerCallback<T> successCallback,
|
|
painting_._AsyncKeyErrorHandler<T> errorCallback
|
|
) {
|
|
T obtainedKey = default;
|
|
bool didError = false;
|
|
|
|
Action<Exception> handleError = (Exception exception) => {
|
|
if (didError) {
|
|
return;
|
|
}
|
|
|
|
if (!didError) {
|
|
errorCallback(obtainedKey, exception);
|
|
}
|
|
|
|
didError = true;
|
|
};
|
|
|
|
Zone dangerZone = Zone.current.fork(
|
|
specification: new ZoneSpecification(
|
|
handleUncaughtError: (Zone self, ZoneDelegate parent, Zone zone, Exception error) => {
|
|
handleError(error);
|
|
}
|
|
)
|
|
);
|
|
dangerZone.runGuarded(() => {
|
|
Future<T> key;
|
|
try {
|
|
key = obtainKey(configuration);
|
|
}
|
|
catch (Exception error) {
|
|
handleError(error);
|
|
return null;
|
|
}
|
|
|
|
key.then_((T reusltKey) => {
|
|
obtainedKey = reusltKey;
|
|
try {
|
|
successCallback(reusltKey, handleError);
|
|
}
|
|
catch (Exception error) {
|
|
handleError(error);
|
|
}
|
|
}).catchError(handleError);
|
|
return null;
|
|
});
|
|
}
|
|
}
|
|
|
|
public class AssetBundleImageKey : IEquatable<AssetBundleImageKey> {
|
|
public AssetBundleImageKey(
|
|
AssetBundle bundle,
|
|
string name,
|
|
float 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 float scale;
|
|
|
|
public bool Equals(AssetBundleImageKey other) {
|
|
if (ReferenceEquals(null, other)) {
|
|
return false;
|
|
}
|
|
|
|
if (ReferenceEquals(this, other)) {
|
|
return true;
|
|
}
|
|
|
|
return Equals(bundle, other.bundle) && string.Equals(name, other.name) &&
|
|
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() != GetType()) {
|
|
return false;
|
|
}
|
|
|
|
return Equals((AssetBundleImageKey) obj);
|
|
}
|
|
|
|
public override int GetHashCode() {
|
|
unchecked {
|
|
var hashCode = (bundle != null ? bundle.GetHashCode() : 0);
|
|
hashCode = (hashCode * 397) ^ (name != null ? name.GetHashCode() : 0);
|
|
hashCode = (hashCode * 397) ^ 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 $"{GetType()}(bundle: {bundle}, name: \"{name}\", scale: {scale})";
|
|
}
|
|
}
|
|
|
|
public abstract class AssetBundleImageProvider : ImageProvider<AssetBundleImageKey> {
|
|
protected AssetBundleImageProvider() {
|
|
}
|
|
|
|
public override ImageStreamCompleter load(AssetBundleImageKey key, DecoderCallback decode) {
|
|
IEnumerable<DiagnosticsNode> infoCollector() {
|
|
yield return new DiagnosticsProperty<ImageProvider>("Image provider", this);
|
|
yield return new DiagnosticsProperty<AssetBundleImageKey>("Image key", key);
|
|
}
|
|
|
|
return new MultiFrameImageStreamCompleter(
|
|
codec: _loadAsync(key, decode),
|
|
scale: key.scale,
|
|
informationCollector: infoCollector
|
|
);
|
|
}
|
|
|
|
Future<Codec> _loadAsync(AssetBundleImageKey key, DecoderCallback decode) {
|
|
Object data;
|
|
// Hot reload/restart could change whether an asset bundle or key in a
|
|
// bundle are available, or if it is a network backed bundle.
|
|
try {
|
|
data = key.bundle.LoadAsset(key.name);
|
|
}
|
|
catch (Exception e) {
|
|
PaintingBinding.instance.imageCache.evict(key);
|
|
throw e;
|
|
}
|
|
|
|
if (data != null && data is Texture2D textureData) {
|
|
return decode(textureData.EncodeToPNG());
|
|
}
|
|
else {
|
|
PaintingBinding.instance.imageCache.evict(key);
|
|
throw new Exception("Unable to read data");
|
|
}
|
|
}
|
|
|
|
IEnumerator _loadAssetAsync(AssetBundleImageKey key) {
|
|
if (key.bundle == null) {
|
|
ResourceRequest request = Resources.LoadAsync(key.name);
|
|
if (request.asset) {
|
|
yield return request.asset;
|
|
}
|
|
else {
|
|
yield return request;
|
|
yield return request.asset;
|
|
}
|
|
}
|
|
else {
|
|
AssetBundleRequest request = key.bundle.LoadAssetAsync(key.name);
|
|
if (request.asset) {
|
|
yield return request.asset;
|
|
}
|
|
else {
|
|
yield return request.asset;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
internal class _SizeAwareCacheKey : IEquatable<_SizeAwareCacheKey> {
|
|
internal _SizeAwareCacheKey(object providerCacheKey, int width, int height) {
|
|
this.providerCacheKey = providerCacheKey;
|
|
this.width = width;
|
|
this.height = height;
|
|
}
|
|
|
|
public readonly object providerCacheKey;
|
|
|
|
public readonly int width;
|
|
|
|
public readonly int height;
|
|
|
|
public static bool operator ==(_SizeAwareCacheKey left, object right) {
|
|
return Equals(left, right);
|
|
}
|
|
|
|
public static bool operator !=(_SizeAwareCacheKey left, object right) {
|
|
return !Equals(left, right);
|
|
}
|
|
|
|
public bool Equals(_SizeAwareCacheKey other) {
|
|
if (ReferenceEquals(null, other)) {
|
|
return false;
|
|
}
|
|
|
|
if (ReferenceEquals(this, other)) {
|
|
return true;
|
|
}
|
|
|
|
return Equals(providerCacheKey, other.providerCacheKey) && width == other.width && height == other.height;
|
|
}
|
|
|
|
public override bool Equals(object obj) {
|
|
if (ReferenceEquals(null, obj)) {
|
|
return false;
|
|
}
|
|
|
|
if (ReferenceEquals(this, obj)) {
|
|
return true;
|
|
}
|
|
|
|
if (obj.GetType() != GetType()) {
|
|
return false;
|
|
}
|
|
|
|
return Equals((_SizeAwareCacheKey) obj);
|
|
}
|
|
|
|
public override int GetHashCode() {
|
|
unchecked {
|
|
var hashCode = (providerCacheKey != null ? providerCacheKey.GetHashCode() : 0);
|
|
hashCode = (hashCode * 397) ^ width;
|
|
hashCode = (hashCode * 397) ^ height;
|
|
return hashCode;
|
|
}
|
|
}
|
|
}
|
|
|
|
internal class ResizeImage : ImageProvider<_SizeAwareCacheKey> {
|
|
public ResizeImage(
|
|
ImageProvider<object> imageProvider,
|
|
int width = 0,
|
|
int height = 0
|
|
) {
|
|
this.imageProvider = imageProvider;
|
|
this.width = width;
|
|
this.height = height;
|
|
}
|
|
|
|
public readonly ImageProvider<object> imageProvider;
|
|
|
|
public readonly int width;
|
|
|
|
public readonly int height;
|
|
|
|
public static ImageProvider resizeIfNeeded(int? cacheWidth, int? cacheHeight, ImageProvider provider) {
|
|
if (cacheWidth != null || cacheHeight != null) {
|
|
return new ResizeImage((ImageProvider<object>) provider, width: cacheWidth.Value,
|
|
height: cacheHeight.Value);
|
|
}
|
|
|
|
return provider;
|
|
}
|
|
|
|
public override ImageStreamCompleter load(_SizeAwareCacheKey assetBundleImageKey, DecoderCallback decode) {
|
|
Future<Codec> decodeResize(byte[] bytes, int? cacheWidth = 0, int? cacheHeight = 0) {
|
|
D.assert(
|
|
cacheWidth == null && cacheHeight == null,
|
|
() =>
|
|
"ResizeImage cannot be composed with another ImageProvider that applies cacheWidth or cacheHeight."
|
|
);
|
|
return decode(bytes, cacheWidth: width, cacheHeight: height);
|
|
}
|
|
|
|
return imageProvider.load(assetBundleImageKey.providerCacheKey, decodeResize);
|
|
}
|
|
|
|
public override Future<_SizeAwareCacheKey> obtainKey(ImageConfiguration configuration) {
|
|
Completer completer = null;
|
|
SynchronousFuture<_SizeAwareCacheKey> result = null;
|
|
imageProvider.obtainKey(configuration).then((object key) => {
|
|
// TODO: completer is always null?
|
|
if (completer == null) {
|
|
result = new SynchronousFuture<_SizeAwareCacheKey>(new _SizeAwareCacheKey(key, width, height));
|
|
}
|
|
else {
|
|
completer.complete(FutureOr.value(new _SizeAwareCacheKey(key, width, height)));
|
|
}
|
|
});
|
|
if (result != null) {
|
|
return result;
|
|
}
|
|
|
|
completer = Completer.create();
|
|
return completer.future.to<_SizeAwareCacheKey>();
|
|
}
|
|
}
|
|
|
|
public class NetworkImage : ImageProvider<NetworkImage>, IEquatable<NetworkImage> {
|
|
public NetworkImage(string url,
|
|
float scale = 1.0f,
|
|
IDictionary<string, string> headers = null) {
|
|
D.assert(url != null);
|
|
this.url = url;
|
|
this.scale = scale;
|
|
this.headers = headers;
|
|
}
|
|
|
|
public readonly string url;
|
|
|
|
public readonly float scale;
|
|
|
|
public readonly IDictionary<string, string> headers;
|
|
|
|
public override Future<NetworkImage> obtainKey(ImageConfiguration configuration) {
|
|
return new SynchronousFuture<NetworkImage>(this);
|
|
}
|
|
|
|
public override ImageStreamCompleter load(NetworkImage key, DecoderCallback decode) {
|
|
IEnumerable<DiagnosticsNode> infoCollector() {
|
|
yield return new ErrorDescription($"url: {url}");
|
|
}
|
|
|
|
return new MultiFrameImageStreamCompleter(
|
|
codec: _loadAsync(key, decode),
|
|
scale: key.scale,
|
|
informationCollector: infoCollector
|
|
);
|
|
}
|
|
|
|
Future<Codec> _loadAsync(NetworkImage key, DecoderCallback decode) {
|
|
var completer = Completer.create();
|
|
var isolate = Isolate.current;
|
|
var panel = UIWidgetsPanelWrapper.current.window;
|
|
if (panel.isActive()) {
|
|
panel.startCoroutine(_loadCoroutine(key.url, completer, isolate));
|
|
return completer.future.to<byte[]>().then_<byte[]>(data => {
|
|
if (data != null && data.Length > 0) {
|
|
return decode(data);
|
|
}
|
|
|
|
throw new Exception("not loaded");
|
|
}).to<Codec>();
|
|
}
|
|
|
|
return new Future<Codec>(Future.create(() => FutureOr.value(null)));
|
|
}
|
|
|
|
IEnumerator _loadCoroutine(string key, Completer completer, Isolate isolate) {
|
|
var url = new Uri(key);
|
|
using (var www = UnityWebRequest.Get(url)) {
|
|
if (headers != null) {
|
|
foreach (var header in headers) {
|
|
www.SetRequestHeader(header.Key, header.Value);
|
|
}
|
|
}
|
|
|
|
yield return www.SendWebRequest();
|
|
|
|
if (www.isNetworkError || www.isHttpError) {
|
|
completer.completeError(new Exception($"Failed to load from url \"{url}\": {www.error}"));
|
|
yield break;
|
|
}
|
|
|
|
var data = www.downloadHandler.data;
|
|
|
|
using (Isolate.getScope(isolate)) {
|
|
completer.complete(data);
|
|
}
|
|
}
|
|
}
|
|
|
|
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 (headers != null) {
|
|
foreach (var header in 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 (headers != null) {
|
|
foreach (var header in 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(url, other.url) && 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() != GetType()) {
|
|
return false;
|
|
}
|
|
|
|
return Equals((NetworkImage) obj);
|
|
}
|
|
|
|
public override int GetHashCode() {
|
|
unchecked {
|
|
return ((url != null ? url.GetHashCode() : 0) * 397) ^ 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(\"{url}\", scale: {scale})";
|
|
}
|
|
}
|
|
|
|
public class FileImage : ImageProvider<FileImage>, IEquatable<FileImage> {
|
|
public FileImage(string file, float scale = 1.0f) {
|
|
D.assert(file != null);
|
|
this.file = file;
|
|
this.scale = scale;
|
|
}
|
|
|
|
public readonly string file;
|
|
|
|
public readonly float scale;
|
|
|
|
public override Future<FileImage> obtainKey(ImageConfiguration configuration) {
|
|
return new SynchronousFuture<FileImage>(this);
|
|
}
|
|
|
|
public override ImageStreamCompleter load(FileImage key, DecoderCallback decode) {
|
|
IEnumerable<DiagnosticsNode> infoCollector() {
|
|
yield return new ErrorDescription($"Path: {file}");
|
|
}
|
|
|
|
return new MultiFrameImageStreamCompleter(_loadAsync(key, decode),
|
|
scale: key.scale,
|
|
informationCollector: infoCollector);
|
|
}
|
|
|
|
Future<Codec> _loadAsync(FileImage key, DecoderCallback decode) {
|
|
#if UNITY_ANDROID && !UNITY_EDITOR
|
|
var path = Path.Combine(Application.streamingAssetsPath, key.file);
|
|
#else
|
|
var path = "file://" + Path.Combine(Application.streamingAssetsPath, key.file);
|
|
#endif
|
|
WWW unpackerWWW = new WWW(path);
|
|
while (!unpackerWWW.isDone) {
|
|
} // This will block in the webplayer.
|
|
|
|
if (unpackerWWW.bytes.Length > 0) {
|
|
return decode(unpackerWWW.bytes);
|
|
}
|
|
|
|
throw new Exception("not loaded");
|
|
}
|
|
|
|
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(file, other.file) && 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() != GetType()) {
|
|
return false;
|
|
}
|
|
|
|
return Equals((FileImage) obj);
|
|
}
|
|
|
|
public override int GetHashCode() {
|
|
unchecked {
|
|
return ((file != null ? file.GetHashCode() : 0) * 397) ^ 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 $"{foundation_.objectRuntimeType(this, "FileImage")}({file}, scale: {scale})";
|
|
}
|
|
}
|
|
|
|
public class MemoryImage : ImageProvider<MemoryImage>, IEquatable<MemoryImage> {
|
|
public MemoryImage(byte[] bytes, float scale = 1.0f) {
|
|
D.assert(bytes != null);
|
|
this.bytes = bytes;
|
|
this.scale = scale;
|
|
}
|
|
|
|
public readonly byte[] bytes;
|
|
|
|
public readonly float scale;
|
|
|
|
public override Future<MemoryImage> obtainKey(ImageConfiguration configuration) {
|
|
return new SynchronousFuture<MemoryImage>(this);
|
|
//Future.value(FutureOr.value(this)).to<MemoryImage>();
|
|
}
|
|
|
|
public override ImageStreamCompleter load(MemoryImage key, DecoderCallback decode) {
|
|
return new MultiFrameImageStreamCompleter(
|
|
_loadAsync(key, decode),
|
|
scale: key.scale);
|
|
}
|
|
|
|
Future<Codec> _loadAsync(MemoryImage key, DecoderCallback decode) {
|
|
D.assert(key == this);
|
|
|
|
return decode(bytes);
|
|
}
|
|
|
|
public bool Equals(MemoryImage other) {
|
|
if (ReferenceEquals(null, other)) {
|
|
return false;
|
|
}
|
|
|
|
if (ReferenceEquals(this, other)) {
|
|
return true;
|
|
}
|
|
|
|
return Equals(bytes, other.bytes) && 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() != GetType()) {
|
|
return false;
|
|
}
|
|
|
|
return Equals((MemoryImage) obj);
|
|
}
|
|
|
|
public override int GetHashCode() {
|
|
unchecked {
|
|
return ((bytes != null ? bytes.GetHashCode() : 0) * 397) ^ 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
|
|
$"{foundation_.objectRuntimeType(this, "MemoryImage")}({foundation_.describeIdentity(bytes)}), scale: {scale}";
|
|
}
|
|
}
|
|
|
|
public class ExactAssetImage : AssetBundleImageProvider, IEquatable<ExactAssetImage> {
|
|
public ExactAssetImage(
|
|
string assetName,
|
|
float scale = 1.0f,
|
|
AssetBundle bundle = null
|
|
) {
|
|
D.assert(assetName != null);
|
|
this.assetName = assetName;
|
|
this.scale = scale;
|
|
this.bundle = bundle;
|
|
}
|
|
|
|
public readonly string assetName;
|
|
|
|
public readonly float scale;
|
|
|
|
public readonly AssetBundle bundle;
|
|
|
|
public override Future<AssetBundleImageKey> obtainKey(ImageConfiguration configuration) {
|
|
return new SynchronousFuture<AssetBundleImageKey>(new AssetBundleImageKey(
|
|
bundle: bundle ? bundle : configuration.bundle,
|
|
name: assetName,
|
|
scale: scale
|
|
));
|
|
}
|
|
|
|
public bool Equals(ExactAssetImage other) {
|
|
if (ReferenceEquals(null, other)) {
|
|
return false;
|
|
}
|
|
|
|
if (ReferenceEquals(this, other)) {
|
|
return true;
|
|
}
|
|
|
|
return string.Equals(assetName, other.assetName) && scale.Equals(other.scale) &&
|
|
Equals(bundle, other.bundle);
|
|
}
|
|
|
|
public override bool Equals(object obj) {
|
|
if (ReferenceEquals(null, obj)) {
|
|
return false;
|
|
}
|
|
|
|
if (ReferenceEquals(this, obj)) {
|
|
return true;
|
|
}
|
|
|
|
if (obj.GetType() != GetType()) {
|
|
return false;
|
|
}
|
|
|
|
return Equals((ExactAssetImage) obj);
|
|
}
|
|
|
|
public override int GetHashCode() {
|
|
unchecked {
|
|
var hashCode = (assetName != null ? assetName.GetHashCode() : 0);
|
|
hashCode = (hashCode * 397) ^ scale.GetHashCode();
|
|
hashCode = (hashCode * 397) ^ (bundle != null ? 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
|
|
$"{foundation_.objectRuntimeType(this, "ExactAssetImage")}(name: \"{assetName}\", scale: {scale}, bundle: {bundle})";
|
|
}
|
|
}
|
|
|
|
internal class _ErrorImageCompleter : ImageStreamCompleter {
|
|
internal _ErrorImageCompleter() {
|
|
}
|
|
|
|
public void setError(
|
|
DiagnosticsNode context,
|
|
Exception exception,
|
|
string stack,
|
|
InformationCollector informationCollector,
|
|
bool silent = false
|
|
) {
|
|
reportError(
|
|
context: context,
|
|
exception: exception,
|
|
informationCollector: informationCollector,
|
|
silent: silent
|
|
);
|
|
}
|
|
}
|
|
|
|
public class NetworkImageLoadException : Exception {
|
|
NetworkImageLoadException(int statusCode, Uri uri) {
|
|
D.assert(uri != null);
|
|
D.assert(statusCode != null);
|
|
this.statusCode = statusCode;
|
|
this.uri = uri;
|
|
_message = $"HTTP request failed, statusCode: {statusCode}, {uri}";
|
|
}
|
|
|
|
public readonly int statusCode;
|
|
|
|
readonly string _message;
|
|
|
|
public readonly Uri uri;
|
|
|
|
public override string ToString() => _message;
|
|
}
|
|
}
|