From a4d597eabb700a27b9a55c638d28e0944d7ac518 Mon Sep 17 00:00:00 2001 From: redhoodsu Date: Sun, 25 Feb 2024 22:59:33 +0800 Subject: [PATCH] release(mask-editor): v0.4.0 --- index.json | 2 +- src/mask-editor/index.ts | 78 +++++++++++++++++++++++++++--------- src/mask-editor/package.json | 2 +- src/mask-editor/react.tsx | 9 ++++- src/mask-editor/story.js | 11 +++-- src/share/util.ts | 13 ++++++ 6 files changed, 90 insertions(+), 25 deletions(-) diff --git a/index.json b/index.json index a40130d..8e9305c 100644 --- a/index.json +++ b/index.json @@ -174,7 +174,7 @@ "dependencies": ["painter"], "style": false, "react": true, - "version": "0.3.0", + "version": "0.4.0", "icon": false, "test": true, "install": false diff --git a/src/mask-editor/index.ts b/src/mask-editor/index.ts index 9b5c4ca..ec0d729 100644 --- a/src/mask-editor/index.ts +++ b/src/mask-editor/index.ts @@ -1,12 +1,15 @@ import Component, { IComponentOptions } from '../share/Component' -import LunaPainter, { Layer, Zoom } from 'luna-painter' +import LunaPainter, { Layer, Zoom, Hand } from 'luna-painter' import debounce from 'licia/debounce' import Color from 'licia/Color' +import { loadImage } from '../share/util' /** IOptions */ export interface IOptions extends IComponentOptions { /** Image src. */ image: string + /** Mask src. */ + mask?: string } /** @@ -59,32 +62,64 @@ export default class MaskEditor extends Component { this.bindEvent() - this.loadImage() + this.loadImage().then(() => this.loadMask()) } /** Get a canvas with mask drawn. */ getCanvas() { return this.canvas } - private loadImage() { + private async loadImage() { const { painter } = this + if (!this.options.image) { + return + } - const image = new Image() - image.onload = () => { - const { width, height } = image - painter.setOption({ - width, - height, - }) + const image = await loadImage(this.options.image) - const ctx = this.baseLayer.getContext() - ctx.drawImage(image, 0, 0, width, height) - painter.renderCanvas() - const zoom = painter.getTool('zoom') as Zoom - zoom.fitScreen() + const { width, height } = image + painter.setOption({ + width, + height, + }) + + const ctx = this.baseLayer.getContext() + ctx.drawImage(image, 0, 0, width, height) + painter.renderCanvas() + + this.renderMask() - this.renderMask() + const zoom = painter.getTool('zoom') as Zoom + zoom.fitScreen() + const hand = painter.getTool('hand') as Hand + hand.centerCanvas() + } + private async loadMask() { + const { painter } = this + if (!this.options.mask) { + return + } + + const image = await loadImage(this.options.mask) + + const width = painter.getOption('width') + const height = painter.getOption('height') + const ctx = this.drawingLayer.getContext() + ctx.drawImage(image, 0, 0, width, height) + const c = new Color(painter.getForegroundColor()) + const rgb = Color.parse(c.toRgb()).val + const canvas = ctx.canvas + const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height) + const { data } = imageData + for (let i = 0, len = data.length; i < len; i += 4) { + data[i + 3] = (data[i] + data[i + 1] + data[i + 2]) / 3 + data[i] = rgb[0] + data[i + 1] = rgb[1] + data[i + 2] = rgb[2] } - image.src = this.options.image + ctx.putImageData(imageData, 0, 0) + painter.renderCanvas() + + this.renderMask() } private renderMask() { const { canvas, ctx, blackCanvas, blackCtx, painter } = this @@ -142,8 +177,13 @@ export default class MaskEditor extends Component { }) this.on('optionChange', (name) => { - if (name === 'image') { - this.loadImage() + switch (name) { + case 'image': + this.loadImage() + break + case 'mask': + this.loadMask() + break } }) } diff --git a/src/mask-editor/package.json b/src/mask-editor/package.json index ef74fd1..0b3f71c 100644 --- a/src/mask-editor/package.json +++ b/src/mask-editor/package.json @@ -1,6 +1,6 @@ { "name": "mask-editor", - "version": "0.3.0", + "version": "0.4.0", "description": "Image mask editing", "luna": { "dependencies": [ diff --git a/src/mask-editor/react.tsx b/src/mask-editor/react.tsx index 27c4440..f35dca6 100644 --- a/src/mask-editor/react.tsx +++ b/src/mask-editor/react.tsx @@ -13,9 +13,10 @@ const LunaMaskEditor: FC = (props) => { const maskEditor = useRef() useEffect(() => { - const { image } = props + const { image, mask } = props maskEditor.current = new MaskEditor(maskEditorRef.current!, { image, + mask, }) props.onCreate && props.onCreate(maskEditor.current) @@ -32,6 +33,12 @@ const LunaMaskEditor: FC = (props) => { } }, [props.image]) + useNonInitialEffect(() => { + if (maskEditor.current) { + maskEditor.current.setOption('mask', props.mask) + } + }, [props.mask]) + return
} diff --git a/src/mask-editor/story.js b/src/mask-editor/story.js index 233df0c..9fbfef4 100644 --- a/src/mask-editor/story.js +++ b/src/mask-editor/story.js @@ -4,8 +4,9 @@ import $ from 'licia/$' import h from 'licia/h' import readme from './README.md' import LunaMaskEditor from './react' -import { text } from '@storybook/addon-knobs' +import { text, files } from '@storybook/addon-knobs' import { useRef } from 'react' +import isEmpty from 'licia/isEmpty' const def = story( 'mask-editor', @@ -33,9 +34,10 @@ const def = story( }) wrapper.appendChild(maskContainer) - const { image } = createKnobs() + const { image, mask } = createKnobs() const maskEditor = new MaskEditor(container, { image, + mask, }) onCreate(maskEditor, maskContainer) @@ -51,13 +53,14 @@ const def = story( source: __STORY__, ReactComponent({ theme }) { const maskContainer = useRef(null) - const { image } = createKnobs() + const { image, mask } = createKnobs() return ( <> { if (maskContainer.current) { onCreate(maskEditor, maskContainer.current) @@ -86,9 +89,11 @@ const def = story( function createKnobs() { const image = text('Image', 'https://res.liriliri.io/luna/pic1.jpg') + const masks = files('Mask') return { image, + mask: !isEmpty(masks) ? masks[0] : '', } } diff --git a/src/share/util.ts b/src/share/util.ts index 029cabc..168f9f1 100644 --- a/src/share/util.ts +++ b/src/share/util.ts @@ -6,6 +6,7 @@ import isNum from 'licia/isNum' import contain from 'licia/contain' import toNum from 'licia/toNum' import detectOs from 'licia/detectOs' +import loadImg from 'licia/loadImg' import isHidden from 'licia/isHidden' export function exportCjs(module: any, clazz: any) { @@ -171,3 +172,15 @@ export function resetCanvasSize(canvas: HTMLCanvasElement) { canvas.width = Math.round(canvas.offsetWidth * window.devicePixelRatio) canvas.height = Math.round(canvas.offsetHeight * window.devicePixelRatio) } + +export function loadImage(url: string): Promise { + return new Promise((resolve, reject) => { + loadImg(url, function (err, img) { + if (err) { + return reject(err) + } + + resolve(img) + }) + }) +} \ No newline at end of file