Initial version. Uses external library for image resizing. Fixed memory consumption, but slow.

This commit is contained in:
ftc
2021-01-14 23:10:11 +03:00
commit 7eb7ac33cc
47 changed files with 5209 additions and 0 deletions
+146
View File
@@ -0,0 +1,146 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
namespace Cedar.Core.Coroutines
{
public class CoroutineRunner : MonoBehaviour
{
private class DelayedCallInfo
{
public Action action;
public Coroutine coroutine;
}
public static CoroutineRunner Instance
{
get
{
if (self == null)
{
self = InstantiateCoroutineRunner();
}
return self;
}
}
public static CoroutineRunner ImmortalInstance
{
get
{
if (immortalSelf == null)
{
immortalSelf = InstantiateCoroutineRunner();
DontDestroyOnLoad(immortalSelf.gameObject);
}
return immortalSelf;
}
}
private static CoroutineRunner self;
private static CoroutineRunner immortalSelf;
//private Dictionary<Action, Coroutine> delayedCallsDictionary = new Dictionary<Action, Coroutine>();
private List<DelayedCallInfo> delayedCallsList = new List<DelayedCallInfo>();
private static CoroutineRunner InstantiateCoroutineRunner()
{
var go = new GameObject("CoroutineRunner");
return go.AddComponent<CoroutineRunner>();
}
private void OnDestroy()
{
if (self == this)
{
self = null;
}
}
public void RunCoroutine(IEnumerator action)
{
StartCoroutine(action);
}
public void WaitFewFramesAndDo(Action action, int frameCount = 1)
{
StartCoroutine(WaitCoroutine(action, frameCount));
}
public void WaitForEndOfFrameAndDo(Action action)
{
StartCoroutine(WaitEndOfFrameCoroutine(action));
}
public void DelayedCall(Action action, float delay, bool ignoreTimeScale)
{
DelayedCallInfo newDelayedCall = new DelayedCallInfo();
newDelayedCall.action = action;
delayedCallsList.Add(newDelayedCall);
newDelayedCall.coroutine = StartCoroutine(DelayedCallCoroutine(newDelayedCall, delay, ignoreTimeScale));
}
public void DelayedCall(Action action, int framesDelay)
{
DelayedCallInfo newDelayedCall = new DelayedCallInfo();
newDelayedCall.action = action;
delayedCallsList.Add(newDelayedCall);
newDelayedCall.coroutine = StartCoroutine(DelayedCallCoroutine(newDelayedCall, framesDelay));
}
public void CancelDelayedCall(Action action)
{
// по аналогии с CancelInvoke - выпиливаем все вызовы этого метода
for (int i = delayedCallsList.Count - 1; i >= 0; i--)
if (delayedCallsList[i].action == action)
{
StopCoroutine(delayedCallsList[i].coroutine);
delayedCallsList.RemoveAt(i);
}
// если отложенные вызовы будут использоваться слишком часто,
// то можно будет завести пул с инфой, дабы не генерить мусор
}
private IEnumerator WaitCoroutine(Action action, int frameCount)
{
while(frameCount > 0)
{
yield return null;
frameCount--;
}
action();
}
private IEnumerator WaitEndOfFrameCoroutine(Action action)
{
yield return new WaitForEndOfFrame();
action();
}
private IEnumerator DelayedCallCoroutine(DelayedCallInfo delayedCall, float delay, bool ignoreTimeScale)
{
if (ignoreTimeScale)
yield return new WaitForSecondsRealtime(delay);
else
yield return new WaitForSeconds(delay);
if (delayedCall.action != null) delayedCall.action();
delayedCallsList.Remove(delayedCall);
}
private IEnumerator DelayedCallCoroutine(DelayedCallInfo delayedCall, int framesDelay)
{
while (framesDelay > 0)
{
yield return null;
framesDelay--;
}
if (delayedCall.action != null) delayedCall.action();
delayedCallsList.Remove(delayedCall);
}
}
}
+12
View File
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 4bc53f984b817ed489702c7877b6e1bb
timeCreated: 1443025350
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+63
View File
@@ -0,0 +1,63 @@
using Cedar.Core.Coroutines;
using HtmlAgilityPack;
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine.Networking;
public enum LoaderStatus
{
Initializing,
Ready,
Error
}
public class ImageLoader
{
private bool ready = false;
private string errorMessage;
private string url;
public LoaderStatus Status
{
get
{
if (!ready) return LoaderStatus.Initializing;
if (string.IsNullOrEmpty(errorMessage)) return LoaderStatus.Ready;
return LoaderStatus.Error;
}
}
public List<string> images;
public ImageLoader(string url)
{
this.url = url;
CoroutineRunner.Instance.StartCoroutine(OnUrlUpdated());
}
private IEnumerator OnUrlUpdated()
{
errorMessage = null;
var request = UnityWebRequest.Get(url);
yield return request.SendWebRequest();
if (request.isNetworkError || request.isHttpError)
{
errorMessage = request.error;
ready = true;
yield break;
}
var responseText = request.downloadHandler.text;
var doc = new HtmlDocument();
doc.LoadHtml(responseText);
var nodes = doc.DocumentNode.SelectNodes("//a[preceding-sibling::img[@alt=\"[IMG]\"]]");
images = new List<string>();
foreach (var node in nodes)
{
var target = node.GetAttributeValue("href", "");
images.Add(url + "/" + target);
}
ready = true;
request.Dispose();
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 45b2fdcd4d8436d49b913061031f9867
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+185
View File
@@ -0,0 +1,185 @@
using System.Collections;
using System.IO;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;
using Image = UnityEngine.UI.Image;
using Color = UnityEngine.Color;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp;
public class PhotoFrame : MonoBehaviour
{
[SerializeField]
GameObject mainUI;
[SerializeField]
RectTransform imageContainer;
[SerializeField]
Image[] images;
[SerializeField]
InputField url;
private int currentImage;
private Image CurrentImage
{
get { return images[currentImage]; }
}
private Image NextImage
{
get { return images[(currentImage + 1) % images.Length]; }
}
public void Start()
{
currentImage = 0;
ActivateUI(true);
}
private void ActivateUI(bool v)
{
mainUI.gameObject.SetActive(v);
for (int i = 0; i < images.Length; i++)
{
images[i].gameObject.SetActive(!v);
}
}
Coroutine mainCoroutine;
public void OnRunButtonClicked()
{
ActivateUI(false);
mainCoroutine = StartCoroutine(SwitchImages());
}
public void OnStopButtonClicked()
{
if (mainCoroutine != null)
{
StopCoroutine(mainCoroutine);
mainCoroutine = null;
}
for (int i = 0; i < images.Length; i++)
{
if (images[i].sprite != null)
{
Destroy(images[i].sprite.texture);
Destroy(images[i].sprite);
images[i].sprite = null;
}
}
ActivateUI(true);
}
private IEnumerator SwitchImages()
{
yield return null;
var loader = new ImageLoader(url.text);
while (loader.Status == LoaderStatus.Initializing) yield return null;
Color opaque = new Color(1, 1, 1, 1);
Color transparent = new Color(1, 1, 1, 0);
CurrentImage.color = transparent;
NextImage.color = transparent;
while (true)
{
int imageIndex = Random.Range(0, loader.images.Count);
string imageUrl = loader.images[imageIndex];
var path = Application.persistentDataPath + "/source." + System.IO.Path.GetExtension(imageUrl);
var request = UnityWebRequest.Get(imageUrl);
request.downloadHandler = new DownloadHandlerFile(path);
yield return request.SendWebRequest();
if (request.isNetworkError || request.isHttpError)
{
yield return new WaitForSeconds(1f);
continue;
}
SetImageTexture(NextImage, path);
request.downloadHandler.Dispose();
request.Dispose();
float currentStart = CurrentImage.color.a;
float currentFinish = transparent.a;
float nextStart = NextImage.color.a;
float nextFinish = opaque.a;
float startTime = Time.time;
while (Time.time < startTime + 2f)
{
float ct = Time.time;
float x = (ct - startTime) / 2f;
float currentA = currentStart + x * (currentFinish - currentStart);
Color cC = new Color(CurrentImage.color.r, CurrentImage.color.g, CurrentImage.color.b, currentA);
CurrentImage.color = cC;
float nextA = nextStart + x * (nextFinish - nextStart);
Color nC = new Color(NextImage.color.r, NextImage.color.g, NextImage.color.b, nextA);
NextImage.color = nC;
yield return null;
}
yield return new WaitForSeconds(5f);
currentImage = (currentImage + 1) % images.Length;
Resources.UnloadUnusedAssets();
System.GC.Collect();
}
}
private void SetImageTexture(Image nextImage, string path)
{
if (nextImage.sprite != null)
{
Destroy(nextImage.sprite.texture);
Destroy(nextImage.sprite);
nextImage.sprite = null;
}
float imageWidth = imageContainer.rect.width;
float imageHeight = imageContainer.rect.height;
//
int maxHeight, maxWidth;
if (imageWidth < imageHeight)
{
// scale to height
maxHeight = 2048;
maxWidth = (int)(2048 / imageHeight * imageWidth);
}
else
{
// scale to width
maxWidth = 2048;
maxHeight = (int)(2048 / imageWidth * imageHeight);
}
Texture2D tex = new Texture2D(2, 2);
tex.LoadImage(File.ReadAllBytes(path));
float factor = Mathf.Min((float) maxWidth / tex.width, (float)maxHeight / tex.height);
int newWidth = (int)(tex.width * factor);
int newHeight = (int)(tex.height * factor);
Destroy(tex);
var newPath = Scale(path, newWidth, newHeight);
tex = new Texture2D(2, 2);
tex.LoadImage(File.ReadAllBytes(newPath));
//TextureScale.Bilinear(tex, newWidth, newHeight);
//TextureScale.Point(tex, newWidth, newHeight);
//Color[] cs = tex.GetPixels();
var sprite = Sprite.Create(tex, new Rect(0, 0, tex.width, tex.height), Vector2.zero);
nextImage.sprite = sprite;
nextImage.rectTransform.anchorMin = new Vector2((maxWidth - (float)newWidth) / maxWidth / 2, (maxHeight - (float)newHeight) / maxHeight / 2);
}
private string Scale(string path, int newWidth, int newHeight)
{
var resultPath = Path.GetDirectoryName(path) + "/target." + Path.GetExtension(path);
using (SixLabors.ImageSharp.Image image = SixLabors.ImageSharp.Image.Load(path))
{
image.Mutate(x => x.Resize(newWidth, newHeight));
image.Save(resultPath);
}
return resultPath;
}
}
+11
View File
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2762b9d539a3dcb4e99f9417cf586838
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+156
View File
@@ -0,0 +1,156 @@
using System.Threading;
using UnityEngine;
public class TextureScale
{
public class ThreadData
{
public int start;
public int end;
public ThreadData(int s, int e)
{
start = s;
end = e;
}
}
private static Color32[] texColors;
private static Color32[] newColors;
private static int w;
private static float ratioX;
private static float ratioY;
private static int w2;
private static int finishCount;
private static Mutex mutex;
public static void Point(Texture2D tex, int newWidth, int newHeight)
{
ThreadedScale(tex, newWidth, newHeight, false);
}
public static void Bilinear(Texture2D tex, int newWidth, int newHeight)
{
ThreadedScale(tex, newWidth, newHeight, true);
}
private static void ThreadedScale(Texture2D tex, int newWidth, int newHeight, bool useBilinear)
{
texColors = tex.GetPixels32();
newColors = new Color32[newWidth * newHeight];
if (useBilinear)
{
ratioX = 1.0f / ((float)newWidth / (tex.width - 1));
ratioY = 1.0f / ((float)newHeight / (tex.height - 1));
}
else
{
ratioX = ((float)tex.width) / newWidth;
ratioY = ((float)tex.height) / newHeight;
}
w = tex.width;
w2 = newWidth;
var cores = Mathf.Min(SystemInfo.processorCount, newHeight);
var slice = newHeight / cores;
finishCount = 0;
if (mutex == null)
{
mutex = new Mutex(false);
}
if (cores > 1)
{
int i = 0;
ThreadData threadData;
for (i = 0; i < cores - 1; i++)
{
threadData = new ThreadData(slice * i, slice * (i + 1));
ParameterizedThreadStart ts = useBilinear ? new ParameterizedThreadStart(BilinearScale) : new ParameterizedThreadStart(PointScale);
Thread thread = new Thread(ts);
thread.Start(threadData);
}
threadData = new ThreadData(slice * i, newHeight);
if (useBilinear)
{
BilinearScale(threadData);
}
else
{
PointScale(threadData);
}
while (finishCount < cores)
{
Thread.Sleep(1);
}
}
else
{
ThreadData threadData = new ThreadData(0, newHeight);
if (useBilinear)
{
BilinearScale(threadData);
}
else
{
PointScale(threadData);
}
}
tex.Resize(newWidth, newHeight);
tex.SetPixels32(newColors);
tex.Apply();
texColors = null;
newColors = null;
}
public static void BilinearScale(System.Object obj)
{
ThreadData threadData = (ThreadData)obj;
for (var y = threadData.start; y < threadData.end; y++)
{
int yFloor = (int)Mathf.Floor(y * ratioY);
var y1 = yFloor * w;
var y2 = (yFloor + 1) * w;
var yw = y * w2;
for (var x = 0; x < w2; x++)
{
int xFloor = (int)Mathf.Floor(x * ratioX);
var xLerp = x * ratioX - xFloor;
newColors[yw + x] = ColorLerpUnclamped(ColorLerpUnclamped(texColors[y1 + xFloor], texColors[y1 + xFloor + 1], xLerp),
ColorLerpUnclamped(texColors[y2 + xFloor], texColors[y2 + xFloor + 1], xLerp),
y * ratioY - yFloor);
}
}
mutex.WaitOne();
finishCount++;
mutex.ReleaseMutex();
}
public static void PointScale(System.Object obj)
{
ThreadData threadData = (ThreadData)obj;
for (var y = threadData.start; y < threadData.end; y++)
{
var thisY = (int)(ratioY * y) * w;
var yw = y * w2;
for (var x = 0; x < w2; x++)
{
newColors[yw + x] = texColors[(int)(thisY + ratioX * x)];
}
}
mutex.WaitOne();
finishCount++;
mutex.ReleaseMutex();
}
private static Color32 ColorLerpUnclamped(Color32 c1, Color32 c2, float value)
{
return new Color32((byte)(c1.r + (c2.r - c1.r) * value),
(byte)(c1.g + (c2.g - c1.g) * value),
(byte)(c1.b + (c2.b - c1.b) * value),
(byte)(c1.a + (c2.a - c1.a) * value));
}
}
+12
View File
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 2fe3d69007758384fa74b123515911a5
timeCreated: 1489994125
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: