mirror of
https://github.com/scratchfoundation/scratch-paint.git
synced 2024-11-14 19:35:30 -05:00
Update import and export types (#412)
* Import/export bitmap * Fix playground and readme * Use constants instead of changing zoom level for every import/export
This commit is contained in:
parent
06a53d305c
commit
89133f42e5
9 changed files with 110 additions and 128 deletions
|
@ -60,7 +60,7 @@ Then go to [http://localhost:8601](http://localhost:8601). 601 is supposed to lo
|
|||
|
||||
### How to include in your own Node.js App
|
||||
For an example of how to use scratch-paint as a library, check out the `scratch-paint/src/playground` directory.
|
||||
In `playground.jsx`, you can change the image that is passed in (which may either be nothing, an SVG string or an HTMLImageElement) and edit the handler `onUpdateImage`, which is called with the new image (either an SVG string or an HTMLCanvasElement) each time the vector drawing is edited.
|
||||
In `playground.jsx`, you can change the image that is passed in (which may either be nothing, an SVG string or a base64 data URI) and edit the handler `onUpdateImage`, which is called with the new image (either an SVG string or an ImageData) each time the vector drawing is edited.
|
||||
|
||||
If the `imageId` parameter changes, then the paint editor will be cleared, the undo stack reset, and the image re-imported.
|
||||
|
||||
|
@ -73,6 +73,7 @@ import PaintEditor from 'scratch-paint';
|
|||
<PaintEditor
|
||||
image={optionalImage}
|
||||
imageId={optionalId}
|
||||
imageFormat='svg' // 'svg', 'png', or 'jpg'
|
||||
rotationCenterX={optionalCenterPointXRelativeToTopLeft}
|
||||
rotationCenterY={optionalCenterPointYRelativeToTopLeft}
|
||||
onUpdateImage={handleUpdateImageFunction}
|
||||
|
|
|
@ -434,6 +434,7 @@ const PaintEditorComponent = props => {
|
|||
<PaperCanvas
|
||||
canvasRef={props.setCanvas}
|
||||
image={props.image}
|
||||
imageFormat={props.imageFormat}
|
||||
imageId={props.imageId}
|
||||
rotationCenterX={props.rotationCenterX}
|
||||
rotationCenterY={props.rotationCenterY}
|
||||
|
@ -541,6 +542,7 @@ PaintEditorComponent.propTypes = {
|
|||
PropTypes.string,
|
||||
PropTypes.instanceOf(HTMLImageElement)
|
||||
]),
|
||||
imageFormat: PropTypes.string,
|
||||
imageId: PropTypes.string,
|
||||
intl: intlShape,
|
||||
isEyeDropping: PropTypes.bool,
|
||||
|
|
|
@ -13,12 +13,13 @@ import {setTextEditTarget} from '../reducers/text-edit-target';
|
|||
import {updateViewBounds} from '../reducers/view-bounds';
|
||||
|
||||
import {getRaster, hideGuideLayers, showGuideLayers} from '../helper/layer';
|
||||
import {trim} from '../helper/bitmap';
|
||||
import {getHitBounds} from '../helper/bitmap';
|
||||
import {performUndo, performRedo, performSnapshot, shouldShowUndo, shouldShowRedo} from '../helper/undo';
|
||||
import {bringToFront, sendBackward, sendToBack, bringForward} from '../helper/order';
|
||||
import {groupSelection, ungroupSelection} from '../helper/group';
|
||||
import {scaleWithStrokes} from '../helper/math';
|
||||
import {getSelectedLeafItems} from '../helper/selection';
|
||||
import {ART_BOARD_WIDTH, ART_BOARD_HEIGHT, SVG_ART_BOARD_WIDTH, SVG_ART_BOARD_HEIGHT} from '../helper/view';
|
||||
import {resetZoom, zoomOnSelection} from '../helper/view';
|
||||
import EyeDropperTool from '../helper/tools/eye-dropper';
|
||||
|
||||
|
@ -119,29 +120,19 @@ class PaintEditor extends React.Component {
|
|||
}
|
||||
}
|
||||
handleUpdateImage (skipSnapshot) {
|
||||
// Store the zoom/pan and restore it after snapshotting
|
||||
// TODO Only doing this because snapshotting at zoom/pan makes export wrong
|
||||
const oldZoom = paper.project.view.zoom;
|
||||
const oldCenter = paper.project.view.center.clone();
|
||||
resetZoom();
|
||||
|
||||
let raster;
|
||||
if (isBitmap(this.props.format)) {
|
||||
raster = trim(getRaster());
|
||||
raster.remove();
|
||||
|
||||
const rect = getHitBounds(getRaster());
|
||||
this.props.onUpdateImage(
|
||||
false /* isVector */,
|
||||
raster.canvas,
|
||||
paper.project.view.center.x - raster.bounds.x,
|
||||
paper.project.view.center.y - raster.bounds.y);
|
||||
getRaster().getImageData(rect),
|
||||
(ART_BOARD_WIDTH / 2) - rect.x,
|
||||
(ART_BOARD_HEIGHT / 2) - rect.y);
|
||||
} else if (isVector(this.props.format)) {
|
||||
const guideLayers = hideGuideLayers(true /* includeRaster */);
|
||||
|
||||
// Export at 0.5x
|
||||
scaleWithStrokes(paper.project.activeLayer, .5, new paper.Point());
|
||||
const bounds = paper.project.activeLayer.bounds;
|
||||
|
||||
this.props.onUpdateImage(
|
||||
true /* isVector */,
|
||||
paper.project.exportSVG({
|
||||
|
@ -149,9 +140,8 @@ class PaintEditor extends React.Component {
|
|||
bounds: 'content',
|
||||
matrix: new paper.Matrix().translate(-bounds.x, -bounds.y)
|
||||
}),
|
||||
(paper.project.view.center.x / 2) - bounds.x,
|
||||
(paper.project.view.center.y / 2) - bounds.y);
|
||||
|
||||
(SVG_ART_BOARD_WIDTH / 2) - bounds.x,
|
||||
(SVG_ART_BOARD_HEIGHT / 2) - bounds.y);
|
||||
scaleWithStrokes(paper.project.activeLayer, 2, new paper.Point());
|
||||
paper.project.activeLayer.applyMatrix = true;
|
||||
|
||||
|
@ -161,10 +151,6 @@ class PaintEditor extends React.Component {
|
|||
if (!skipSnapshot) {
|
||||
performSnapshot(this.props.undoSnapshot, this.props.format);
|
||||
}
|
||||
|
||||
// Restore old zoom
|
||||
paper.project.view.zoom = oldZoom;
|
||||
paper.project.view.center = oldCenter;
|
||||
}
|
||||
handleUndo () {
|
||||
performUndo(this.props.undoState, this.props.onUndo, this.props.setSelectedItems, this.handleUpdateImage);
|
||||
|
@ -289,6 +275,7 @@ class PaintEditor extends React.Component {
|
|||
colorInfo={this.state.colorInfo}
|
||||
format={this.props.format}
|
||||
image={this.props.image}
|
||||
imageFormat={this.props.imageFormat}
|
||||
imageId={this.props.imageId}
|
||||
isEyeDropping={this.props.isEyeDropping}
|
||||
name={this.props.name}
|
||||
|
@ -321,13 +308,14 @@ PaintEditor.propTypes = {
|
|||
changeColorToEyeDropper: PropTypes.func,
|
||||
changeMode: PropTypes.func.isRequired,
|
||||
clearSelectedItems: PropTypes.func.isRequired,
|
||||
format: PropTypes.oneOf(Object.keys(Formats)),
|
||||
format: PropTypes.oneOf(Object.keys(Formats)), // Internal, up-to-date data format
|
||||
handleSwitchToBitmap: PropTypes.func.isRequired,
|
||||
handleSwitchToVector: PropTypes.func.isRequired,
|
||||
image: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.instanceOf(HTMLImageElement)
|
||||
]),
|
||||
imageFormat: PropTypes.string, // The incoming image's data format, used during import
|
||||
imageId: PropTypes.string,
|
||||
isEyeDropping: PropTypes.bool,
|
||||
mode: PropTypes.oneOf(Object.keys(Modes)).isRequired,
|
||||
|
|
|
@ -13,7 +13,7 @@ import {undoSnapshot, clearUndoState} from '../reducers/undo';
|
|||
import {clearRaster, getRaster, setupLayers, hideGuideLayers, showGuideLayers} from '../helper/layer';
|
||||
import {deleteSelection, getSelectedLeafItems} from '../helper/selection';
|
||||
import {clearSelectedItems, setSelectedItems} from '../reducers/selected-items';
|
||||
import {clampViewBounds, pan, resetZoom, zoomOnFixedPoint} from '../helper/view';
|
||||
import {ART_BOARD_WIDTH, ART_BOARD_HEIGHT, pan, resetZoom, zoomOnFixedPoint} from '../helper/view';
|
||||
import {ensureClockwise, scaleWithStrokes} from '../helper/math';
|
||||
import {clearHoveredItem} from '../reducers/hover';
|
||||
import {clearPasteOffset} from '../reducers/clipboard';
|
||||
|
@ -28,7 +28,6 @@ class PaperCanvas extends React.Component {
|
|||
constructor (props) {
|
||||
super(props);
|
||||
bindAll(this, [
|
||||
'checkFormat',
|
||||
'convertToBitmap',
|
||||
'convertToVector',
|
||||
'setCanvas',
|
||||
|
@ -41,8 +40,7 @@ class PaperCanvas extends React.Component {
|
|||
componentDidMount () {
|
||||
document.addEventListener('keydown', this.handleKeyDown);
|
||||
paper.setup(this.canvas);
|
||||
paper.view.zoom = .5;
|
||||
clampViewBounds();
|
||||
resetZoom();
|
||||
|
||||
const context = this.canvas.getContext('2d');
|
||||
context.webkitImageSmoothingEnabled = false;
|
||||
|
@ -52,26 +50,13 @@ class PaperCanvas extends React.Component {
|
|||
paper.settings.handleSize = 0;
|
||||
// Make layers.
|
||||
setupLayers();
|
||||
if (this.props.image) {
|
||||
if (isBitmap(this.checkFormat(this.props.image))) {
|
||||
// import bitmap
|
||||
this.props.changeFormat(Formats.BITMAP_SKIP_CONVERT);
|
||||
performSnapshot(this.props.undoSnapshot, this.props.format);
|
||||
getRaster().drawImage(
|
||||
this.props.image,
|
||||
paper.project.view.center.x - this.props.rotationCenterX,
|
||||
paper.project.view.center.y - this.props.rotationCenterY);
|
||||
} else if (isVector(this.checkFormat(this.props.image))) {
|
||||
this.props.changeFormat(Formats.VECTOR_SKIP_CONVERT);
|
||||
this.importSvg(this.props.image, this.props.rotationCenterX, this.props.rotationCenterY);
|
||||
}
|
||||
} else {
|
||||
performSnapshot(this.props.undoSnapshot, this.props.format);
|
||||
}
|
||||
this.importImage(
|
||||
this.props.imageFormat, this.props.image, this.props.rotationCenterX, this.props.rotationCenterY);
|
||||
}
|
||||
componentWillReceiveProps (newProps) {
|
||||
if (this.props.imageId !== newProps.imageId) {
|
||||
this.switchCostume(newProps.image, newProps.rotationCenterX, newProps.rotationCenterY);
|
||||
this.switchCostume(
|
||||
newProps.imageFormat, newProps.image, newProps.rotationCenterX, newProps.rotationCenterY);
|
||||
} else if (isVector(this.props.format) && newProps.format === Formats.BITMAP) {
|
||||
this.convertToBitmap();
|
||||
} else if (isBitmap(this.props.format) && newProps.format === Formats.VECTOR) {
|
||||
|
@ -97,13 +82,8 @@ class PaperCanvas extends React.Component {
|
|||
convertToBitmap () {
|
||||
// @todo if the active layer contains only rasters, drawing them directly to the raster layer
|
||||
// would be more efficient.
|
||||
// Export svg
|
||||
|
||||
// Store the zoom/pan and restore it after snapshotting
|
||||
const oldZoom = paper.project.view.zoom;
|
||||
const oldCenter = paper.project.view.center.clone();
|
||||
paper.project.view.zoom = 1;
|
||||
|
||||
// Export svg
|
||||
const guideLayers = hideGuideLayers(true /* includeRaster */);
|
||||
const bounds = paper.project.activeLayer.bounds;
|
||||
const svg = paper.project.exportSVG({
|
||||
|
@ -111,7 +91,7 @@ class PaperCanvas extends React.Component {
|
|||
matrix: new paper.Matrix().translate(-bounds.x, -bounds.y)
|
||||
});
|
||||
showGuideLayers(guideLayers);
|
||||
|
||||
|
||||
// Get rid of anti-aliasing
|
||||
// @todo get crisp text?
|
||||
svg.setAttribute('shape-rendering', 'crispEdges');
|
||||
|
@ -120,22 +100,14 @@ class PaperCanvas extends React.Component {
|
|||
// Put anti-aliased SVG into image, and dump image back into canvas
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
const raster = new paper.Raster(img);
|
||||
raster.remove();
|
||||
raster.onLoad = () => {
|
||||
const subCanvas = raster.canvas;
|
||||
getRaster().drawImage(
|
||||
subCanvas,
|
||||
new paper.Point(Math.floor(bounds.topLeft.x), Math.floor(bounds.topLeft.y)));
|
||||
paper.project.activeLayer.removeChildren();
|
||||
this.props.onUpdateImage();
|
||||
};
|
||||
getRaster().drawImage(
|
||||
img,
|
||||
new paper.Point(Math.floor(bounds.topLeft.x), Math.floor(bounds.topLeft.y)));
|
||||
|
||||
paper.project.activeLayer.removeChildren();
|
||||
this.props.onUpdateImage();
|
||||
};
|
||||
img.src = `data:image/svg+xml;charset=utf-8,${svgString}`;
|
||||
|
||||
// Restore old zoom
|
||||
paper.project.view.zoom = oldZoom;
|
||||
paper.project.view.center = oldCenter;
|
||||
}
|
||||
convertToVector () {
|
||||
this.props.clearSelectedItems();
|
||||
|
@ -148,13 +120,7 @@ class PaperCanvas extends React.Component {
|
|||
clearRaster();
|
||||
this.props.onUpdateImage();
|
||||
}
|
||||
checkFormat (image) {
|
||||
if (image instanceof HTMLImageElement) return Formats.BITMAP;
|
||||
if (typeof image === 'string') return Formats.VECTOR;
|
||||
log.error(`Image could not be read.`);
|
||||
return null;
|
||||
}
|
||||
switchCostume (image, rotationCenterX, rotationCenterY) {
|
||||
switchCostume (format, image, rotationCenterX, rotationCenterY) {
|
||||
for (const layer of paper.project.layers) {
|
||||
if (layer.data.isRasterLayer) {
|
||||
clearRaster();
|
||||
|
@ -166,31 +132,38 @@ class PaperCanvas extends React.Component {
|
|||
this.props.clearSelectedItems();
|
||||
this.props.clearHoveredItem();
|
||||
this.props.clearPasteOffset();
|
||||
if (image) {
|
||||
if (isBitmap(this.checkFormat(image))) {
|
||||
// import bitmap
|
||||
this.props.changeFormat(Formats.BITMAP_SKIP_CONVERT);
|
||||
this.importImage(format, image, rotationCenterX, rotationCenterY);
|
||||
}
|
||||
importImage (format, image, rotationCenterX, rotationCenterY) {
|
||||
if (!image) {
|
||||
this.props.changeFormat(Formats.VECTOR_SKIP_CONVERT);
|
||||
performSnapshot(this.props.undoSnapshot, Formats.VECTOR_SKIP_CONVERT);
|
||||
return;
|
||||
}
|
||||
|
||||
if (format === 'jpg' || format === 'png') {
|
||||
// import bitmap
|
||||
this.props.changeFormat(Formats.BITMAP_SKIP_CONVERT);
|
||||
const imgElement = new Image();
|
||||
imgElement.onload = () => {
|
||||
getRaster().drawImage(
|
||||
image,
|
||||
paper.project.view.center.x - rotationCenterX,
|
||||
paper.project.view.center.y - rotationCenterY);
|
||||
performSnapshot(this.props.undoSnapshot, this.props.format);
|
||||
} else if (isVector(this.checkFormat(image))) {
|
||||
this.props.changeFormat(Formats.VECTOR_SKIP_CONVERT);
|
||||
// Store the zoom/pan and restore it after importing a new SVG
|
||||
const oldZoom = paper.project.view.zoom;
|
||||
const oldCenter = paper.project.view.center.clone();
|
||||
resetZoom();
|
||||
this.props.updateViewBounds(paper.view.matrix);
|
||||
this.importSvg(image, rotationCenterX, rotationCenterY);
|
||||
paper.project.view.zoom = oldZoom;
|
||||
paper.project.view.center = oldCenter;
|
||||
} else {
|
||||
log.error(`Couldn't open image.`);
|
||||
performSnapshot(this.props.undoSnapshot, this.props.format);
|
||||
}
|
||||
imgElement,
|
||||
(ART_BOARD_WIDTH / 2) - rotationCenterX,
|
||||
(ART_BOARD_HEIGHT / 2) - rotationCenterY);
|
||||
getRaster().drawImage(
|
||||
imgElement,
|
||||
(ART_BOARD_WIDTH / 2) - rotationCenterX,
|
||||
(ART_BOARD_HEIGHT / 2) - rotationCenterY);
|
||||
performSnapshot(this.props.undoSnapshot, Formats.BITMAP_SKIP_CONVERT);
|
||||
};
|
||||
imgElement.src = image;
|
||||
} else if (format === 'svg') {
|
||||
this.props.changeFormat(Formats.VECTOR_SKIP_CONVERT);
|
||||
this.importSvg(image, rotationCenterX, rotationCenterY);
|
||||
} else {
|
||||
performSnapshot(this.props.undoSnapshot, this.props.format);
|
||||
log.error(`Didn't recognize format: ${format}. Use 'jpg', 'png' or 'svg'.`);
|
||||
this.props.changeFormat(Formats.VECTOR_SKIP_CONVERT);
|
||||
performSnapshot(this.props.undoSnapshot, Formats.VECTOR_SKIP_CONVERT);
|
||||
}
|
||||
}
|
||||
importSvg (svg, rotationCenterX, rotationCenterY) {
|
||||
|
@ -225,7 +198,8 @@ class PaperCanvas extends React.Component {
|
|||
if (!item) {
|
||||
log.error('SVG import failed:');
|
||||
log.info(svg);
|
||||
performSnapshot(paperCanvas.props.undoSnapshot, paperCanvas.props.format);
|
||||
this.props.changeFormat(Formats.VECTOR_SKIP_CONVERT);
|
||||
performSnapshot(paperCanvas.props.undoSnapshot, Formats.VECTOR_SKIP_CONVERT);
|
||||
return;
|
||||
}
|
||||
const itemWidth = item.bounds.width;
|
||||
|
@ -245,7 +219,7 @@ class PaperCanvas extends React.Component {
|
|||
}
|
||||
|
||||
// Reduce single item nested in groups
|
||||
if (item.children && item.children.length === 1) {
|
||||
if (item instanceof paper.Group && item.children.length === 1) {
|
||||
item = item.reduce();
|
||||
}
|
||||
|
||||
|
@ -257,15 +231,15 @@ class PaperCanvas extends React.Component {
|
|||
if (viewBox && viewBox.length >= 2 && !isNaN(viewBox[0]) && !isNaN(viewBox[1])) {
|
||||
rotationPoint = rotationPoint.subtract(viewBox[0], viewBox[1]);
|
||||
}
|
||||
item.translate(paper.project.view.center
|
||||
item.translate(new paper.Point(ART_BOARD_WIDTH / 2, ART_BOARD_HEIGHT / 2)
|
||||
.subtract(rotationPoint.multiply(2)));
|
||||
} else {
|
||||
// Center
|
||||
item.translate(paper.project.view.center
|
||||
.subtract(itemWidth / 2, itemHeight / 2));
|
||||
item.translate(new paper.Point(ART_BOARD_WIDTH / 2, ART_BOARD_HEIGHT / 2)
|
||||
.subtract(itemWidth, itemHeight));
|
||||
}
|
||||
|
||||
performSnapshot(paperCanvas.props.undoSnapshot, paperCanvas.props.format);
|
||||
performSnapshot(paperCanvas.props.undoSnapshot, Formats.VECTOR_SKIP_CONVERT);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -322,11 +296,12 @@ PaperCanvas.propTypes = {
|
|||
clearPasteOffset: PropTypes.func.isRequired,
|
||||
clearSelectedItems: PropTypes.func.isRequired,
|
||||
clearUndo: PropTypes.func.isRequired,
|
||||
format: PropTypes.oneOf(Object.keys(Formats)),
|
||||
format: PropTypes.oneOf(Object.keys(Formats)), // Internal, up-to-date data format
|
||||
image: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.instanceOf(HTMLImageElement)
|
||||
]),
|
||||
imageFormat: PropTypes.string, // The incoming image's data format, used during import
|
||||
imageId: PropTypes.string,
|
||||
mode: PropTypes.oneOf(Object.keys(Modes)),
|
||||
onUpdateImage: PropTypes.func.isRequired,
|
||||
|
|
|
@ -124,8 +124,8 @@ const columnBlank_ = function (imageData, width, x, top, bottom) {
|
|||
};
|
||||
|
||||
// Adapted from Tim Down's https://gist.github.com/timdown/021d9c8f2aabc7092df564996f5afbbf
|
||||
// Trims transparent pixels from edges.
|
||||
const trim = function (raster) {
|
||||
// Get bounds, trimming transparent pixels from edges.
|
||||
const getHitBounds = function (raster) {
|
||||
const width = raster.width;
|
||||
const imageData = raster.getImageData(raster.bounds);
|
||||
let top = 0;
|
||||
|
@ -138,11 +138,16 @@ const trim = function (raster) {
|
|||
while (left < right && columnBlank_(imageData, width, left, top, bottom)) ++left;
|
||||
while (right - 1 > left && columnBlank_(imageData, width, right - 1, top, bottom)) --right;
|
||||
|
||||
return raster.getSubRaster(new paper.Rectangle(left, top, right - left, bottom - top));
|
||||
return new paper.Rectangle(left, top, right - left, bottom - top);
|
||||
};
|
||||
|
||||
const trim = function (raster) {
|
||||
return raster.getSubRaster(getHitBounds(raster));
|
||||
};
|
||||
|
||||
export {
|
||||
getBrushMark,
|
||||
getHitBounds,
|
||||
fillEllipse,
|
||||
forEachLinePoint,
|
||||
trim
|
||||
|
|
|
@ -26,21 +26,14 @@ const clearRaster = function () {
|
|||
raster.parent = layer;
|
||||
raster.guide = true;
|
||||
raster.locked = true;
|
||||
raster.position = paper.view.center;
|
||||
raster.position = new paper.Point(ART_BOARD_WIDTH / 2, ART_BOARD_HEIGHT / 2);
|
||||
};
|
||||
|
||||
const getRaster = function () {
|
||||
const layer = _getLayer('isRasterLayer');
|
||||
// Generate blank raster
|
||||
if (layer.children.length === 0) {
|
||||
const tmpCanvas = document.createElement('canvas');
|
||||
tmpCanvas.width = ART_BOARD_WIDTH;
|
||||
tmpCanvas.height = ART_BOARD_HEIGHT;
|
||||
const raster = new paper.Raster(tmpCanvas);
|
||||
raster.parent = layer;
|
||||
raster.guide = true;
|
||||
raster.locked = true;
|
||||
raster.position = paper.view.center;
|
||||
clearRaster();
|
||||
}
|
||||
return _getLayer('isRasterLayer').children[0];
|
||||
};
|
||||
|
@ -163,7 +156,7 @@ const _makeBackgroundGuideLayer = function () {
|
|||
guideLayer.locked = true;
|
||||
|
||||
const vBackground = _makeBackgroundPaper(120, 90, '#E5E5E5');
|
||||
vBackground.position = paper.view.center;
|
||||
vBackground.position = new paper.Point(ART_BOARD_WIDTH / 2, ART_BOARD_HEIGHT / 2);
|
||||
vBackground.scaling = new paper.Point(8, 8);
|
||||
vBackground.guide = true;
|
||||
vBackground.locked = true;
|
||||
|
@ -171,21 +164,21 @@ const _makeBackgroundGuideLayer = function () {
|
|||
const vLine = new paper.Path.Line(new paper.Point(0, -7), new paper.Point(0, 7));
|
||||
vLine.strokeWidth = 2;
|
||||
vLine.strokeColor = '#ccc';
|
||||
vLine.position = paper.view.center;
|
||||
vLine.position = new paper.Point(ART_BOARD_WIDTH / 2, ART_BOARD_HEIGHT / 2);
|
||||
vLine.guide = true;
|
||||
vLine.locked = true;
|
||||
|
||||
const hLine = new paper.Path.Line(new paper.Point(-7, 0), new paper.Point(7, 0));
|
||||
hLine.strokeWidth = 2;
|
||||
hLine.strokeColor = '#ccc';
|
||||
hLine.position = paper.view.center;
|
||||
hLine.position = new paper.Point(ART_BOARD_WIDTH / 2, ART_BOARD_HEIGHT / 2);
|
||||
hLine.guide = true;
|
||||
hLine.locked = true;
|
||||
|
||||
const circle = new paper.Shape.Circle(new paper.Point(0, 0), 5);
|
||||
circle.strokeWidth = 2;
|
||||
circle.strokeColor = '#ccc';
|
||||
circle.position = paper.view.center;
|
||||
circle.position = new paper.Point(ART_BOARD_WIDTH / 2, ART_BOARD_HEIGHT / 2);
|
||||
circle.guide = true;
|
||||
circle.locked = true;
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import paper from '@scratch/paper';
|
|||
import {hideGuideLayers, showGuideLayers, getRaster} from '../helper/layer';
|
||||
import Formats from '../lib/format';
|
||||
import {isVector, isBitmap} from '../lib/format';
|
||||
import log from '../log/log';
|
||||
|
||||
/**
|
||||
* Take an undo snapshot
|
||||
|
@ -11,6 +12,9 @@ import {isVector, isBitmap} from '../lib/format';
|
|||
* @param {Formats} format Either Formats.BITMAP or Formats.VECTOR
|
||||
*/
|
||||
const performSnapshot = function (dispatchPerformSnapshot, format) {
|
||||
if (!format) {
|
||||
log.error('Format must be specified.');
|
||||
}
|
||||
const guideLayers = hideGuideLayers();
|
||||
dispatchPerformSnapshot({
|
||||
json: paper.project.exportJSON({asString: false}),
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
import paper from '@scratch/paper';
|
||||
import {getSelectedRootItems} from './selection';
|
||||
|
||||
// Vectors are imported and exported at SVG_ART_BOARD size.
|
||||
// Once they are imported however, both SVGs and bitmaps are on
|
||||
// canvases of ART_BOARD size.
|
||||
const SVG_ART_BOARD_WIDTH = 480;
|
||||
const SVG_ART_BOARD_HEIGHT = 360;
|
||||
const ART_BOARD_WIDTH = 480 * 2;
|
||||
const ART_BOARD_HEIGHT = 360 * 2;
|
||||
|
||||
const clampViewBounds = () => {
|
||||
const _clampViewBounds = () => {
|
||||
const {left, right, top, bottom} = paper.project.view.bounds;
|
||||
if (left < 0) {
|
||||
paper.project.view.scrollBy(new paper.Point(-left, 0));
|
||||
|
@ -32,7 +37,7 @@ const zoomOnFixedPoint = (deltaZoom, fixedPoint) => {
|
|||
.subtract(preZoomCenter);
|
||||
view.zoom = newZoom;
|
||||
view.translate(postZoomOffset.multiply(-1));
|
||||
clampViewBounds();
|
||||
_clampViewBounds();
|
||||
};
|
||||
|
||||
// Zoom keeping the selection center (if any) fixed.
|
||||
|
@ -57,18 +62,19 @@ const zoomOnSelection = deltaZoom => {
|
|||
|
||||
const resetZoom = () => {
|
||||
paper.project.view.zoom = .5;
|
||||
clampViewBounds();
|
||||
_clampViewBounds();
|
||||
};
|
||||
|
||||
const pan = (dx, dy) => {
|
||||
paper.project.view.scrollBy(new paper.Point(dx, dy));
|
||||
clampViewBounds();
|
||||
_clampViewBounds();
|
||||
};
|
||||
|
||||
export {
|
||||
ART_BOARD_HEIGHT,
|
||||
ART_BOARD_WIDTH,
|
||||
clampViewBounds,
|
||||
SVG_ART_BOARD_WIDTH,
|
||||
SVG_ART_BOARD_HEIGHT,
|
||||
pan,
|
||||
resetZoom,
|
||||
zoomOnSelection,
|
||||
|
|
|
@ -34,7 +34,8 @@ class Playground extends React.Component {
|
|||
name: 'meow',
|
||||
rotationCenterX: 20,
|
||||
rotationCenterY: 400,
|
||||
image: svgString
|
||||
imageFormat: 'svg', // 'svg', 'png', or 'jpg'
|
||||
image: svgString // svg string or data URI
|
||||
};
|
||||
}
|
||||
handleUpdateName (name) {
|
||||
|
@ -46,9 +47,16 @@ class Playground extends React.Component {
|
|||
if (isVector) {
|
||||
this.setState({image, rotationCenterX, rotationCenterY});
|
||||
} else { // is Bitmap
|
||||
const imageElement = new Image();
|
||||
imageElement.src = image.toDataURL("image/png");
|
||||
this.setState({imageElement, rotationCenterX, rotationCenterY});
|
||||
// image parameter has type ImageData
|
||||
// paint editor takes dataURI as input
|
||||
const canvas = document.createElement('canvas');
|
||||
const context = canvas.getContext('2d');
|
||||
context.putImageData(image, 0, 0);
|
||||
this.setState({
|
||||
image: canvas.toDataURL('image/png'),
|
||||
rotationCenterX: rotationCenterX,
|
||||
rotationCenterY: rotationCenterY
|
||||
});
|
||||
}
|
||||
}
|
||||
render () {
|
||||
|
|
Loading…
Reference in a new issue