skia2/modules/canvaskit/color.js
Kevin Lubick d1c6cc1692 Reland "[canvaskit] Break up helper.js into smaller files"
This is a reland of 2c3cec998f

Original change's description:
> [canvaskit] Break up helper.js into smaller files
>
> This will hopefully make some things easier to find/understand.
> I have a few more ideas for breaking up other parts.
>
> Bug: skia:11203
> Change-Id: Ia54c13fd6e3c897e04a737b258f6e77c50a1aed3
> Reviewed-on: https://skia-review.googlesource.com/c/skia/+/356839
> Reviewed-by: Nathaniel Nifong <nifong@google.com>

Bug: skia:11203
Change-Id: I8850e8e9e5f39f537232e0f1f8a814f763ab7853
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/363942
Reviewed-by: Kevin Lubick <kjlubick@google.com>
2021-02-01 20:00:16 +00:00

201 lines
6.8 KiB
JavaScript

/*
* This file houses functions that deal with color.
*/
// Constructs a Color with the same API as CSS's rgba(), that is
// r,g,b are 0-255, and a is 0.0 to 1.0.
// if a is omitted, it will be assumed to be 1.0
// Internally, Colors are a TypedArray of four unpremultiplied 32-bit floats: a, r, g, b
// In order to construct one with more precision or in a wider gamut, use
// CanvasKit.Color4f
CanvasKit.Color = function(r, g, b, a) {
if (a === undefined) {
a = 1;
}
return CanvasKit.Color4f(clamp(r)/255, clamp(g)/255, clamp(b)/255, a);
};
// Constructs a Color as a 32 bit unsigned integer, with 8 bits assigned to each channel.
// Channels are expected to be between 0 and 255 and will be clamped as such.
CanvasKit.ColorAsInt = function(r, g, b, a) {
// default to opaque
if (a === undefined) {
a = 255;
}
// This is consistent with how Skia represents colors in C++, as an unsigned int.
// This is also consistent with how Flutter represents colors:
// https://github.com/flutter/engine/blob/243bb59c7179a7e701ce478080d6ce990710ae73/lib/web_ui/lib/src/ui/painting.dart#L50
return (((clamp(a) << 24) | (clamp(r) << 16) | (clamp(g) << 8) | (clamp(b) << 0)
& 0xFFFFFFF) // This truncates the unsigned to 32 bits and signals to JS engines they can
// represent the number with an int instead of a double.
>>> 0); // This makes the value an unsigned int.
};
// Construct a 4-float color.
// Opaque if opacity is omitted.
CanvasKit.Color4f = function(r, g, b, a) {
if (a === undefined) {
a = 1;
}
return Float32Array.of(r, g, b, a);
};
// Color constants use property getters to prevent other code from accidentally
// changing them.
Object.defineProperty(CanvasKit, 'TRANSPARENT', {
get: function() { return CanvasKit.Color4f(0, 0, 0, 0); }
});
Object.defineProperty(CanvasKit, 'BLACK', {
get: function() { return CanvasKit.Color4f(0, 0, 0, 1); }
});
Object.defineProperty(CanvasKit, 'WHITE', {
get: function() { return CanvasKit.Color4f(1, 1, 1, 1); }
});
Object.defineProperty(CanvasKit, 'RED', {
get: function() { return CanvasKit.Color4f(1, 0, 0, 1); }
});
Object.defineProperty(CanvasKit, 'GREEN', {
get: function() { return CanvasKit.Color4f(0, 1, 0, 1); }
});
Object.defineProperty(CanvasKit, 'BLUE', {
get: function() { return CanvasKit.Color4f(0, 0, 1, 1); }
});
Object.defineProperty(CanvasKit, 'YELLOW', {
get: function() { return CanvasKit.Color4f(1, 1, 0, 1); }
});
Object.defineProperty(CanvasKit, 'CYAN', {
get: function() { return CanvasKit.Color4f(0, 1, 1, 1); }
});
Object.defineProperty(CanvasKit, 'MAGENTA', {
get: function() { return CanvasKit.Color4f(1, 0, 1, 1); }
});
// returns a css style [r, g, b, a] from a CanvasKit.Color
// where r, g, b are returned as ints in the range [0, 255]
// where a is scaled between 0 and 1.0
CanvasKit.getColorComponents = function(color) {
return [
Math.floor(color[0]*255),
Math.floor(color[1]*255),
Math.floor(color[2]*255),
color[3]
];
};
// parseColorString takes in a CSS color value and returns a CanvasKit.Color
// (which is an array of 4 floats in RGBA order). An optional colorMap
// may be provided which maps custom strings to values.
// In the CanvasKit canvas2d shim layer, we provide this map for processing
// canvas2d calls, but not here for code size reasons.
CanvasKit.parseColorString = function(colorStr, colorMap) {
colorStr = colorStr.toLowerCase();
// See https://drafts.csswg.org/css-color/#typedef-hex-color
if (colorStr.startsWith('#')) {
var r, g, b, a = 255;
switch (colorStr.length) {
case 9: // 8 hex chars #RRGGBBAA
a = parseInt(colorStr.slice(7, 9), 16);
case 7: // 6 hex chars #RRGGBB
r = parseInt(colorStr.slice(1, 3), 16);
g = parseInt(colorStr.slice(3, 5), 16);
b = parseInt(colorStr.slice(5, 7), 16);
break;
case 5: // 4 hex chars #RGBA
// multiplying by 17 is the same effect as
// appending another character of the same value
// e.g. e => ee == 14 => 238
a = parseInt(colorStr.slice(4, 5), 16) * 17;
case 4: // 6 hex chars #RGB
r = parseInt(colorStr.slice(1, 2), 16) * 17;
g = parseInt(colorStr.slice(2, 3), 16) * 17;
b = parseInt(colorStr.slice(3, 4), 16) * 17;
break;
}
return CanvasKit.Color(r, g, b, a/255);
} else if (colorStr.startsWith('rgba')) {
// Trim off rgba( and the closing )
colorStr = colorStr.slice(5, -1);
var nums = colorStr.split(',');
return CanvasKit.Color(+nums[0], +nums[1], +nums[2],
valueOrPercent(nums[3]));
} else if (colorStr.startsWith('rgb')) {
// Trim off rgba( and the closing )
colorStr = colorStr.slice(4, -1);
var nums = colorStr.split(',');
// rgb can take 3 or 4 arguments
return CanvasKit.Color(+nums[0], +nums[1], +nums[2],
valueOrPercent(nums[3]));
} else if (colorStr.startsWith('gray(')) {
// TODO(kjlubick)
} else if (colorStr.startsWith('hsl')) {
// TODO(kjlubick)
} else if (colorMap) {
// Try for named color
var nc = colorMap[colorStr];
if (nc !== undefined) {
return nc;
}
}
Debug('unrecognized color ' + colorStr);
return CanvasKit.BLACK;
};
function isCanvasKitColor(ob) {
if (!ob) {
return false;
}
return (ob.constructor === Float32Array && ob.length === 4);
}
// Warning information is lost by this conversion
function toUint32Color(c) {
return ((clamp(c[3]*255) << 24) | (clamp(c[0]*255) << 16) | (clamp(c[1]*255) << 8) | (clamp(c[2]*255) << 0)) >>> 0;
}
// Accepts various colors representations and converts them to an array of int colors.
// Does not handle builders.
function assureIntColors(arr) {
if (arr instanceof Float32Array) {
var count = Math.floor(arr.length / 4);
var result = new Uint32Array(count);
for (var i = 0; i < count; i ++) {
result[i] = toUint32Color(arr.slice(i*4, (i+1)*4));
}
return result;
} else if (arr instanceof Uint32Array) {
return arr;
} else if (arr instanceof Array && arr[0] instanceof Float32Array) {
return arr.map(toUint32Color);
}
}
function uIntColorToCanvasKitColor(c) {
return CanvasKit.Color(
(c >> 16) & 0xFF,
(c >> 8) & 0xFF,
(c >> 0) & 0xFF,
((c >> 24) & 0xFF) / 255
);
}
function valueOrPercent(aStr) {
if (aStr === undefined) {
return 1; // default to opaque.
}
var a = parseFloat(aStr);
if (aStr && aStr.indexOf('%') !== -1) {
return a / 100;
}
return a;
}
function clamp(c) {
return Math.round(Math.max(0, Math.min(c || 0, 255)));
}
// TODO(kjlubick) delete this, as it is now trivial with 4f colors
CanvasKit.multiplyByAlpha = function(color, alpha) {
// make a copy of the color so the function remains pure.
var result = color.slice();
result[3] = Math.max(0, Math.min(result[3] * alpha, 1));
return result;
};