Loupe issues fixed #378 (#382)

This commit is contained in:
Jacco Kulman 2018-08-09 00:27:01 +02:00 committed by DD Liu
parent 680b403de6
commit 8056a788e7
6 changed files with 71 additions and 53 deletions

View file

@ -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",

View file

@ -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}
/> />

View file

@ -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 {

View file

@ -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;

View file

@ -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

View file

@ -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
}; };