Browse Source

add Charset colorspace utilities

pull/193/head
Adrian Siekierka 1 year ago
parent
commit
67a6f28af1

+ 3
- 1
src/main/java/pl/asie/charset/lib/CharsetLib.java View File

@@ -69,6 +69,8 @@ import pl.asie.charset.lib.resources.CharsetFakeResourcePack;
import pl.asie.charset.lib.resources.ColorPaletteParser;
import pl.asie.charset.lib.scheduler.Scheduler;
import pl.asie.charset.lib.utils.*;
import pl.asie.charset.lib.utils.color.Colorspace;
import pl.asie.charset.lib.utils.color.Colorspaces;

import java.lang.reflect.Field;
import java.util.Calendar;
@@ -161,7 +163,7 @@ public class CharsetLib {
MinecraftForge.EVENT_BUS.register(new CharsetLibEventHandler());
MinecraftForge.EVENT_BUS.register(Scheduler.INSTANCE);

config.save();
Colorspaces.init();
}

@SubscribeEvent(priority = EventPriority.LOWEST)

+ 0
- 46
src/main/java/pl/asie/charset/lib/utils/ColorUtils.java View File

@@ -144,50 +144,4 @@ public final class ColorUtils {
public static String getUnderscoredSuffix(EnumDyeColor color) {
return UNDERSCORE_DYE_SUFFIXES[color.getMetadata()];
}

public static float[] getYIQ(int c1) {
float r1 = ((c1 >> 16) & 0xFF) / 255.0f;
float g1 = ((c1 >> 8) & 0xFF) / 255.0f;
float b1 = (c1 & 0xFF) / 255.0f;

// YUV
/* float y1 = 0.299f * r1 + 0.587f * g1 + 0.114f * b1;
float u1 = -0.147f * r1 - 0.289f * g1 + 0.436f * b1;
float v1 = 0.615f * r1 - 0.515f * g1 - 0.100f * b1; */

// YIQ
float y1 = 0.299f * r1 + 0.587f * g1 + 0.114f * b1;
float u1 = 0.596f * r1 - 0.274f * g1 - 0.322f * b1;
float v1 = 0.211f * r1 - 0.523f * g1 + 0.312f * b1;

// weighted RGB
/* float y1 = (float) Math.sqrt(2 + (r1 / 255)) * r1;
float u1 = 2 * g1;
float v1 = (float) Math.sqrt(3 - (r1 / 255)) * b1; */

return new float[]{y1, u1, v1};
}

public static double getColorDistance(float[] c1, float[] c2) {
return Math.sqrt(getColorDistanceSq(c1, c2));
}

public static double getColorDistance(int c1, int c2) {
return Math.sqrt(getColorDistanceSq(c1, c2));
}

public static double getColorDistanceSq(float[] f1, float[] f2) {
return (f1[0] - f2[0]) * (f1[0] - f2[0]) +
(f1[1] - f2[1]) * (f1[1] - f2[1]) +
(f1[2] - f2[2]) * (f1[2] - f2[2]);
}

public static double getColorDistanceSq(int c1, int c2) {
float[] f1 = getYIQ(c1);
float[] f2 = getYIQ(c2);

return (f1[0] - f2[0]) * (f1[0] - f2[0]) +
(f1[1] - f2[1]) * (f1[1] - f2[1]) +
(f1[2] - f2[2]) * (f1[2] - f2[2]);
}
}

+ 9
- 0
src/main/java/pl/asie/charset/lib/utils/color/Colorspace.java View File

@@ -0,0 +1,9 @@
package pl.asie.charset.lib.utils.color;

public enum Colorspace {
sRGB,
YUV,
YIQ,
XYZ,
LAB
}

+ 87
- 0
src/main/java/pl/asie/charset/lib/utils/color/ColorspaceFunctions.java View File

@@ -0,0 +1,87 @@
package pl.asie.charset.lib.utils.color;

final class ColorspaceFunctions {
private static final float[] D65_WHITE = {0.9504f, 1.0000f, 1.0888f};
private static final float E = 0.008856f;
private static final float K = 903.3f;
private static final float KE = K*E;
private static final float E_CBRT = 0.2068930344f;

public static float[] XYZtoLAB(float[] v) {
float xr = v[0] / D65_WHITE[0];
float yr = v[1] / D65_WHITE[1];
float zr = v[2] / D65_WHITE[2];

float fx = (xr > E) ? (float) Math.cbrt(xr) : (K*xr + 16)/116.0f;
float fy = (yr > E) ? (float) Math.cbrt(yr) : (K*yr + 16)/116.0f;
float fz = (zr > E) ? (float) Math.cbrt(zr) : (K*zr + 16)/116.0f;

return new float[] {
116*fy - 16,
500*(fx - fy),
200*(fy - fz)
};
}

public static float[] LABtoXYZ(float[] v) {
float fy = (v[0] + 16)/116.0f;
float fx = v[1]/500.0f + fy;
float fz = fy - v[2]/200.0f;

float yr;
float xr = (fx > E_CBRT) ? (fx*fx*fx) : (116*fx - 16)/K;
float zr = (fz > E_CBRT) ? (fz*fz*fz) : (116*fz - 16)/K;
if (v[0] > KE) {
yr = ((v[0]+16)/116.0f);
yr *= yr * yr;
} else {
yr = v[0]/K;
}

return new float[] {
xr * D65_WHITE[0],
yr * D65_WHITE[1],
zr * D65_WHITE[2]
};
}

public static float[] sRGBtoXYZ(float[] v) {
return new float[] {
(float) (0.4124564*v[0] + 0.3575761*v[1] + 0.1804375*v[2]),
(float) (0.2126729*v[0] + 0.7151522*v[1] + 0.0721750*v[2]),
(float) (0.0193339*v[0] + 0.1191920*v[1] + 0.9503041*v[2])
};
}

public static float[] XYZtosRGB(float[] v) {
return new float[] {
(float) (3.2404542*v[0] + -1.5371385*v[1] + -0.4985314*v[2]),
(float) (-0.9692660*v[0] + 1.8760108*v[1] + 0.0415560*v[2]),
(float) (0.0556434*v[0] + -0.2040259*v[1] + 1.0572252*v[2])
};
}

public static float[] sRGBtoYUV(float[] v) {
return new float[] {
0.299f * v[0] + 0.587f * v[1] + 0.114f * v[2],
-0.147f * v[0] - 0.289f * v[1] + 0.436f * v[2],
0.615f * v[0] - 0.515f * v[1] - 0.100f * v[2]
};
}

public static float[] sRGBtoYIQ(float[] v) {
return new float[] {
0.299f * v[0] + 0.587f * v[1] + 0.114f * v[2],
0.596f * v[0] - 0.274f * v[1] - 0.322f * v[2],
0.211f * v[0] - 0.523f * v[1] + 0.312f * v[2]
};
}

public static float[] sRGBtoWeightedRGB(float[] v) {
return new float[] {
(float) Math.sqrt(2 + (v[0] / 255)) * v[0],
2 * v[1],
(float) Math.sqrt(3 - (v[0] / 255)) * v[2]
};
}
}

+ 153
- 0
src/main/java/pl/asie/charset/lib/utils/color/Colorspaces.java View File

@@ -0,0 +1,153 @@
package pl.asie.charset.lib.utils.color;

import com.google.common.base.Functions;
import com.google.common.base.Joiner;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Table;
import com.google.common.graph.MutableValueGraph;
import com.google.common.graph.ValueGraph;
import com.google.common.graph.ValueGraphBuilder;
import org.apache.commons.lang3.tuple.Pair;

import java.awt.color.ColorSpace;
import java.util.*;
import java.util.function.Function;

public class Colorspaces {
private static boolean initialized = false;
private static Table<Colorspace, Colorspace, Function<float[], float[]>> conversionTable;

private static void buildConversionTable(ValueGraph<Colorspace, Function<float[], float[]>> conversionGraph) {
for (Colorspace from : Colorspace.values()) {
int[] distances = new int[Colorspace.values().length];
Colorspace[] predecessors = new Colorspace[distances.length];

PriorityQueue<Colorspace> nodesToTraverse = new PriorityQueue<>(distances.length, Comparator.comparingInt(colorspace -> distances[colorspace.ordinal()]));

for (Colorspace c : Colorspace.values()) {
if (c != from) {
distances[c.ordinal()] = Integer.MAX_VALUE;
} else {
distances[c.ordinal()] = 0;
}

nodesToTraverse.add(c);
}

while (!nodesToTraverse.isEmpty()) {
Colorspace c = nodesToTraverse.poll();
for (Colorspace other : conversionGraph.successors(c)) {
if ((distances[other.ordinal()] - 1) > distances[c.ordinal()]) {
distances[other.ordinal()] = distances[c.ordinal()] + 1;
predecessors[other.ordinal()] = c;
}
}
}

System.out.println(Arrays.toString(distances));

for (Colorspace to : Colorspace.values()) {
if (from == to || predecessors[to.ordinal()] == null) continue;

Colorspace[] path = new Colorspace[distances[to.ordinal()]];
Colorspace current = to;
for (int i = path.length - 1; i >= 0; i--) {
path[i] = current;
current = predecessors[current.ordinal()];
}

Function<float[], float[]> function = null;

for (int i = 0; i < path.length - 1; i++) {
if (function == null) {
function = conversionGraph.edgeValue(path[i], path[i + 1]);
} else {
function = conversionGraph.edgeValue(path[i], path[i + 1]).compose(function);
}
}

if (function != null) {
conversionTable.put(from, to, function);
}
}
}
}

private static int asFF(float f) {
if (f >= 1.0f) return 255;
else if (f <= 0.0f) return 0;
else return (Math.round(f * 255.0f) & 0xFF);
}

public static float[] convert(float[] data, Colorspace from, Colorspace to) {
if (from == to) {
return data;
} else {
Function<float[], float[]> converter = conversionTable.get(from, to);
if (converter != null) {
return converter.apply(data);
} else {
throw new RuntimeException("Could not convert from colorspace " + from + " to " + to + "!");
}
}
}

public static int convertToRGB(float[] data, Colorspace from) {
float[] v = convert(data, from, Colorspace.sRGB);
return (asFF(v[0]) << 16) | (asFF(v[1]) << 8) | asFF(v[2]);
}

public static float[] convertFromRGB(int v, Colorspace to) {
float[] data = new float[] {
((v >> 16) & 0xFF) / 255.0f,
((v >> 8) & 0xFF) / 255.0f,
(v & 0xFF) / 255.0f
};
return convert(data, Colorspace.sRGB, to);
}

public static double getColorDistance(float[] c1, float[] c2) {
return Math.sqrt(getColorDistanceSq(c1, c2));
}

public static double getColorDistance(int c1, int c2, Colorspace space) {
return Math.sqrt(getColorDistanceSq(c1, c2, space));
}

public static double getColorDistanceSq(float[] f1, float[] f2) {
return (f1[0] - f2[0]) * (f1[0] - f2[0]) +
(f1[1] - f2[1]) * (f1[1] - f2[1]) +
(f1[2] - f2[2]) * (f1[2] - f2[2]);
}

public static double getColorDistanceSq(int c1, int c2, Colorspace space) {
float[] f1 = convertFromRGB(c1, space);
float[] f2 = convertFromRGB(c2, space);

return (f1[0] - f2[0]) * (f1[0] - f2[0]) +
(f1[1] - f2[1]) * (f1[1] - f2[1]) +
(f1[2] - f2[2]) * (f1[2] - f2[2]);
}

public static void init() {
if (!initialized) {
initialized = true;
MutableValueGraph<Colorspace, Function<float[], float[]>> conversionGraph = ValueGraphBuilder.directed().allowsSelfLoops(false).build();
for (Colorspace c : Colorspace.values()) {
conversionGraph.addNode(c);
}

conversionGraph.putEdgeValue(Colorspace.sRGB, Colorspace.XYZ, ColorspaceFunctions::sRGBtoXYZ);
conversionGraph.putEdgeValue(Colorspace.XYZ, Colorspace.sRGB, ColorspaceFunctions::XYZtosRGB);

conversionGraph.putEdgeValue(Colorspace.XYZ, Colorspace.LAB, ColorspaceFunctions::XYZtoLAB);
conversionGraph.putEdgeValue(Colorspace.LAB, Colorspace.XYZ, ColorspaceFunctions::LABtoXYZ);

conversionGraph.putEdgeValue(Colorspace.sRGB, Colorspace.YUV, ColorspaceFunctions::sRGBtoYUV);
conversionGraph.putEdgeValue(Colorspace.sRGB, Colorspace.YIQ, ColorspaceFunctions::sRGBtoYIQ);

conversionTable = HashBasedTable.create();
buildConversionTable(conversionGraph);
}
}
}

+ 3
- 1
src/main/java/pl/asie/charset/module/storage/locks/LockEventHandler.java View File

@@ -55,6 +55,8 @@ import pl.asie.charset.lib.item.FontRendererFancy;
import pl.asie.charset.lib.notify.Notice;
import pl.asie.charset.lib.utils.ColorUtils;
import pl.asie.charset.lib.utils.ThreeState;
import pl.asie.charset.lib.utils.color.Colorspace;
import pl.asie.charset.lib.utils.color.Colorspaces;

import java.util.ArrayList;
import java.util.Collection;
@@ -197,7 +199,7 @@ public class LockEventHandler {
break;
}

double d = ColorUtils.getColorDistanceSq(c, color);
double d = Colorspaces.getColorDistance(c, color, Colorspace.LAB);
if (d < maxDistance) {
maxDistance = d;
closestColor = dyeColor;

Loading…
Cancel
Save