Skip to content

Commit

Permalink
image occlusion project
Browse files Browse the repository at this point in the history
* basic shapes
   - rectangle
   - ellipse
   - polygon
* some other tools
    - undo & redo
    - group & ungroup
    - zoom tools
    - copy
  • Loading branch information
krmanik committed Feb 9, 2023
1 parent 88e0292 commit d56ea36
Show file tree
Hide file tree
Showing 18 changed files with 1,364 additions and 6 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
"codemirror": "^5.63.1",
"css-browser-selector": "^0.6.5",
"d3": "^7.0.0",
"fabric": "^5.3.0",
"fuse.js": "^6.6.2",
"gemoji": "^7.1.0",
"intl-pluralrules": "^1.2.2",
Expand All @@ -82,6 +83,7 @@
"lodash-es": "^4.17.21",
"marked": "^4.0.0",
"mathjax": "^3.1.2",
"panzoom": "^9.4.3",
"protobufjs": "^7"
},
"resolutions": {
Expand Down
96 changes: 96 additions & 0 deletions ts/image-occlusion/MaskEditor.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<!--
Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import { fabric } from "fabric";
import panzoom from "panzoom";
import protobuf from "protobufjs";
import { getImageClozeMetadata } from "./lib";
import SideToolbar from "./SideToolbar.svelte";
import { zoomResetValue } from "./store";
import { undoRedoInit } from "./tools/tool-undo-redo";
export let path: string;
let instance;
let innerWidth = 0;
let canvas: fabric.Canvas;
getImageClozeMetadata(path).then((metadata) => {
const b64encoded = protobuf.util.base64.encode(
metadata.data,
0,
metadata.data.length,
);
const data = "data:image/png;base64," + b64encoded;
canvas = new fabric.Canvas("canvas", {
hoverCursor: "pointer",
selectionBorderColor: "green",
});
// for debug in devtools
globalThis.canvas = canvas;
// get image width and height
const image = new Image();
image.onload = function () {
canvas.setWidth(image.width);
canvas.setHeight(image.height);
fabric.Image.fromURL(image.src, function (image) {
canvas.setBackgroundImage(image, canvas.renderAll.bind(canvas), {
scaleX: canvas.width! / image.width!,
scaleY: canvas.height! / image.height!,
});
const zoomRatio = innerWidth / canvas.width!;
zoomResetValue.set(zoomRatio);
instance!.smoothZoom(0, 0, zoomRatio);
});
};
undoRedoInit(canvas);
image.src = data;
image.remove();
});
function initPanzoom(node) {
instance = panzoom(node, {
bounds: true,
maxZoom: 3,
minZoom: 0.1,
zoomDoubleClickSpeed: 1,
});
instance.pause();
}
</script>

<div><SideToolbar {instance} {canvas} /></div>
<div class="editor-main" bind:clientWidth={innerWidth}>
<div class="editor-container" use:initPanzoom>
<canvas id="canvas" />
</div>
</div>

<style lang="scss">
.editor-main {
position: absolute;
top: 80px;
left: 36px;
bottom: 2px;
right: 2px;
border: 1px solid rgb(96, 141, 225);
overflow: auto;
padding-bottom: 100px;
}
.editor-container {
width: 100%;
height: 100%;
}
</style>
109 changes: 109 additions & 0 deletions ts/image-occlusion/SideToolbar.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<!--
Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="ts">
import IconButton from "../components/IconButton.svelte";
import { drawRectangle, drawEllipse, drawPolygon } from "./tools/index";
import { enableSelectable, fillShapeColor, stopDraw } from "./tools/lib";
import { tools } from "./tools/tool-buttons";
import TopToolbar from "./TopToolbar.svelte";
export let instance;
export let canvas;
const iconSize = 80;
let activeTool = "cursor";
let toolTopPos = 0;
function setActive(toolId) {
activeTool = toolId;
disableFunctions();
switch (toolId) {
case "cursor":
enableSelectable(canvas, true);
break;
case "magnify":
instance.resume();
break;
case "draw-rectangle":
drawRectangle(canvas);
break;
case "draw-ellipse":
drawEllipse(canvas);
break;
case "draw-polygon":
drawPolygon(canvas, instance);
break;
case "shape-fill-color":
enableSelectable(canvas, true);
break;
default:
break;
}
}
const disableFunctions = () => {
instance.pause();
stopDraw(canvas);
enableSelectable(canvas, false);
};
const changeShapeFillColor = (node: any) => {
if (node) {
fillShapeColor(canvas, node.target.value);
}
};
</script>

<TopToolbar {canvas} {activeTool} {instance} {iconSize} />

<div class="tool-bar-container">
{#each tools as tool}
<IconButton
class="tool-icon-button {activeTool == tool.id ? 'active-tool' : ''}"
{iconSize}
active={activeTool === tool.id}
on:click={(e) => {
setActive(tool.id);
// popup position for color dialog
toolTopPos = e.pageY - 60;
}}>{@html tool.icon}</IconButton
>
{/each}
</div>

<style>
.tool-bar-container {
position: fixed;
top: 42px;
left: 0;
height: 100%;
border-right: 1px solid #e3e3e3;
overflow-y: auto;
width: 32px;
z-index: 99;
background: white;
padding-bottom: 100px;
}
:global(.tool-icon-button) {
border: unset;
display: block;
width: 32px;
height: 32px;
margin: unset;
padding: 6px !important;
}
:global(.active-tool) {
color: red !important;
background: unset !important;
}
::-webkit-scrollbar {
width: 0.2em !important;
height: 0.2em !important;
}
</style>
76 changes: 76 additions & 0 deletions ts/image-occlusion/TopToolbar.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<!--
Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script>
import IconButton from "../components/IconButton.svelte";
import { cursorTools, zoomTools } from "./tools/more-tools";
import { undoRedoTools } from "./tools/tool-undo-redo";
export let activeTool = "cursor";
export let canvas;
export let instance;
export let iconSize;
</script>

<div class="top-tool-bar-container">
{#each undoRedoTools as undoRedoTool}
<IconButton
class="top-tool-icon-button"
{iconSize}
on:click={() => {
undoRedoTool.action(canvas);
}}
>
{@html undoRedoTool.icon}
</IconButton>
{/each}

{#each zoomTools as zoomBottomTool}
<IconButton
class="top-tool-icon-button"
{iconSize}
on:click={() => {
zoomBottomTool.action(instance);
}}
>
{@html zoomBottomTool.icon}
</IconButton>
{/each}

{#if activeTool === "cursor"}
{#each cursorTools as cursorBottomTool}
<IconButton
class="top-tool-icon-button"
{iconSize}
on:click={() => {
cursorBottomTool.action(canvas);
}}
>
{@html cursorBottomTool.icon}
</IconButton>
{/each}
{/if}
</div>

<style>
.top-tool-bar-container {
position: fixed;
top: 42px;
left: 36px;
width: 100%;
border-right: 1px solid #e3e3e3;
overflow-y: auto;
z-index: 99;
background: white;
}
:global(.top-tool-icon-button) {
border: unset;
display: inline;
width: 32px;
height: 32px;
margin: unset;
padding: 6px !important;
}
</style>
Loading

0 comments on commit d56ea36

Please sign in to comment.