Skip to content

feat(Gradient): BREAKING: Remove opacity from colorstops in live Gradient class #9622

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 31 commits into from
Apr 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
170d05a
remove-colorparsing-rendering
asturur Jan 21, 2024
90f34b6
not broken
asturur Jan 21, 2024
ffa6310
update CHANGELOG.md
github-actions[bot] Jan 21, 2024
fc9b162
mutation protect
asturur Jan 21, 2024
57db454
Merge branch 'colorstops-in-render' of github.com:fabricjs/fabric.js …
asturur Jan 21, 2024
ec7567b
Merge branch 'master' into colorstops-in-render
asturur Mar 16, 2024
2cc5abb
Merge branch 'master' into colorstops-in-render
asturur Mar 27, 2024
1946d69
restored code as before
asturur Mar 28, 2024
292fd3f
Merge branch 'master' into colorstops-in-render
asturur Apr 4, 2025
aa78ef8
fix parser test
asturur Apr 4, 2025
9b36525
fix visual test
asturur Apr 4, 2025
8760117
typo in string
asturur Apr 4, 2025
c555ec5
slowly moving
asturur Apr 4, 2025
6c6f0ec
unit tests restored
asturur Apr 4, 2025
5c288a4
coverage
asturur Apr 4, 2025
e109833
started converter
asturur Apr 4, 2025
6b730b4
coma
asturur Apr 4, 2025
92af2fe
coma
asturur Apr 4, 2025
8ce55d8
more exclusions
asturur Apr 4, 2025
f9539c2
more exclusions
asturur Apr 4, 2025
e2959b3
Merge branch 'master' into colorstops-in-render
asturur Apr 6, 2025
c0fd0c1
Merge branch 'master' into colorstops-in-render
asturur Apr 7, 2025
97d55bb
Merge branch 'master' into colorstops-in-render
asturur Apr 12, 2025
9a092d1
fix some coverage and inclusion issue
asturur Apr 12, 2025
80f3814
am fixed actions
asturur Apr 12, 2025
224b3a0
progress
asturur Apr 13, 2025
0174fac
no dists
asturur Apr 13, 2025
2fe279c
strange stats
asturur Apr 13, 2025
517bf4a
start testing it
asturur Apr 13, 2025
4b35a79
tests
asturur Apr 13, 2025
a43355c
do not cover publish
asturur Apr 13, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions .github/workflows/build-stats.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,13 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install dependencies
uses: ./.github/actions/cached-install
with:
node-version: 20.x
install-system-deps: false
- name: Build fabric.js
uses: ./.github/actions/build-fabric-cached
run: npm run build
- name: Recover build stats
uses: actions/download-artifact@v4
with:
Expand Down
4 changes: 1 addition & 3 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,11 @@ jobs:
bundled: dist/index.mjs
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: ./.github/actions/cached-install
with:
node-version: 20.x
- name: Build fabric.js
uses: ./.github/actions/build-fabric-cached
run: npm run build
- name: Create prnumber artifact
run: echo "${{ github.event.pull_request.number }}" >> ./prnumber.txt
- name: Upload Pr Number
Expand Down
5 changes: 3 additions & 2 deletions .github/workflows/npmpublish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ jobs:
- run: npm run build
- run: node publish.js
env:
NODE_AUTH_TOKEN: ${{secrets.npm_token}}
PRE_RELEASE: ${{github.event.release.prerelease}}
NODE_AUTH_TOKEN: ${{ secrets.npm_token }}
PRE_RELEASE: ${{ github.event.release.prerelease }}
TAG_NAME: ${{ github.event.release.tag_name }}
update-bug-report:
runs-on: ubuntu-24.04
# wait for publishing to complete
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## [next]

- feat(Gradient): BREAKING: remove opacity from colorstops in live Gradient class [#9622](https://github.com/fabricjs/fabric.js/pull/9622)
- refactor(tests): move canvas tests from qunit to vitest [#10499](https://github.com/fabricjs/fabric.js/pull/10499)
- refactor(tests): move group tests from qunit to vitest [#10495](https://github.com/fabricjs/fabric.js/pull/10495)
- ci(): New safe worflow for build stats [#10518](https://github.com/fabricjs/fabric.js/pull/10518)
Expand Down
1 change: 1 addition & 0 deletions extensions/aligning_guidelines/util/basic.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { getDistance, setPositionDir } from './basic';
import { Rect } from '../../../src/shapes/Rect';
import { Point } from '../../../src/Point';
import { describe, expect, it } from 'vitest';

describe('getDistance', () => {
it('returns the distabnce between the 2 numbers', () => {
Expand Down
75 changes: 75 additions & 0 deletions extensions/data_updaters/gradient/__snapshots__/index.spec.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`installGradientUpdater > After intalling the wrapper > FromObject will merge the old opacity into color 1`] = `
[
{
"color": "rgba(255,0,0,0.5)",
"offset": 1,
},
{
"color": "rgba(0,0,255,0.5)",
"offset": 0.5,
},
{
"color": "rgba(0,255,0,0.3)",
"offset": 0,
},
]
`;

exports[`installGradientUpdater > After intalling the wrapper > Init gradient from options still preserve old color stops 1`] = `
[
{
"color": "red",
"offset": 1,
"opacity": 0.5,
},
{
"color": "rgba(0,0,255,0.5)",
"offset": 0.5,
},
{
"color": "rgba(0,255,0,0.5)",
"offset": 0,
"opacity": 0.3,
},
]
`;

exports[`installGradientUpdater > Without using it opacity information is lost > FromObject will preserve the old color stop 1`] = `
[
{
"color": "red",
"offset": 1,
"opacity": 0.5,
},
{
"color": "rgba(0,0,255,0.5)",
"offset": 0.5,
},
{
"color": "rgba(0,255,0,0.5)",
"offset": 0,
"opacity": 0.3,
},
]
`;

exports[`installGradientUpdater > Without using it opacity information is lost > Init gradient from options preserve old color stops 1`] = `
[
{
"color": "red",
"offset": 1,
"opacity": 0.5,
},
{
"color": "rgba(0,0,255,0.5)",
"offset": 0.5,
},
{
"color": "rgba(0,255,0,0.5)",
"offset": 0,
"opacity": 0.3,
},
]
`;
126 changes: 126 additions & 0 deletions extensions/data_updaters/gradient/index.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { describe, vitest, expect, test, beforeAll } from 'vitest';
import type {
SerializedGradientProps,
GradientOptions,
ColorStop,
} from 'fabric';
import { Gradient } from 'fabric';
import { installGradientUpdater } from './index';

const oldGradientOptions: GradientOptions<'linear'> & {
colorStops: (ColorStop & { opacity?: number })[];
} = {
type: 'linear',
colorStops: [
{
color: 'red',
offset: 1,
opacity: 0.5,
},
{
color: 'rgba(0,0,255,0.5)',
offset: 0.5,
},
{
color: 'rgba(0,255,0,0.5)',
offset: 0,
opacity: 0.3,
},
],
};

const oldSerializedGradient: SerializedGradientProps<'linear'> & {
colorStops: (ColorStop & { opacity?: number })[];
} = {
type: 'linear',
colorStops: [
{
color: 'red',
offset: 1,
opacity: 0.5,
},
{
color: 'rgba(0,0,255,0.5)',
offset: 0.5,
},
{
color: 'rgba(0,255,0,0.5)',
offset: 0,
opacity: 0.3,
},
],
};

const addColorStopMock = vitest.fn();

const ctxMock = {
createLinearGradient: () => ({
addColorStop: addColorStopMock,
}),
} as unknown as CanvasRenderingContext2D;

describe('installGradientUpdater', () => {
describe('Without using it opacity information is lost', () => {
test('Init gradient from options preserve old color stops', () => {
const gradient = new Gradient(oldGradientOptions);
expect(gradient.colorStops).toMatchSnapshot();
});
test('old color stops do not render the old opacity', () => {
const gradient = new Gradient(oldGradientOptions);
gradient.toLive(ctxMock);
oldGradientOptions.colorStops.forEach((colorStop) => {
expect(addColorStopMock).toHaveBeenCalledWith(
colorStop.offset,
colorStop.color,
);
});
});
test('FromObject will preserve the old color stop', async () => {
const gradient = await Gradient.fromObject(oldSerializedGradient);
expect(gradient.colorStops).toMatchSnapshot();
});
test('FromObject will preserve the but wont render the old opacity', async () => {
const gradient = await Gradient.fromObject(oldSerializedGradient);
gradient.toLive(ctxMock);
oldGradientOptions.colorStops.forEach((colorStop) => {
expect(addColorStopMock).toHaveBeenCalledWith(
colorStop.offset,
colorStop.color,
);
});
});
});
describe('After intalling the wrapper', () => {
beforeAll(() => {
installGradientUpdater();
});
test('Init gradient from options still preserve old color stops', () => {
const gradient = new Gradient(oldGradientOptions);
expect(gradient.colorStops).toMatchSnapshot();
});
test('old color stops do not render the old opacity', () => {
const gradient = new Gradient(oldGradientOptions);
gradient.toLive(ctxMock);
oldGradientOptions.colorStops.forEach((colorStop) => {
expect(addColorStopMock).toHaveBeenCalledWith(
colorStop.offset,
colorStop.color,
);
});
});
test('FromObject will merge the old opacity into color', async () => {
const gradient = await Gradient.fromObject(oldSerializedGradient);
expect(gradient.colorStops).toMatchSnapshot();
});
test('FromObject will render with the new colors', async () => {
const gradient = await Gradient.fromObject(oldSerializedGradient);
gradient.toLive(ctxMock);
oldGradientOptions.colorStops.forEach((colorStop, index) => {
expect(addColorStopMock).toHaveBeenCalledWith(
colorStop.offset,
gradient.colorStops[index].color,
);
});
});
});
});
58 changes: 58 additions & 0 deletions extensions/data_updaters/gradient/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import type { GradientOptions, ColorStop } from 'fabric';
import { Color, Gradient } from 'fabric';

/**
* Updates the fromObject function of a Gradient to return a version that can restore old data
* with opactiy in color Stops
* Used to upgrade from fabric 6 to fabric 7
* @param originalFn the original fromObject function of an object,
* @returns a wrapped fromObject function for the object
*/

type OldColorStop = ColorStop & {
opacity?: number;
};

export const gradientUpdaterWrapper = <S, T extends Gradient<S> = Gradient<S>>(
originalFn: (
options: GradientOptions<'linear'> | GradientOptions<'radial'>,
) => Promise<T>,
): ((
options: GradientOptions<'linear'> | GradientOptions<'radial'>,
) => Promise<T>) =>
async function (this: T, serializedGradient) {
// we default to left and top because those are defaults before deprecation
const { colorStops } = serializedGradient;
// and we do not want to pass those properties on the object anymore
const newColorStops: ColorStop[] = (
colorStops as OldColorStop[]
)?.map<ColorStop>(({ color, opacity, offset }) => {
if (opacity === undefined || opacity === 1) {
return {
color,
offset,
};
}
const col = new Color(color).setAlpha(opacity).toRgba();
return {
color: col,
offset,
};
});
const gradient = await originalFn.call(this, {
...serializedGradient,
colorStops: newColorStops,
});
return gradient;
};

/**
* Wraps and override the current fabricJS fromObject static functions
* Used to upgrade from fabric 7 to fabric 8
* If you used to export with includeDefaultValues = false, you have to specify
* which were yours default origins values
*/
export const installGradientUpdater = () => {
// @ts-expect-error untypable
Gradient.fromObject = gradientUpdaterWrapper(Gradient.fromObject);
};
6 changes: 4 additions & 2 deletions extensions/data_updaters/origins/index.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import type { TSimplePathData } from 'fabric';
import type { TSimplePathData } from '../../../fabric';
import { installOriginWrapperUpdater } from './index';
import { BaseFabricObject, Path, Rect, Group } from 'fabric';
import { BaseFabricObject, Path, Rect, Group } from '../../../fabric';

import { describe, expect, it } from 'vitest';

installOriginWrapperUpdater();

Expand Down
9 changes: 5 additions & 4 deletions extensions/data_updaters/origins/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@
* @returns a wrapped fromObject function for the object
*/
export const originUpdaterWrapper = <T extends FabricObject = FabricObject>(
originalFn: (...args: any[]) => Promise<T>,

Check warning on line 21 in extensions/data_updaters/origins/index.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
defaultOriginX: TOriginX = 'left',
defaultOriginY: TOriginY = 'top',
): ((...args: any[]) => Promise<T>) =>

Check warning on line 24 in extensions/data_updaters/origins/index.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
async function (this: T, serializedObject, ...args) {
// we default to left and top because those are defaults before deprecation
const { originX = defaultOriginX, originY = defaultOriginY } =
Expand All @@ -41,10 +41,11 @@

/**
* Wraps and override the current fabricJS fromObject static functions
* Used to upgrade from fabric 6 to fabric 7
* @param defaultOriginX optional default value for non exported originX,
* @param defaultOriginY optional default value for non exported originY,
* @returns a wrapped fromObject function for the object
* Used to upgrade from fabric 7 to fabric 8
* If you used to export with includeDefaultValues = false, you have to specify
* which were yours default origins values
* @param originX optional default value for non exported originX,
* @param originY optional default value for non exported originY,
*/
export const installOriginWrapperUpdater = (
originX?: TOriginX,
Expand Down
2 changes: 2 additions & 0 deletions extensions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ export {
originUpdaterWrapper,
installOriginWrapperUpdater,
} from './data_updaters/origins';

export { gradientUpdaterWrapper } from './data_updaters/gradient';
Loading
Loading