From a304dea3384c0bde5c6c362643c7f3fc856e1cee Mon Sep 17 00:00:00 2001 From: adroitwhiz Date: Tue, 9 Jun 2020 15:02:36 -0400 Subject: [PATCH] Add gradients to bitmap shape tools --- src/containers/bit-oval-mode.jsx | 14 +-- src/containers/bit-rect-mode.jsx | 14 +-- src/containers/color-indicator.jsx | 22 ++-- src/containers/fill-color-indicator.jsx | 3 + src/containers/stroke-color-indicator.jsx | 5 +- src/helper/bit-tools/oval-tool.js | 51 +++------ src/helper/bit-tools/rect-tool.js | 35 ++---- src/helper/bitmap.js | 127 ++++++++++++++++++++-- src/helper/style-path.js | 21 +--- 9 files changed, 179 insertions(+), 113 deletions(-) diff --git a/src/containers/bit-oval-mode.jsx b/src/containers/bit-oval-mode.jsx index edd7ebb2..bf3ae380 100644 --- a/src/containers/bit-oval-mode.jsx +++ b/src/containers/bit-oval-mode.jsx @@ -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)); }, diff --git a/src/containers/bit-rect-mode.jsx b/src/containers/bit-rect-mode.jsx index 61aa53fa..6263953a 100644 --- a/src/containers/bit-rect-mode.jsx +++ b/src/containers/bit-rect-mode.jsx @@ -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)); }, diff --git a/src/containers/color-indicator.jsx b/src/containers/color-indicator.jsx index 37e2b40a..762b57dd 100644 --- a/src/containers/color-indicator.jsx +++ b/src/containers/color-indicator.jsx @@ -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, diff --git a/src/containers/fill-color-indicator.jsx b/src/containers/fill-color-indicator.jsx index 3d9276c8..f3704276 100644 --- a/src/containers/fill-color-indicator.jsx +++ b/src/containers/fill-color-indicator.jsx @@ -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 }); diff --git a/src/containers/stroke-color-indicator.jsx b/src/containers/stroke-color-indicator.jsx index 45360c3c..5fafaff1 100644 --- a/src/containers/stroke-color-indicator.jsx +++ b/src/containers/stroke-color-indicator.jsx @@ -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 }); diff --git a/src/helper/bit-tools/oval-tool.js b/src/helper/bit-tools/oval-tool.js index c7ae45a2..f13f595b 100644 --- a/src/helper/bit-tools/oval-tool.js +++ b/src/helper/bit-tools/oval-tool.js @@ -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(); } } diff --git a/src/helper/bit-tools/rect-tool.js b/src/helper/bit-tools/rect-tool.js index 64aa6b78..e31aeedb 100644 --- a/src/helper/bit-tools/rect-tool.js +++ b/src/helper/bit-tools/rect-tool.js @@ -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(); } } diff --git a/src/helper/bitmap.js b/src/helper/bitmap.js index b4b7303c..b070e2a2 100644 --- a/src/helper/bitmap.js +++ b/src/helper/bitmap.js @@ -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 { diff --git a/src/helper/style-path.js b/src/helper/style-path.js index c9482ca9..98561f62 100644 --- a/src/helper/style-path.js +++ b/src/helper/style-path.js @@ -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;