Manipulate RGBA Javascript - javascript

In Javascript i have a Uint8Array() RGBA of image, here is console.log of this :
Here is image with rgba array as a string in HTML:
Is that possible to manipulate this array, to ex. change colors?
Thanks for help!

Here's a JS algorithm of plotting a pixel to this kind of array:
function changeColor(x, y, c)
{
colorArray[(x * 4) + (y * (imageWidth * 4))] = c.r;
colorArray[(x * 4) + (y * (imageWidth * 4)) + 1] = c.g;
colorArray[(x * 4) + (y * (imageWidth * 4)) + 2] = c.b;
colorArray[(x * 4) + (y * (imageWidth * 4)) + 3] = c.a;
}
where x and y are the coordinates of the pixel you want to change, imageWidth being the width of the image this array produces, c being the colour you want to change the pixel to, and colorArray is the array itself.

Related

How to plot an ellipse on canvas from 2 points on the ellipse, where slope of major axis (rx), and minor axis (ry) length are unknown

This may be more of a mathematics problem, but maybe there is a simple javascript solution that I am missing.
I want to plot an ellipse on html canvas from user input of a center point, radius of the major (longest) axis, and 2 points will fall on the ellipse.
This should potentially create 2 possible ellipse paths, both of which will center around the center point, and cross through the 2 points.
So for example, if the center = [2, 1] major axis radius a = 10, point 1 u = [4, 2] and point 2 v = [5, 6], what is the minor axis radius b and angle of rotation theta?
So far I have tried to implement an equation that I found from https://math.stackexchange.com/questions/3210414/find-the-angle-of-rotation-and-minor-axis-length-of-ellipse-from-major-axis-leng,
but it does not return valid values. My javascript code looks like this:
function getEllipseFrom2Points(center, u, v, a) {
function getSlope(plusOrMinus) {
return Math.sqrt(((uy * vx - ux * vy) ** 2) / (-ux * uy * (a * (v2x + v2y) - 1) + vx * vy * (a * (u2x + u2y) - 1) - plusOrMinus * (uy * vx - ux * vy) * q) / (u2x * (1 - a * v2y) + v2x * (a * u2y - 1)));
}
function getMinorAxis(plusOrMinus) {
return (u2x + u2y + v2x + v2y - a * (2 * u2x * v2x + 2 * u2y * v2y + 2 * ux * uy * vx * vy + u2y * v2x + u2x * v2y) + plusOrMinus * 2 * (ux * vx + uy * vy) * q);
}
var vx = v[0],
vy = v[1],
ux = u[0],
uy = u[1],
v2x = vx ** 2,
v2y = vy ** 2,
u2x = ux ** 2,
u2y = uy ** 2,
q = Math.sqrt((1 - a * (u2x + u2y)) * (1 - a * (v2x + v2y))),
ellipse1 = { rx: a, ry: getMinorAxis(1), origin: center, rotation: getSlope(1) },
ellipse2 = { rx: a, ry: getMinorAxis(-1), origin: center, rotation: getSlope(-1) };
}
Either the equation that I am following is wrong, or I have implemented it wrong
In case anyone is interested, here is my solution to the problem, which isn't really "the" solution. If anyone can solve this I would still be happy to know.
Since I can't solve for both slope of the major axis and length of the minor axis, I just take a guess at slope and then test how close it is, and then refine the result by trying in a smaller and smaller region. Since the final ellipse that gets drawn is actually an estimation constructed from bezier curves, I can get close enough in a reasonable amount of time.
function getEllipseFrom2Points (center, u, v, a) {
function getSemiMinorAxis([x, y], a, t) {
// equation for rotated ellipse
// b = a(ycos(t) - xsin(t)) / sqrt(a^2 - x^2cos^2(t) - 2xysin(t)cos(t) - y^2sin^2(t)) and
// b = a(xsin(t) - ycos(t)) / sqrt(a^2 - x^2cos^2(t) - 2xysin(t)cos(t) - y^2sin^2(t))
// where a^2 !== (xcos(t) + ysin(t))^2
// and aycos(t) !== axsin(t)
if (a ** 2 !== (x * Math.cos(t) + y * Math.sin(t)) ** 2 &&
a * y * Math.cos(t) !== a * x * Math.sin(t)) {
var b = [],
q = (Math.sqrt(a ** 2 - x ** 2 * (Math.cos(t)) ** 2 - 2 * x * y * Math.sin(t) * Math.cos(t) - y ** 2 * (Math.sin(t)) ** 2));
b[0] = (a * (y * Math.cos(t) - x * Math.sin(t))) / q;
b[1] = (a * (x * Math.sin(t) - y * Math.cos(t))) / q;
return b;
}
}
function getAngle_radians(point1, point2){
return Math.atan2(point2[1] - point1[1], point2[0] - point1[0]);
}
function getDistance(point1, point2) {
return Math.sqrt((point2[0] - point1[0]) ** 2 + (point2[1] - point1[1]) ** 2);
}
function rotatePoint(point, center, radians) {
var x = (point[0] - center[0]) * Math.cos(radians) - (point[1] - center[1]) * Math.sin(radians) + center[0];
var y = (point[1] - center[1]) * Math.cos(radians) + (point[0] - center[0]) * Math.sin(radians) + center[1];
return [x, y];
}
function measure(ellipseRotation, pointOnEllipse, minorAxisLength) {
var d = getDistance(point, pointOnEllipse);
if (d < bestDistanceBetweenPointAndEllipse) {
bestDistanceBetweenPointAndEllipse = d;
bestEstimationOfB = minorAxisLength;
bestEstimationOfR = ellipseRotation;
}
}
function getBestEstimate(min, max) {
var testIncrement = (max - min) / 10;
for (let r = min; r < max; r = r + testIncrement) {
if (radPoint1 < r && radPoint2 < r || radPoint1 > r && radPoint2 > r) {//points both on same side of ellipse
semiMinorAxis = getSemiMinorAxis(v, a, r);
if (semiMinorAxis) {
for (let t = 0; t < circle; t = t + degree) {
ellipsePoint1 = [a * Math.cos(t), semiMinorAxis[0] * Math.sin(t)];
ellipsePoint2 = [a * Math.cos(t), semiMinorAxis[1] * Math.sin(t)];
point = rotatePoint(u, [0, 0], -r);
measure(r, ellipsePoint1, semiMinorAxis[0]);
measure(r, ellipsePoint2, semiMinorAxis[1]);
}
}
}
}
count++;
if (new Date().getTime() - startTime < 200 && count < 10) //refine estimate
getBestEstimate(bestEstimationOfR - testIncrement, bestEstimationOfR + testIncrement);
}
if (center instanceof Array &&
typeof center[0] === "number" &&
typeof center[1] === "number" &&
u instanceof Array &&
typeof u[0] === "number" &&
typeof u[1] === "number" &&
v instanceof Array &&
typeof v[0] === "number" &&
typeof v[1] === "number" &&
typeof a === "number") {
// translate points
u = [u[0] - center[0], u[1] - center[1]];
v = [v[0] - center[0], v[1] - center[1]];
var bestDistanceBetweenPointAndEllipse = a,
point,
semiMinorAxis,
ellipsePoint1,
ellipsePoint2,
bestEstimationOfB,
bestEstimationOfR,
radPoint1 = getAngle_radians([0, 0], v),
radPoint2 = getAngle_radians([0, 0], u),
circle = 2 * Math.PI,
degree = circle / 360,
startTime = new Date().getTime(),
count = 0;
getBestEstimate(0, circle);
var ellipseModel = MakerJs.$(new MakerJs.models.Ellipse(a, bestEstimationOfB))
.rotate(MakerJs.angle.toDegrees(bestEstimationOfR), [0, 0])
.move(center)
.originate([0, 0])
.$result;
return ellipseModel;
}

JavaScript: Function that simulates CSS cubic-bezier()

I am working on an animation library and have a variable that is incrementing from 0 to 1 (X axis in the graphic) while a timer is running. I now want to apply easing to my function in a similar fashion as the CSS function cubic-bezier.
My goal is that the y value doesn't increment linearly but instead is converted by the easing function. The function should look something like this:
function cubic-bezier(x, p0x, p0y, p1x, p1y){
// Any suggestions for a formula or algorithm?
return y;
};
I am not using any libraries and can't use any third party libraries on this project.
Thanks!
EDIT: This is the code I came up with. It is an adaption of this code jsfiddle.net/fQYsU.
var p1 = {0.5, 0.5};
var p2 = {0.5, 0.5};
cubic-bezier(t, p1, p2){
var cX = 3 * (p1.x),
bX = 3 * (p2.x - p1.x) - cX,
aX = 1 - cX - bX;
var cY = 3 * (p1.y - 1),
bY = 3 * (p2.y - p1.y) - cY,
aY = -1 - cY - bY;
var x = (aX * Math.pow(t, 3)) + (bX * Math.pow(t, 2)) + (cX * t);
var y = (aY * Math.pow(t, 3)) + (bY * Math.pow(t, 2)) + (cY * t) + 1;
return {x: x, y: y};
}
var y = cubic-bezier(progress, p1, p2).y;
But the value it returns corresponds to x which might not be t. So I still don't know how to get the value at the correct position.

convert RGB color to XY

I have using RUCKUS API for changing color of lamp and I need to convert color from RGB to XY to call an API.
I have tried this code:
1) Get the RGB values from your color object and convert them to be between 0 and 1
function rgb_to_cie(red, green, blue)
{
//Apply a gamma correction to the RGB values, which makes the color more vivid and more the like the color displayed on the screen of your device
var red = (red > 0.04045) ? Math.pow((red + 0.055) / (1.0 + 0.055), 2.4) : (red / 12.92);
var green = (green > 0.04045) ? Math.pow((green + 0.055) / (1.0 + 0.055), 2.4) : (green / 12.92);
var blue = (blue > 0.04045) ? Math.pow((blue + 0.055) / (1.0 + 0.055), 2.4) : (blue / 12.92);
//RGB values to XYZ using the Wide RGB D65 conversion formula
var X = red * 0.664511 + green * 0.154324 + blue * 0.162028;
var Y = red * 0.283881 + green * 0.668433 + blue * 0.047685;
var Z = red * 0.000088 + green * 0.072310 + blue * 0.986039;
//Calculate the xy values from the XYZ values
var x = (X / (X + Y + Z)).toFixed(4);
var y = (Y / (X + Y + Z)).toFixed(4);
if (isNaN(x))
x = 0;
if (isNaN(y))
y = 0;
return [x, y];
}
but it didn't provide a proper solution.
SO if i pass r:182 g: 255 B: 65 as this then I got x as 22932 and y as 35249 (AS PER DOC OF API.)
How can I do that?
I got the solution and here I attached the answer if you're looing for,
let red = 100
let green = 100
let blue = 100
let redC = (red / 255)
let greenC = (green / 255)
let blueC = (blue / 255)
console.log(redC, greenC , blueC)
let redN = (redC > 0.04045) ? Math.pow((redC + 0.055) / (1.0 + 0.055), 2.4): (redC / 12.92)
let greenN = (greenC > 0.04045) ? Math.pow((greenC + 0.055) / (1.0 + 0.055), 2.4) : (greenC / 12.92)
let blueN = (blueC > 0.04045) ? Math.pow((blueC + 0.055) / (1.0 + 0.055), 2.4) : (blueC / 12.92)
console.log(redN, greenN, blueN)
let X = redN * 0.664511 + greenN * 0.154324 + blueN * 0.162028;
let Y = redN * 0.283881 + greenN * 0.668433 + blueN * 0.047685;
let Z = redN * 0.000088 + greenN * 0.072310 + blueN * 0.986039;
console.log(X, Y, Z)
let x = X / (X + Y + Z);
let y = Y / (X + Y + Z);
X = x * 65536
Y = y * 65536
There are few errors:
First: You are using the normal transformation formula, where red, green, and blue should be in the range 0.0 to 1.0. So you may need to divide the values by 255.
Second: you are applying gamma. To transform to X,Y,Z you need linear values. Normally we have gamma corrected RGB (e.g. pixel values). So you are overcorrecting. Instead, you should apply the inverse of gamma, so that you will get the linear R,G,B, so that you can apply linear transformation to get X,Y,Z.
Third: you are returning x,y and not X,Y, as your question (and comment). On the other hand, are you sure you need X,Y? Often x,y are used for "colour". I would expect a x,y + Y (or some brightness parameter).
Possibly you should scale back the values. As you see, often the range is 0 to 1, or 0 to 100 (in past), or 0 to 255, 0 to 65535. There is no standard: these are just numbers without a unit. [Contrary to computer screens, a Phillips Hue should know what it is the maximum brightness, but .. this is an exception].

Centering grid of objects

As in the title, im trying to create a grid of objects in P5 Spot(x, y, size), with a 4 pixel space between them and center it on the canvas without using translate, heres what i've got:
gridSize = 7;
spotSize = 60;
spots = [];
for (var y = height / 2 - ((gridSize * spotSize + gridSize * 4) / 2); y < (height / 2 - ((gridSize * spotSize + gridSize * 4) / 2)) + (gridSize * spotSize + gridSize * 4); y += spotSize + 4) {
for (var x = width / 2 - ((gridSize * spotSize + gridSize * 4) / 2); x < (width / 2 - ((gridSize * spotSize + gridSize * 4) / 2)) + (gridSize * spotSize + gridSize * 4); x += spotSize + 4) {
spots.push(new Spot(x, y, spotSize));
}
}
Problem is that my grid looks off, why is it not centered? Probably a really simple and stupid mistake but i cant find it. Any help appreciated.
My Spot object just draws an ellipse at the given x and y. Entire code at http://codepen.io/felipe_mare/pen/GWyMOL
-SOLVED-
spots.push(new Spot(x + spotSize/2, y + spotSize/2, spotSize));
Wasn't taking into account the fact that the ellipse is drawn from the center, so i have to add the radius of the circle spotSize/2

Javascript Calculate darker colour

how can i change this calculation to a darker and not brighter color?
function increase_brightness(hex, percent){
// strip the leading # if it's there
hex = hex.replace(/^\s*#|\s*$/g, '');
// convert 3 char codes --> 6, e.g. `E0F` --> `EE00FF`
if(hex.length == 3){
hex = hex.replace(/(.)/g, '$1$1');
}
var r = parseInt(hex.substr(0, 2), 16),
g = parseInt(hex.substr(2, 2), 16),
b = parseInt(hex.substr(4, 2), 16);
return '#' +
((0|(1<<8) + r + (256 - r) * percent / 100).toString(16)).substr(1) +
((0|(1<<8) + g + (256 - g) * percent / 100).toString(16)).substr(1) +
((0|(1<<8) + b + (256 - b) * percent / 100).toString(16)).substr(1);
}
src = JavaScript Calculate brighter colour
demo http://jsbin.com/utavax/3/edit
You can change
return '#' +
((0|(1<<8) + r + (256 - r) * percent / 100).toString(16)).substr(1) +
((0|(1<<8) + g + (256 - g) * percent / 100).toString(16)).substr(1) +
((0|(1<<8) + b + (256 - b) * percent / 100).toString(16)).substr(1);
to
return '#' +
((0|(1<<8) + r * (1 - percent / 100)).toString(16)).substr(1) +
((0|(1<<8) + g * (1 - percent / 100)).toString(16)).substr(1) +
((0|(1<<8) + b * (1 - percent / 100).toString(16)).substr(1);
will fix your problem. demo.
But darker color is not a good definition. Darker can be interpreted as less brightness or less saturation. So the better approach is to convert the RGB color space to HSB color space, and tweak the S/B channels, then convert them back.
A little explanation on why negative value to original code is not OK.
Give -100 as percent, and some channel, say r, less than 128. Then
r + (256 - r) * percent / 100
is less than 0, after plus 1 << 8 = 256
((0|(1<<8) + r + (256 - r) * percent / 100)
is less than 256.
Take a number less than 256 will generate at most two hex digits by calling toString(16), so .substr(1) will contain 0 or 1 digit only. By combining all these wrong digits together will not generate a proper color in hex representation.
I updated your original function to do a cheap version of lightening/darkening by basically multiplying a percentage (up or down) off the original RGB values.
function adjust(hexInput: string, percent: number) {
let hex = hexInput;
// strip the leading # if it's there
hex = hex.replace(/^\s*#|\s*$/g, "");
// convert 3 char codes --> 6, e.g. `E0F` --> `EE00FF`
if (hex.length === 3) {
hex = hex.replace(/(.)/g, "$1$1");
}
let r = parseInt(hex.substr(0, 2), 16);
let g = parseInt(hex.substr(2, 2), 16);
let b = parseInt(hex.substr(4, 2), 16);
const calculatedPercent = (100 + percent) / 100;
r = Math.round(Math.min(255, Math.max(0, r * calculatedPercent)));
g = Math.round(Math.min(255, Math.max(0, g * calculatedPercent)));
b = Math.round(Math.min(255, Math.max(0, b * calculatedPercent)));
return `#${r.toString(16).toUpperCase()}${g.toString(16).toUpperCase()}${b
.toString(16)
.toUpperCase()}`;
}
console.log(adjust("#49D174", -14)) // Darken by 14% = #3FB464
console.log(adjust("#49D174", -27)) // Darken by 27% = #359955
The function takes the percent as an integer, but could easily be modified for a decimal. Negative to darken, positive to lighten.
I've taken the main idea from Stylus (1, 2) and a few code parts from Stack Overflow answers and produced a library, darken_color.js.
Here are a few usage examples:
darken('lightgreen', '0.1'); // -> #63e763
darken('lightgreen', '10'); // -> #63e763
darken('#90EE90', '10'); // -> #63e763
darken('#9e9', '10%'); // -> #98ed98
return '#' +
((0|(1<<8) + r + (256 - r) * percent / 100).toString(16)).substr(1) +
((0|(1<<8) + g + (256 - g) * percent / 100).toString(16)).substr(1) +
((0|(1<<8) + b + (256 - b) * percent / 100).toString(16)).substr(1);
Here you are adding some percentage of r, g and b to current values which makes the color lighter. If you use a negative value for percentage, it will decrease from current values and make the color darker.

Categories