diff --git a/package.json b/package.json index ddc40c5d..7c48a297 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "url": "git+ssh://git@github.com/LLK/scratch-paint.git" }, "dependencies": { - "@scratch/paper": "0.11.20180802201231", + "@scratch/paper": "0.11.20180806212106", "classnames": "2.2.5", "keymirror": "0.1.1", "lodash.bindall": "4.4.0", diff --git a/src/components/loupe/loupe.jsx b/src/components/loupe/loupe.jsx index 127db61e..794483a4 100644 --- a/src/components/loupe/loupe.jsx +++ b/src/components/loupe/loupe.jsx @@ -4,12 +4,10 @@ import bindAll from 'lodash.bindall'; 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'; -const ZOOM_SCALE = 3; - class LoupeComponent extends React.Component { constructor (props) { super(props); @@ -21,38 +19,40 @@ class LoupeComponent extends React.Component { this.draw(); } draw () { - const boxSize = 6 / ZOOM_SCALE; - const boxLineWidth = 1 / ZOOM_SCALE; - const colorRingWidth = 15 / ZOOM_SCALE; + const boxSize = 5; + const boxLineWidth = 1; + const colorRingWidth = 15; + const loupeRadius = ZOOM_SCALE * LOUPE_RADIUS; + const loupeDiameter = loupeRadius * 2; const color = this.props.colorInfo.color; const ctx = this.canvas.getContext('2d'); - this.canvas.width = ZOOM_SCALE * (LOUPE_RADIUS * 2); - this.canvas.height = ZOOM_SCALE * (LOUPE_RADIUS * 2); + this.canvas.width = loupeDiameter; + 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 const tmpCanvas = document.createElement('canvas'); - tmpCanvas.width = LOUPE_RADIUS * 2; - tmpCanvas.height = LOUPE_RADIUS * 2; + tmpCanvas.width = loupeDiameter; + tmpCanvas.height = loupeDiameter; const tmpCtx = tmpCanvas.getContext('2d'); const imageData = tmpCtx.createImageData( - LOUPE_RADIUS * 2, LOUPE_RADIUS * 2 + loupeDiameter, loupeDiameter ); imageData.data.set(this.props.colorInfo.data); tmpCtx.putImageData(imageData, 0, 0); // Scale the loupe canvas and draw the zoomed image - ctx.save(); - ctx.scale(ZOOM_SCALE, ZOOM_SCALE); - ctx.drawImage(tmpCanvas, 0, 0, LOUPE_RADIUS * 2, LOUPE_RADIUS * 2); + ctx.drawImage(tmpCanvas, 0, 0); // Draw an outlined square at the cursor position (cursor is hidden) ctx.lineWidth = boxLineWidth; ctx.strokeStyle = 'black'; ctx.fillStyle = `rgba(${color[0]}, ${color[1]}, ${color[2]}, ${color[3]})`; 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.stroke(); @@ -60,10 +60,9 @@ class LoupeComponent extends React.Component { ctx.strokeStyle = `rgba(${color[0]}, ${color[1]}, ${color[2]}, ${color[3]})`; ctx.lineWidth = colorRingWidth; ctx.beginPath(); - ctx.moveTo(LOUPE_RADIUS * 2, LOUPE_RADIUS); - ctx.arc(LOUPE_RADIUS, LOUPE_RADIUS, LOUPE_RADIUS, 0, 2 * Math.PI); + ctx.moveTo(loupeDiameter, loupeDiameter); + ctx.arc(loupeRadius, loupeRadius, loupeRadius, 0, 2 * Math.PI); ctx.stroke(); - ctx.restore(); } setCanvas (element) { this.canvas = element; @@ -74,6 +73,7 @@ class LoupeComponent extends React.Component { pixelRatio, ...boxProps } = this.props; + const loupeDiameter = ZOOM_SCALE * LOUPE_RADIUS * 2; return ( diff --git a/src/components/paint-editor/paint-editor.css b/src/components/paint-editor/paint-editor.css index 937ff6d7..079955dd 100644 --- a/src/components/paint-editor/paint-editor.css +++ b/src/components/paint-editor/paint-editor.css @@ -121,7 +121,6 @@ $border-radius: 0.25rem; width: 100%; height: 100%; pointer-events: none; - overflow: hidden; } .canvas-controls { diff --git a/src/containers/paint-editor.jsx b/src/containers/paint-editor.jsx index 75bcd309..5ec601aa 100644 --- a/src/containers/paint-editor.jsx +++ b/src/containers/paint-editor.jsx @@ -340,7 +340,8 @@ class PaintEditor extends React.Component { paper.project.view.pixelRatio, paper.view.zoom, 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.pickY = -1; diff --git a/src/helper/layer.js b/src/helper/layer.js index b89b67ec..c811f0c3 100644 --- a/src/helper/layer.js +++ b/src/helper/layer.js @@ -50,7 +50,7 @@ const getRaster = function () { return _getLayer('isRasterLayer').children[0]; }; -const _getBackgroundGuideLayer = function () { +const getBackgroundGuideLayer = function () { 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. */ const hideGuideLayers = function (includeRaster) { - const backgroundGuideLayer = _getBackgroundGuideLayer(); + const backgroundGuideLayer = getBackgroundGuideLayer(); const guideLayer = getGuideLayer(); guideLayer.remove(); backgroundGuideLayer.remove(); @@ -213,6 +213,7 @@ export { hideGuideLayers, showGuideLayers, getGuideLayer, + getBackgroundGuideLayer, clearRaster, getRaster, setupLayers diff --git a/src/helper/tools/eye-dropper.js b/src/helper/tools/eye-dropper.js index d1fe2c3b..6a5ee62e 100644 --- a/src/helper/tools/eye-dropper.js +++ b/src/helper/tools/eye-dropper.js @@ -1,11 +1,36 @@ import paper from '@scratch/paper'; +import {createCanvas, getRaster, getBackgroundGuideLayer} from '../layer'; const LOUPE_RADIUS = 20; +const ZOOM_SCALE = 3; class EyeDropperTool extends paper.Tool { - constructor (canvas, width, height, pixelRatio, zoom, offsetX, offsetY) { + constructor (canvas, width, height, pixelRatio, zoom, offsetX, offsetY, isBitmap) { 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.onMouseMove = this.handleMouseMove; @@ -21,23 +46,6 @@ class EyeDropperTool extends paper.Tool { this.pickX = -1; this.pickY = -1; 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) { // Set the pickX/Y for the color picker loop to pick up @@ -54,6 +62,11 @@ class EyeDropperTool extends paper.Tool { if (!this.hideLoupe) { const colorInfo = this.getColorInfo(this.pickX, this.pickY, this.hideLoupe); if (!colorInfo) return; + if (colorInfo.color[3] === 0) { + // Alpha 0 + this.colorString = null; + return; + } const r = colorInfo.color[0]; const g = colorInfo.color[1]; const b = colorInfo.color[2]; @@ -68,18 +81,21 @@ class EyeDropperTool extends paper.Tool { } } getColorInfo (x, y, hideLoupe) { + const artX = x / this.pixelRatio; + const artY = y / this.pixelRatio; if (!this.bufferLoaded) return null; - const ctx = this.bufferCanvas.getContext('2d'); - const colors = ctx.getImageData(x, y, 1, 1); + const colorContext = this.colorCanvas.getContext('2d'); + const bufferContext = this.bufferCanvas.getContext('2d'); + const colors = colorContext.getImageData(artX * ZOOM_SCALE, artY * ZOOM_SCALE, 1, 1); return { x: x, y: y, color: colors.data, - data: ctx.getImageData( - x - LOUPE_RADIUS, - y - LOUPE_RADIUS, - LOUPE_RADIUS * 2, - LOUPE_RADIUS * 2 + data: bufferContext.getImageData( + (artX * ZOOM_SCALE) - (LOUPE_RADIUS * ZOOM_SCALE), + (artY * ZOOM_SCALE) - (LOUPE_RADIUS * ZOOM_SCALE), + LOUPE_RADIUS * 2 * ZOOM_SCALE, + LOUPE_RADIUS * 2 * ZOOM_SCALE ).data, hideLoupe: hideLoupe }; @@ -88,5 +104,6 @@ class EyeDropperTool extends paper.Tool { export { EyeDropperTool as default, - LOUPE_RADIUS + LOUPE_RADIUS, + ZOOM_SCALE };