Skip to content

Instantly share code, notes, and snippets.

@garzaa
Last active December 19, 2021 21:41
Show Gist options
  • Save garzaa/32ec27c98218d57e41c5ee753d7c3275 to your computer and use it in GitHub Desktop.
Save garzaa/32ec27c98218d57e41c5ee753d7c3275 to your computer and use it in GitHub Desktop.
Runtime Pixelart Smoothing
using UnityEngine;
using System.Collections.Generic;
using System.Collections;
public class AsyncTexture {
public int width { get; private set; }
public int height { get; private set; }
Color[] pixels;
public AsyncTexture(Color[] pixels, Vector2Int size) {
this.pixels = pixels;
this.width = size.x;
this.height = size.y;
}
public AsyncTexture(Vector2Int size) {
this.pixels = new Color[size.x * size.y];
this.width = size.x;
this.height = size.y;
}
public Color GetPixel(int x, int y) {
// if it's within bounds, then return x and y
// otherwise, return a clamped value
if (x>=width) x = width-1;
else if (x<0) x = 0;
if (y>=height) y = height-1;
else if (y<0) y = 0;
// origin is at the bottom left
return pixels[y*width + x];
}
public void SetPixel(int x, int y, Color color) {
if (x>=width) x = width-1;
else if (x<0) x = 0;
if (y>=height) y = height-1;
else if (y<0) y = 0;
pixels[y*width + x] = color;
}
public Color[] GetPixels() {
return pixels;
}
}
using UnityEngine;
public class NoSmoothSprite : MonoBehaviour {
}
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class SmoothSpriteChild : MonoBehaviour {
SpriteRenderer spriteRenderer;
SpriteSmoother spriteSmoother;
string spriteLastFrame;
public void Initialize(SpriteSmoother s) {
this.spriteSmoother = s;
spriteRenderer = GetComponent<SpriteRenderer>();
spriteRenderer.sprite = spriteSmoother.GetUpscaledSprite(spriteRenderer.sprite, this);
spriteLastFrame = spriteRenderer.sprite.name;
}
void LateUpdate() {
// if the sprites differ, get the upscaled version from spritesmoother
// sprites may differ every frame due to some animator keying, or they may not
// so for performance reasons just check every frame, and optimize the check if needed
if (spriteRenderer.sprite.name != spriteLastFrame) {
spriteRenderer.sprite = spriteSmoother.GetUpscaledSprite(spriteRenderer.sprite, this);
}
spriteLastFrame = spriteRenderer.sprite.name;
}
public void ForceUpscaledSprite() {
spriteRenderer.sprite = spriteSmoother.GetUpscaledSprite(spriteRenderer.sprite, this);
spriteLastFrame = spriteRenderer.sprite.name;
}
}
using UnityEngine;
using System.Collections;
using System;
using System.Threading.Tasks;
using System.Collections.Generic;
// TEXTURES THIS USES HAVE TO BE READABLE IN IMPORT SETTINGS
// https://www.scale2x.it/algorithm
public class SpriteSmoother : MonoBehaviour {
const float scaleFactor = 4f;
Dictionary<string, Texture2D> upscaledTextures = new Dictionary<string, Texture2D>();
Dictionary<string, Sprite> upscaledSprites = new Dictionary<string, Sprite>();
HashSet<string> queuedTextures = new HashSet<string>();
List<SmoothSpriteChild> children = new List<SmoothSpriteChild>();
void Start() {
foreach (SpriteRenderer s in GetComponentsInChildren<SpriteRenderer>()) {
if (s.GetComponent<NoSmoothSprite>()) {
continue;
}
SmoothSpriteChild smoothSprite = s.gameObject.AddComponent<SmoothSpriteChild>();
smoothSprite.Initialize(this);
children.Add(smoothSprite);
}
}
Task<AsyncTexture> UpscaleTexture(AsyncTexture input) {
return Scale4x(input);
}
async Task<AsyncTexture> Scale4x(AsyncTexture input) {
AsyncTexture t = await Task.Run(() => Scale2x(Scale2x(input)));
return t;
}
AsyncTexture Scale2x(AsyncTexture input) {
AsyncTexture output = new AsyncTexture(new Vector2Int(input.width*2, input.height*2));
for (int x=0; x<input.width; x++) {
for (int y=0; y<input.height; y++) {
/*
A B C
D E F
G H I
E0 E1
E2 E3
*/
Color b = input.GetPixel(x, y-1);
Color h = input.GetPixel(x, y+1);
Color d = input.GetPixel(x-1, y);
Color f = input.GetPixel(x+1, y);
Color e = input.GetPixel(x, y);
if (!SameColor(b, h) && !SameColor(d, f)) {
output.SetPixel(2*x, 2*y, SameColor(d, b) ? d : e);
output.SetPixel(2*x+1, 2*y, SameColor(b, f) ? f : e);
output.SetPixel(2*x, 2*y+1, SameColor(d, h) ? d : e);
output.SetPixel(2*x+1, 2*y+1, SameColor(h, f) ? f : e);
} else {
output.SetPixel(2*x, 2*y, e);
output.SetPixel(2*x+1, 2*y, e);
output.SetPixel(2*x, 2*y+1, e);
output.SetPixel(2*x+1, 2*y+1, e);
}
}
}
return output;
}
bool SameColor(Color a, Color b) {
return
Mathf.Approximately(a.r, b.r)
&& Mathf.Approximately(a.g, b.g)
&& Mathf.Approximately(a.b, b.b)
&& Mathf.Approximately(a.a, b.a);
}
public Sprite GetUpscaledSprite(Sprite s, SmoothSpriteChild caller) {
if (upscaledSprites.ContainsKey(s.name)) {
return upscaledSprites[s.name];
}
string textureName = s.texture.name;
if (!upscaledTextures.ContainsKey(textureName)) {
if (!queuedTextures.Contains(textureName)) {
queuedTextures.Add(textureName);
Debug.Log("upscaling texture "+textureName+" from "+caller.gameObject.name);
AddUpscaledTexture(
new AsyncTexture(s.texture.GetPixels(),
new Vector2Int(s.texture.width, s.texture.height)),
textureName
);
}
return s;
} else {
Sprite upscaled = ExtractSprite(s, upscaledTextures[textureName]);
upscaledSprites.Add(s.name, upscaled);
return upscaled;
}
}
async void AddUpscaledTexture(AsyncTexture texture, string name) {
AsyncTexture upscaledAsync = await UpscaleTexture(texture);
Texture2D upscaled = new Texture2D(upscaledAsync.width, upscaledAsync.height);
upscaled.filterMode = FilterMode.Point;
upscaled.mipMapBias = -10;
upscaled.SetPixels(upscaledAsync.GetPixels());
upscaled.Apply(true, false);
upscaled.name = name;
upscaledTextures[name] = upscaled;
queuedTextures.Remove(name);
// abort the loop if the editor exits play mode
if (!Application.isPlaying) return;
foreach (SmoothSpriteChild c in children) {
c.ForceUpscaledSprite();
}
}
Sprite ExtractSprite(Sprite original, Texture2D upscaledAtlas) {
Rect spriteRect = original.rect;
spriteRect.position *= scaleFactor;
spriteRect.size *= scaleFactor;
Sprite upscaled = Sprite.Create(
upscaledAtlas,
spriteRect,
original.pivot*scaleFactor / spriteRect.size,
original.pixelsPerUnit * scaleFactor,
1,
SpriteMeshType.FullRect,
original.border,
false
);
upscaled.name = original.name;
return upscaled;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment