-
-
Save yagudaev/0c2b89674c6aee8b38cd379752ef58d0 to your computer and use it in GitHub Desktop.
import { | |
convertGradientHandlesToTransform, | |
convertTransformToGradientHandles | |
} from "./setPropertyFill" | |
describe("convertGradientHandlesToTransform", () => { | |
it("identity matrix", () => { | |
expect( | |
convertGradientHandlesToTransform([ | |
{ | |
x: 0, | |
y: 0.5 | |
}, | |
{ | |
x: 1, | |
y: 0.5 | |
}, | |
{ | |
x: 0, | |
y: 1 | |
} | |
]) | |
).toEqual([ | |
[1, 0, 0], | |
[0, 1, 0] | |
]) | |
}) | |
it('scale "up" matrix', () => { | |
expect( | |
convertGradientHandlesToTransform([ | |
{ | |
x: 0, | |
y: 0.25 | |
}, | |
{ | |
x: 0.5, | |
y: 0.25 | |
}, | |
{ | |
x: 0, | |
y: 0.5 | |
} | |
]) | |
).toEqual([ | |
[2, 0, 0], | |
[0, 2, 0] | |
]) | |
}) | |
it("complex transform", () => { | |
expect( | |
convertGradientHandlesToTransform([ | |
{ | |
x: 0.06041662833364192, | |
y: 0.9474249294453632 | |
}, | |
{ | |
x: 0.9397033965856045, | |
y: 0.05248769196422948 | |
}, | |
{ | |
x: 0.5078852470742088, | |
y: 1.4138814828771724 | |
} | |
]) | |
).toEqual( | |
[ | |
[0.5754421949386597, -0.5520178079605103, 0.4882291555404663], | |
[0.5520178079605103, 0.542364239692688, -0.04720045626163483] | |
].map((row) => row.map((v) => expect.closeTo(v, 15))) | |
) | |
}) | |
}) | |
describe("convertTransformToGradientHandles", () => { | |
it("identity matrix", () => { | |
expect( | |
convertTransformToGradientHandles([ | |
[1, 0, 0], | |
[0, 1, 0], | |
[0, 0, 1] | |
]) | |
).toEqual([ | |
{ | |
x: 0, | |
y: 0.5 | |
}, | |
{ | |
x: 1, | |
y: 0.5 | |
}, | |
{ | |
x: 0, | |
y: 1 | |
} | |
]) | |
}) | |
// TODO: test adds row if missing | |
it('matrix with scale "up"', () => { | |
expect( | |
convertTransformToGradientHandles([ | |
[2, 0, 0], | |
[0, 2, 0], | |
[0, 0, 1] | |
]) | |
).toEqual([ | |
{ | |
x: 0, | |
y: 0.25 | |
}, | |
{ | |
x: 0.5, | |
y: 0.25 | |
}, | |
{ | |
x: 0, | |
y: 0.5 | |
} | |
]) | |
}) | |
it("complex matrix", () => { | |
expect( | |
convertTransformToGradientHandles([ | |
[0.5754421949386597, -0.5520178079605103, 0.4882291555404663], | |
[0.5520178079605103, 0.542364239692688, -0.04720045626163483], | |
[0, 0, 1] | |
]) | |
).toEqual( | |
[ | |
{ | |
x: 0.06041662833364192, | |
y: 0.9474249294453632 | |
}, | |
{ | |
x: 0.9397033965856045, | |
y: 0.05248769196422948 | |
}, | |
{ | |
x: 0.5078852470742088, | |
y: 1.4138814828771724 | |
} | |
].map(({ x, y }) => ({ x: expect.closeTo(x, 15), y: expect.closeTo(y, 15) })) | |
) | |
}) | |
}) |
import * as math from "mathjs" | |
const identityMatrixHandlePositions = [ | |
[0, 1, 0], | |
[0.5, 0.5, 1], | |
[1, 1, 1] | |
] | |
export function convertGradientHandlesToTransform( | |
gradientHandlePositions: [ | |
{ x: number; y: number }, | |
{ x: number; y: number }, | |
{ x: number; y: number } | |
] | |
) { | |
const gh = gradientHandlePositions | |
const d = [ | |
[gh[0].x, gh[1].x, gh[2].x], | |
[gh[0].y, gh[1].y, gh[2].y], | |
[1, 1, 1] | |
] | |
const o = identityMatrixHandlePositions | |
const m = math.multiply(o, math.inv(d)) | |
return [m[0], m[1]] | |
} | |
export function convertTransformToGradientHandles(transform: number[][]) { | |
const inverseTransform = math.inv(transform) | |
// point matrix | |
const mp = math.multiply(inverseTransform, identityMatrixHandlePositions) | |
return [ | |
{ x: mp[0][0], y: mp[1][0] }, | |
{ x: mp[0][1], y: mp[1][1] }, | |
{ x: mp[0][2], y: mp[1][2] } | |
] | |
} |
Glad it is helpful @KyrieChen 😊.
Q1) The identity matrix is always [[1, 0], [0, 1]] (x and y in normal positions). The initial position of a gradient when created through figma is just that. I found it through observation. I created it through the UI and through the plugin api and looked at the values.
Q2) The inverse is used to solve the equation. We have 3 points from the REST API, they are the points that Figma displays when you look at the gradient in the UI (the 3 dots). Our second thing that is known is the initial values of the 3 points when we create a brand new gradient without any changes. We use that to solve for the transformation matrix needed to change the points.
Note I tried to solve this initially by breaking it down to SRT (scale, rotate, transform) values independently. The math there is much more complex and I got a large margin of error +/-10%.
Using linear algebra instead of trigonometry was far easier here.
Q3) As far as I remember Figma uses a normalized vector [0, 1], so to fill a shape you need to scale it. This causes some surprises compared to typical Computer Graphics and probably when I ended up doing that.
Hope this help, sorry it's been almost 2 years so forgot some of this 😊.
Btw give the css gradient repo a try, lots of goodies there. Also got the help of html.to.design CTO there.
Hi guys, Im not very good in handling matrix and vectors, maybe you guys can help me. I need to convert figma gradient into css (Opposite to your css->figma plugin), but I have problem calculating gradient angle from 'gradientTransform' matrix from figma's plugin api. I would really appreciate it if someone could help me out with this. :)
@urosrakovicdev take a look at: https://github.com/bernaferrari/FigmaToCode/blob/7066b2745c87c620e298b779ecae3e6d94896f1c/packages/backend/src/html/builderImpl/htmlColor.ts#L61
Also take a look at: https://github.com/BuilderIO/figma-html
Super cool open-source stuff available now, exciting times 😊
Thanks for this!!This has been of great assistance to me.
By the way, can I ask you some questions? It has been bothering me for a long time.
Q1: How do you find the identity matrix? [[0, 0.5], [1, 0.5]]
Q2: Why do we need to use the inverse of the transformation matrix instead of transformation matrix itself? Isn't the transformation matrix representing the transformation from identity matrix to new matrix ?
Q3: When we want to obtain the coordinates of the gradient line, why do we need to multiply GradientHandles by the width and height of the shape?
I look forward to your response. Thank you very much.