mirror of https://github.com/adamdruppe/arsd.git
merge image.d definitions; kill phobos dependency - potentially shaves 200 K off the exe!
This commit is contained in:
parent
93248ce308
commit
171bd2ee8e
296
color.d
296
color.d
|
@ -1,9 +1,112 @@
|
||||||
module arsd.color;
|
module arsd.color;
|
||||||
|
|
||||||
import std.math : abs;
|
// importing phobos explodes the size of this code 10x, so not doing it.
|
||||||
import std.conv : to;
|
|
||||||
import std.algorithm : startsWith, min, max;
|
private {
|
||||||
import std.string : strip, split;
|
real toInternal(T)(string s) {
|
||||||
|
real accumulator = 0.0;
|
||||||
|
size_t i = s.length;
|
||||||
|
foreach(idx, c; s) {
|
||||||
|
if(c >= '0' && c <= '9') {
|
||||||
|
accumulator *= 10;
|
||||||
|
accumulator += c - '0';
|
||||||
|
} else if(c == '.') {
|
||||||
|
i = idx + 1;
|
||||||
|
break;
|
||||||
|
} else
|
||||||
|
throw new Exception("bad char to make real from " ~ s);
|
||||||
|
}
|
||||||
|
|
||||||
|
real accumulator2 = 0.0;
|
||||||
|
real count = 1;
|
||||||
|
foreach(c; s[i .. $]) {
|
||||||
|
if(c >= '0' && c <= '9') {
|
||||||
|
accumulator2 *= 10;
|
||||||
|
accumulator2 += c - '0';
|
||||||
|
count *= 10;
|
||||||
|
} else
|
||||||
|
throw new Exception("bad char to make real from " ~ s);
|
||||||
|
}
|
||||||
|
|
||||||
|
return accumulator + accumulator2 / count;
|
||||||
|
}
|
||||||
|
|
||||||
|
string toInternal(T)(int a) {
|
||||||
|
if(a == 0)
|
||||||
|
return "0";
|
||||||
|
char[] ret;
|
||||||
|
while(a) {
|
||||||
|
ret ~= (a % 10) + '0';
|
||||||
|
a /= 10;
|
||||||
|
}
|
||||||
|
for(int i = 0; i < ret.length / 2; i++) {
|
||||||
|
char c = ret[i];
|
||||||
|
ret[i] = ret[$ - i - 1];
|
||||||
|
ret[$ - i - 1] = c;
|
||||||
|
}
|
||||||
|
return cast(string) ret;
|
||||||
|
}
|
||||||
|
string toInternal(T)(real a) {
|
||||||
|
// a simplifying assumption here is the fact that we only use this in one place: toInternal!string(cast(real) a / 255)
|
||||||
|
// thus we know this will always be between 0.0 and 1.0, inclusive.
|
||||||
|
if(a <= 0.0)
|
||||||
|
return "0.0";
|
||||||
|
if(a >= 1.0)
|
||||||
|
return "1.0";
|
||||||
|
string ret = "0.";
|
||||||
|
// I wonder if I can handle round off error any better. Phobos does, but that isn't worth 100 KB of code.
|
||||||
|
int amt = cast(int)(a * 1000);
|
||||||
|
return ret ~ toInternal!string(amt);
|
||||||
|
}
|
||||||
|
|
||||||
|
real absInternal(real a) { return a < 0 ? -a : a; }
|
||||||
|
real minInternal(real a, real b, real c) {
|
||||||
|
auto m = a;
|
||||||
|
if(b < m) m = b;
|
||||||
|
if(c < m) m = c;
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
real maxInternal(real a, real b, real c) {
|
||||||
|
auto m = a;
|
||||||
|
if(b > m) m = b;
|
||||||
|
if(c > m) m = c;
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
bool startsWithInternal(string a, string b) {
|
||||||
|
return (a.length >= b.length && a[0 .. b.length] == b);
|
||||||
|
}
|
||||||
|
string[] splitInternal(string a, char c) {
|
||||||
|
string[] ret;
|
||||||
|
size_t previous = 0;
|
||||||
|
foreach(i, char ch; a) {
|
||||||
|
if(ch == c) {
|
||||||
|
ret ~= a[previous .. i];
|
||||||
|
previous = i + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(previous != a.length)
|
||||||
|
ret ~= a[previous .. $];
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
string stripInternal(string s) {
|
||||||
|
foreach(i, char c; s)
|
||||||
|
if(c != ' ' && c != '\t' && c != '\n') {
|
||||||
|
s = s[i .. $];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
for(int a = cast(int)(s.length - 1); a > 0; a--) {
|
||||||
|
char c = s[a];
|
||||||
|
if(c != ' ' && c != '\t' && c != '\n') {
|
||||||
|
s = s[0 .. a + 1];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// done with mini-phobos
|
||||||
|
|
||||||
struct Color {
|
struct Color {
|
||||||
ubyte r;
|
ubyte r;
|
||||||
|
@ -35,19 +138,18 @@ struct Color {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
string toCssString() {
|
string toCssString() {
|
||||||
import std.string : format;
|
|
||||||
if(a == 255)
|
if(a == 255)
|
||||||
return format("#%02x%02x%02x", r, g, b);
|
return "#" ~ toHexInternal(r) ~ toHexInternal(g) ~ toHexInternal(b);
|
||||||
else
|
else {
|
||||||
return format("rgba(%d, %d, %d, %s)", r, g, b, cast(real)a / 255.0);
|
return "rgba("~toInternal!string(r)~", "~toInternal!string(g)~", "~toInternal!string(b)~", "~toInternal!string(cast(real)a / 255.0)~")";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
string toString() {
|
string toString() {
|
||||||
import std.string : format;
|
|
||||||
if(a == 255)
|
if(a == 255)
|
||||||
return format("%02x%02x%02x", r, g, b);
|
return toCssString()[1 .. $];
|
||||||
else
|
else
|
||||||
return format("%02x%02x%02x%02x", r, g, b, a);
|
return toHexInternal(r) ~ toHexInternal(g) ~ toHexInternal(b) ~ toHexInternal(a);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Color fromNameString(string s) {
|
static Color fromNameString(string s) {
|
||||||
|
@ -62,7 +164,7 @@ struct Color {
|
||||||
}
|
}
|
||||||
|
|
||||||
static Color fromString(string s) {
|
static Color fromString(string s) {
|
||||||
s = s.strip();
|
s = s.stripInternal();
|
||||||
|
|
||||||
Color c;
|
Color c;
|
||||||
c.a = 255;
|
c.a = 255;
|
||||||
|
@ -78,19 +180,19 @@ struct Color {
|
||||||
// try various notations borrowed from CSS (though a little extended)
|
// try various notations borrowed from CSS (though a little extended)
|
||||||
|
|
||||||
// hsl(h,s,l,a) where h is degrees and s,l,a are 0 >= x <= 1.0
|
// hsl(h,s,l,a) where h is degrees and s,l,a are 0 >= x <= 1.0
|
||||||
if(s.startsWith("hsl(") || s.startsWith("hsla(")) {
|
if(s.startsWithInternal("hsl(") || s.startsWithInternal("hsla(")) {
|
||||||
assert(s[$-1] == ')');
|
assert(s[$-1] == ')');
|
||||||
s = s[s.startsWith("hsl(") ? 4 : 5 .. $ - 1]; // the closing paren
|
s = s[s.startsWithInternal("hsl(") ? 4 : 5 .. $ - 1]; // the closing paren
|
||||||
|
|
||||||
real[3] hsl;
|
real[3] hsl;
|
||||||
ubyte a = 255;
|
ubyte a = 255;
|
||||||
|
|
||||||
auto parts = s.split(",");
|
auto parts = s.splitInternal(',');
|
||||||
foreach(i, part; parts) {
|
foreach(i, part; parts) {
|
||||||
if(i < 3)
|
if(i < 3)
|
||||||
hsl[i] = to!real(part.strip);
|
hsl[i] = toInternal!real(part.stripInternal);
|
||||||
else
|
else
|
||||||
a = cast(ubyte) (to!real(part.strip) * 255);
|
a = cast(ubyte) (toInternal!real(part.stripInternal) * 255);
|
||||||
}
|
}
|
||||||
|
|
||||||
c = .fromHsl(hsl);
|
c = .fromHsl(hsl);
|
||||||
|
@ -100,14 +202,14 @@ struct Color {
|
||||||
}
|
}
|
||||||
|
|
||||||
// rgb(r,g,b,a) where r,g,b are 0-255 and a is 0-1.0
|
// rgb(r,g,b,a) where r,g,b are 0-255 and a is 0-1.0
|
||||||
if(s.startsWith("rgb(") || s.startsWith("rgba(")) {
|
if(s.startsWithInternal("rgb(") || s.startsWithInternal("rgba(")) {
|
||||||
assert(s[$-1] == ')');
|
assert(s[$-1] == ')');
|
||||||
s = s[s.startsWith("rgb(") ? 4 : 5 .. $ - 1]; // the closing paren
|
s = s[s.startsWithInternal("rgb(") ? 4 : 5 .. $ - 1]; // the closing paren
|
||||||
|
|
||||||
auto parts = s.split(",");
|
auto parts = s.splitInternal(',');
|
||||||
foreach(i, part; parts) {
|
foreach(i, part; parts) {
|
||||||
// lol the loop-switch pattern
|
// lol the loop-switch pattern
|
||||||
auto v = to!real(part.strip);
|
auto v = toInternal!real(part.stripInternal);
|
||||||
switch(i) {
|
switch(i) {
|
||||||
case 0: // red
|
case 0: // red
|
||||||
c.r = cast(ubyte) v;
|
c.r = cast(ubyte) v;
|
||||||
|
@ -162,9 +264,27 @@ struct Color {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string toHexInternal(ubyte b) {
|
||||||
|
string s;
|
||||||
|
if(b < 16)
|
||||||
|
s ~= '0';
|
||||||
|
else {
|
||||||
|
ubyte t = (b & 0xf0) >> 4;
|
||||||
|
if(t >= 10)
|
||||||
|
s ~= 'A' + t - 10;
|
||||||
|
else
|
||||||
|
s ~= '0' + t;
|
||||||
|
b &= 0x0f;
|
||||||
|
}
|
||||||
|
if(b >= 10)
|
||||||
|
s ~= 'A' + b - 10;
|
||||||
|
else
|
||||||
|
s ~= '0' + b;
|
||||||
|
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
private ubyte fromHexInternal(string s) {
|
private ubyte fromHexInternal(string s) {
|
||||||
import std.range;
|
|
||||||
int result = 0;
|
int result = 0;
|
||||||
|
|
||||||
int exp = 1;
|
int exp = 1;
|
||||||
|
@ -194,11 +314,11 @@ Color fromHsl(real[3] hsl) {
|
||||||
Color fromHsl(real h, real s, real l, real a = 255) {
|
Color fromHsl(real h, real s, real l, real a = 255) {
|
||||||
h = h % 360;
|
h = h % 360;
|
||||||
|
|
||||||
real C = (1 - abs(2 * l - 1)) * s;
|
real C = (1 - absInternal(2 * l - 1)) * s;
|
||||||
|
|
||||||
real hPrime = h / 60;
|
real hPrime = h / 60;
|
||||||
|
|
||||||
real X = C * (1 - abs(hPrime % 2 - 1));
|
real X = C * (1 - absInternal(hPrime % 2 - 1));
|
||||||
|
|
||||||
real r, g, b;
|
real r, g, b;
|
||||||
|
|
||||||
|
@ -248,8 +368,8 @@ real[3] toHsl(Color c, bool useWeightedLightness = false) {
|
||||||
real g1 = cast(real) c.g / 255;
|
real g1 = cast(real) c.g / 255;
|
||||||
real b1 = cast(real) c.b / 255;
|
real b1 = cast(real) c.b / 255;
|
||||||
|
|
||||||
real maxColor = max(r1, g1, b1);
|
real maxColor = maxInternal(r1, g1, b1);
|
||||||
real minColor = min(r1, g1, b1);
|
real minColor = minInternal(r1, g1, b1);
|
||||||
|
|
||||||
real L = (maxColor + minColor) / 2 ;
|
real L = (maxColor + minColor) / 2 ;
|
||||||
if(useWeightedLightness) {
|
if(useWeightedLightness) {
|
||||||
|
@ -462,7 +582,6 @@ ubyte makeAlpha(ubyte colorYouHave, ubyte backgroundColor/*, ubyte foreground =
|
||||||
|
|
||||||
|
|
||||||
int fromHex(string s) {
|
int fromHex(string s) {
|
||||||
import std.range;
|
|
||||||
int result = 0;
|
int result = 0;
|
||||||
|
|
||||||
int exp = 1;
|
int exp = 1;
|
||||||
|
@ -510,9 +629,9 @@ void main() {
|
||||||
import browser.document;
|
import browser.document;
|
||||||
foreach(ele; document.querySelectorAll("input")) {
|
foreach(ele; document.querySelectorAll("input")) {
|
||||||
ele.addEventListener("change", {
|
ele.addEventListener("change", {
|
||||||
auto h = to!real(document.querySelector("input[name=h]").value);
|
auto h = toInternal!real(document.querySelector("input[name=h]").value);
|
||||||
auto s = to!real(document.querySelector("input[name=s]").value);
|
auto s = toInternal!real(document.querySelector("input[name=s]").value);
|
||||||
auto l = to!real(document.querySelector("input[name=l]").value);
|
auto l = toInternal!real(document.querySelector("input[name=l]").value);
|
||||||
|
|
||||||
Color c = Color.fromHsl(h, s, l);
|
Color c = Color.fromHsl(h, s, l);
|
||||||
|
|
||||||
|
@ -525,3 +644,120 @@ void main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
This provides two image classes and a bunch of functions that work on them.
|
||||||
|
|
||||||
|
Why are they separate classes? I think the operations on the two of them
|
||||||
|
are necessarily different. There's a whole bunch of operations that only
|
||||||
|
really work on truecolor (blurs, gradients), and a few that only work
|
||||||
|
on indexed images (palette swaps).
|
||||||
|
|
||||||
|
Even putpixel is pretty different. On indexed, it is a palette entry's
|
||||||
|
index number. On truecolor, it is the actual color.
|
||||||
|
|
||||||
|
A greyscale image is the weird thing in the middle. It is truecolor, but
|
||||||
|
fits in the same size as indexed. Still, I'd say it is a specialization
|
||||||
|
of truecolor.
|
||||||
|
|
||||||
|
There is a subset that works on both
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
interface MemoryImage {
|
||||||
|
//IndexedImage convertToIndexedImage() const;
|
||||||
|
//TrueColorImage convertToTrueColor() const;
|
||||||
|
TrueColorImage getAsTrueColorImage();
|
||||||
|
}
|
||||||
|
|
||||||
|
class IndexedImage : MemoryImage {
|
||||||
|
bool hasAlpha;
|
||||||
|
Color[] palette;
|
||||||
|
ubyte[] data;
|
||||||
|
|
||||||
|
int width;
|
||||||
|
int height;
|
||||||
|
|
||||||
|
this(int w, int h) {
|
||||||
|
width = w;
|
||||||
|
height = h;
|
||||||
|
data = new ubyte[w*h];
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
void resize(int w, int h, bool scale) {
|
||||||
|
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
override TrueColorImage getAsTrueColorImage() {
|
||||||
|
return convertToTrueColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
TrueColorImage convertToTrueColor() const {
|
||||||
|
auto tci = new TrueColorImage(width, height);
|
||||||
|
foreach(i, b; data) {
|
||||||
|
tci.imageData.colors[i] = palette[b];
|
||||||
|
}
|
||||||
|
return tci;
|
||||||
|
}
|
||||||
|
|
||||||
|
ubyte getOrAddColor(Color c) {
|
||||||
|
foreach(i, co; palette) {
|
||||||
|
if(c == co)
|
||||||
|
return cast(ubyte) i;
|
||||||
|
}
|
||||||
|
|
||||||
|
return addColor(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
int numColors() const {
|
||||||
|
return palette.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
ubyte addColor(Color c) {
|
||||||
|
assert(palette.length < 256);
|
||||||
|
if(c.a != 255)
|
||||||
|
hasAlpha = true;
|
||||||
|
palette ~= c;
|
||||||
|
|
||||||
|
return cast(ubyte) (palette.length - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TrueColorImage : MemoryImage {
|
||||||
|
// bool hasAlpha;
|
||||||
|
// bool isGreyscale;
|
||||||
|
//ubyte[] data; // stored as rgba quads, upper left to right to bottom
|
||||||
|
union Data {
|
||||||
|
ubyte[] bytes;
|
||||||
|
Color[] colors;
|
||||||
|
|
||||||
|
static assert(Color.sizeof == 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
Data imageData;
|
||||||
|
alias imageData.bytes data;
|
||||||
|
|
||||||
|
int width;
|
||||||
|
int height;
|
||||||
|
|
||||||
|
this(int w, int h) {
|
||||||
|
width = w;
|
||||||
|
height = h;
|
||||||
|
imageData.bytes = new ubyte[w*h*4];
|
||||||
|
}
|
||||||
|
|
||||||
|
override TrueColorImage getAsTrueColorImage() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/+
|
||||||
|
IndexedImage convertToIndexedImage(int maxColors = 256) {
|
||||||
|
|
||||||
|
}
|
||||||
|
+/
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue