TASK: #113126 - add gauss blur component
This commit is contained in:
parent
31f5e51c83
commit
b6d1047169
|
@ -24,3 +24,4 @@
|
||||||
- add function for dragging icon into container
|
- add function for dragging icon into container
|
||||||
- add homescreen function for storing apps' order
|
- add homescreen function for storing apps' order
|
||||||
- fix bugs of container
|
- fix bugs of container
|
||||||
|
- add gauss blur component
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
content="width=device-width, user-scalable=no, initial-scale=1.0"
|
content="width=device-width, user-scalable=no, initial-scale=1.0"
|
||||||
/>
|
/>
|
||||||
<title>Star Web Components</title>
|
<title>Star Web Components</title>
|
||||||
<script type="module" src="./start-element.js"></script>
|
<script type="module" src="./star-element.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<!-- main -->
|
<!-- main -->
|
||||||
|
|
|
@ -21,6 +21,11 @@
|
||||||
"description": "StarWeb组件"
|
"description": "StarWeb组件"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"permissions": {
|
||||||
|
"settings": {
|
||||||
|
"access": "readwrite"
|
||||||
|
}
|
||||||
|
},
|
||||||
"core": true,
|
"core": true,
|
||||||
"version": "0.0.1"
|
"version": "0.0.1"
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
## 高斯模糊小组件
|
||||||
|
|
||||||
|
小组件为解决用 `Css` 属性进行高斯模糊后,浏览器卡顿的问题。原理是用一个 `canvas` 将根据不同 `sigma` 值进行高斯模糊处理后的图展示出来。
|
||||||
|
|
||||||
|
该组件的模糊算法当 `sigma` 越高时 ,其处理模糊的速度越快,越低时处理速度越慢,然而 `sigma` 越低,模糊程度越小,与原图越类似,可以用降低分辨率的方法替换模糊算法。因此组件设定了一个门槛值 `threshold` ,使用者可以根据机器性能和图片质量选择门槛值的高低。
|
||||||
|
|
||||||
|
因为模糊算法耗时受 `sigma` 值和图片分辨率的影响,无法准确掌控,因此为了不阻塞主线程的渲染工作,模糊算法需要放入 `Web worker` 中运行。
|
||||||
|
|
||||||
|
### 属性
|
||||||
|
|
||||||
|
- `src`: 要高斯模糊的图片URL
|
||||||
|
- `sigma`: 高斯模糊程度系数,除了第一次传值时不会有模糊渐变,之后传值时图片模糊会呈渐变,如若不需要动画则调用方法 `showImmediately` 并传值
|
||||||
|
- `threshold`: 当 `sigma` 大于该值时,组件采用模糊算法,否则采用降低分辨率的方法模糊图片,默认值为1
|
||||||
|
- `during`: 模糊渐变的最长时间,单位为 `ms`,默认值为 500
|
||||||
|
- `bezier`: 模糊渐变的贝塞尔系数,接受参数为一个有四个数字元素的数组,默认值为 `[0.19, 1, 0.22, 1]`
|
||||||
|
|
||||||
|
### 使用
|
||||||
|
|
||||||
|
注意,请不要使用跨域图片资源,否则无法转化为 `ImageData` 进行模糊计算
|
||||||
|
|
||||||
|
|
||||||
|
```html
|
||||||
|
<gauss-canvas src="./test.png" sigma="1"></gauss-canvas>
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
import "@star-web-element/gauss"
|
||||||
|
const canvas = document.querySelector('gauss-canvas');
|
||||||
|
|
||||||
|
canvas.addEventListener('click', () => {
|
||||||
|
// 会有模糊渐变
|
||||||
|
canvas.sigma ^= 10
|
||||||
|
})
|
||||||
|
|
||||||
|
canvas.addEventListener('mousedown', handleEvent);
|
||||||
|
canvas.addEventListener('mousemove', handleEvent);
|
||||||
|
canvas.addEventListener('mouseup', handleEvent);
|
||||||
|
|
||||||
|
let mouseData = {
|
||||||
|
start: 0,
|
||||||
|
moveDistance: 0,
|
||||||
|
}
|
||||||
|
function handleEvent(evt) {
|
||||||
|
switch (evt.type) {
|
||||||
|
case 'mousedown':
|
||||||
|
mouseData.start = evt.clientY;
|
||||||
|
break;
|
||||||
|
case ' mousemove':
|
||||||
|
if (mouseData.start) {
|
||||||
|
mouseData.moveDistance = evt.clientY - mouseData.start;
|
||||||
|
const conHeight = canvas.parentElement.offsetHeight;
|
||||||
|
const targetSigma = (mouseData.moveDistance / conHeight) * 10;
|
||||||
|
// 不会有模糊渐变
|
||||||
|
canvas.showImmediately(targetSigma);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'mouseup':
|
||||||
|
mouseData.start = mouseData.moveDistance = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
|
@ -0,0 +1,287 @@
|
||||||
|
import {html, css, LitElement} from 'lit'
|
||||||
|
import {customElement, property, query} from 'lit/decorators.js'
|
||||||
|
import workerUrl from './worker'
|
||||||
|
enum ErrorType {
|
||||||
|
IMAGE_LOAD_FAILED = 'Image load failed!',
|
||||||
|
IMAGE_NOT_PREPARED = 'The Image resource is not completely prepared!',
|
||||||
|
IMAGE_CROS_ERROR = 'The Image resource crosses domain!',
|
||||||
|
CANVAS_PUTIMAGEDATA_ERROR = 'Canvas putImageData failed!',
|
||||||
|
}
|
||||||
|
|
||||||
|
const cubic_bezier = (
|
||||||
|
p0: number,
|
||||||
|
p1: number,
|
||||||
|
p2: number,
|
||||||
|
p3: number,
|
||||||
|
t: number
|
||||||
|
): number => {
|
||||||
|
return (
|
||||||
|
p0 * Math.pow(1 - t, 3) +
|
||||||
|
3 * p1 * t * Math.pow(1 - t, 2) +
|
||||||
|
3 * p2 * t * t * (1 - t) +
|
||||||
|
p3 * Math.pow(t, 3)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@customElement('gauss-canvas')
|
||||||
|
export default class GaussCanvas extends LitElement {
|
||||||
|
@query('#display') display!: HTMLCanvasElement
|
||||||
|
@property({type: String})
|
||||||
|
get src() {
|
||||||
|
return this._src
|
||||||
|
}
|
||||||
|
set src(value) {
|
||||||
|
if (value && value !== this._src) {
|
||||||
|
const loadCb = () => {
|
||||||
|
this._src = value
|
||||||
|
}
|
||||||
|
const errCb = () => {
|
||||||
|
this._img.src = this._src
|
||||||
|
this._img.removeEventListener('load', loadCb)
|
||||||
|
}
|
||||||
|
|
||||||
|
this._img.addEventListener('error', errCb, {once: true})
|
||||||
|
this._img.addEventListener('load', loadCb, {once: true})
|
||||||
|
|
||||||
|
this._img.src = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@property() threshold: number = 1
|
||||||
|
@property() transition: boolean = false
|
||||||
|
|
||||||
|
@property()
|
||||||
|
get sigma() {
|
||||||
|
return this._sigma
|
||||||
|
}
|
||||||
|
set sigma(value) {
|
||||||
|
if (value < 0) {
|
||||||
|
value = 0
|
||||||
|
}
|
||||||
|
if (this._sigma == -1) {
|
||||||
|
this._sigma = Number(value)
|
||||||
|
this.show(value)
|
||||||
|
} else if (value !== this._sigma && this._targetSigma == -1) {
|
||||||
|
this._targetSigma = value
|
||||||
|
this.openAnimation()
|
||||||
|
} else {
|
||||||
|
console.info('changed target sigma')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@property({type: Number}) bezier: [number, number, number, number] = [
|
||||||
|
0.19, 1, 0.22, 1,
|
||||||
|
]
|
||||||
|
@property() during = 500
|
||||||
|
|
||||||
|
readonly _img: HTMLImageElement = document.createElement('img')
|
||||||
|
_loadStatus: boolean = false
|
||||||
|
_imgWidth: number = 0
|
||||||
|
_imgHeight: number = 0
|
||||||
|
|
||||||
|
readonly _trCanvas: HTMLCanvasElement = document.createElement('canvas')
|
||||||
|
readonly _trCtx = this._trCanvas.getContext('2d')!
|
||||||
|
_imageData: ImageData | undefined
|
||||||
|
|
||||||
|
_src: string = ''
|
||||||
|
_ctx!: CanvasRenderingContext2D
|
||||||
|
_sigma: number = -1
|
||||||
|
_targetSigma: number = -1
|
||||||
|
|
||||||
|
_worker: Worker | undefined
|
||||||
|
_id!: number
|
||||||
|
|
||||||
|
disWidth: number = 0
|
||||||
|
disHeight: number = 0
|
||||||
|
|
||||||
|
constructor(url: string) {
|
||||||
|
super()
|
||||||
|
this._img.addEventListener('error', this.loadError)
|
||||||
|
this._img.addEventListener('load', this.loadSuccess)
|
||||||
|
this._id = new Date().getTime()
|
||||||
|
this.src = url
|
||||||
|
}
|
||||||
|
|
||||||
|
loadError = () => {
|
||||||
|
this._loadStatus = false
|
||||||
|
this._imgWidth = this._imgHeight = 0
|
||||||
|
|
||||||
|
console.error(ErrorType.IMAGE_LOAD_FAILED)
|
||||||
|
}
|
||||||
|
|
||||||
|
loadSuccess = () => {
|
||||||
|
this._loadStatus = true
|
||||||
|
this._imgHeight = this._img.height
|
||||||
|
this._imgWidth = this._img.width
|
||||||
|
this.show(this.sigma)
|
||||||
|
}
|
||||||
|
|
||||||
|
drawWorker = (
|
||||||
|
sigma: number,
|
||||||
|
imageData: ImageData,
|
||||||
|
canvas: HTMLCanvasElement,
|
||||||
|
draw: Function
|
||||||
|
) => {
|
||||||
|
if (sigma > this.threshold) {
|
||||||
|
if (!this._worker) {
|
||||||
|
this._worker = new Worker(workerUrl)
|
||||||
|
this._worker.addEventListener('message', (evt) => {
|
||||||
|
draw(evt.data.data, evt.data.id)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
this._worker.postMessage({
|
||||||
|
imageData,
|
||||||
|
sigma,
|
||||||
|
id: this._id,
|
||||||
|
height: canvas.height,
|
||||||
|
width: canvas.width,
|
||||||
|
})
|
||||||
|
this.isFormatting = true
|
||||||
|
} else {
|
||||||
|
draw(imageData, this._id)
|
||||||
|
this.cb?.()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cb: Function | undefined
|
||||||
|
isFormatting: boolean = false
|
||||||
|
show = (sigma: number, cb?: Function) => {
|
||||||
|
this.cb = cb
|
||||||
|
return new Promise((res, rej) => {
|
||||||
|
if (!this._loadStatus) {
|
||||||
|
if (!this._img.src.includes(this._src)) {
|
||||||
|
this._img.src = this._src
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isFormatting) {
|
||||||
|
return res(void 0)
|
||||||
|
}
|
||||||
|
let ratio: number = sigma
|
||||||
|
if (sigma == 0) {
|
||||||
|
ratio = 1
|
||||||
|
} else if (sigma < this.threshold) {
|
||||||
|
ratio = 2 * this.threshold - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
const canvas = document.createElement('canvas')
|
||||||
|
const ctx = canvas.getContext('2d')!
|
||||||
|
canvas.height = this._img.height / ratio
|
||||||
|
canvas.width = this._img.width / ratio
|
||||||
|
|
||||||
|
ctx.drawImage(
|
||||||
|
this._img,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
this._img.width,
|
||||||
|
this._img.height,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
canvas.width,
|
||||||
|
canvas.height
|
||||||
|
)
|
||||||
|
|
||||||
|
const draw = (imageData: ImageData, dataId: number) => {
|
||||||
|
if (dataId == this._id) {
|
||||||
|
if (this.cb) {
|
||||||
|
this.cb?.()
|
||||||
|
}
|
||||||
|
// +100 为了清除拖影
|
||||||
|
this.disHeight = this.display.height = imageData.height /* + 100 */
|
||||||
|
this.disWidth = this.display.width = imageData.width
|
||||||
|
this._ctx.putImageData(imageData, 0, 0)
|
||||||
|
this.isFormatting = false
|
||||||
|
res(void 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
let imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
|
||||||
|
this.drawWorker(sigma, imageData, canvas, draw)
|
||||||
|
} catch (error: any) {
|
||||||
|
if (error.code == 18) {
|
||||||
|
return rej(ErrorType.IMAGE_CROS_ERROR)
|
||||||
|
}
|
||||||
|
return rej(ErrorType.CANVAS_PUTIMAGEDATA_ERROR)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
showImmediately(sigma: number) {
|
||||||
|
return new Promise((res) => {
|
||||||
|
this._sigma = sigma
|
||||||
|
this.show(sigma).then(res)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
_startTime!: number
|
||||||
|
animation: Function | undefined
|
||||||
|
openAnimation() {
|
||||||
|
this._startTime = new Date().getTime()
|
||||||
|
const getCurSigma = () => {
|
||||||
|
const t = (new Date().getTime() - this._startTime) / this.during
|
||||||
|
let ratio = cubic_bezier(...this.bezier, t)
|
||||||
|
|
||||||
|
if (ratio > 0.95) {
|
||||||
|
return this._targetSigma
|
||||||
|
}
|
||||||
|
const result = (this._targetSigma - this._sigma) * ratio + this._sigma
|
||||||
|
|
||||||
|
return result > 0 ? result : 0
|
||||||
|
}
|
||||||
|
let changeSigma = () => {
|
||||||
|
this._sigma = getCurSigma()
|
||||||
|
|
||||||
|
if (this._targetSigma !== this._sigma) {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.animation = () => {
|
||||||
|
if (changeSigma()) {
|
||||||
|
// console.time(`sigma: ${this._sigma}`)
|
||||||
|
this.show(this._sigma, () => {
|
||||||
|
// console.timeEnd(`sigma: ${this._sigma}`)
|
||||||
|
// @ts-ignore
|
||||||
|
requestAnimationFrame(this.animation!)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this._targetSigma = -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.animation()
|
||||||
|
}
|
||||||
|
|
||||||
|
protected firstUpdated() {
|
||||||
|
this._ctx = this.display.getContext('2d')!
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return html`
|
||||||
|
<canvas id="display"></canvas>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
static styles = css`
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
canvas {
|
||||||
|
display: block;
|
||||||
|
margin: auto;
|
||||||
|
height: calc(100%);
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
'gauss-canvas': GaussCanvas
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"name": "@star-web-components/gauss",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "",
|
||||||
|
"type": "module",
|
||||||
|
"main": "./index.js",
|
||||||
|
"module": "./index.js",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"default": "./index.js"
|
||||||
|
},
|
||||||
|
"./index": {
|
||||||
|
"default": "./index.js"
|
||||||
|
},
|
||||||
|
"./index.js": {
|
||||||
|
"default": "./index.js"
|
||||||
|
},
|
||||||
|
"./icons/index.js": {
|
||||||
|
"default": "./icons/index.js"
|
||||||
|
},
|
||||||
|
"./package.json": "./package.json"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC"
|
||||||
|
}
|
|
@ -0,0 +1,181 @@
|
||||||
|
// import {
|
||||||
|
// cubic_bezier,
|
||||||
|
// fastBlur,
|
||||||
|
// genSrcAndDest,
|
||||||
|
// mergeChannels,
|
||||||
|
// genKernelsForGaussian,
|
||||||
|
// _fastBlur,
|
||||||
|
// hFastMotionBlur,
|
||||||
|
// vFastMotionBlur,
|
||||||
|
// } from './utils'
|
||||||
|
// @ts-ignore
|
||||||
|
const workCode = () => {
|
||||||
|
function genKernelsForGaussian(sigma: number, n: number) {
|
||||||
|
const wIdeal = Math.sqrt((12 * Math.pow(sigma, 2)) / n + 1)
|
||||||
|
const sizes = []
|
||||||
|
let wl = Math.floor(wIdeal)
|
||||||
|
|
||||||
|
if (wl % 2 === 0) {
|
||||||
|
wl--
|
||||||
|
}
|
||||||
|
const wu = wl + 2
|
||||||
|
let m =
|
||||||
|
(12 * Math.pow(sigma, 2) - n * Math.pow(wl, 2) - 4 * n * wl - 3 * n) /
|
||||||
|
(-4 * wl - 4)
|
||||||
|
m = Math.round(m)
|
||||||
|
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
sizes.push(i < m ? wl : wu)
|
||||||
|
}
|
||||||
|
|
||||||
|
return sizes
|
||||||
|
}
|
||||||
|
|
||||||
|
function genSrcAndDest(data: Uint8ClampedArray) {
|
||||||
|
const dataInt8 = new Uint8ClampedArray(data.buffer)
|
||||||
|
const simpleChannelLength = dataInt8.length / 4
|
||||||
|
const r = new Uint8ClampedArray(simpleChannelLength)
|
||||||
|
const g = new Uint8ClampedArray(simpleChannelLength)
|
||||||
|
const b = new Uint8ClampedArray(simpleChannelLength)
|
||||||
|
const a = new Uint8ClampedArray(simpleChannelLength)
|
||||||
|
const _r = new Uint8ClampedArray(simpleChannelLength)
|
||||||
|
const _g = new Uint8ClampedArray(simpleChannelLength)
|
||||||
|
const _b = new Uint8ClampedArray(simpleChannelLength)
|
||||||
|
const _a = new Uint8ClampedArray(simpleChannelLength)
|
||||||
|
for (let i = 0; i < simpleChannelLength; i++) {
|
||||||
|
_r[i] = r[i] = dataInt8[i * 4]
|
||||||
|
_g[i] = g[i] = dataInt8[i * 4 + 1]
|
||||||
|
_b[i] = b[i] = dataInt8[i * 4 + 2]
|
||||||
|
_a[i] = a[i] = dataInt8[i * 4 + 3]
|
||||||
|
}
|
||||||
|
return {src: [r, g, b, a], dest: [_r, _g, _b, _a]}
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergeChannels([r, g, b, a]: Uint8ClampedArray[]) {
|
||||||
|
const simpleChannelLength = r.length
|
||||||
|
const data = new Uint8ClampedArray(simpleChannelLength * 4)
|
||||||
|
for (let i = 0; i < simpleChannelLength; i++) {
|
||||||
|
data[4 * i] = r[i]
|
||||||
|
data[4 * i + 1] = g[i]
|
||||||
|
data[4 * i + 2] = b[i]
|
||||||
|
data[4 * i + 3] = a[i]
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
function hFastMotionBlur(
|
||||||
|
src: Uint8ClampedArray,
|
||||||
|
dest: Uint8ClampedArray,
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
radius: number
|
||||||
|
) {
|
||||||
|
for (let i = 0; i < height; i++) {
|
||||||
|
let accumulation = radius * src[i * width]
|
||||||
|
for (let j = 0; j <= radius; j++) {
|
||||||
|
accumulation += src[i * width + j]
|
||||||
|
}
|
||||||
|
|
||||||
|
dest[i * width] = Math.round(accumulation / (2 * radius + 1))
|
||||||
|
|
||||||
|
for (let j = 1; j < width; j++) {
|
||||||
|
const left = Math.max(0, j - radius - 1)
|
||||||
|
const right = Math.min(width - 1, j + radius)
|
||||||
|
accumulation =
|
||||||
|
accumulation + (src[i * width + right] - src[i * width + left])
|
||||||
|
dest[i * width + j] = Math.round(accumulation / (2 * radius + 1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function vFastMotionBlur(
|
||||||
|
src: Uint8ClampedArray,
|
||||||
|
dest: Uint8ClampedArray,
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
radius: number
|
||||||
|
) {
|
||||||
|
for (let i = 0; i < width; i++) {
|
||||||
|
let accumulation = radius * src[i]
|
||||||
|
for (let j = 0; j <= radius; j++) {
|
||||||
|
accumulation += src[j * width + i]
|
||||||
|
}
|
||||||
|
|
||||||
|
dest[i] = Math.round(accumulation / (2 * radius + 1))
|
||||||
|
|
||||||
|
for (let j = 1; j < height; j++) {
|
||||||
|
const top = Math.max(0, j - radius - 1)
|
||||||
|
const bottom = Math.min(height - 1, j + radius)
|
||||||
|
accumulation =
|
||||||
|
accumulation + src[bottom * width + i] - src[top * width + i]
|
||||||
|
dest[j * width + i] = Math.round(accumulation / (2 * radius + 1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function _fastBlur(
|
||||||
|
src: Uint8ClampedArray,
|
||||||
|
dest: Uint8ClampedArray,
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
radius: number
|
||||||
|
) {
|
||||||
|
hFastMotionBlur(dest, src, width, height, radius)
|
||||||
|
vFastMotionBlur(src, dest, width, height, radius)
|
||||||
|
}
|
||||||
|
|
||||||
|
function fastBlur(
|
||||||
|
src: Uint8ClampedArray,
|
||||||
|
dest: Uint8ClampedArray,
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
sigma: number
|
||||||
|
) {
|
||||||
|
const boxes = genKernelsForGaussian(sigma, 3)
|
||||||
|
|
||||||
|
for (let i = 0; i < src.length; i++) {
|
||||||
|
dest[i] = src[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
_fastBlur(src, dest, width, height, (boxes[0] - 1) / 2)
|
||||||
|
_fastBlur(src, dest, width, height, (boxes[1] - 1) / 2)
|
||||||
|
_fastBlur(src, dest, width, height, (boxes[2] - 1) / 2)
|
||||||
|
|
||||||
|
return dest
|
||||||
|
}
|
||||||
|
|
||||||
|
onmessage = (evt) => {
|
||||||
|
postMessage({data: blurCanvas(evt.data), id: evt.data.id})
|
||||||
|
}
|
||||||
|
let blurCanvas = ({
|
||||||
|
imageData,
|
||||||
|
height,
|
||||||
|
width,
|
||||||
|
sigma,
|
||||||
|
}: {
|
||||||
|
imageData: ImageData
|
||||||
|
sigma: number
|
||||||
|
height: number
|
||||||
|
width: number
|
||||||
|
}) => {
|
||||||
|
const {src: srcRgba, dest: destRgba} = genSrcAndDest(imageData.data)
|
||||||
|
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
fastBlur(srcRgba[i], destRgba[i], width, height, sigma)
|
||||||
|
}
|
||||||
|
const destData = mergeChannels(destRgba)
|
||||||
|
imageData.data.set(destData)
|
||||||
|
return imageData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const transfer = () => {
|
||||||
|
let codeStr: string = ''
|
||||||
|
|
||||||
|
codeStr += `(${workCode.toString()})()`
|
||||||
|
return codeStr
|
||||||
|
}
|
||||||
|
|
||||||
|
let workBlob = new Blob([transfer()])
|
||||||
|
|
||||||
|
export default URL.createObjectURL(workBlob)
|
|
@ -1,5 +1,5 @@
|
||||||
import {html, LitElement, css} from 'lit'
|
import {html, LitElement, css} from 'lit'
|
||||||
import {customElement, property} from 'lit/decorators.js'
|
import {customElement} from 'lit/decorators.js'
|
||||||
import '../../../components/button/button'
|
import '../../../components/button/button'
|
||||||
import '../../../components/overlay/active-overlay'
|
import '../../../components/overlay/active-overlay'
|
||||||
import {OverlayStack} from '../../../components/overlay/overlay-stack'
|
import {OverlayStack} from '../../../components/overlay/overlay-stack'
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import {html, LitElement, css} from 'lit'
|
import {html, LitElement, css} from 'lit'
|
||||||
import {customElement, property} from 'lit/decorators.js'
|
import {customElement} from 'lit/decorators.js'
|
||||||
import '../icon/icon'
|
import '../icon/icon'
|
||||||
|
|
||||||
@customElement('panel-digicipher')
|
@customElement('panel-digicipher')
|
||||||
|
|
|
@ -0,0 +1,127 @@
|
||||||
|
import {html, css, LitElement, CSSResultGroup} from 'lit'
|
||||||
|
import {customElement, property, query} from 'lit/decorators.js'
|
||||||
|
import '../../../components/gauss_canvas/index'
|
||||||
|
import GaussCanvas from '../../../components/gauss_canvas/index'
|
||||||
|
|
||||||
|
@customElement('panel-gauss')
|
||||||
|
export class GaussBlur extends LitElement {
|
||||||
|
@query('#container') container!: HTMLDivElement
|
||||||
|
@query('gauss-canvas') canvas!: GaussCanvas
|
||||||
|
// _src = '/src/test/panels/gauss_canvas/big.jpeg'
|
||||||
|
@property() _src!: string
|
||||||
|
// 'https://fanyiapp.cdn.bcebos.com/cms/image/cfacf96e5beb2a8444e016b96fb96ab6.jpg'
|
||||||
|
@property({type: Number}) sigma: number = 2
|
||||||
|
@property({type: Number}) topPositionSigma: number = 20
|
||||||
|
@property({type: Number}) bottomPositionSigma: number = 2
|
||||||
|
|
||||||
|
_moveFlag: boolean = false
|
||||||
|
_offsetY: number = 0
|
||||||
|
_start: number = 0
|
||||||
|
_moveDistance: number = 0
|
||||||
|
set offsetY(value: number) {
|
||||||
|
this._offsetY = -value
|
||||||
|
this.canvas.display.style.transform = `translateY(${value}px)`
|
||||||
|
}
|
||||||
|
|
||||||
|
handleEvent = (evt: TouchEvent | MouseEvent) => {
|
||||||
|
switch (evt.type) {
|
||||||
|
case 'touchstart':
|
||||||
|
case 'mousedown':
|
||||||
|
this._moveFlag = false
|
||||||
|
if (evt instanceof MouseEvent) {
|
||||||
|
this._start = evt.clientY
|
||||||
|
} else {
|
||||||
|
this._start = evt.touches[0].pageY
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'touchmove':
|
||||||
|
case 'mousemove':
|
||||||
|
if (this._start) {
|
||||||
|
if (evt instanceof MouseEvent) {
|
||||||
|
this._moveDistance = evt.clientY - this._start
|
||||||
|
} else {
|
||||||
|
this._moveDistance = evt.touches[0].pageY - this._start
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._moveDistance > 0) this._moveDistance = 0
|
||||||
|
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
this.offsetY = this._moveDistance
|
||||||
|
this.changeSigma()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this._moveFlag = true
|
||||||
|
|
||||||
|
break
|
||||||
|
case 'touchend':
|
||||||
|
case 'mouseup':
|
||||||
|
this._start = this._moveDistance = 0
|
||||||
|
break
|
||||||
|
case 'click':
|
||||||
|
!this._moveFlag && (this.canvas.sigma ^= 10)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* change sigma fowllowing this.offsetY
|
||||||
|
*/
|
||||||
|
changeSigma() {
|
||||||
|
const conHeight = this.container.offsetHeight
|
||||||
|
const targetSigma =
|
||||||
|
(this._offsetY / conHeight) *
|
||||||
|
(this.topPositionSigma - this.bottomPositionSigma) +
|
||||||
|
this.bottomPositionSigma
|
||||||
|
this.canvas.showImmediately(targetSigma)
|
||||||
|
}
|
||||||
|
|
||||||
|
firstUpdated() {
|
||||||
|
;(window as any).panel = this
|
||||||
|
|
||||||
|
this.addEventListener('touchstart', this)
|
||||||
|
this.addEventListener('touchmove', this)
|
||||||
|
this.addEventListener('touchend', this)
|
||||||
|
this.addEventListener('mousedown', this)
|
||||||
|
this.addEventListener('mousemove', this)
|
||||||
|
this.addEventListener('mouseup', this)
|
||||||
|
this.canvas.addEventListener('click', this)
|
||||||
|
}
|
||||||
|
handleInputFile(evt: Event) {
|
||||||
|
const imgfile = (evt.target as HTMLInputElement).files?.[0]
|
||||||
|
if (imgfile) {
|
||||||
|
this._src = URL.createObjectURL(imgfile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
return html`
|
||||||
|
<div id="container">
|
||||||
|
<input type="file" @change=${this.handleInputFile} />
|
||||||
|
<gauss-canvas src=${this._src} sigma=${this.sigma}></gauss-canvas>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
static styles?: CSSResultGroup | undefined = css`
|
||||||
|
#container {
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
will-change: transform;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
input {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
gauss-canvas {
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
'panel-gauss': GaussBlur
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
import {html, LitElement, css, CSSResultArray} from 'lit'
|
import {html, LitElement, css, CSSResultArray} from 'lit'
|
||||||
import {customElement, property} from 'lit/decorators.js'
|
import {customElement} from 'lit/decorators.js'
|
||||||
import '../../../components/button/button'
|
import '../../../components/button/button'
|
||||||
import '../../../components/ul/ul'
|
import '../../../components/ul/ul'
|
||||||
import '../../../components/li//li'
|
import '../../../components/li//li'
|
||||||
|
|
|
@ -14,6 +14,7 @@ import './card/card'
|
||||||
import './indicators/indicators'
|
import './indicators/indicators'
|
||||||
import './indicators/home-indicator'
|
import './indicators/home-indicator'
|
||||||
import './blur/use-blur'
|
import './blur/use-blur'
|
||||||
|
import './gauss_canvas/gauss-blur'
|
||||||
import './button/button'
|
import './button/button'
|
||||||
import './container/container'
|
import './container/container'
|
||||||
import './radio/radio'
|
import './radio/radio'
|
||||||
|
@ -249,6 +250,14 @@ export class PanelRoot extends LitElement {
|
||||||
href="#blur"
|
href="#blur"
|
||||||
></star-li>
|
></star-li>
|
||||||
<hr />
|
<hr />
|
||||||
|
<star-li
|
||||||
|
type=${LiType.ICON_LABEL}
|
||||||
|
label="高斯模糊"
|
||||||
|
icon="achievement"
|
||||||
|
iconcolor="gold"
|
||||||
|
href="#gauss"
|
||||||
|
></star-li>
|
||||||
|
<hr />
|
||||||
<star-li
|
<star-li
|
||||||
type=${LiType.ICON_LABEL}
|
type=${LiType.ICON_LABEL}
|
||||||
label="主屏"
|
label="主屏"
|
||||||
|
|
Loading…
Reference in New Issue