您最多选择25个主题
主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
603 行
20 KiB
603 行
20 KiB
using System;
|
|
using System.Collections.Generic;
|
|
using Unity.UIWidgets.foundation;
|
|
using UnityEngine;
|
|
|
|
namespace Unity.UIWidgets.ui {
|
|
public enum TileMode : int {
|
|
clamp = 0,
|
|
mirror = 1,
|
|
repeated = 2,
|
|
}
|
|
|
|
public abstract class PaintShader {
|
|
}
|
|
|
|
|
|
public class Gradient : PaintShader {
|
|
|
|
public static Gradient linear(
|
|
Offset start, Offset end, List<Color> colors,
|
|
List<double> colorStops = null, TileMode tileMode = TileMode.clamp,
|
|
Matrix3 matrix = null) {
|
|
D.assert(PaintingUtils._offsetIsValid(start));
|
|
D.assert(PaintingUtils._offsetIsValid(end));
|
|
D.assert(colors != null && colors.Count >= 2);
|
|
|
|
_validateColorStops(ref colors, ref colorStops);
|
|
|
|
return new _LinearGradient(start, end, colors, colorStops, tileMode, matrix);
|
|
}
|
|
|
|
public static Gradient radial(
|
|
Offset center, double radius, List<Color> colors,
|
|
List<double> colorStops = null, TileMode tileMode = TileMode.clamp,
|
|
Matrix3 matrix = null) {
|
|
D.assert(PaintingUtils._offsetIsValid(center));
|
|
D.assert(colors != null && colors.Count >= 2);
|
|
|
|
_validateColorStops(ref colors, ref colorStops);
|
|
|
|
return new _RadialGradient(center, radius, colors, colorStops, tileMode, matrix);
|
|
}
|
|
|
|
public static Gradient sweep(
|
|
Offset center, List<Color> colors,
|
|
List<double> colorStops = null, TileMode tileMode = TileMode.clamp,
|
|
double startAngle = 0.0, double endAngle = Math.PI * 2,
|
|
Matrix3 matrix = null) {
|
|
D.assert(PaintingUtils._offsetIsValid(center));
|
|
D.assert(colors != null && colors.Count >= 2);
|
|
D.assert(startAngle < endAngle);
|
|
|
|
_validateColorStops(ref colors, ref colorStops);
|
|
|
|
return new _SweepGradient(center, colors, colorStops, tileMode, startAngle, endAngle, matrix);
|
|
}
|
|
|
|
static void _validateColorStops(ref List<Color> colors, ref List<double> colorStops) {
|
|
if (colorStops == null) {
|
|
colors = new List<Color>(colors);
|
|
|
|
colorStops = new List<double>(colors.Count);
|
|
colorStops.Add(0);
|
|
var stepCount = colors.Count - 1;
|
|
var step = 1.0 / stepCount;
|
|
for (int i = 1; i < stepCount; i++) {
|
|
colorStops.Add(colorStops[i - 1] + step);
|
|
}
|
|
colorStops.Add(1);
|
|
|
|
return;
|
|
}
|
|
|
|
if (colors.Count != colorStops.Count) {
|
|
throw new ArgumentException("\"colors\" and \"colorStops\" arguments must have equal length.");
|
|
}
|
|
|
|
var dummyFirst = colorStops[0] != 0;
|
|
var dummyLast = colorStops[colorStops.Count - 1] != 1;
|
|
var count = colors.Count + (dummyFirst ? 1 : 0) + (dummyFirst ? 1 : 0);
|
|
|
|
var newColors = new List<Color>(count);
|
|
if (dummyFirst) {
|
|
newColors.Add(colors[0]);
|
|
}
|
|
for (int i = 0; i < colors.Count; i++) {
|
|
newColors.Add(colors[i]);
|
|
}
|
|
if (dummyLast) {
|
|
newColors.Add(colors[colors.Count - 1]);
|
|
}
|
|
|
|
var newColorStops = new List<double>(count);
|
|
if (dummyFirst) {
|
|
newColorStops.Add(0.0);
|
|
}
|
|
var prevStop = 0.0;
|
|
for (int i = 0; i < colorStops.Count; i++) {
|
|
var stop = Math.Max(Math.Min(colorStops[i], 1.0), prevStop);
|
|
newColorStops.Add(stop);
|
|
prevStop = stop;
|
|
}
|
|
if (dummyLast) {
|
|
newColorStops.Add(1.0);
|
|
}
|
|
|
|
colors = newColors;
|
|
colorStops = newColorStops;
|
|
}
|
|
|
|
static readonly GradientBitmapCache _cache = new GradientBitmapCache();
|
|
|
|
internal static Image makeTexturedColorizer(List<Color> colors, List<double> positions) {
|
|
int count = colors.Count;
|
|
D.assert(count >= 2);
|
|
|
|
bool bottomHardStop = ScalarUtils.ScalarNearlyEqual((float) positions[0], (float) positions[1]);
|
|
bool topHardStop = ScalarUtils.ScalarNearlyEqual((float) positions[count - 2], (float) positions[count - 1]);
|
|
|
|
int offset = 0;
|
|
if (bottomHardStop) {
|
|
offset += 1;
|
|
count--;
|
|
}
|
|
if (topHardStop) {
|
|
count--;
|
|
}
|
|
|
|
if (offset != 0 || count != colors.Count) {
|
|
colors = colors.GetRange(offset, count);
|
|
positions = positions.GetRange(offset, count);
|
|
}
|
|
|
|
return _cache.getGradient(colors, positions);
|
|
}
|
|
}
|
|
|
|
class GradientBitmapCache : IDisposable {
|
|
public GradientBitmapCache(int maxEntries = 32, int resolution = 256) {
|
|
this.maxEntries = maxEntries;
|
|
this.resolution = resolution;
|
|
this._entryCount = 0;
|
|
this._head = this._tail = null;
|
|
|
|
D.assert(this.validate);
|
|
}
|
|
|
|
public readonly int maxEntries;
|
|
public readonly int resolution;
|
|
|
|
int _entryCount;
|
|
_Entry _head;
|
|
_Entry _tail;
|
|
|
|
public Image getGradient(List<Color> colors, List<double> positions) {
|
|
var key = new _Key(colors, positions);
|
|
|
|
if (!this.find(key, out var image)) {
|
|
image = this.fillGradient(colors, positions);
|
|
this.add(key, image);
|
|
}
|
|
|
|
return image;
|
|
}
|
|
|
|
public void Dispose() {
|
|
D.assert(this.validate);
|
|
|
|
// just remove the references, Image will dispose by themselves.
|
|
this._entryCount = 0;
|
|
this._head = this._tail = null;
|
|
}
|
|
|
|
_Entry release(_Entry entry) {
|
|
if (entry.prev != null) {
|
|
D.assert(this._head != entry);
|
|
entry.prev.next = entry.next;
|
|
} else {
|
|
D.assert(this._head == entry);
|
|
this._head = entry.next;
|
|
}
|
|
|
|
if (entry.next != null) {
|
|
D.assert(this._tail != entry);
|
|
entry.next.prev = entry.prev;
|
|
} else {
|
|
D.assert(this._tail == entry);
|
|
this._tail = entry.prev;
|
|
}
|
|
|
|
return entry;
|
|
}
|
|
|
|
void attachToHead(_Entry entry) {
|
|
entry.prev = null;
|
|
entry.next = this._head;
|
|
if (this._head != null) {
|
|
this._head.prev = entry;
|
|
} else {
|
|
this._tail = entry;
|
|
}
|
|
this._head = entry;
|
|
}
|
|
|
|
bool find(_Key key, out Image image) {
|
|
D.assert(this.validate);
|
|
|
|
var entry = this._head;
|
|
while (entry != null) {
|
|
if (entry.key == key) {
|
|
image = entry.image;
|
|
|
|
// move to the head of our list, so we purge it last
|
|
this.release(entry);
|
|
this.attachToHead(entry);
|
|
D.assert(this.validate);
|
|
return true;
|
|
}
|
|
entry = entry.next;
|
|
}
|
|
|
|
D.assert(this.validate);
|
|
image = null;
|
|
return false;
|
|
}
|
|
|
|
void add(_Key key, Image image) {
|
|
if (this._entryCount == this.maxEntries) {
|
|
D.assert(this._tail != null);
|
|
this.release(this._tail);
|
|
this._entryCount--;
|
|
}
|
|
|
|
var entry = new _Entry {key = key, image = image};
|
|
this.attachToHead(entry);
|
|
this._entryCount++;
|
|
}
|
|
|
|
Image fillGradient(List<Color> colors, List<double> positions) {
|
|
Texture2D tex = new Texture2D(this.resolution, 1, TextureFormat.RGBA32, false);
|
|
tex.hideFlags = HideFlags.HideAndDontSave;
|
|
tex.wrapMode = TextureWrapMode.Clamp;
|
|
|
|
var bytes = new byte[this.resolution * 4];
|
|
|
|
int count = colors.Count;
|
|
int prevIndex = 0;
|
|
for (int i = 1; i < count; i++) {
|
|
// Historically, stops have been mapped to [0, 256], with 256 then nudged to the next
|
|
// smaller value, then truncate for the texture index. This seems to produce the best
|
|
// results for some common distributions, so we preserve the behavior.
|
|
int nextIndex = (int) Mathf.Min((float) positions[i] * this.resolution, this.resolution - 1);
|
|
|
|
if (nextIndex > prevIndex) {
|
|
var c0 = colors[i - 1];
|
|
var c1 = colors[i];
|
|
|
|
var step = 1.0f / (nextIndex - prevIndex);
|
|
var t = 0.0;
|
|
|
|
for (int curIndex = prevIndex; curIndex <= nextIndex; ++curIndex) {
|
|
var c = Color.lerp(c0, c1, t);
|
|
|
|
var baseIndex = curIndex << 2;
|
|
bytes[baseIndex] = (byte) c.red;
|
|
bytes[baseIndex + 1] = (byte) c.green;
|
|
bytes[baseIndex + 2] = (byte) c.blue;
|
|
bytes[baseIndex + 3] = (byte) c.alpha;
|
|
|
|
t += step;
|
|
}
|
|
}
|
|
|
|
prevIndex = nextIndex;
|
|
}
|
|
|
|
D.assert(prevIndex == this.resolution - 1);
|
|
|
|
tex.LoadRawTextureData(bytes);
|
|
tex.Apply();
|
|
return new Image(tex);
|
|
}
|
|
|
|
bool validate() {
|
|
D.assert(this._entryCount >= 0 && this._entryCount <= this.maxEntries);
|
|
|
|
if (this._entryCount > 0) {
|
|
D.assert(null == this._head.prev);
|
|
D.assert(null == this._tail.next);
|
|
|
|
if (this._entryCount == 1) {
|
|
D.assert(this._head == this._tail);
|
|
} else {
|
|
D.assert(this._head != this._tail);
|
|
}
|
|
|
|
var entry = this._head;
|
|
int count = 0;
|
|
while (entry != null) {
|
|
count += 1;
|
|
entry = entry.next;
|
|
}
|
|
D.assert(count == this._entryCount);
|
|
|
|
entry = this._tail;
|
|
while (entry != null) {
|
|
count -= 1;
|
|
entry = entry.prev;
|
|
}
|
|
D.assert(0 == count);
|
|
} else {
|
|
D.assert(null == this._head);
|
|
D.assert(null == this._tail);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
class _Entry {
|
|
public _Entry prev;
|
|
public _Entry next;
|
|
|
|
public _Key key;
|
|
public Image image;
|
|
}
|
|
|
|
class _Key : IEquatable<_Key> {
|
|
public _Key(List<Color> colors, List<double> positions) {
|
|
D.assert(colors != null);
|
|
D.assert(positions != null);
|
|
D.assert(colors.Count == positions.Count);
|
|
|
|
this.colors = colors;
|
|
this.positions = positions;
|
|
this._hashCode = _getHashCode(this.colors) ^ _getHashCode(this.positions);
|
|
;
|
|
}
|
|
|
|
public readonly List<Color> colors;
|
|
|
|
public readonly List<double> positions;
|
|
|
|
readonly int _hashCode;
|
|
|
|
public bool Equals(_Key other) {
|
|
if (ReferenceEquals(null, other)) {
|
|
return false;
|
|
}
|
|
if (ReferenceEquals(this, other)) {
|
|
return true;
|
|
}
|
|
return _listEquals(this.colors, other.colors) &&
|
|
_listEquals(this.positions, other.positions);
|
|
}
|
|
|
|
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((_Key) obj);
|
|
}
|
|
|
|
public override int GetHashCode() {
|
|
return this._hashCode;
|
|
}
|
|
|
|
public static bool operator ==(_Key left, _Key right) {
|
|
return Equals(left, right);
|
|
}
|
|
|
|
public static bool operator !=(_Key left, _Key right) {
|
|
return !Equals(left, right);
|
|
}
|
|
|
|
static int _getHashCode<T>(List<T> list) {
|
|
unchecked {
|
|
var hashCode = 0;
|
|
foreach (var item in list) {
|
|
hashCode = (hashCode * 397) ^ item.GetHashCode();
|
|
}
|
|
return hashCode;
|
|
}
|
|
}
|
|
|
|
static bool _listEquals<T>(List<T> left, List<T> right) {
|
|
if (left.Count != right.Count) {
|
|
return false;
|
|
}
|
|
|
|
for (int i = 0; i < left.Count; i++) {
|
|
if (!left[i].Equals(right[i])) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
class _LinearGradient : Gradient {
|
|
public _LinearGradient(
|
|
Offset start, Offset end, List<Color> colors,
|
|
List<double> colorStops, TileMode tileMode,
|
|
Matrix3 matrix = null) {
|
|
this.start = start;
|
|
this.end = end;
|
|
this.colors = colors;
|
|
this.colorStops = colorStops;
|
|
this.tileMode = tileMode;
|
|
this.matrix = matrix;
|
|
this.ptsToUnit = ptsToUnitMatrix(start, end);
|
|
this.gradientTex = makeTexturedColorizer(colors, colorStops);
|
|
}
|
|
|
|
public readonly Offset start;
|
|
public readonly Offset end;
|
|
public readonly List<Color> colors;
|
|
public readonly List<double> colorStops;
|
|
public readonly TileMode tileMode;
|
|
public readonly Matrix3 matrix;
|
|
public readonly Matrix3 ptsToUnit;
|
|
public readonly Image gradientTex;
|
|
|
|
public Color leftColor {
|
|
get { return this.colors[0]; }
|
|
}
|
|
|
|
public Color rightColor {
|
|
get { return this.colors[this.colors.Count - 1]; }
|
|
}
|
|
|
|
public Matrix3 getGradientMat(Matrix3 ctm) {
|
|
var mat = Matrix3.I();
|
|
ctm.invert(mat); // just use I() if not invertible.
|
|
|
|
if (this.matrix != null) {
|
|
mat.postConcat(this.matrix);
|
|
}
|
|
mat.postConcat(this.ptsToUnit);
|
|
return mat;
|
|
}
|
|
|
|
static Matrix3 ptsToUnitMatrix(Offset start, Offset end) {
|
|
var vec = end - start;
|
|
var mag = vec.distance;
|
|
var inv = mag != 0 ? 1 / mag : 0;
|
|
vec = vec.scale(inv);
|
|
|
|
var matrix = Matrix3.I();
|
|
matrix.setSinCos((float) -vec.dy, (float) vec.dx, (float) start.dx, (float) start.dy);
|
|
matrix.postTranslate((float) -start.dx, (float) -start.dy);
|
|
matrix.postScale((float) inv, (float) inv);
|
|
return matrix;
|
|
}
|
|
}
|
|
|
|
class _RadialGradient : Gradient {
|
|
public _RadialGradient(
|
|
Offset center, double radius, List<Color> colors,
|
|
List<double> colorStops = null, TileMode tileMode = TileMode.clamp,
|
|
Matrix3 matrix = null
|
|
) {
|
|
this.center = center;
|
|
this.radius = radius;
|
|
this.colors = colors;
|
|
this.colorStops = colorStops;
|
|
this.tileMode = tileMode;
|
|
this.matrix = matrix;
|
|
this.ptsToUnit = radToUnitMatrix(center, radius);
|
|
this.gradientTex = makeTexturedColorizer(colors, colorStops);
|
|
}
|
|
|
|
public readonly Offset center;
|
|
public readonly double radius;
|
|
public readonly List<Color> colors;
|
|
public readonly List<double> colorStops;
|
|
public readonly TileMode tileMode;
|
|
public readonly Matrix3 matrix;
|
|
public readonly Matrix3 ptsToUnit;
|
|
public readonly Image gradientTex;
|
|
|
|
public Color leftColor {
|
|
get { return this.colors[0]; }
|
|
}
|
|
|
|
public Color rightColor {
|
|
get { return this.colors[this.colors.Count - 1]; }
|
|
}
|
|
|
|
public Matrix3 getGradientMat(Matrix3 ctm) {
|
|
var mat = Matrix3.I();
|
|
ctm.invert(mat); // just use I() if not invertible.
|
|
|
|
if (this.matrix != null) {
|
|
mat.postConcat(this.matrix);
|
|
}
|
|
mat.postConcat(this.ptsToUnit);
|
|
return mat;
|
|
}
|
|
|
|
static Matrix3 radToUnitMatrix(Offset center, double radius) {
|
|
var inv = radius != 0 ? 1 / radius : 0;
|
|
|
|
var matrix = Matrix3.I();
|
|
matrix.setTranslate((float) -center.dx, (float) -center.dy);
|
|
matrix.postScale((float) inv, (float) inv);
|
|
return matrix;
|
|
}
|
|
}
|
|
|
|
class _SweepGradient : Gradient {
|
|
|
|
public _SweepGradient(
|
|
Offset center, List<Color> colors,
|
|
List<double> colorStops = null, TileMode tileMode = TileMode.clamp,
|
|
double startAngle = 0.0, double endAngle = Math.PI * 2,
|
|
Matrix3 matrix = null
|
|
) {
|
|
this.center = center;
|
|
this.colors = colors;
|
|
this.colorStops = colorStops;
|
|
this.tileMode = tileMode;
|
|
this.startAngle = startAngle;
|
|
this.endAngle = endAngle;
|
|
this.matrix = matrix;
|
|
|
|
var t0 = startAngle / (Math.PI * 2f);
|
|
var t1 = endAngle / (Math.PI * 2f);
|
|
this.bias = -t0;
|
|
this.scale = 1f / (t1 - t0);
|
|
|
|
var ptsToUnit = Matrix3.I();
|
|
ptsToUnit.setTranslate((float) -center.dx, (float) -center.dy);
|
|
this.ptsToUnit = ptsToUnit;
|
|
|
|
this.gradientTex = makeTexturedColorizer(colors, colorStops);
|
|
}
|
|
|
|
public readonly Offset center;
|
|
public readonly List<Color> colors;
|
|
public readonly List<double> colorStops;
|
|
public readonly TileMode tileMode;
|
|
public readonly double startAngle;
|
|
public readonly double endAngle;
|
|
public readonly Matrix3 matrix;
|
|
public readonly Matrix3 ptsToUnit;
|
|
public readonly Image gradientTex;
|
|
public readonly double bias;
|
|
public readonly double scale;
|
|
|
|
public Color leftColor {
|
|
get { return this.colors[0]; }
|
|
}
|
|
|
|
public Color rightColor {
|
|
get { return this.colors[this.colors.Count - 1]; }
|
|
}
|
|
|
|
public Matrix3 getGradientMat(Matrix3 ctm) {
|
|
var mat = Matrix3.I();
|
|
ctm.invert(mat); // just use I() if not invertible.
|
|
|
|
if (this.matrix != null) {
|
|
mat.postConcat(this.matrix);
|
|
}
|
|
mat.postConcat(this.ptsToUnit);
|
|
return mat;
|
|
}
|
|
}
|
|
|
|
public class ImageShader : PaintShader {
|
|
public ImageShader(Image image,
|
|
TileMode tileMode = TileMode.clamp, Matrix3 matrix = null) {
|
|
this.image = image;
|
|
this.tileMode = tileMode;
|
|
this.matrix = matrix;
|
|
}
|
|
|
|
public readonly Image image;
|
|
public readonly TileMode tileMode;
|
|
public readonly Matrix3 matrix;
|
|
|
|
public Matrix3 getShaderMat(Matrix3 ctm) {
|
|
var mat = Matrix3.I();
|
|
ctm.invert(mat); // just use I() if not invertible.
|
|
|
|
if (this.matrix != null) {
|
|
mat.postConcat(this.matrix);
|
|
}
|
|
|
|
mat.postScale(1f / this.image.width, 1f / this.image.height);
|
|
return mat;
|
|
}
|
|
}
|
|
}
|