Skip to content

Instantly share code, notes, and snippets.

@n-yoda
Created May 8, 2016 17:46
Show Gist options
  • Save n-yoda/a12cd9f7826ff05c2bb95f388da7c7ac to your computer and use it in GitHub Desktop.
Save n-yoda/a12cd9f7826ff05c2bb95f388da7c7ac to your computer and use it in GitHub Desktop.
using UnityEngine;
using UnityEditor;
using UnityEngine.Assertions;
using System.Collections.Generic;
using System.IO;
public class BlackWhite
{
[MenuItem("Assets/Compose")]
static void Compose()
{
Assert.IsTrue(Selection.objects.Length == 2, "Two textures should be selected.");
var black = Selection.objects[0] as Texture2D;
var white = Selection.objects[1] as Texture2D;
Assert.IsNotNull(black, "First selection should be a texture.");
Assert.IsNotNull(white, "Second selection should be a texture.");
while (!EditorUtility.DisplayDialog("Black White", "Black: " + black.name + ", White: " + white.name, "OK", "Swap"))
{
var x = black;
black = white;
white = x;
}
var result = Compose(black, white);
var dir = Path.GetDirectoryName(AssetDatabase.GetAssetPath(black));
var path = dir + "/" + black.name + "-" + white.name + ".png";
File.WriteAllBytes(path, result.EncodeToPNG());
Object.DestroyImmediate(result);
AssetDatabase.Refresh();
}
static Texture2D Compose(Texture2D black, Texture2D white,
float dynamicRange = 0.5f, float offsetForBlack = 0f, float offsetForWhite = 1f)
{
Assert.AreEqual(black.width, white.width, "Widths should be equivalent to each other.");
Assert.AreEqual(black.height, white.height, "Heights should be equivalent to each other.");
var result = new Texture2D(black.width, black.height, TextureFormat.RGBA32, false);
var b = ShrinkDynamicRange(black.GetPixels(), dynamicRange, offsetForBlack);
var w = ShrinkDynamicRange(white.GetPixels(), dynamicRange, offsetForWhite);
var c = result.GetPixels();
for (int y = 0; y < result.height; ++y)
{
for (int x = 0; x < result.width; ++x)
{
var i = y * result.width + x;
c[i] = Compose(b[i], w[i]);
// var bError = b[i] - (c[i] * c[i].a);
// var wError = w[i] - (Color.white * (1f - c[i].a) + c[i] * c[i].a);
}
}
result.SetPixels(c);
return result;
}
static Color[] ShrinkDynamicRange(Color[] colors, float rate, float addRate)
{
var add = new Color(1 - rate, 1 - rate, 1 - rate, 0) * addRate;
for (int i = 0; i < colors.Length; ++i)
{
colors[i] = colors[i] * rate + add;
}
return colors;
}
struct ValueErrorPair
{
public float value;
public float error;
}
static Color Compose(Color black, Color white)
{
Color result = new Color(0, 0, 0, 0);
float error = CalcError(black.r, white.r, 0, 0).error
+ CalcError(black.g, white.g, 0, 0).error
+ CalcError(black.b, white.b, 0, 0).error;
for (int i = 1; i <= 255; i++)
{
var alpha = i / 255f;
var r = GetBestRGB(black.r, white.r, alpha);
var g = GetBestRGB(black.g, white.g, alpha);
var b = GetBestRGB(black.b, white.b, alpha);
var e = r.error + g.error + b.error;
if (e <= error)
{
error = e;
result.r = r.value;
result.g = g.value;
result.b = b.value;
result.a = alpha;
}
}
return result;
}
static ValueErrorPair GetBestRGB(float b, float w, float a)
{
ValueErrorPair result = CalcError(b, w, a, 0);
ValueErrorPair one = CalcError(b, w, a, 1);
if (one.error < result.error)
{
result = one;
}
var minPoint = (b + w + a - 1) / (2 * a);
if (0f <= minPoint && minPoint <= 1f)
{
ValueErrorPair min = CalcError(b, w, a, minPoint);
if (min.error < result.error)
{
result = min;
}
}
return result;
}
static ValueErrorPair CalcError(float b, float w, float a, float c)
{
float diffB = a * c - b;
float diffW = ((1 - a) + a * c) - w;
return new ValueErrorPair()
{
value = c,
error = diffB * diffB + diffW * diffW,
};
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment