mirror of
https://github.com/igorski/bitmappery.git
synced 2026-07-05 14:59:15 +02:00
Implemented layer mirroring effect
This commit is contained in:
@@ -56,6 +56,7 @@ npm run lint
|
||||
# TODO / Roadmap
|
||||
|
||||
* Moving of masks broken ?
|
||||
* Issue with drawing mask on mirrored content
|
||||
* Verify saving and restoring of masked and rotated and positioned content
|
||||
* Transparency blocks should be rendered by zCanvas, not parent div
|
||||
* Copying selections on rotated content doesn't work
|
||||
|
||||
22
src/assets/icons/tool-mirror.svg
Normal file
22
src/assets/icons/tool-mirror.svg
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Icons" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 32 32" style="enable-background:new 0 0 32 32;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:none;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
|
||||
.st1{fill:none;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;}
|
||||
.st2{fill:none;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:6,6;}
|
||||
.st3{fill:none;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:4,4;}
|
||||
.st4{fill:none;stroke:#000000;stroke-width:2;stroke-linecap:round;}
|
||||
.st5{fill:none;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-dasharray:3.1081,3.1081;}
|
||||
|
||||
.st6{fill:none;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;stroke-dasharray:4,3;}
|
||||
</style>
|
||||
<polygon class="st0" points="12,22 3,27 3,5 12,10 "/>
|
||||
<polygon class="st0" points="20,10 29,5 29,27 20,22 "/>
|
||||
<line class="st0" x1="16" y1="3" x2="16" y2="6"/>
|
||||
<line class="st0" x1="16" y1="27" x2="16" y2="30"/>
|
||||
<line class="st0" x1="16" y1="15" x2="16" y2="18"/>
|
||||
<line class="st0" x1="16" y1="9" x2="16" y2="12"/>
|
||||
<line class="st0" x1="16" y1="21" x2="16" y2="24"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -160,6 +160,7 @@ export default {
|
||||
<style lang="scss" scoped>
|
||||
@import "@/styles/_mixins";
|
||||
@import "@/styles/typography";
|
||||
@import "@/styles/options-panel";
|
||||
|
||||
h3 {
|
||||
color: #FFF;
|
||||
@@ -203,14 +204,4 @@ h3 {
|
||||
color: #FFF;
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
margin-top: $spacing-small;
|
||||
display: flex;
|
||||
|
||||
button {
|
||||
flex: 1;
|
||||
margin: 0 $spacing-small;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -74,5 +74,5 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "@/styles/tool-option";
|
||||
@import "@/styles/options-panel";
|
||||
</style>
|
||||
|
||||
@@ -94,5 +94,5 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "@/styles/tool-option";
|
||||
@import "@/styles/options-panel";
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"en-US": {
|
||||
"mirror": "Mirror",
|
||||
"flipHorizontal": "Flip horizontal",
|
||||
"flipVertical": "Flip vertical"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Igor Zinken 2020 - https://www.igorski.nl
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
<template>
|
||||
<div class="tool-option">
|
||||
<h3 v-t="'mirror'"></h3>
|
||||
<div class="actions">
|
||||
<button
|
||||
v-t="'flipHorizontal'"
|
||||
type="button"
|
||||
class="button button--small"
|
||||
@click="flipHorizontal"
|
||||
></button>
|
||||
<button
|
||||
v-t="'flipVertical'"
|
||||
type="button"
|
||||
class="button button--small"
|
||||
@click="flipVertical"
|
||||
></button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters, mapMutations } from "vuex";
|
||||
import messages from "./messages.json";
|
||||
|
||||
export default {
|
||||
i18n: { messages },
|
||||
computed: {
|
||||
...mapGetters([
|
||||
"activeLayerIndex",
|
||||
"activeLayerEffects",
|
||||
]),
|
||||
},
|
||||
methods: {
|
||||
...mapMutations([
|
||||
"updateLayerEffects",
|
||||
]),
|
||||
flipHorizontal() {
|
||||
this.updateLayerEffects({
|
||||
index: this.activeLayerIndex,
|
||||
effects: { mirrorX: !this.activeLayerEffects.mirrorX }
|
||||
});
|
||||
},
|
||||
flipVertical() {
|
||||
this.updateLayerEffects({
|
||||
index: this.activeLayerIndex,
|
||||
effects: { mirrorY: !this.activeLayerEffects.mirrorY }
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "@/styles/options-panel";
|
||||
</style>
|
||||
@@ -75,5 +75,5 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "@/styles/tool-option";
|
||||
@import "@/styles/options-panel";
|
||||
</style>
|
||||
|
||||
@@ -95,14 +95,5 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "@/styles/tool-option";
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
margin-top: $spacing-medium;
|
||||
button {
|
||||
flex: 1;
|
||||
margin: 0 $spacing-small;
|
||||
}
|
||||
}
|
||||
@import "@/styles/options-panel";
|
||||
</style>
|
||||
|
||||
@@ -126,6 +126,8 @@ export default {
|
||||
return () => import( "./components/tool-options-brush/tool-options-brush" );
|
||||
case ToolTypes.ROTATE:
|
||||
return () => import( "./components/tool-options-rotate/tool-options-rotate" );
|
||||
case ToolTypes.MIRROR:
|
||||
return () => import( "./components/tool-options-mirror/tool-options-mirror" );
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"polygonalLasso": "Polygonal lasso",
|
||||
"eyedropper": "Eyedropper",
|
||||
"rotateLayer": "Rotate layer",
|
||||
"mirrorLayer": "Mirror layer",
|
||||
"zoom": "Zoom",
|
||||
"eraser": "Eraser",
|
||||
"brush": "Brush",
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
<button v-for="(tool, index) in tools"
|
||||
:key="tool.type"
|
||||
type="button"
|
||||
v-tooltip="$t( tool.i18n )"
|
||||
v-tooltip="`${$t( tool.i18n )} (${tool.key})`"
|
||||
:title="$t( tool.i18n )"
|
||||
class="tool-button"
|
||||
:class="{
|
||||
@@ -92,37 +92,42 @@ export default {
|
||||
return [
|
||||
{
|
||||
type: ToolTypes.MOVE,
|
||||
i18n: "dragLayer", icon: "drag",
|
||||
i18n: "dragLayer", icon: "drag", key: "M",
|
||||
disabled: !this.activeDocument
|
||||
},
|
||||
{
|
||||
type: ToolTypes.LASSO,
|
||||
i18n: "polygonalLasso", icon: "selection",
|
||||
i18n: "polygonalLasso", icon: "selection", key: "L",
|
||||
disabled: !this.activeDocument
|
||||
},
|
||||
{
|
||||
type: ToolTypes.EYEDROPPER,
|
||||
i18n: "eyedropper", icon: "eyedropper",
|
||||
i18n: "eyedropper", icon: "eyedropper", key: "I",
|
||||
disabled: !this.activeLayer
|
||||
},
|
||||
{
|
||||
type: ToolTypes.ROTATE,
|
||||
i18n: "rotateLayer", icon: "rotate",
|
||||
i18n: "rotateLayer", icon: "rotate", key: "R",
|
||||
disabled: !this.activeLayer
|
||||
},
|
||||
{
|
||||
type: ToolTypes.MIRROR,
|
||||
i18n: "mirrorLayer", icon: "mirror", key: "F",
|
||||
disabled: !this.activeLayer
|
||||
},
|
||||
{
|
||||
type: ToolTypes.ERASER,
|
||||
i18n: "eraser", icon: "eraser",
|
||||
i18n: "eraser", icon: "eraser", key: "E",
|
||||
disabled: !this.activeDocument || !( this.activeLayer?.mask || this.activeLayer?.type === LAYER_GRAPHIC )
|
||||
},
|
||||
{
|
||||
type: ToolTypes.BRUSH,
|
||||
i18n: "brush", icon: "paintbrush",
|
||||
i18n: "brush", icon: "paintbrush", key: "B",
|
||||
disabled: !this.activeDocument || !( this.activeLayer?.mask || this.activeLayer?.type === LAYER_GRAPHIC )
|
||||
},
|
||||
{
|
||||
type: ToolTypes.ZOOM,
|
||||
i18n: "zoom", icon: "zoom",
|
||||
i18n: "zoom", icon: "zoom", key: "Z",
|
||||
disabled: !this.activeDocument
|
||||
},
|
||||
]
|
||||
|
||||
56
src/factories/effects-factory.js
Normal file
56
src/factories/effects-factory.js
Normal file
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Igor Zinken 2020 - https://www.igorski.nl
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||
* the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
const EffectFactory = {
|
||||
create({ rotation = 0, mirrorX = false, mirrorY = false } = {}) {
|
||||
return {
|
||||
rotation,
|
||||
mirrorX,
|
||||
mirrorY,
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Saving effects properties into a simplified JSON structure
|
||||
* for project storage
|
||||
*/
|
||||
serialize( effects ) {
|
||||
return {
|
||||
r: effects.rotation,
|
||||
x: effects.mirrorX,
|
||||
y: effects.mirrorY,
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Creating a new effects lists from a stored effects structure
|
||||
* inside a stored projects layer
|
||||
*/
|
||||
deserialize( effects ) {
|
||||
return EffectFactory.create({
|
||||
rotation: effects.r,
|
||||
mirrorX: effects.x,
|
||||
mirrorY: effects.y,
|
||||
});
|
||||
}
|
||||
};
|
||||
export default EffectFactory;
|
||||
@@ -22,6 +22,7 @@
|
||||
*/
|
||||
import { LAYER_GRAPHIC, LAYER_MASK } from "@/definitions/layer-types";
|
||||
import { imageToBase64, base64ToLayerImage } from "@/utils/canvas-util";
|
||||
import EffectsFactory from "@/factories/effects-factory";
|
||||
let UID_COUNTER = 0;
|
||||
|
||||
const LayerFactory = {
|
||||
@@ -32,7 +33,7 @@ const LayerFactory = {
|
||||
name = "New Layer",
|
||||
type = LAYER_GRAPHIC, source = null, mask = null,
|
||||
x = 0, y = 0, maskX = 0, maskY = 0, width = 1, height = 1, visible = true,
|
||||
effects = { rotation: 0 }
|
||||
effects = {}
|
||||
} = {}) {
|
||||
return {
|
||||
id: `layer_${( ++UID_COUNTER )}`,
|
||||
@@ -47,7 +48,7 @@ const LayerFactory = {
|
||||
width,
|
||||
height,
|
||||
visible,
|
||||
effects,
|
||||
effects: EffectsFactory.create( effects ),
|
||||
// only used at runtime, will not be serialized
|
||||
selection: null,
|
||||
}
|
||||
@@ -69,7 +70,7 @@ const LayerFactory = {
|
||||
y2: layer.maskY,
|
||||
w: layer.width,
|
||||
h: layer.height,
|
||||
fx: layer.effects,
|
||||
f: EffectsFactory.serialize( layer.effects ),
|
||||
v: layer.visible,
|
||||
};
|
||||
},
|
||||
@@ -93,7 +94,7 @@ const LayerFactory = {
|
||||
width: layer.w,
|
||||
height: layer.h,
|
||||
visible: layer.v,
|
||||
effects: layer.fx,
|
||||
effects: EffectsFactory.deserialize( layer.f ),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -204,6 +204,12 @@ function handleKeyDown( event ) {
|
||||
}
|
||||
break;
|
||||
|
||||
case 70: // F
|
||||
if ( getters.activeLayer ) {
|
||||
setActiveTool( ToolTypes.MIRROR );
|
||||
}
|
||||
break;
|
||||
|
||||
case 73: // I
|
||||
if ( getters.activeLayer ) {
|
||||
setActiveTool( ToolTypes.EYEDROPPER );
|
||||
|
||||
@@ -47,7 +47,7 @@ export const renderEffectsForLayer = async layer => {
|
||||
const ctx = cvs.getContext( "2d" );
|
||||
|
||||
if ( hasEffects( layer )) {
|
||||
await renderTransformedSource( layer, ctx, layer.source, width, height, effects.rotation );
|
||||
await renderTransformedSource( layer, ctx, layer.source, width, height, effects );
|
||||
} else {
|
||||
ctx.drawImage( layer.source, 0, 0 );
|
||||
}
|
||||
@@ -60,16 +60,31 @@ export const renderEffectsForLayer = async layer => {
|
||||
/* internal methods */
|
||||
|
||||
const hasEffects = ( layer ) => {
|
||||
if ( !!layer.mask ) {
|
||||
return true;
|
||||
}
|
||||
const { effects } = layer;
|
||||
return !!layer.mask || effects.rotation !== 0;
|
||||
return effects.rotation !== 0 || effects.mirrorX || effects.mirrorY;
|
||||
};
|
||||
|
||||
const renderTransformedSource = async ( layer, ctx, sourceBitmap, width, height, rotation ) => {
|
||||
const renderTransformedSource = async ( layer, ctx, sourceBitmap, width, height, { mirrorX, mirrorY, rotation }) => {
|
||||
const rotate = ( rotation % 360 ) !== 0;
|
||||
let targetX = 0, targetY = 0;
|
||||
let targetX = mirrorX ? -width : 0;
|
||||
let targetY = mirrorY ? -height : 0;
|
||||
|
||||
const xScale = mirrorX ? -1 : 1;
|
||||
const yScale = mirrorY ? -1 : 1;
|
||||
|
||||
ctx.save();
|
||||
ctx.scale( xScale, yScale );
|
||||
|
||||
if ( rotate ) {
|
||||
const { x, y } = getRotationCenter({ left: 0, top: 0, width, height });
|
||||
ctx.save();
|
||||
const { x, y } = getRotationCenter({
|
||||
left : 0,
|
||||
top : 0,
|
||||
width : mirrorX ? -width : width,
|
||||
height : mirrorY ? -height : height
|
||||
});
|
||||
ctx.translate( x, y );
|
||||
ctx.rotate( rotation );
|
||||
ctx.translate( -x, -y );
|
||||
@@ -78,9 +93,8 @@ const renderTransformedSource = async ( layer, ctx, sourceBitmap, width, height,
|
||||
}
|
||||
ctx.drawImage( sourceBitmap, targetX, targetY );
|
||||
await renderMask( layer, ctx, targetX, targetY );
|
||||
if ( rotate ) {
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
const renderMask = async( layer, ctx, tX = 0, tY = 0 ) => {
|
||||
|
||||
@@ -10,3 +10,13 @@
|
||||
color: #FFF;
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
margin: $spacing-small 0;
|
||||
display: flex;
|
||||
|
||||
button {
|
||||
flex: 1;
|
||||
margin: 0 $spacing-small;
|
||||
}
|
||||
}
|
||||
41
tests/unit/factories/effects-factory.spec.js
Normal file
41
tests/unit/factories/effects-factory.spec.js
Normal file
@@ -0,0 +1,41 @@
|
||||
import EffectsFactory from "@/factories/effects-factory";
|
||||
|
||||
describe( "Effects factory", () => {
|
||||
describe( "when creating a new Effects list", () => {
|
||||
it( "should create a default Effects structure when no arguments are passed", () => {
|
||||
const effects = EffectsFactory.create();
|
||||
expect( effects ).toEqual({
|
||||
mirrorX: false,
|
||||
mirrorY: false,
|
||||
rotation: expect.any( Number )
|
||||
});
|
||||
});
|
||||
|
||||
it( "should be able to create a Effects list from given arguments", () => {
|
||||
const effects = EffectsFactory.create({
|
||||
mirrorX: true,
|
||||
mirrorY: true,
|
||||
rotation: -90
|
||||
});
|
||||
expect( effects ).toEqual({
|
||||
mirrorX: true,
|
||||
mirrorY: true,
|
||||
rotation: -90
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe( "when serializing and deserializing a Effects list", () => {
|
||||
it( "should do so without data loss", async () => {
|
||||
const effects = EffectsFactory.create({
|
||||
mirrorX: true,
|
||||
mirrorY: true,
|
||||
rotation: 270
|
||||
});
|
||||
const serialized = EffectsFactory.serialize( effects );
|
||||
const deserialized = EffectsFactory.deserialize( serialized );
|
||||
|
||||
expect( deserialized ).toEqual( effects );
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -6,10 +6,17 @@ jest.mock( "@/utils/canvas-util", () => ({
|
||||
imageToBase64: (...args) => mockUpdateFn?.( "imageToBase64", ...args ),
|
||||
base64ToLayerImage: (...args) => mockUpdateFn?.( "base64ToLayerImage", ...args ),
|
||||
}));
|
||||
jest.mock( "@/factories/effects-factory", () => ({
|
||||
create: (...args) => mockUpdateFn?.( "create", ...args ),
|
||||
serialize: (...args) => mockUpdateFn?.( "serialize", ...args ),
|
||||
deserialize: (...args) => mockUpdateFn?.( "deserialize", ...args ),
|
||||
}));
|
||||
|
||||
describe( "Layer factory", () => {
|
||||
describe( "when creating a new layer", () => {
|
||||
it( "should create a default Layer structure when no arguments are passed", () => {
|
||||
const mockEffects = { foo: "bar" };
|
||||
mockUpdateFn = fn => fn === "create" ? mockEffects : {};
|
||||
const layer = LayerFactory.create();
|
||||
expect( layer ).toEqual({
|
||||
id: expect.any( String ),
|
||||
@@ -24,14 +31,13 @@ describe( "Layer factory", () => {
|
||||
width: 1,
|
||||
height: 1,
|
||||
visible: true,
|
||||
effects: {
|
||||
rotation: 0,
|
||||
},
|
||||
effects: mockEffects,
|
||||
selection: null,
|
||||
});
|
||||
});
|
||||
|
||||
it( "should be able to create a layer from given arguments", () => {
|
||||
mockUpdateFn = ( fn, data ) => data;
|
||||
const layer = LayerFactory.create({
|
||||
name: "foo",
|
||||
type: LAYER_IMAGE,
|
||||
@@ -70,32 +76,33 @@ describe( "Layer factory", () => {
|
||||
});
|
||||
|
||||
describe( "when serializing and deserializing a Layer", () => {
|
||||
const layer = LayerFactory.create({
|
||||
name: "foo",
|
||||
type: LAYER_IMAGE,
|
||||
source: { src: "bitmap" },
|
||||
mask: { src: "mask" },
|
||||
x: 100,
|
||||
y: 50,
|
||||
width: 16,
|
||||
height: 9,
|
||||
visible: false,
|
||||
effects: {
|
||||
rotation: -90,
|
||||
},
|
||||
});
|
||||
|
||||
it( "should do so without data loss", async () => {
|
||||
mockUpdateFn = jest.fn(( fn, data ) => JSON.stringify( data ));
|
||||
const layer = LayerFactory.create({
|
||||
name: "foo",
|
||||
type: LAYER_IMAGE,
|
||||
source: { src: "bitmap" },
|
||||
mask: { src: "mask" },
|
||||
x: 100,
|
||||
y: 50,
|
||||
width: 16,
|
||||
height: 9,
|
||||
visible: false,
|
||||
effects: {
|
||||
rotation: -90,
|
||||
},
|
||||
});
|
||||
mockUpdateFn = jest.fn(( fn, data ) => data );
|
||||
|
||||
const serialized = LayerFactory.serialize( layer );
|
||||
expect( mockUpdateFn ).toHaveBeenNthCalledWith( 1, "imageToBase64", layer.source, layer.width, layer.height );
|
||||
expect( mockUpdateFn ).toHaveBeenNthCalledWith( 2, "imageToBase64", layer.mask, layer.width, layer.height );
|
||||
expect( mockUpdateFn ).toHaveBeenNthCalledWith( 3, "serialize", layer.effects );
|
||||
|
||||
mockUpdateFn = jest.fn(( fn, data ) => JSON.parse( data ));
|
||||
mockUpdateFn = jest.fn(( fn, data ) => data );
|
||||
const deserialized = await LayerFactory.deserialize( serialized );
|
||||
expect( mockUpdateFn ).toHaveBeenNthCalledWith( 1, "base64ToLayerImage", expect.any( String ), LAYER_IMAGE, layer.width, layer.height );
|
||||
expect( mockUpdateFn ).toHaveBeenNthCalledWith( 2, "base64ToLayerImage", expect.any( String ), LAYER_MASK, layer.width, layer.height );
|
||||
expect( mockUpdateFn ).toHaveBeenNthCalledWith( 1, "base64ToLayerImage", expect.any( Object ), LAYER_IMAGE, layer.width, layer.height );
|
||||
expect( mockUpdateFn ).toHaveBeenNthCalledWith( 2, "base64ToLayerImage", expect.any( Object ), LAYER_MASK, layer.width, layer.height );
|
||||
expect( mockUpdateFn ).toHaveBeenNthCalledWith( 3, "deserialize", layer.effects );
|
||||
|
||||
// note id's are unique per created session instance and therefor will differ
|
||||
expect({
|
||||
|
||||
Reference in New Issue
Block a user