mirror of
https://github.com/scratchfoundation/scratch-paint.git
synced 2024-12-22 21:42:30 -05:00
Add gradients to bitmap shape tools
This commit is contained in:
parent
9f77faf5c1
commit
a304dea338
9 changed files with 179 additions and 113 deletions
|
@ -4,9 +4,10 @@ import React from 'react';
|
|||
import {connect} from 'react-redux';
|
||||
import bindAll from 'lodash.bindall';
|
||||
import Modes from '../lib/modes';
|
||||
import ColorStyleProptype from '../lib/color-style-proptype';
|
||||
import {MIXED} from '../helper/style-path';
|
||||
|
||||
import {changeFillColor, clearFillGradient, DEFAULT_COLOR} from '../reducers/fill-style';
|
||||
import {changeFillColor, DEFAULT_COLOR} from '../reducers/fill-style';
|
||||
import {changeMode} from '../reducers/modes';
|
||||
import {clearSelectedItems, setSelectedItems} from '../reducers/selected-items';
|
||||
import {setCursor} from '../reducers/cursor';
|
||||
|
@ -61,9 +62,8 @@ class BitOvalMode extends React.Component {
|
|||
}
|
||||
activateTool () {
|
||||
clearSelection(this.props.clearSelectedItems);
|
||||
this.props.clearGradient();
|
||||
// Force the default brush color if fill is MIXED or transparent
|
||||
const fillColorPresent = this.props.color !== MIXED && this.props.color !== null;
|
||||
const fillColorPresent = this.props.color.primary !== MIXED && this.props.color.primary !== null;
|
||||
if (!fillColorPresent) {
|
||||
this.props.onChangeFillColor(DEFAULT_COLOR);
|
||||
}
|
||||
|
@ -94,9 +94,8 @@ class BitOvalMode extends React.Component {
|
|||
}
|
||||
|
||||
BitOvalMode.propTypes = {
|
||||
clearGradient: PropTypes.func.isRequired,
|
||||
clearSelectedItems: PropTypes.func.isRequired,
|
||||
color: PropTypes.string,
|
||||
color: ColorStyleProptype,
|
||||
filled: PropTypes.bool,
|
||||
handleMouseDown: PropTypes.func.isRequired,
|
||||
isOvalModeActive: PropTypes.bool.isRequired,
|
||||
|
@ -110,7 +109,7 @@ BitOvalMode.propTypes = {
|
|||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
color: state.scratchPaint.color.fillColor.primary,
|
||||
color: state.scratchPaint.color.fillColor,
|
||||
filled: state.scratchPaint.fillBitmapShapes,
|
||||
isOvalModeActive: state.scratchPaint.mode === Modes.BIT_OVAL,
|
||||
selectedItems: state.scratchPaint.selectedItems,
|
||||
|
@ -121,9 +120,6 @@ const mapDispatchToProps = dispatch => ({
|
|||
clearSelectedItems: () => {
|
||||
dispatch(clearSelectedItems());
|
||||
},
|
||||
clearGradient: () => {
|
||||
dispatch(clearFillGradient());
|
||||
},
|
||||
setCursor: cursorString => {
|
||||
dispatch(setCursor(cursorString));
|
||||
},
|
||||
|
|
|
@ -4,9 +4,10 @@ import React from 'react';
|
|||
import {connect} from 'react-redux';
|
||||
import bindAll from 'lodash.bindall';
|
||||
import Modes from '../lib/modes';
|
||||
import ColorStyleProptype from '../lib/color-style-proptype';
|
||||
import {MIXED} from '../helper/style-path';
|
||||
|
||||
import {changeFillColor, clearFillGradient, DEFAULT_COLOR} from '../reducers/fill-style';
|
||||
import {changeFillColor, DEFAULT_COLOR} from '../reducers/fill-style';
|
||||
import {changeMode} from '../reducers/modes';
|
||||
import {clearSelectedItems, setSelectedItems} from '../reducers/selected-items';
|
||||
import {setCursor} from '../reducers/cursor';
|
||||
|
@ -61,9 +62,8 @@ class BitRectMode extends React.Component {
|
|||
}
|
||||
activateTool () {
|
||||
clearSelection(this.props.clearSelectedItems);
|
||||
this.props.clearGradient();
|
||||
// Force the default brush color if fill is MIXED or transparent
|
||||
const fillColorPresent = this.props.color !== MIXED && this.props.color !== null;
|
||||
const fillColorPresent = this.props.color.primary !== MIXED && this.props.color.primary !== null;
|
||||
if (!fillColorPresent) {
|
||||
this.props.onChangeFillColor(DEFAULT_COLOR);
|
||||
}
|
||||
|
@ -94,9 +94,8 @@ class BitRectMode extends React.Component {
|
|||
}
|
||||
|
||||
BitRectMode.propTypes = {
|
||||
clearGradient: PropTypes.func.isRequired,
|
||||
clearSelectedItems: PropTypes.func.isRequired,
|
||||
color: PropTypes.string,
|
||||
color: ColorStyleProptype,
|
||||
filled: PropTypes.bool,
|
||||
handleMouseDown: PropTypes.func.isRequired,
|
||||
isRectModeActive: PropTypes.bool.isRequired,
|
||||
|
@ -110,7 +109,7 @@ BitRectMode.propTypes = {
|
|||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
color: state.scratchPaint.color.fillColor.primary,
|
||||
color: state.scratchPaint.color.fillColor,
|
||||
filled: state.scratchPaint.fillBitmapShapes,
|
||||
isRectModeActive: state.scratchPaint.mode === Modes.BIT_RECT,
|
||||
selectedItems: state.scratchPaint.selectedItems,
|
||||
|
@ -121,9 +120,6 @@ const mapDispatchToProps = dispatch => ({
|
|||
clearSelectedItems: () => {
|
||||
dispatch(clearSelectedItems());
|
||||
},
|
||||
clearGradient: () => {
|
||||
dispatch(clearFillGradient());
|
||||
},
|
||||
setCursor: cursorString => {
|
||||
dispatch(setCursor(cursorString));
|
||||
},
|
||||
|
|
|
@ -52,30 +52,34 @@ const makeColorIndicator = (label, isStroke) => {
|
|||
}
|
||||
}
|
||||
|
||||
const formatIsBitmap = isBitmap(this.props.format);
|
||||
// Apply color and update redux, but do not update svg until picker closes.
|
||||
const isDifferent = applyColorToSelection(
|
||||
newColor,
|
||||
this.props.colorIndex,
|
||||
this.props.gradientType === GradientTypes.SOLID,
|
||||
isBitmap(this.props.format),
|
||||
isStroke,
|
||||
formatIsBitmap,
|
||||
// In bitmap mode, only the fill color selector is used, but it applies to stroke if fillBitmapShapes
|
||||
// is set to true via the "Fill"/"Outline" selector button
|
||||
isStroke || (formatIsBitmap && !this.props.fillBitmapShapes),
|
||||
this.props.textEditTarget);
|
||||
this._hasChanged = this._hasChanged || isDifferent;
|
||||
this.props.onChangeColor(newColor, this.props.colorIndex);
|
||||
}
|
||||
handleChangeGradientType (gradientType) {
|
||||
const formatIsBitmap = isBitmap(this.props.format);
|
||||
// Apply color and update redux, but do not update svg until picker closes.
|
||||
const isDifferent = applyGradientTypeToSelection(
|
||||
gradientType,
|
||||
isBitmap(this.props.format),
|
||||
isStroke,
|
||||
formatIsBitmap,
|
||||
isStroke || (formatIsBitmap && !this.props.fillBitmapShapes),
|
||||
this.props.textEditTarget);
|
||||
this._hasChanged = this._hasChanged || isDifferent;
|
||||
const hasSelectedItems = getSelectedLeafItems().length > 0;
|
||||
if (hasSelectedItems) {
|
||||
if (isDifferent) {
|
||||
// Recalculates the swatch colors
|
||||
this.props.setSelectedItems();
|
||||
this.props.setSelectedItems(this.props.format);
|
||||
}
|
||||
}
|
||||
if (this.props.gradientType === GradientTypes.SOLID && gradientType !== GradientTypes.SOLID) {
|
||||
|
@ -100,11 +104,12 @@ const makeColorIndicator = (label, isStroke) => {
|
|||
}
|
||||
handleSwap () {
|
||||
if (getSelectedLeafItems().length) {
|
||||
const formatIsBitmap = isBitmap(this.props.format);
|
||||
const isDifferent = swapColorsInSelection(
|
||||
isBitmap(this.props.format),
|
||||
isStroke,
|
||||
formatIsBitmap,
|
||||
isStroke || (formatIsBitmap && !this.props.fillBitmapShapes),
|
||||
this.props.textEditTarget);
|
||||
this.props.setSelectedItems();
|
||||
this.props.setSelectedItems(this.props.format);
|
||||
this._hasChanged = this._hasChanged || isDifferent;
|
||||
} else {
|
||||
let color1 = this.props.color;
|
||||
|
@ -136,6 +141,7 @@ const makeColorIndicator = (label, isStroke) => {
|
|||
color: PropTypes.string,
|
||||
color2: PropTypes.string,
|
||||
colorModalVisible: PropTypes.bool.isRequired,
|
||||
fillBitmapShapes: PropTypes.bool.isRequired,
|
||||
format: PropTypes.oneOf(Object.keys(Formats)),
|
||||
gradientType: PropTypes.oneOf(Object.keys(GradientTypes)).isRequired,
|
||||
intl: intlShape,
|
||||
|
|
|
@ -28,6 +28,7 @@ const mapStateToProps = state => ({
|
|||
color: state.scratchPaint.color.fillColor.primary,
|
||||
color2: state.scratchPaint.color.fillColor.secondary,
|
||||
colorModalVisible: state.scratchPaint.modals.fillColor,
|
||||
fillBitmapShapes: state.scratchPaint.fillBitmapShapes,
|
||||
format: state.scratchPaint.format,
|
||||
gradientType: state.scratchPaint.color.fillColor.gradientType,
|
||||
isEyeDropping: state.scratchPaint.color.eyeDropper.active,
|
||||
|
@ -38,6 +39,8 @@ const mapStateToProps = state => ({
|
|||
state.scratchPaint.mode === Modes.RECT ||
|
||||
state.scratchPaint.mode === Modes.OVAL ||
|
||||
state.scratchPaint.mode === Modes.BIT_SELECT ||
|
||||
state.scratchPaint.mode === Modes.BIT_RECT ||
|
||||
state.scratchPaint.mode === Modes.BIT_OVAL ||
|
||||
state.scratchPaint.mode === Modes.BIT_FILL,
|
||||
textEditTarget: state.scratchPaint.textEditTarget
|
||||
});
|
||||
|
|
|
@ -30,6 +30,7 @@ const mapStateToProps = state => ({
|
|||
state.scratchPaint.mode === Modes.FILL,
|
||||
color: state.scratchPaint.color.strokeColor.primary,
|
||||
color2: state.scratchPaint.color.strokeColor.secondary,
|
||||
fillBitmapShapes: state.scratchPaint.fillBitmapShapes,
|
||||
colorModalVisible: state.scratchPaint.modals.strokeColor,
|
||||
format: state.scratchPaint.format,
|
||||
gradientType: state.scratchPaint.color.strokeColor.gradientType,
|
||||
|
@ -38,7 +39,9 @@ const mapStateToProps = state => ({
|
|||
shouldShowGradientTools: state.scratchPaint.mode === Modes.SELECT ||
|
||||
state.scratchPaint.mode === Modes.RESHAPE ||
|
||||
state.scratchPaint.mode === Modes.RECT ||
|
||||
state.scratchPaint.mode === Modes.OVAL,
|
||||
state.scratchPaint.mode === Modes.OVAL ||
|
||||
state.scratchPaint.mode === Modes.BIT_RECT ||
|
||||
state.scratchPaint.mode === Modes.BIT_OVAL,
|
||||
textEditTarget: state.scratchPaint.textEditTarget
|
||||
});
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import paper from '@scratch/paper';
|
||||
import Modes from '../../lib/modes';
|
||||
import {styleShape} from '../style-path';
|
||||
import {commitOvalToBitmap} from '../bitmap';
|
||||
import {getRaster} from '../layer';
|
||||
import {clearSelection} from '../selection';
|
||||
|
@ -83,29 +84,22 @@ class OvalTool extends paper.Tool {
|
|||
this.commitOval();
|
||||
}
|
||||
}
|
||||
styleOval () {
|
||||
styleShape(this.oval, {
|
||||
fillColor: this.filled ? this.color : null,
|
||||
strokeColor: this.filled ? null : this.color,
|
||||
strokeWidth: this.filled ? 0 : this.thickness
|
||||
});
|
||||
}
|
||||
setColor (color) {
|
||||
this.color = color;
|
||||
if (this.oval) {
|
||||
if (this.filled) {
|
||||
this.oval.fillColor = this.color;
|
||||
} else {
|
||||
this.oval.strokeColor = this.color;
|
||||
}
|
||||
}
|
||||
if (this.oval) this.styleOval();
|
||||
}
|
||||
setFilled (filled) {
|
||||
if (this.filled === filled) return;
|
||||
this.filled = filled;
|
||||
if (this.oval && this.oval.isInserted()) {
|
||||
if (this.filled) {
|
||||
this.oval.fillColor = this.color;
|
||||
this.oval.strokeWidth = 0;
|
||||
this.oval.strokeColor = null;
|
||||
} else {
|
||||
this.oval.fillColor = null;
|
||||
this.oval.strokeWidth = this.thickness;
|
||||
this.oval.strokeColor = this.color;
|
||||
}
|
||||
this.styleOval();
|
||||
this.onUpdateImage();
|
||||
}
|
||||
}
|
||||
|
@ -131,23 +125,12 @@ class OvalTool extends paper.Tool {
|
|||
this.isBoundingBoxMode = false;
|
||||
clearSelection(this.clearSelectedItems);
|
||||
this.commitOval();
|
||||
if (this.filled) {
|
||||
this.oval = new paper.Shape.Ellipse({
|
||||
fillColor: this.color,
|
||||
point: event.downPoint,
|
||||
strokeWidth: 0,
|
||||
strokeScaling: false,
|
||||
size: 0
|
||||
});
|
||||
} else {
|
||||
this.oval = new paper.Shape.Ellipse({
|
||||
strokeColor: this.color,
|
||||
strokeWidth: this.thickness,
|
||||
point: event.downPoint,
|
||||
strokeScaling: false,
|
||||
size: 0
|
||||
});
|
||||
}
|
||||
this.oval = new paper.Shape.Ellipse({
|
||||
point: event.downPoint,
|
||||
size: 0,
|
||||
strokeScaling: false
|
||||
});
|
||||
this.styleOval();
|
||||
this.oval.data = {zoomLevel: paper.view.zoom};
|
||||
}
|
||||
}
|
||||
|
@ -175,6 +158,7 @@ class OvalTool extends paper.Tool {
|
|||
} else {
|
||||
this.oval.position = downPoint.subtract(this.oval.size.multiply(0.5));
|
||||
}
|
||||
this.styleOval();
|
||||
}
|
||||
handleMouseMove (event) {
|
||||
this.boundingBoxTool.onMouseMove(event, this.getHitOptions());
|
||||
|
@ -197,6 +181,7 @@ class OvalTool extends paper.Tool {
|
|||
// Hit testing does not work correctly unless the width and height are positive
|
||||
this.oval.size = new paper.Point(Math.abs(this.oval.size.width), Math.abs(this.oval.size.height));
|
||||
this.oval.selected = true;
|
||||
this.styleOval();
|
||||
this.setSelectedItems();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import paper from '@scratch/paper';
|
||||
import Modes from '../../lib/modes';
|
||||
import {styleShape} from '../../helper/style-path';
|
||||
import {commitRectToBitmap} from '../bitmap';
|
||||
import {getRaster} from '../layer';
|
||||
import {clearSelection} from '../selection';
|
||||
|
@ -81,29 +82,22 @@ class RectTool extends paper.Tool {
|
|||
this.commitRect();
|
||||
}
|
||||
}
|
||||
styleRect () {
|
||||
styleShape(this.rect, {
|
||||
fillColor: this.filled ? this.color : null,
|
||||
strokeColor: this.filled ? null : this.color,
|
||||
strokeWidth: this.filled ? 0 : this.thickness
|
||||
});
|
||||
}
|
||||
setColor (color) {
|
||||
this.color = color;
|
||||
if (this.rect) {
|
||||
if (this.filled) {
|
||||
this.rect.fillColor = this.color;
|
||||
} else {
|
||||
this.rect.strokeColor = this.color;
|
||||
}
|
||||
}
|
||||
if (this.rect) this.styleRect();
|
||||
}
|
||||
setFilled (filled) {
|
||||
if (this.filled === filled) return;
|
||||
this.filled = filled;
|
||||
if (this.rect && this.rect.isInserted()) {
|
||||
if (this.filled) {
|
||||
this.rect.fillColor = this.color;
|
||||
this.rect.strokeWidth = 0;
|
||||
this.rect.strokeColor = null;
|
||||
} else {
|
||||
this.rect.fillColor = null;
|
||||
this.rect.strokeWidth = this.thickness;
|
||||
this.rect.strokeColor = this.color;
|
||||
}
|
||||
this.styleRect();
|
||||
this.onUpdateImage();
|
||||
}
|
||||
}
|
||||
|
@ -148,16 +142,10 @@ class RectTool extends paper.Tool {
|
|||
|
||||
if (this.rect) this.rect.remove();
|
||||
this.rect = new paper.Shape.Rectangle(baseRect);
|
||||
if (this.filled) {
|
||||
this.rect.fillColor = this.color;
|
||||
this.rect.strokeWidth = 0;
|
||||
} else {
|
||||
this.rect.strokeColor = this.color;
|
||||
this.rect.strokeWidth = this.thickness;
|
||||
}
|
||||
this.rect.strokeJoin = 'round';
|
||||
this.rect.strokeScaling = false;
|
||||
this.rect.data = {zoomLevel: paper.view.zoom};
|
||||
this.styleRect();
|
||||
|
||||
if (event.modifiers.alt) {
|
||||
this.rect.position = event.downPoint;
|
||||
|
@ -188,6 +176,7 @@ class RectTool extends paper.Tool {
|
|||
// Hit testing does not work correctly unless the width and height are positive
|
||||
this.rect.size = new paper.Point(Math.abs(this.rect.size.width), Math.abs(this.rect.size.height));
|
||||
this.rect.selected = true;
|
||||
this.styleRect();
|
||||
this.setSelectedItems();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -275,8 +275,24 @@ const drawEllipse = function (options, context) {
|
|||
if (!matrix.isInvertible()) return false;
|
||||
const inverse = matrix.clone().invert();
|
||||
|
||||
const isGradient = context.fillStyle instanceof CanvasGradient;
|
||||
|
||||
// If drawing a gradient, we need to draw the shape onto a temporary canvas, then draw the gradient atop that canvas
|
||||
// only where the shape appears. drawShearedEllipse draws some pixels twice, which would be a problem if the
|
||||
// gradient fades to transparent as those pixels would end up looking more opaque. Instead, mask in the gradient.
|
||||
// https://github.com/LLK/scratch-paint/issues/1152
|
||||
// Outlines are drawn as a series of brush mark images and as such can't be drawn as gradients in the first place.
|
||||
let origContext;
|
||||
let tmpCanvas;
|
||||
const {width: canvasWidth, height: canvasHeight} = context.canvas;
|
||||
if (isGradient) {
|
||||
tmpCanvas = createCanvas(canvasWidth, canvasHeight);
|
||||
origContext = context;
|
||||
context = tmpCanvas.getContext('2d');
|
||||
}
|
||||
|
||||
if (!isFilled) {
|
||||
const brushMark = getBrushMark(thickness, context.fillStyle);
|
||||
const brushMark = getBrushMark(thickness, isGradient ? 'black' : context.fillStyle);
|
||||
const roundedUpRadius = Math.ceil(thickness / 2);
|
||||
drawFn = (x, y) => {
|
||||
context.drawImage(brushMark, ~~x - roundedUpRadius, ~~y - roundedUpRadius);
|
||||
|
@ -295,7 +311,7 @@ const drawEllipse = function (options, context) {
|
|||
const radiusA = Math.sqrt(-4 * C / ((B * B) - (4 * A * C)));
|
||||
const slope = B / 2 / C;
|
||||
|
||||
return drawShearedEllipse_({
|
||||
const wasDrawn = drawShearedEllipse_({
|
||||
centerX: positionX,
|
||||
centerY: positionY,
|
||||
radiusX: radiusA,
|
||||
|
@ -304,6 +320,17 @@ const drawEllipse = function (options, context) {
|
|||
isFilled: isFilled,
|
||||
drawFn: drawFn
|
||||
}, context);
|
||||
|
||||
// Mask in the gradient only where the shape was drawn, and draw it. Then draw the gradientified shape onto the
|
||||
// original canvas normally.
|
||||
if (isGradient && wasDrawn) {
|
||||
context.globalCompositeOperation = 'source-in';
|
||||
context.fillStyle = origContext.fillStyle;
|
||||
context.fillRect(0, 0, canvasWidth, canvasHeight);
|
||||
origContext.drawImage(tmpCanvas, 0, 0);
|
||||
}
|
||||
|
||||
return wasDrawn;
|
||||
};
|
||||
|
||||
const rowBlank_ = function (imageData, width, y) {
|
||||
|
@ -658,6 +685,20 @@ const outlineRect = function (rect, thickness, context) {
|
|||
context.drawImage(brushMark, ~~x - roundedUpRadius, ~~y - roundedUpRadius);
|
||||
};
|
||||
|
||||
const isGradient = context.fillStyle instanceof CanvasGradient;
|
||||
|
||||
// If drawing a gradient, we need to draw the shape onto a temporary canvas, then draw the gradient atop that canvas
|
||||
// only where the shape appears. Outlines are drawn as a series of brush mark images and as such can't be drawn as
|
||||
// gradients.
|
||||
let origContext;
|
||||
let tmpCanvas;
|
||||
const {width: canvasWidth, height: canvasHeight} = context.canvas;
|
||||
if (isGradient) {
|
||||
tmpCanvas = createCanvas(canvasWidth, canvasHeight);
|
||||
origContext = context;
|
||||
context = tmpCanvas.getContext('2d');
|
||||
}
|
||||
|
||||
const startPoint = rect.matrix.transform(new paper.Point(-rect.size.width / 2, -rect.size.height / 2));
|
||||
const widthPoint = rect.matrix.transform(new paper.Point(rect.size.width / 2, -rect.size.height / 2));
|
||||
const heightPoint = rect.matrix.transform(new paper.Point(-rect.size.width / 2, rect.size.height / 2));
|
||||
|
@ -667,6 +708,16 @@ const outlineRect = function (rect, thickness, context) {
|
|||
forEachLinePoint(startPoint, heightPoint, drawFn);
|
||||
forEachLinePoint(endPoint, widthPoint, drawFn);
|
||||
forEachLinePoint(endPoint, heightPoint, drawFn);
|
||||
|
||||
// Mask in the gradient only where the shape was drawn, and draw it. Then draw the gradientified shape onto the
|
||||
// original canvas normally.
|
||||
if (isGradient) {
|
||||
context.globalCompositeOperation = 'source-in';
|
||||
context.fillStyle = origContext.fillStyle;
|
||||
context.fillRect(0, 0, canvasWidth, canvasHeight);
|
||||
origContext.drawImage(tmpCanvas, 0, 0);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
const flipBitmapHorizontal = function (canvas) {
|
||||
|
@ -773,6 +824,62 @@ const commitSelectionToBitmap = function (selection, bitmap) {
|
|||
commitArbitraryTransformation_(selection, bitmap);
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts a Paper.js color style (an item's fillColor or strokeColor) into a canvas-applicable color style.
|
||||
* Note that a "color style" as applied to an item is different from a plain paper.Color or paper.Gradient.
|
||||
* For instance, a gradient "color style" has origin and destination points whereas an unattached paper.Gradient
|
||||
* does not.
|
||||
* @param {paper.Color} color The color to convert to a canvas color/gradient
|
||||
* @param {CanvasRenderingContext2D} context The rendering context on which the style will be used
|
||||
* @returns {string|CanvasGradient} The canvas fill/stroke style.
|
||||
*/
|
||||
const _paperColorToCanvasStyle = function (color, context) {
|
||||
if (!color) return null;
|
||||
if (color.type === 'gradient') {
|
||||
let canvasGradient;
|
||||
const {origin, destination} = color;
|
||||
if (color.gradient.radial) {
|
||||
// Adapted from:
|
||||
// https://github.com/paperjs/paper.js/blob/b081fd72c72cd61331313c3961edb48f3dfaffbd/src/style/Color.js#L926-L935
|
||||
let {highlight} = color;
|
||||
const start = highlight || origin;
|
||||
const radius = destination.getDistance(origin);
|
||||
if (highlight) {
|
||||
const vector = highlight.subtract(origin);
|
||||
if (vector.getLength() > radius) {
|
||||
// Paper ¯\_(ツ)_/¯
|
||||
highlight = origin.add(vector.normalize(radius - 0.1));
|
||||
}
|
||||
}
|
||||
canvasGradient = context.createRadialGradient(
|
||||
start.x, start.y,
|
||||
0,
|
||||
origin.x, origin.y,
|
||||
radius
|
||||
);
|
||||
} else {
|
||||
canvasGradient = context.createLinearGradient(
|
||||
origin.x, origin.y,
|
||||
destination.x, destination.y
|
||||
);
|
||||
}
|
||||
|
||||
const {stops} = color.gradient;
|
||||
// Adapted from:
|
||||
// https://github.com/paperjs/paper.js/blob/b081fd72c72cd61331313c3961edb48f3dfaffbd/src/style/Color.js#L940-L950
|
||||
for (let i = 0, len = stops.length; i < len; i++) {
|
||||
const stop = stops[i];
|
||||
const offset = stop.offset;
|
||||
canvasGradient.addColorStop(
|
||||
offset || i / (len - 1),
|
||||
stop.color.toCSS()
|
||||
);
|
||||
}
|
||||
return canvasGradient;
|
||||
}
|
||||
return color.toCSS();
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {paper.Shape.Ellipse} oval Vector oval to convert
|
||||
* @param {paper.Raster} bitmap raster to draw selection
|
||||
|
@ -784,12 +891,12 @@ const commitOvalToBitmap = function (oval, bitmap) {
|
|||
const context = bitmap.getContext('2d');
|
||||
const filled = oval.strokeWidth === 0;
|
||||
|
||||
const canvasColor = filled ? oval.fillColor : oval.strokeColor;
|
||||
// If the color is null (e.g. fully transparent/"no fill"), don't bother drawing anything,
|
||||
// and especially don't try calling `toCSS` on it
|
||||
const canvasColor = _paperColorToCanvasStyle(filled ? oval.fillColor : oval.strokeColor, context);
|
||||
// If the color is null (e.g. fully transparent/"no fill"), don't bother drawing anything
|
||||
if (!canvasColor) return;
|
||||
|
||||
context.fillStyle = canvasColor.toCSS();
|
||||
context.fillStyle = canvasColor;
|
||||
|
||||
const drew = drawEllipse({
|
||||
position: oval.position,
|
||||
radiusX,
|
||||
|
@ -811,12 +918,12 @@ const commitRectToBitmap = function (rect, bitmap) {
|
|||
const context = tmpCanvas.getContext('2d');
|
||||
const filled = rect.strokeWidth === 0;
|
||||
|
||||
const canvasColor = filled ? rect.fillColor : rect.strokeColor;
|
||||
// If the color is null (e.g. fully transparent/"no fill"), don't bother drawing anything,
|
||||
// and especially don't try calling `toCSS` on it
|
||||
const canvasColor = _paperColorToCanvasStyle(filled ? rect.fillColor : rect.strokeColor, context);
|
||||
// If the color is null (e.g. fully transparent/"no fill"), don't bother drawing anything
|
||||
if (!canvasColor) return;
|
||||
|
||||
context.fillStyle = canvasColor.toCSS();
|
||||
context.fillStyle = canvasColor;
|
||||
|
||||
if (filled) {
|
||||
fillRect(rect, context);
|
||||
} else {
|
||||
|
|
|
@ -120,20 +120,6 @@ const applyColorToSelection = function (
|
|||
item = item.parent;
|
||||
}
|
||||
|
||||
// In bitmap mode, fill color applies to the stroke if there is a stroke
|
||||
if (
|
||||
bitmapMode &&
|
||||
!applyToStroke &&
|
||||
item.strokeColor !== null &&
|
||||
item.strokeWidth
|
||||
) {
|
||||
if (!_colorMatch(item.strokeColor, colorString)) {
|
||||
changed = true;
|
||||
item.strokeColor = colorString;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const itemColorProp = applyToStroke ? 'strokeColor' : 'fillColor';
|
||||
const itemColor = item[itemColorProp];
|
||||
|
||||
|
@ -179,8 +165,6 @@ const applyColorToSelection = function (
|
|||
* @return {boolean} Whether the color application actually changed visibly.
|
||||
*/
|
||||
const swapColorsInSelection = function (bitmapMode, applyToStroke, textEditTargetId) {
|
||||
if (bitmapMode) return; // @todo
|
||||
|
||||
const items = _getColorStateListeners(textEditTargetId);
|
||||
let changed = false;
|
||||
for (const item of items) {
|
||||
|
@ -255,10 +239,7 @@ const applyGradientTypeToSelection = function (gradientType, bitmapMode, applyTo
|
|||
itemColor2 = itemColor.gradient.stops[1].color.toCSS();
|
||||
}
|
||||
|
||||
if (bitmapMode) {
|
||||
// @todo Add when we apply gradients to selections in bitmap mode
|
||||
continue;
|
||||
} else if (gradientType === GradientTypes.SOLID) {
|
||||
if (gradientType === GradientTypes.SOLID) {
|
||||
if (itemColor && itemColor.gradient) {
|
||||
changed = true;
|
||||
item[itemColorProp] = itemColor1;
|
||||
|
|
Loading…
Reference in a new issue