浏览代码
Merge branch 'image_updates' into 'master'
Merge branch 'image_updates' into 'master'
image updates. See merge request upm-packages/ui-widgets/com.unity.uiwidgets!2/main
Fan Zhang
6 年前
当前提交
13a4a45d
共有 57 个文件被更改,包括 3177 次插入 和 762 次删除
-
6Runtime/animation/listener_helpers.mixin.njk
-
2Runtime/async/microtask_queue.cs
-
109Runtime/async/timer.cs
-
7Runtime/debugger/inpsector_panel.cs
-
23Runtime/editor/editor_window.cs
-
5Runtime/editor/surface.cs
-
18Runtime/engine/WidgetCanvas.cs
-
21Runtime/flow/raster_cache.cs
-
15Runtime/foundation/basic_types.cs
-
11Runtime/foundation/debug.cs
-
12Runtime/foundation/node.mixin.gen.cs
-
14Runtime/foundation/node.mixin.njk
-
3Runtime/gestures/binding.cs
-
8Runtime/gestures/drag_details.cs
-
22Runtime/gestures/events.cs
-
4Runtime/gestures/monodrag.cs
-
6Runtime/gestures/velocity_tracker.cs
-
33Runtime/painting/binding.cs
-
102Runtime/painting/decoration_image.cs
-
177Runtime/painting/image_cache.cs
-
636Runtime/painting/image_provider.cs
-
385Runtime/painting/image_stream.cs
-
2Runtime/promise/Promise.cs
-
2Runtime/promise/Promise_NonGeneric.cs
-
8Runtime/rendering/binding.cs
-
6Runtime/rendering/box.mixin.njk
-
289Runtime/rendering/image.cs
-
4Runtime/rendering/object.mixin.njk
-
6Runtime/rendering/proxy_box.mixin.njk
-
17Runtime/ui/geometry.cs
-
21Runtime/ui/painting/canvas.cs
-
25Runtime/ui/painting/canvas_impl.cs
-
7Runtime/ui/painting/draw_cmd.cs
-
67Runtime/ui/painting/image.cs
-
4Runtime/ui/pointer.cs
-
62Runtime/ui/window.cs
-
135Runtime/widgets/basic.cs
-
4Runtime/widgets/container.cs
-
296Runtime/widgets/image.cs
-
6Runtime/widgets/scroll_activity.cs
-
4Runtime/widgets/scroll_notification.mixin.njk
-
38Tests/Editor/CanvasAndLayers.cs
-
4Tests/Editor/EditableTextWiget.cs
-
7Tests/Editor/Widgets.cs
-
2Runtime/async/coroutine.cs.meta
-
371Runtime/async/coroutine.cs
-
678Runtime/ui/painting/GifDecoder.cs
-
11Runtime/ui/painting/GifDecoder.cs.meta
-
63Runtime/ui/painting/codec.cs
-
11Runtime/ui/painting/codec.cs.meta
-
148Runtime/ui/painting/codec_gif.cs
-
11Runtime/ui/painting/codec_gif.cs.meta
-
3Runtime/lib.meta
-
8Assets.meta
-
0/Runtime/async/coroutine.cs.meta
|
|||
using Unity.UIWidgets.ui; |
|||
using Unity.UIWidgets.gestures; |
|||
public class PaintingBinding { |
|||
|
|||
public PaintingBinding(Window window) { |
|||
this._window = window; |
|||
public class PaintingBinding : GestureBinding { |
|||
public static new PaintingBinding instance { |
|||
get { return (PaintingBinding) GestureBinding.instance; } |
|||
set { GestureBinding.instance = value; } |
|||
|
|||
private static PaintingBinding _instance; |
|||
public readonly Window _window; |
|||
public static PaintingBinding instance { |
|||
get { return _instance; } |
|||
public PaintingBinding() { |
|||
private ImageCache _imageCache; |
|||
ImageCache _imageCache; |
|||
get { return _imageCache; } |
|||
} |
|||
get { |
|||
if (this._imageCache == null) { |
|||
this._imageCache = this.createImageCache(); |
|||
} |
|||
public ImageCache createImageCache() { |
|||
return new ImageCache(); |
|||
return this._imageCache; |
|||
} |
|||
public void initInstances() { |
|||
_instance = this; |
|||
_imageCache = createImageCache(); |
|||
protected virtual ImageCache createImageCache() { |
|||
return new ImageCache(); |
|||
} |
|||
} |
|||
} |
|
|||
using System; |
|||
using Unity.UIWidgets.async; |
|||
using Unity.UIWidgets.foundation; |
|||
public class Image { |
|||
public Image(byte[] raw = null, Texture2D texture = null) { |
|||
this.rawData = raw ?? new byte[0]; |
|||
public class Image : IEquatable<Image>, IDisposable { |
|||
Texture _texture; |
|||
readonly bool _isOwner; |
|||
|
|||
public Image(Texture texture, bool isOwner = true) { |
|||
this._isOwner = isOwner; |
|||
public byte[] rawData; |
|||
public int width => this._texture != null ? this._texture.width : 0; |
|||
|
|||
public int height => this._texture != null ? this._texture.height : 0; |
|||
|
|||
public Texture texture => this._texture; |
|||
|
|||
~Image() { |
|||
Timer.runInMain(this._dispose); |
|||
} |
|||
|
|||
void _dispose() { |
|||
if (this._isOwner) { |
|||
this._texture = ObjectUtils.SafeDestroy(this._texture); |
|||
} |
|||
} |
|||
|
|||
public void Dispose() { |
|||
this._dispose(); |
|||
GC.SuppressFinalize(this); |
|||
} |
|||
|
|||
public bool Equals(Image other) { |
|||
if (ReferenceEquals(null, other)) return false; |
|||
if (ReferenceEquals(this, other)) return true; |
|||
return Equals(this._texture, other._texture); |
|||
} |
|||
public int height { |
|||
get { return texture != null ? texture.height : 0; } |
|||
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((Image) obj); |
|||
public int width { |
|||
get { return texture != null ? texture.width : 0; } |
|||
public override int GetHashCode() { |
|||
return (this._texture != null ? this._texture.GetHashCode() : 0); |
|||
public Texture2D texture { |
|||
get { |
|||
if (_texture == null && rawData.Length != 0) { |
|||
_texture = new Texture2D(2, 2); |
|||
_texture.LoadImage(rawData); |
|||
} |
|||
public static bool operator ==(Image left, Image right) { |
|||
return Equals(left, right); |
|||
} |
|||
return _texture; |
|||
} |
|||
public static bool operator !=(Image left, Image right) { |
|||
return !Equals(left, right); |
|||
private Texture2D _texture; |
|||
public override string ToString() { |
|||
return $"[{this.width}\u00D7{this.height}]"; |
|||
} |
|||
} |
|||
} |
|
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading; |
|||
using RSG; |
|||
using Unity.UIWidgets.foundation; |
|||
using Unity.UIWidgets.ui; |
|||
using UnityEngine; |
|||
|
|||
namespace Unity.UIWidgets.async { |
|||
public class UIWidgetsCoroutine { |
|||
_WaitforSecondsProcessor _waitProcessor; |
|||
_WaitForCoroutineProcessor _waitForCoroutine; |
|||
_WaitForAsyncOPProcessor _waitForAsyncOPProcessor; |
|||
|
|||
readonly IEnumerator _routine; |
|||
readonly Window _window; |
|||
readonly IDisposable _unhook; |
|||
readonly bool _isBackground; |
|||
|
|||
internal volatile object lastResult; |
|||
internal volatile Exception lastError; |
|||
internal bool isDone; |
|||
|
|||
readonly Promise<object> _promise = new Promise<object>(); |
|||
public IPromise<object> promise => this._promise; |
|||
|
|||
internal UIWidgetsCoroutine(IEnumerator routine, Window window, bool isBackground = false) { |
|||
D.assert(routine != null); |
|||
D.assert(window != null); |
|||
|
|||
this._routine = routine; |
|||
this._window = window; |
|||
|
|||
if (isBackground && BackgroundCallbacks.getInstance() != null) { |
|||
this._unhook = BackgroundCallbacks.getInstance().addCallback(this._moveNext); |
|||
} else { |
|||
this._unhook = this._window.onUpdate(this._moveNext); |
|||
} |
|||
|
|||
this._isBackground = isBackground; |
|||
} |
|||
|
|||
void _moveNext() { |
|||
D.assert(!this.isDone); |
|||
|
|||
var lastError = this.lastError; |
|||
if (lastError != null) { |
|||
this._unhook.Dispose(); |
|||
|
|||
this.isDone = true; |
|||
this.lastResult = null; |
|||
if (this._isBackground) { |
|||
Timer.runInMain(() => { this._window.run(() => { this._promise.Reject(lastError); }); }); |
|||
} else { |
|||
this._promise.Reject(lastError); |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
bool hasNext; |
|||
try { |
|||
hasNext = this._processIEnumeratorRecursive(this._routine); |
|||
} |
|||
catch (Exception ex) { |
|||
this.stop(ex); |
|||
return; |
|||
} |
|||
|
|||
if (!hasNext && !this.isDone) { |
|||
this._unhook.Dispose(); |
|||
|
|||
this.isDone = true; |
|||
D.assert(this.lastError == null); |
|||
if (this._isBackground) { |
|||
Timer.runInMain(() => { this._window.run(() => { this._promise.Resolve(this.lastResult); }); }); |
|||
} else { |
|||
this._promise.Resolve(this.lastResult); |
|||
} |
|||
} |
|||
} |
|||
|
|||
bool _processIEnumeratorRecursive(IEnumerator child) { |
|||
D.assert(child != null); |
|||
|
|||
if (child.Current is IEnumerator nestedEnumerator) { |
|||
return this._processIEnumeratorRecursive(nestedEnumerator) || child.MoveNext(); |
|||
} |
|||
|
|||
if (child.Current is UIWidgetsCoroutine nestedCoroutine) { |
|||
if (this._isBackground) { |
|||
throw new Exception("nestedCoroutine is not supported in Background Coroutine"); |
|||
} |
|||
|
|||
this._waitForCoroutine.set(nestedCoroutine); |
|||
return this._waitForCoroutine.moveNext(child, this); |
|||
} |
|||
|
|||
if (child.Current is UIWidgetsWaitForSeconds waitForSeconds) { |
|||
if (this._isBackground) { |
|||
throw new Exception("waitForSeconds is not supported in Background Coroutine"); |
|||
} |
|||
|
|||
this._waitProcessor.set(waitForSeconds); |
|||
return this._waitProcessor.moveNext(child); |
|||
} |
|||
|
|||
if (child.Current is AsyncOperation waitForAsyncOP) { |
|||
if (this._isBackground) { |
|||
throw new Exception("asyncOperation is not supported in Background Coroutine"); |
|||
} |
|||
|
|||
this._waitForAsyncOPProcessor.set(waitForAsyncOP); |
|||
return this._waitForAsyncOPProcessor.moveNext(child); |
|||
} |
|||
|
|||
this.lastResult = child.Current; |
|||
return child.MoveNext(); |
|||
} |
|||
|
|||
public void stop() { |
|||
this.stop(null); |
|||
} |
|||
|
|||
internal void stop(Exception ex) { |
|||
if (this.lastError == null) { |
|||
this.lastError = ex ?? new CoroutineCanceledException(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
struct _WaitforSecondsProcessor { |
|||
UIWidgetsWaitForSeconds _current; |
|||
double _targetTime; |
|||
|
|||
public void set(UIWidgetsWaitForSeconds yieldStatement) { |
|||
if (this._current != yieldStatement) { |
|||
this._current = yieldStatement; |
|||
this._targetTime = Timer.timeSinceStartup + yieldStatement.waitTime; |
|||
} |
|||
} |
|||
|
|||
public bool moveNext(IEnumerator enumerator) { |
|||
if (this._targetTime <= Timer.timeSinceStartup) { |
|||
this._current = null; |
|||
this._targetTime = 0; |
|||
return enumerator.MoveNext(); |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
} |
|||
|
|||
struct _WaitForCoroutineProcessor { |
|||
UIWidgetsCoroutine _current; |
|||
|
|||
public void set(UIWidgetsCoroutine routine) { |
|||
if (this._current != routine) { |
|||
this._current = routine; |
|||
} |
|||
} |
|||
|
|||
public bool moveNext(IEnumerator enumerator, UIWidgetsCoroutine parent) { |
|||
if (this._current.isDone) { |
|||
var current = this._current; |
|||
this._current = null; |
|||
|
|||
if (current.lastError != null) { |
|||
parent.stop(current.lastError); |
|||
return false; |
|||
} |
|||
|
|||
parent.lastResult = current.lastResult; |
|||
return enumerator.MoveNext(); |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
} |
|||
|
|||
struct _WaitForAsyncOPProcessor { |
|||
AsyncOperation _current; |
|||
|
|||
public void set(AsyncOperation operation) { |
|||
if (this._current != operation) { |
|||
this._current = operation; |
|||
} |
|||
} |
|||
|
|||
public bool moveNext(IEnumerator enumerator) { |
|||
if (this._current.isDone) { |
|||
this._current = null; |
|||
return enumerator.MoveNext(); |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
} |
|||
|
|||
public static class Coroutine { |
|||
public static UIWidgetsCoroutine startCoroutine(this Window owner, IEnumerator routine) { |
|||
return new UIWidgetsCoroutine(routine, owner); |
|||
} |
|||
|
|||
public static UIWidgetsCoroutine startBackgroundCoroutine(this Window owner, IEnumerator routine) { |
|||
return new UIWidgetsCoroutine(routine, owner, isBackground: true); |
|||
} |
|||
} |
|||
|
|||
public class CoroutineCanceledException : Exception { |
|||
} |
|||
|
|||
public class UIWidgetsWaitForSeconds { |
|||
public double waitTime { get; } |
|||
|
|||
public UIWidgetsWaitForSeconds(float time) { |
|||
this.waitTime = time; |
|||
} |
|||
} |
|||
|
|||
internal class WindowCallbacks { |
|||
readonly LinkedList<VoidCallback> _callbackList; |
|||
readonly Window _window; |
|||
|
|||
public WindowCallbacks() { |
|||
this._callbackList = new LinkedList<VoidCallback>(); |
|||
} |
|||
|
|||
public void update() { |
|||
var callbackList = this._callbackList.ToArray(); |
|||
foreach (var callback in callbackList) { |
|||
try { |
|||
callback(); |
|||
} |
|||
catch (Exception ex) { |
|||
Debug.LogError("Failed to execute callback in WindowCallbacks: " + ex); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public IDisposable onUpdate(VoidCallback callback) { |
|||
var node = this._callbackList.AddLast(callback); |
|||
|
|||
return new _CallbackDisposable(this, node); |
|||
} |
|||
|
|||
class _CallbackDisposable : IDisposable { |
|||
readonly WindowCallbacks _parent; |
|||
readonly LinkedListNode<VoidCallback> _node; |
|||
|
|||
public _CallbackDisposable(WindowCallbacks parent, LinkedListNode<VoidCallback> node) { |
|||
this._parent = parent; |
|||
this._node = node; |
|||
} |
|||
|
|||
public void Dispose() { |
|||
this._parent._callbackList.Remove(this._node); |
|||
} |
|||
} |
|||
} |
|||
|
|||
internal class BackgroundCallbacks : IDisposable { |
|||
static BackgroundCallbacks _instance; |
|||
|
|||
public static BackgroundCallbacks getInstance() { |
|||
#if UNITY_WEBGL
|
|||
return null; |
|||
#endif
|
|||
if (_instance == null) { |
|||
_instance = new BackgroundCallbacks(2); |
|||
} |
|||
|
|||
return _instance; |
|||
} |
|||
|
|||
readonly LinkedList<_CallbackNode> _callbackList; |
|||
readonly ManualResetEvent _event; |
|||
|
|||
readonly Thread[] _threads; |
|||
volatile bool _aborted = false; |
|||
|
|||
public BackgroundCallbacks(int threadCount = 1) { |
|||
this._callbackList = new LinkedList<_CallbackNode>(); |
|||
this._event = new ManualResetEvent(false); |
|||
|
|||
this._threads = new Thread[threadCount]; |
|||
for (var i = 0; i < this._threads.Length; i++) { |
|||
this._threads[i] = new Thread(this._threadLoop); |
|||
this._threads[i].Start(); |
|||
} |
|||
} |
|||
|
|||
public void Dispose() { |
|||
foreach (var t in this._threads) { |
|||
this._aborted = true; |
|||
this._event.Set(); |
|||
t.Join(); |
|||
} |
|||
|
|||
this._callbackList.Clear(); |
|||
} |
|||
|
|||
void _threadLoop() { |
|||
while (true) { |
|||
if (this._aborted) { |
|||
break; |
|||
} |
|||
|
|||
LinkedListNode<_CallbackNode> node; |
|||
lock (this._callbackList) { |
|||
node = this._callbackList.First; |
|||
if (node != null) { |
|||
this._callbackList.Remove(node); |
|||
} |
|||
} |
|||
|
|||
if (node == null) { |
|||
this._event.WaitOne(); |
|||
this._event.Reset(); |
|||
continue; |
|||
} |
|||
|
|||
var callbackNode = node.Value; |
|||
D.assert(!callbackNode.isDone); |
|||
|
|||
try { |
|||
callbackNode.callback(); |
|||
} |
|||
catch (Exception ex) { |
|||
Debug.LogError("Failed to execute callback in BackgroundCallbacks: " + ex); |
|||
} |
|||
|
|||
if (!callbackNode.isDone) { |
|||
lock (this._callbackList) { |
|||
this._callbackList.AddLast(node); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
public IDisposable addCallback(VoidCallback callback) { |
|||
var node = new _CallbackNode {callback = callback}; |
|||
lock (this._callbackList) { |
|||
this._callbackList.AddLast(node); |
|||
} |
|||
|
|||
this._event.Set(); |
|||
|
|||
return new _CallbackDisposable(node); |
|||
} |
|||
|
|||
class _CallbackDisposable : IDisposable { |
|||
readonly _CallbackNode _node; |
|||
|
|||
public _CallbackDisposable(_CallbackNode node) { |
|||
this._node = node; |
|||
} |
|||
|
|||
public void Dispose() { |
|||
this._node.isDone = true; |
|||
} |
|||
} |
|||
|
|||
class _CallbackNode { |
|||
public VoidCallback callback; |
|||
public volatile bool isDone; |
|||
} |
|||
} |
|||
} |
|
|||
using System; |
|||
using System.IO; |
|||
using System.Text; |
|||
|
|||
namespace Unity.UIWidgets.ui { |
|||
// from https://github.com/avianbc/NGif/blob/master/Components/GifDecoder.cs
|
|||
// https://gist.github.com/devunwired/4479231
|
|||
// No DISPOSAL_PREVIOUS as its not actually widely used.
|
|||
public class GifDecoder : IDisposable { |
|||
/** |
|||
* File read status: No errors. |
|||
*/ |
|||
public const int STATUS_OK = 0; |
|||
|
|||
/** |
|||
* File read status: Error decoding file (may be partially decoded) |
|||
*/ |
|||
public const int STATUS_FORMAT_ERROR = 1; |
|||
|
|||
/** |
|||
* File read status: Unable to open source. |
|||
*/ |
|||
public const int STATUS_OPEN_ERROR = 2; |
|||
|
|||
// max decoder pixel stack size
|
|||
const int MAX_STACK_SIZE = 4096; |
|||
|
|||
// input stream
|
|||
Stream _inStream; |
|||
|
|||
/** |
|||
* Global status code of GIF data parsing |
|||
*/ |
|||
int _status; |
|||
|
|||
// Global File Header values and parsing flags
|
|||
volatile int _width; // full image width
|
|||
volatile int _height; // full image height
|
|||
bool _gctFlag; // global color table used
|
|||
int _gctSize; // size of global color table
|
|||
volatile int _loopCount = 1; // iterations; 0 = repeat forever
|
|||
|
|||
int[] _gct; // global color table
|
|||
int[] _lct; // local color table
|
|||
int[] _act; // active color table
|
|||
|
|||
int _bgIndex; // background color index
|
|||
int _bgColor; // background color
|
|||
int _lastBgColor; // previous bg color
|
|||
int _pixelAspect; // pixel aspect ratio
|
|||
|
|||
bool _lctFlag; // local color table flag
|
|||
bool _interlace; // interlace flag
|
|||
int _lctSize; // local color table size
|
|||
|
|||
int _ix, _iy, _iw, _ih; // current image rectangle
|
|||
int _lix, _liy, _liw, _lih; // last image rect
|
|||
int[] _image; // current frame
|
|||
|
|||
byte[] _block = new byte[256]; // current data block
|
|||
int _blockSize = 0; // block size
|
|||
|
|||
// last graphic control extension info
|
|||
int _dispose = 0; |
|||
|
|||
// 0=no action; 1=leave in place; 2=restore to bg; 3=restore to prev
|
|||
int _lastDispose = 0; |
|||
bool _transparency = false; // use transparent color
|
|||
int _delay = 0; // delay in milliseconds
|
|||
int _transIndex; // transparent color index
|
|||
|
|||
// LZW decoder working arrays
|
|||
short[] _prefix; |
|||
byte[] _suffix; |
|||
byte[] _pixelStack; |
|||
byte[] _pixels; |
|||
|
|||
volatile GifFrame _currentFrame; // frames read from current file
|
|||
volatile int _frameCount; |
|||
volatile bool _done; |
|||
|
|||
public class GifFrame { |
|||
public byte[] bytes; |
|||
public int delay; |
|||
} |
|||
|
|||
public int frameWidth => this._width; |
|||
|
|||
public int frameHeight => this._height; |
|||
|
|||
public GifFrame currentFrame => this._currentFrame; |
|||
|
|||
public int frameCount => this._frameCount; |
|||
|
|||
public int loopCount => this._loopCount; |
|||
|
|||
public bool done => this._done; |
|||
|
|||
void _setPixels() { |
|||
// fill in starting image contents based on last image's dispose code
|
|||
if (this._lastDispose > 0) { |
|||
var n = this._frameCount - 1; |
|||
if (n > 0) { |
|||
if (this._lastDispose == 2) { |
|||
// fill last image rect area with background color
|
|||
var fillcolor = this._transparency ? 0 : this._lastBgColor; |
|||
for (var i = 0; i < this._lih; i++) { |
|||
var line = i + this._liy; |
|||
if (line >= this._height) { |
|||
continue; |
|||
} |
|||
|
|||
line = this._height - line - 1; |
|||
var dx = line * this._width + this._lix; |
|||
var endx = dx + this._liw; |
|||
while (dx < endx) { |
|||
this._image[dx++] = fillcolor; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
// copy each source line to the appropriate place in the destination
|
|||
int pass = 1; |
|||
int inc = 8; |
|||
int iline = 0; |
|||
for (int i = 0; i < this._ih; i++) { |
|||
int line = i; |
|||
if (this._interlace) { |
|||
if (iline >= this._ih) { |
|||
pass++; |
|||
switch (pass) { |
|||
case 2: |
|||
iline = 4; |
|||
break; |
|||
case 3: |
|||
iline = 2; |
|||
inc = 4; |
|||
break; |
|||
case 4: |
|||
iline = 1; |
|||
inc = 2; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
line = iline; |
|||
iline += inc; |
|||
} |
|||
|
|||
line += this._iy; |
|||
if (line >= this._height) { |
|||
continue; |
|||
} |
|||
|
|||
var sx = i * this._iw; |
|||
line = this._height - line - 1; |
|||
var dx = line * this._width + this._ix; |
|||
var endx = dx + this._iw; |
|||
|
|||
for (; dx < endx; dx++) { |
|||
var c = this._act[this._pixels[sx++] & 0xff]; |
|||
if (c != 0) { |
|||
this._image[dx] = c; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Reads GIF image from stream |
|||
* |
|||
* @param BufferedInputStream containing GIF file. |
|||
* @return read status code (0 = no errors) |
|||
*/ |
|||
public int read(Stream inStream) { |
|||
this._init(); |
|||
if (inStream != null) { |
|||
this._inStream = inStream; |
|||
this._readHeader(); |
|||
} else { |
|||
this._status = STATUS_OPEN_ERROR; |
|||
} |
|||
|
|||
return this._status; |
|||
} |
|||
|
|||
public void Dispose() { |
|||
if (this._inStream != null) { |
|||
this._inStream.Dispose(); |
|||
this._inStream = null; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Decodes LZW image data into pixel array. |
|||
* Adapted from John Cristy's ImageMagick. |
|||
*/ |
|||
void _decodeImageData() { |
|||
const int NullCode = -1; |
|||
int npix = this._iw * this._ih; |
|||
int available, |
|||
clear, |
|||
code_mask, |
|||
code_size, |
|||
end_of_information, |
|||
in_code, |
|||
old_code, |
|||
bits, |
|||
code, |
|||
count, |
|||
i, |
|||
datum, |
|||
data_size, |
|||
first, |
|||
top, |
|||
bi, |
|||
pi; |
|||
|
|||
if ((this._pixels == null) || (this._pixels.Length < npix)) { |
|||
this._pixels = new byte[npix]; // allocate new pixel array
|
|||
} |
|||
|
|||
if (this._prefix == null) { |
|||
this._prefix = new short[MAX_STACK_SIZE]; |
|||
} |
|||
|
|||
if (this._suffix == null) { |
|||
this._suffix = new byte[MAX_STACK_SIZE]; |
|||
} |
|||
|
|||
if (this._pixelStack == null) { |
|||
this._pixelStack = new byte[MAX_STACK_SIZE + 1]; |
|||
} |
|||
|
|||
// Initialize GIF data stream decoder.
|
|||
|
|||
data_size = this._read(); |
|||
clear = 1 << data_size; |
|||
end_of_information = clear + 1; |
|||
available = clear + 2; |
|||
old_code = NullCode; |
|||
code_size = data_size + 1; |
|||
code_mask = (1 << code_size) - 1; |
|||
for (code = 0; code < clear; code++) { |
|||
this._prefix[code] = 0; |
|||
this._suffix[code] = (byte) code; |
|||
} |
|||
|
|||
// Decode GIF pixel stream.
|
|||
|
|||
datum = bits = count = first = top = pi = bi = 0; |
|||
|
|||
for (i = 0; i < npix;) { |
|||
if (top == 0) { |
|||
if (bits < code_size) { |
|||
// Load bytes until there are enough bits for a code.
|
|||
if (count == 0) { |
|||
// Read a new data block.
|
|||
count = this._readBlock(); |
|||
if (count <= 0) { |
|||
break; |
|||
} |
|||
|
|||
bi = 0; |
|||
} |
|||
|
|||
datum += (this._block[bi] & 0xff) << bits; |
|||
bits += 8; |
|||
bi++; |
|||
count--; |
|||
continue; |
|||
} |
|||
|
|||
// Get the next code.
|
|||
|
|||
code = datum & code_mask; |
|||
datum >>= code_size; |
|||
bits -= code_size; |
|||
|
|||
// Interpret the code
|
|||
|
|||
if ((code > available) || (code == end_of_information)) |
|||
break; |
|||
if (code == clear) { |
|||
// Reset decoder.
|
|||
code_size = data_size + 1; |
|||
code_mask = (1 << code_size) - 1; |
|||
available = clear + 2; |
|||
old_code = NullCode; |
|||
continue; |
|||
} |
|||
|
|||
if (old_code == NullCode) { |
|||
this._pixelStack[top++] = this._suffix[code]; |
|||
old_code = code; |
|||
first = code; |
|||
continue; |
|||
} |
|||
|
|||
in_code = code; |
|||
if (code == available) { |
|||
this._pixelStack[top++] = (byte) first; |
|||
code = old_code; |
|||
} |
|||
|
|||
while (code > clear) { |
|||
this._pixelStack[top++] = this._suffix[code]; |
|||
code = this._prefix[code]; |
|||
} |
|||
|
|||
first = this._suffix[code] & 0xff; |
|||
|
|||
// Add a new string to the string table,
|
|||
|
|||
if (available >= MAX_STACK_SIZE) { |
|||
break; |
|||
} |
|||
|
|||
this._pixelStack[top++] = (byte) first; |
|||
this._prefix[available] = (short) old_code; |
|||
this._suffix[available] = (byte) first; |
|||
available++; |
|||
if (((available & code_mask) == 0) |
|||
&& (available < MAX_STACK_SIZE)) { |
|||
code_size++; |
|||
code_mask += available; |
|||
} |
|||
|
|||
old_code = in_code; |
|||
} |
|||
|
|||
// Pop a pixel off the pixel stack.
|
|||
|
|||
top--; |
|||
this._pixels[pi++] = this._pixelStack[top]; |
|||
i++; |
|||
} |
|||
|
|||
for (i = pi; i < npix; i++) { |
|||
this._pixels[i] = 0; // clear missing pixels
|
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Returns true if an error was encountered during reading/decoding |
|||
*/ |
|||
bool _error() { |
|||
return this._status != STATUS_OK; |
|||
} |
|||
|
|||
/** |
|||
* Initializes or re-initializes reader |
|||
*/ |
|||
void _init() { |
|||
this._status = STATUS_OK; |
|||
this._currentFrame = null; |
|||
this._frameCount = 0; |
|||
this._done = false; |
|||
this._gct = null; |
|||
this._lct = null; |
|||
} |
|||
|
|||
/** |
|||
* Reads a single byte from the input stream. |
|||
*/ |
|||
int _read() { |
|||
int curByte = 0; |
|||
try { |
|||
curByte = this._inStream.ReadByte(); |
|||
} |
|||
catch (IOException) { |
|||
this._status = STATUS_FORMAT_ERROR; |
|||
} |
|||
|
|||
return curByte; |
|||
} |
|||
|
|||
/** |
|||
* Reads next variable length block from input. |
|||
* |
|||
* @return number of bytes stored in "buffer" |
|||
*/ |
|||
int _readBlock() { |
|||
this._blockSize = this._read(); |
|||
int n = 0; |
|||
if (this._blockSize > 0) { |
|||
try { |
|||
int count = 0; |
|||
while (n < this._blockSize) { |
|||
count = this._inStream.Read(this._block, n, this._blockSize - n); |
|||
if (count == -1) { |
|||
break; |
|||
} |
|||
|
|||
n += count; |
|||
} |
|||
} |
|||
catch (IOException) { |
|||
} |
|||
|
|||
if (n < this._blockSize) { |
|||
this._status = STATUS_FORMAT_ERROR; |
|||
} |
|||
} |
|||
|
|||
return n; |
|||
} |
|||
|
|||
/** |
|||
* Reads color table as 256 RGB integer values |
|||
* |
|||
* @param ncolors int number of colors to read |
|||
* @return int array containing 256 colors (packed ARGB with full alpha) |
|||
*/ |
|||
int[] _readColorTable(int ncolors) { |
|||
int nbytes = 3 * ncolors; |
|||
int[] tab = null; |
|||
byte[] c = new byte[nbytes]; |
|||
int n = 0; |
|||
try { |
|||
n = this._inStream.Read(c, 0, c.Length); |
|||
} |
|||
catch (IOException) { |
|||
} |
|||
|
|||
if (n < nbytes) { |
|||
this._status = STATUS_FORMAT_ERROR; |
|||
} else { |
|||
tab = new int[256]; // max size to avoid bounds checks
|
|||
int i = 0; |
|||
int j = 0; |
|||
while (i < ncolors) { |
|||
int r = c[j++] & 0xff; |
|||
int g = c[j++] & 0xff; |
|||
int b = c[j++] & 0xff; |
|||
tab[i++] = (int) (0xff000000 | (r << 16) | (g << 8) | b); |
|||
} |
|||
} |
|||
|
|||
return tab; |
|||
} |
|||
|
|||
/** |
|||
* Main file parser. Reads GIF content blocks. |
|||
*/ |
|||
public int nextFrame() { |
|||
// read GIF file content blocks
|
|||
bool done = false; |
|||
while (!(done || this._error())) { |
|||
int code = this._read(); |
|||
switch (code) { |
|||
case 0x2C: // image separator
|
|||
this._readImage(); |
|||
done = true; |
|||
break; |
|||
|
|||
case 0x21: // extension
|
|||
code = this._read(); |
|||
switch (code) { |
|||
case 0xf9: // graphics control extension
|
|||
this._readGraphicControlExt(); |
|||
break; |
|||
|
|||
case 0xff: // application extension
|
|||
this._readBlock(); |
|||
|
|||
var appBuilder = new StringBuilder(); |
|||
for (int i = 0; i < 11; i++) { |
|||
appBuilder.Append((char) this._block[i]); |
|||
} |
|||
|
|||
String app = appBuilder.ToString(); |
|||
if (app.Equals("NETSCAPE2.0")) { |
|||
this._readNetscapeExt(); |
|||
} else { |
|||
this._skip(); // don't care
|
|||
} |
|||
|
|||
break; |
|||
|
|||
default: // uninteresting extension
|
|||
this._skip(); |
|||
break; |
|||
} |
|||
|
|||
break; |
|||
|
|||
case 0x3b: // terminator
|
|||
this._done = true; |
|||
done = true; |
|||
break; |
|||
|
|||
case 0x00: // bad byte, but keep going and see what happens
|
|||
break; |
|||
|
|||
default: |
|||
this._status = STATUS_FORMAT_ERROR; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
return this._status; |
|||
} |
|||
|
|||
/** |
|||
* Reads Graphics Control Extension values |
|||
*/ |
|||
void _readGraphicControlExt() { |
|||
this._read(); // block size
|
|||
int packed = this._read(); // packed fields
|
|||
this._dispose = (packed & 0x1c) >> 2; // disposal method
|
|||
if (this._dispose == 0) { |
|||
this._dispose = 1; // elect to keep old image if discretionary
|
|||
} |
|||
|
|||
this._transparency = (packed & 1) != 0; |
|||
this._delay = this._readShort() * 10; // delay in milliseconds
|
|||
this._transIndex = this._read(); // transparent color index
|
|||
this._read(); // block terminator
|
|||
} |
|||
|
|||
/** |
|||
* Reads GIF file header information. |
|||
*/ |
|||
void _readHeader() { |
|||
var idBuilder = new StringBuilder(); |
|||
for (int i = 0; i < 6; i++) { |
|||
idBuilder.Append((char) this._read()); |
|||
} |
|||
|
|||
var id = idBuilder.ToString(); |
|||
if (!id.StartsWith("GIF")) { |
|||
this._status = STATUS_FORMAT_ERROR; |
|||
return; |
|||
} |
|||
|
|||
this._readLSD(); |
|||
if (this._gctFlag && !this._error()) { |
|||
this._gct = this._readColorTable(this._gctSize); |
|||
this._bgColor = this._gct[this._bgIndex]; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Reads next frame image |
|||
*/ |
|||
void _readImage() { |
|||
this._ix = this._readShort(); // (sub)image position & size
|
|||
this._iy = this._readShort(); |
|||
this._iw = this._readShort(); |
|||
this._ih = this._readShort(); |
|||
|
|||
int packed = this._read(); |
|||
this._lctFlag = (packed & 0x80) != 0; // 1 - local color table flag
|
|||
this._interlace = (packed & 0x40) != 0; // 2 - interlace flag
|
|||
// 3 - sort flag
|
|||
// 4-5 - reserved
|
|||
this._lctSize = 2 << (packed & 7); // 6-8 - local color table size
|
|||
|
|||
if (this._lctFlag) { |
|||
this._lct = this._readColorTable(this._lctSize); // read table
|
|||
this._act = this._lct; // make local table active
|
|||
} else { |
|||
this._act = this._gct; // make global table active
|
|||
if (this._bgIndex == this._transIndex) { |
|||
this._bgColor = 0; |
|||
} |
|||
} |
|||
|
|||
int save = 0; |
|||
if (this._transparency) { |
|||
save = this._act[this._transIndex]; |
|||
this._act[this._transIndex] = 0; // set transparent color if specified
|
|||
} |
|||
|
|||
if (this._act == null) { |
|||
this._status = STATUS_FORMAT_ERROR; // no color table defined
|
|||
} |
|||
|
|||
if (this._error()) { |
|||
return; |
|||
} |
|||
|
|||
this._decodeImageData(); // decode pixel data
|
|||
this._skip(); |
|||
|
|||
if (this._error()) { |
|||
return; |
|||
} |
|||
|
|||
// create new image to receive frame data
|
|||
// image =
|
|||
// new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB_PRE);
|
|||
|
|||
this._image = this._image ?? new int[this._width * this._height]; |
|||
|
|||
this._setPixels(); // transfer pixel data to image
|
|||
|
|||
var bytes = new byte[this._width * this._height * sizeof(int)]; |
|||
Buffer.BlockCopy(this._image, 0, bytes, 0, bytes.Length); |
|||
this._currentFrame = new GifFrame {bytes = bytes, delay = this._delay}; |
|||
this._frameCount++; |
|||
|
|||
if (this._transparency) { |
|||
this._act[this._transIndex] = save; |
|||
} |
|||
|
|||
this._resetFrame(); |
|||
} |
|||
|
|||
/** |
|||
* Reads Logical Screen Descriptor |
|||
*/ |
|||
void _readLSD() { |
|||
// logical screen size
|
|||
this._width = this._readShort(); |
|||
this._height = this._readShort(); |
|||
|
|||
// packed fields
|
|||
int packed = this._read(); |
|||
this._gctFlag = (packed & 0x80) != 0; // 1 : global color table flag
|
|||
// 2-4 : color resolution
|
|||
// 5 : gct sort flag
|
|||
this._gctSize = 2 << (packed & 7); // 6-8 : gct size
|
|||
|
|||
this._bgIndex = this._read(); // background color index
|
|||
this._pixelAspect = this._read(); // pixel aspect ratio
|
|||
} |
|||
|
|||
/** |
|||
* Reads Netscape extenstion to obtain iteration count |
|||
*/ |
|||
void _readNetscapeExt() { |
|||
do { |
|||
this._readBlock(); |
|||
if (this._block[0] == 1) { |
|||
// loop count sub-block
|
|||
int b1 = this._block[1] & 0xff; |
|||
int b2 = this._block[2] & 0xff; |
|||
this._loopCount = (b2 << 8) | b1; |
|||
} |
|||
} while (this._blockSize > 0 && !this._error()); |
|||
} |
|||
|
|||
/** |
|||
* Reads next 16-bit value, LSB first |
|||
*/ |
|||
int _readShort() { |
|||
// read 16-bit value, LSB first
|
|||
return this._read() | (this._read() << 8); |
|||
} |
|||
|
|||
/** |
|||
* Resets frame state for reading next image. |
|||
*/ |
|||
void _resetFrame() { |
|||
this._lastDispose = this._dispose; |
|||
this._lix = this._ix; |
|||
this._liy = this._iy; |
|||
this._liw = this._iw; |
|||
this._lih = this._ih; |
|||
this._lastBgColor = this._bgColor; |
|||
this._lct = null; |
|||
} |
|||
|
|||
/** |
|||
* Skips variable length blocks up to and including |
|||
* next zero length block. |
|||
*/ |
|||
void _skip() { |
|||
do { |
|||
this._readBlock(); |
|||
} while ((this._blockSize > 0) && !this._error()); |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 657b3aa341be94fc09e9c79dcc086f75 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System; |
|||
using RSG; |
|||
using Unity.UIWidgets.foundation; |
|||
using UnityEngine; |
|||
|
|||
namespace Unity.UIWidgets.ui { |
|||
public class FrameInfo { |
|||
public Image image; |
|||
public TimeSpan duration; |
|||
} |
|||
|
|||
public interface Codec : IDisposable { |
|||
int frameCount { get; } |
|||
int repetitionCount { get; } |
|||
IPromise<FrameInfo> getNextFrame(); |
|||
} |
|||
|
|||
public class ImageCodec : Codec { |
|||
Image _image; |
|||
|
|||
public ImageCodec(Image image) { |
|||
D.assert(image != null); |
|||
this._image = image; |
|||
} |
|||
|
|||
public int frameCount => 1; |
|||
|
|||
public int repetitionCount => 0; |
|||
|
|||
public IPromise<FrameInfo> getNextFrame() { |
|||
D.assert(this._image != null); |
|||
|
|||
return Promise<FrameInfo>.Resolved(new FrameInfo { |
|||
duration = TimeSpan.Zero, |
|||
image = this._image |
|||
}); |
|||
} |
|||
|
|||
public void Dispose() { |
|||
if (this._image != null) { |
|||
this._image.Dispose(); |
|||
this._image = null; |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
public static class CodecUtils { |
|||
public static IPromise<Codec> getCodec(byte[] bytes) { |
|||
if (GifCodec.isGif(bytes)) { |
|||
return Promise<Codec>.Resolved(new GifCodec(bytes)); |
|||
} |
|||
|
|||
var texture = new Texture2D(2, 2); |
|||
texture.LoadImage(bytes); |
|||
return Promise<Codec>.Resolved(new ImageCodec(new Image(texture))); |
|||
} |
|||
|
|||
public static IPromise<Codec> getCodec(Texture2D texture) { |
|||
return Promise<Codec>.Resolved(new ImageCodec(new Image(texture))); |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: a76c61db5ad8a4faf9c0667883dce90d |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using System.IO; |
|||
using RSG; |
|||
using Unity.UIWidgets.async; |
|||
using Unity.UIWidgets.foundation; |
|||
using UnityEngine; |
|||
using Timer = Unity.UIWidgets.async.Timer; |
|||
|
|||
namespace Unity.UIWidgets.ui { |
|||
public class GifCodec : Codec { |
|||
public static bool isGif(byte[] bytes) { |
|||
return bytes != null && bytes.Length >= 3 && bytes[0] == 'G' && bytes[1] == 'I' && bytes[2] == 'F'; |
|||
} |
|||
|
|||
public class FrameData { |
|||
public FrameInfo frameInfo; |
|||
public GifDecoder.GifFrame gifFrame; |
|||
} |
|||
|
|||
readonly List<Promise<FrameData>> _frames; |
|||
volatile int _width; |
|||
volatile int _height; |
|||
volatile int _frameCount; |
|||
volatile int _repetitionCount; |
|||
volatile bool _isDone; |
|||
volatile int _frameIndex; |
|||
readonly UIWidgetsCoroutine _coroutine; |
|||
|
|||
public GifCodec(byte[] bytes) { |
|||
D.assert(bytes != null); |
|||
D.assert(isGif(bytes)); |
|||
|
|||
this._frames = new List<Promise<FrameData>>(); |
|||
this._width = 0; |
|||
this._height = 0; |
|||
this._frameCount = 0; |
|||
this._repetitionCount = 0; |
|||
this._isDone = false; |
|||
this._frameIndex = 0; |
|||
|
|||
this._coroutine = Window.instance.startBackgroundCoroutine( |
|||
this._startDecoding(bytes, Window.instance)); |
|||
} |
|||
|
|||
IEnumerator _startDecoding(byte[] bytes, Window window) { |
|||
var bytesStream = new MemoryStream(bytes); |
|||
|
|||
var gifDecoder = new GifDecoder(); |
|||
if (gifDecoder.read(bytesStream) != GifDecoder.STATUS_OK) { |
|||
throw new Exception("Failed to decode gif."); |
|||
} |
|||
|
|||
this._width = gifDecoder.frameWidth; |
|||
this._height = gifDecoder.frameHeight; |
|||
|
|||
int i = 0; |
|||
while (true) { |
|||
yield return null; |
|||
|
|||
if (gifDecoder.nextFrame() != GifDecoder.STATUS_OK) { |
|||
throw new Exception("Failed to decode gif."); |
|||
} |
|||
if (gifDecoder.done) { |
|||
break; |
|||
} |
|||
|
|||
var frameData = new FrameData { |
|||
gifFrame = gifDecoder.currentFrame, |
|||
}; |
|||
|
|||
Promise<FrameData> frame; |
|||
|
|||
lock (this._frames) { |
|||
if (i < this._frames.Count) { |
|||
} else { |
|||
D.assert(this._frames.Count == i); |
|||
this._frames.Add(new Promise<FrameData>()); |
|||
} |
|||
|
|||
frame = this._frames[i]; |
|||
} |
|||
|
|||
Timer.runInMain(() => { window.run(() => { frame.Resolve(frameData); }); }); |
|||
|
|||
i++; |
|||
} |
|||
|
|||
D.assert(gifDecoder.frameCount == i); |
|||
this._frameCount = gifDecoder.frameCount; |
|||
this._repetitionCount = gifDecoder.loopCount; |
|||
this._isDone = true; |
|||
} |
|||
|
|||
public int frameCount => this._frameCount; |
|||
|
|||
public int repetitionCount => this._repetitionCount - 1; |
|||
|
|||
|
|||
void _nextFrame() { |
|||
this._frameIndex++; |
|||
|
|||
if (this._isDone && this._frameIndex >= this._frameCount) { |
|||
this._frameIndex = 0; |
|||
} |
|||
} |
|||
|
|||
public IPromise<FrameInfo> getNextFrame() { |
|||
Promise<FrameData> frame; |
|||
|
|||
lock (this._frames) { |
|||
if (this._frameIndex == this._frames.Count) { |
|||
this._frames.Add(new Promise<FrameData>()); |
|||
} else { |
|||
D.assert(this._frameIndex < this._frames.Count); |
|||
} |
|||
|
|||
frame = this._frames[this._frameIndex]; |
|||
} |
|||
|
|||
|
|||
return frame.Then(frameData => { |
|||
this._nextFrame(); |
|||
|
|||
if (frameData.frameInfo != null) { |
|||
return frameData.frameInfo; |
|||
} |
|||
|
|||
var tex = new Texture2D(this._width, this._height, TextureFormat.BGRA32, false); |
|||
tex.LoadRawTextureData(frameData.gifFrame.bytes); |
|||
tex.Apply(); |
|||
|
|||
frameData.frameInfo = new FrameInfo { |
|||
image = new Image(tex), |
|||
duration = TimeSpan.FromMilliseconds(frameData.gifFrame.delay) |
|||
}; |
|||
frameData.gifFrame = null; // dispose gifFrame
|
|||
|
|||
return frameData.frameInfo; |
|||
}); |
|||
} |
|||
|
|||
public void Dispose() { |
|||
this._coroutine.stop(); |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: e60cc21bf138c4059a6a65888c5959e5 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
fileFormatVersion: 2 |
|||
guid: d5b7bf0751a64454ba399aad57ab71fc |
|||
timeCreated: 1535510135 |
|
|||
fileFormatVersion: 2 |
|||
guid: 99b1966ad095147729a4d478ccab99c7 |
|||
folderAsset: yes |
|||
DefaultImporter: |
|||
externalObjects: {} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
撰写
预览
正在加载...
取消
保存
Reference in new issue