mirror of
https://github.com/scratchfoundation/scratch-paint.git
synced 2024-12-22 21:42:30 -05:00
parent
680b403de6
commit
8056a788e7
6 changed files with 71 additions and 53 deletions
|
@ -23,7 +23,7 @@
|
||||||
"url": "git+ssh://git@github.com/LLK/scratch-paint.git"
|
"url": "git+ssh://git@github.com/LLK/scratch-paint.git"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@scratch/paper": "0.11.20180802201231",
|
"@scratch/paper": "0.11.20180806212106",
|
||||||
"classnames": "2.2.5",
|
"classnames": "2.2.5",
|
||||||
"keymirror": "0.1.1",
|
"keymirror": "0.1.1",
|
||||||
"lodash.bindall": "4.4.0",
|
"lodash.bindall": "4.4.0",
|
||||||
|
|
|
@ -4,12 +4,10 @@ import bindAll from 'lodash.bindall';
|
||||||
|
|
||||||
import Box from '../box/box.jsx';
|
import Box from '../box/box.jsx';
|
||||||
|
|
||||||
import {LOUPE_RADIUS} from '../../helper/tools/eye-dropper';
|
import {LOUPE_RADIUS, ZOOM_SCALE} from '../../helper/tools/eye-dropper';
|
||||||
|
|
||||||
import styles from './loupe.css';
|
import styles from './loupe.css';
|
||||||
|
|
||||||
const ZOOM_SCALE = 3;
|
|
||||||
|
|
||||||
class LoupeComponent extends React.Component {
|
class LoupeComponent extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
@ -21,38 +19,40 @@ class LoupeComponent extends React.Component {
|
||||||
this.draw();
|
this.draw();
|
||||||
}
|
}
|
||||||
draw () {
|
draw () {
|
||||||
const boxSize = 6 / ZOOM_SCALE;
|
const boxSize = 5;
|
||||||
const boxLineWidth = 1 / ZOOM_SCALE;
|
const boxLineWidth = 1;
|
||||||
const colorRingWidth = 15 / ZOOM_SCALE;
|
const colorRingWidth = 15;
|
||||||
|
const loupeRadius = ZOOM_SCALE * LOUPE_RADIUS;
|
||||||
|
const loupeDiameter = loupeRadius * 2;
|
||||||
|
|
||||||
const color = this.props.colorInfo.color;
|
const color = this.props.colorInfo.color;
|
||||||
|
|
||||||
const ctx = this.canvas.getContext('2d');
|
const ctx = this.canvas.getContext('2d');
|
||||||
this.canvas.width = ZOOM_SCALE * (LOUPE_RADIUS * 2);
|
this.canvas.width = loupeDiameter;
|
||||||
this.canvas.height = ZOOM_SCALE * (LOUPE_RADIUS * 2);
|
this.canvas.height = loupeDiameter;
|
||||||
|
ctx.fillStyle = 'white';
|
||||||
|
ctx.fillRect(0, 0, loupeDiameter, loupeDiameter);
|
||||||
|
|
||||||
// In order to scale the image data, must draw to a tmp canvas first
|
// In order to scale the image data, must draw to a tmp canvas first
|
||||||
const tmpCanvas = document.createElement('canvas');
|
const tmpCanvas = document.createElement('canvas');
|
||||||
tmpCanvas.width = LOUPE_RADIUS * 2;
|
tmpCanvas.width = loupeDiameter;
|
||||||
tmpCanvas.height = LOUPE_RADIUS * 2;
|
tmpCanvas.height = loupeDiameter;
|
||||||
const tmpCtx = tmpCanvas.getContext('2d');
|
const tmpCtx = tmpCanvas.getContext('2d');
|
||||||
const imageData = tmpCtx.createImageData(
|
const imageData = tmpCtx.createImageData(
|
||||||
LOUPE_RADIUS * 2, LOUPE_RADIUS * 2
|
loupeDiameter, loupeDiameter
|
||||||
);
|
);
|
||||||
imageData.data.set(this.props.colorInfo.data);
|
imageData.data.set(this.props.colorInfo.data);
|
||||||
tmpCtx.putImageData(imageData, 0, 0);
|
tmpCtx.putImageData(imageData, 0, 0);
|
||||||
|
|
||||||
// Scale the loupe canvas and draw the zoomed image
|
// Scale the loupe canvas and draw the zoomed image
|
||||||
ctx.save();
|
ctx.drawImage(tmpCanvas, 0, 0);
|
||||||
ctx.scale(ZOOM_SCALE, ZOOM_SCALE);
|
|
||||||
ctx.drawImage(tmpCanvas, 0, 0, LOUPE_RADIUS * 2, LOUPE_RADIUS * 2);
|
|
||||||
|
|
||||||
// Draw an outlined square at the cursor position (cursor is hidden)
|
// Draw an outlined square at the cursor position (cursor is hidden)
|
||||||
ctx.lineWidth = boxLineWidth;
|
ctx.lineWidth = boxLineWidth;
|
||||||
ctx.strokeStyle = 'black';
|
ctx.strokeStyle = 'black';
|
||||||
ctx.fillStyle = `rgba(${color[0]}, ${color[1]}, ${color[2]}, ${color[3]})`;
|
ctx.fillStyle = `rgba(${color[0]}, ${color[1]}, ${color[2]}, ${color[3]})`;
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.rect((20) - (boxSize / 2), (20) - (boxSize / 2), boxSize, boxSize);
|
ctx.rect(loupeRadius - (boxSize / 2), loupeRadius - (boxSize / 2), boxSize, boxSize);
|
||||||
ctx.fill();
|
ctx.fill();
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
|
|
||||||
|
@ -60,10 +60,9 @@ class LoupeComponent extends React.Component {
|
||||||
ctx.strokeStyle = `rgba(${color[0]}, ${color[1]}, ${color[2]}, ${color[3]})`;
|
ctx.strokeStyle = `rgba(${color[0]}, ${color[1]}, ${color[2]}, ${color[3]})`;
|
||||||
ctx.lineWidth = colorRingWidth;
|
ctx.lineWidth = colorRingWidth;
|
||||||
ctx.beginPath();
|
ctx.beginPath();
|
||||||
ctx.moveTo(LOUPE_RADIUS * 2, LOUPE_RADIUS);
|
ctx.moveTo(loupeDiameter, loupeDiameter);
|
||||||
ctx.arc(LOUPE_RADIUS, LOUPE_RADIUS, LOUPE_RADIUS, 0, 2 * Math.PI);
|
ctx.arc(loupeRadius, loupeRadius, loupeRadius, 0, 2 * Math.PI);
|
||||||
ctx.stroke();
|
ctx.stroke();
|
||||||
ctx.restore();
|
|
||||||
}
|
}
|
||||||
setCanvas (element) {
|
setCanvas (element) {
|
||||||
this.canvas = element;
|
this.canvas = element;
|
||||||
|
@ -74,6 +73,7 @@ class LoupeComponent extends React.Component {
|
||||||
pixelRatio,
|
pixelRatio,
|
||||||
...boxProps
|
...boxProps
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
const loupeDiameter = ZOOM_SCALE * LOUPE_RADIUS * 2;
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
{...boxProps}
|
{...boxProps}
|
||||||
|
@ -82,10 +82,10 @@ class LoupeComponent extends React.Component {
|
||||||
element="canvas"
|
element="canvas"
|
||||||
height={LOUPE_RADIUS * 2}
|
height={LOUPE_RADIUS * 2}
|
||||||
style={{
|
style={{
|
||||||
top: (colorInfo.y / pixelRatio) - ((ZOOM_SCALE * (LOUPE_RADIUS * 2)) / 2),
|
top: (colorInfo.y / pixelRatio) - (loupeDiameter / 2),
|
||||||
left: (colorInfo.x / pixelRatio) - ((ZOOM_SCALE * (LOUPE_RADIUS * 2)) / 2),
|
left: (colorInfo.x / pixelRatio) - (loupeDiameter / 2),
|
||||||
width: (LOUPE_RADIUS * 2) * ZOOM_SCALE,
|
width: loupeDiameter,
|
||||||
height: (LOUPE_RADIUS * 2) * ZOOM_SCALE
|
height: loupeDiameter
|
||||||
}}
|
}}
|
||||||
width={LOUPE_RADIUS * 2}
|
width={LOUPE_RADIUS * 2}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -121,7 +121,6 @@ $border-radius: 0.25rem;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
overflow: hidden;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.canvas-controls {
|
.canvas-controls {
|
||||||
|
|
|
@ -340,7 +340,8 @@ class PaintEditor extends React.Component {
|
||||||
paper.project.view.pixelRatio,
|
paper.project.view.pixelRatio,
|
||||||
paper.view.zoom,
|
paper.view.zoom,
|
||||||
paper.project.view.bounds.x,
|
paper.project.view.bounds.x,
|
||||||
paper.project.view.bounds.y
|
paper.project.view.bounds.y,
|
||||||
|
isBitmap(this.props.format)
|
||||||
);
|
);
|
||||||
this.eyeDropper.pickX = -1;
|
this.eyeDropper.pickX = -1;
|
||||||
this.eyeDropper.pickY = -1;
|
this.eyeDropper.pickY = -1;
|
||||||
|
|
|
@ -50,7 +50,7 @@ const getRaster = function () {
|
||||||
return _getLayer('isRasterLayer').children[0];
|
return _getLayer('isRasterLayer').children[0];
|
||||||
};
|
};
|
||||||
|
|
||||||
const _getBackgroundGuideLayer = function () {
|
const getBackgroundGuideLayer = function () {
|
||||||
return _getLayer('isBackgroundGuideLayer');
|
return _getLayer('isBackgroundGuideLayer');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -75,7 +75,7 @@ const getGuideLayer = function () {
|
||||||
* @return {object} an object of the removed layers, which should be passed to showGuideLayers to re-add them.
|
* @return {object} an object of the removed layers, which should be passed to showGuideLayers to re-add them.
|
||||||
*/
|
*/
|
||||||
const hideGuideLayers = function (includeRaster) {
|
const hideGuideLayers = function (includeRaster) {
|
||||||
const backgroundGuideLayer = _getBackgroundGuideLayer();
|
const backgroundGuideLayer = getBackgroundGuideLayer();
|
||||||
const guideLayer = getGuideLayer();
|
const guideLayer = getGuideLayer();
|
||||||
guideLayer.remove();
|
guideLayer.remove();
|
||||||
backgroundGuideLayer.remove();
|
backgroundGuideLayer.remove();
|
||||||
|
@ -213,6 +213,7 @@ export {
|
||||||
hideGuideLayers,
|
hideGuideLayers,
|
||||||
showGuideLayers,
|
showGuideLayers,
|
||||||
getGuideLayer,
|
getGuideLayer,
|
||||||
|
getBackgroundGuideLayer,
|
||||||
clearRaster,
|
clearRaster,
|
||||||
getRaster,
|
getRaster,
|
||||||
setupLayers
|
setupLayers
|
||||||
|
|
|
@ -1,11 +1,36 @@
|
||||||
import paper from '@scratch/paper';
|
import paper from '@scratch/paper';
|
||||||
|
import {createCanvas, getRaster, getBackgroundGuideLayer} from '../layer';
|
||||||
|
|
||||||
const LOUPE_RADIUS = 20;
|
const LOUPE_RADIUS = 20;
|
||||||
|
const ZOOM_SCALE = 3;
|
||||||
|
|
||||||
class EyeDropperTool extends paper.Tool {
|
class EyeDropperTool extends paper.Tool {
|
||||||
constructor (canvas, width, height, pixelRatio, zoom, offsetX, offsetY) {
|
constructor (canvas, width, height, pixelRatio, zoom, offsetX, offsetY, isBitmap) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
const layer = isBitmap ? getRaster().layer : paper.project.activeLayer;
|
||||||
|
const contentRaster3x = layer.rasterize(
|
||||||
|
72 * ZOOM_SCALE * paper.view.zoom, false /* insert */, paper.view.bounds);
|
||||||
|
const backgroundRaster3x = getBackgroundGuideLayer().rasterize(
|
||||||
|
72 * ZOOM_SCALE * paper.view.zoom, false /* insert */, paper.view.bounds);
|
||||||
|
|
||||||
|
// Canvas from which loupe is cut, shows art and grid
|
||||||
|
this.bufferCanvas = createCanvas(canvas.width * ZOOM_SCALE, canvas.height * ZOOM_SCALE);
|
||||||
|
const bufferCanvasContext = this.bufferCanvas.getContext('2d');
|
||||||
|
// Canvas to sample colors from; just the art
|
||||||
|
this.colorCanvas = createCanvas(canvas.width * ZOOM_SCALE, canvas.height * ZOOM_SCALE);
|
||||||
|
const colorCanvasContext = this.colorCanvas.getContext('2d');
|
||||||
|
|
||||||
|
backgroundRaster3x.onLoad = () => {
|
||||||
|
bufferCanvasContext.drawImage(backgroundRaster3x.canvas, 0, 0);
|
||||||
|
contentRaster3x.onLoad = () => {
|
||||||
|
colorCanvasContext.drawImage(contentRaster3x.canvas, 0, 0);
|
||||||
|
bufferCanvasContext.drawImage(this.colorCanvas, 0, 0);
|
||||||
|
this.bufferLoaded = true;
|
||||||
|
};
|
||||||
|
if (contentRaster3x.loaded) contentRaster3x.onLoad();
|
||||||
|
};
|
||||||
|
|
||||||
this.onMouseDown = this.handleMouseDown;
|
this.onMouseDown = this.handleMouseDown;
|
||||||
this.onMouseMove = this.handleMouseMove;
|
this.onMouseMove = this.handleMouseMove;
|
||||||
|
|
||||||
|
@ -21,23 +46,6 @@ class EyeDropperTool extends paper.Tool {
|
||||||
this.pickX = -1;
|
this.pickX = -1;
|
||||||
this.pickY = -1;
|
this.pickY = -1;
|
||||||
this.hideLoupe = true;
|
this.hideLoupe = true;
|
||||||
|
|
||||||
/*
|
|
||||||
Chrome 64 has a bug that makes it impossible to use getImageData directly
|
|
||||||
a 2d canvas. Until that is resolved, copy the canvas to a buffer canvas
|
|
||||||
and read the data from there.
|
|
||||||
https://github.com/LLK/scratch-paint/issues/276
|
|
||||||
*/
|
|
||||||
this.bufferLoaded = false;
|
|
||||||
this.bufferCanvas = document.createElement('canvas');
|
|
||||||
this.bufferCanvas.width = canvas.width;
|
|
||||||
this.bufferCanvas.height = canvas.height;
|
|
||||||
this.bufferImage = new Image();
|
|
||||||
this.bufferImage.onload = () => {
|
|
||||||
this.bufferCanvas.getContext('2d').drawImage(this.bufferImage, 0, 0);
|
|
||||||
this.bufferLoaded = true;
|
|
||||||
};
|
|
||||||
this.bufferImage.src = canvas.toDataURL();
|
|
||||||
}
|
}
|
||||||
handleMouseMove (event) {
|
handleMouseMove (event) {
|
||||||
// Set the pickX/Y for the color picker loop to pick up
|
// Set the pickX/Y for the color picker loop to pick up
|
||||||
|
@ -54,6 +62,11 @@ class EyeDropperTool extends paper.Tool {
|
||||||
if (!this.hideLoupe) {
|
if (!this.hideLoupe) {
|
||||||
const colorInfo = this.getColorInfo(this.pickX, this.pickY, this.hideLoupe);
|
const colorInfo = this.getColorInfo(this.pickX, this.pickY, this.hideLoupe);
|
||||||
if (!colorInfo) return;
|
if (!colorInfo) return;
|
||||||
|
if (colorInfo.color[3] === 0) {
|
||||||
|
// Alpha 0
|
||||||
|
this.colorString = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
const r = colorInfo.color[0];
|
const r = colorInfo.color[0];
|
||||||
const g = colorInfo.color[1];
|
const g = colorInfo.color[1];
|
||||||
const b = colorInfo.color[2];
|
const b = colorInfo.color[2];
|
||||||
|
@ -68,18 +81,21 @@ class EyeDropperTool extends paper.Tool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
getColorInfo (x, y, hideLoupe) {
|
getColorInfo (x, y, hideLoupe) {
|
||||||
|
const artX = x / this.pixelRatio;
|
||||||
|
const artY = y / this.pixelRatio;
|
||||||
if (!this.bufferLoaded) return null;
|
if (!this.bufferLoaded) return null;
|
||||||
const ctx = this.bufferCanvas.getContext('2d');
|
const colorContext = this.colorCanvas.getContext('2d');
|
||||||
const colors = ctx.getImageData(x, y, 1, 1);
|
const bufferContext = this.bufferCanvas.getContext('2d');
|
||||||
|
const colors = colorContext.getImageData(artX * ZOOM_SCALE, artY * ZOOM_SCALE, 1, 1);
|
||||||
return {
|
return {
|
||||||
x: x,
|
x: x,
|
||||||
y: y,
|
y: y,
|
||||||
color: colors.data,
|
color: colors.data,
|
||||||
data: ctx.getImageData(
|
data: bufferContext.getImageData(
|
||||||
x - LOUPE_RADIUS,
|
(artX * ZOOM_SCALE) - (LOUPE_RADIUS * ZOOM_SCALE),
|
||||||
y - LOUPE_RADIUS,
|
(artY * ZOOM_SCALE) - (LOUPE_RADIUS * ZOOM_SCALE),
|
||||||
LOUPE_RADIUS * 2,
|
LOUPE_RADIUS * 2 * ZOOM_SCALE,
|
||||||
LOUPE_RADIUS * 2
|
LOUPE_RADIUS * 2 * ZOOM_SCALE
|
||||||
).data,
|
).data,
|
||||||
hideLoupe: hideLoupe
|
hideLoupe: hideLoupe
|
||||||
};
|
};
|
||||||
|
@ -88,5 +104,6 @@ class EyeDropperTool extends paper.Tool {
|
||||||
|
|
||||||
export {
|
export {
|
||||||
EyeDropperTool as default,
|
EyeDropperTool as default,
|
||||||
LOUPE_RADIUS
|
LOUPE_RADIUS,
|
||||||
|
ZOOM_SCALE
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue