master
yutent 2023-12-18 18:39:41 +08:00
parent 620ff68a45
commit 691b9633d0
1 changed files with 159 additions and 70 deletions

View File

@ -27,6 +27,78 @@ function round(n) {
return n return n
} }
class ImageTool {
#img
#size = { w: 0, h: 0 }
#canvas = new OffscreenCanvas(1, 1)
#ctx = this.#canvas.getContext('2d')
#stat = {
x: 1,
y: 1,
deg: 0
}
constructor(img, w, h) {
this.#img = img
this.#size = { w, h }
this.#canvas.width = w
this.#canvas.height = h
}
rotate(v = 0) {
let { w, h } = this.#size
let { deg } = this.#stat
let can = this.#canvas
let ctx = this.#ctx
deg += v
if (deg === 360) {
deg = 0
}
this.#stat.deg = deg
if (deg % 180 === 0) {
can.width = w
can.height = h
ctx.clearRect(0, 0, w, h)
ctx.translate(w / 2, h / 2)
} else {
can.width = h
can.height = w
ctx.clearRect(0, 0, h, w)
ctx.translate(h / 2, w / 2)
}
ctx.rotate((deg * Math.PI) / 180)
ctx.drawImage(this.#img, -w / 2, -h / 2)
}
scale(_x = 1, _y = 1) {
let can = this.#canvas
let ctx = this.#ctx
let { w, h } = this.#size
let { x, y } = this.#stat
x *= _x
y *= _y
this.#stat = { x, y, deg: 0 }
can.width = w
can.height = h
ctx.clearRect(0, 0, w, h)
ctx.scale(x, y)
ctx.translate(x < 1 ? -w : 0, y < 1 ? -h : 0)
ctx.drawImage(this.#img, 0, 0)
}
export() {
return this.#canvas.convertToBlob({ type: 'image/png' })
}
}
class Uploader extends Component { class Uploader extends Component {
static props = { static props = {
value: 'str!https://static.reduzixun.com/r-time/common/2cc153c8018.webp', value: 'str!https://static.reduzixun.com/r-time/common/2cc153c8018.webp',
@ -104,6 +176,28 @@ class Uploader extends Component {
display: none; display: none;
} }
} }
.alpha-bg {
background: linear-gradient(
45deg,
var(--color-grey-2) 25%,
transparent 25%,
transparent 75%,
var(--color-grey-2) 75%,
var(--color-grey-2)
),
linear-gradient(
45deg,
var(--color-grey-2) 25%,
transparent 25%,
transparent 75%,
var(--color-grey-2) 75%,
var(--color-grey-2)
),
var(--color-plain-2);
background-size: 16px 16px;
background-position: 0 0, 8px 8px;
}
`, `,
css` css`
.image-thumb { .image-thumb {
@ -212,26 +306,6 @@ class Uploader extends Component {
width: 640px; width: 640px;
height: 480px; height: 480px;
background: linear-gradient(
45deg,
var(--color-grey-1) 25%,
transparent 25%,
transparent 75%,
var(--color-grey-1) 75%,
var(--color-grey-1)
),
linear-gradient(
45deg,
var(--color-grey-1) 25%,
transparent 25%,
transparent 75%,
var(--color-grey-1) 75%,
var(--color-grey-1)
);
background-size: 16px 16px;
background-position: 0 0, 8px 8px;
box-shadow: 0 0 0 1px var(--color-grey-2);
canvas { canvas {
display: block; display: block;
width: 100%; width: 100%;
@ -313,6 +387,10 @@ class Uploader extends Component {
} }
legend { legend {
font-size: 12px; font-size: 12px;
color: var(--color-red-1);
&.blue {
color: var(--color-blue-3);
}
} }
.button { .button {
@ -346,6 +424,8 @@ class Uploader extends Component {
#img #img
#panelShow = false #panelShow = false
#cache
// 原图信息 // 原图信息
#origin = { #origin = {
x: 0, x: 0,
@ -354,29 +434,23 @@ class Uploader extends Component {
h: 0, // 原图高度 h: 0, // 原图高度
rw: 0, // 渲染宽度 rw: 0, // 渲染宽度
rh: 0, // 渲染高度 rh: 0, // 渲染高度
scale: 1 //缩放比例 scale: 1, //缩放比例
size: 0
} }
#cropData = { #cropData = {
x: 0, x: 0,
y: 0, y: 0,
w: 300, w: 300,
h: 300 h: 300,
size: 0
} }
#ratio = 1 #ratio = 1
#stat = {
blur: false,
scalex: false,
scaley: false,
rotate: 0,
grey: false
}
#fileChange(ev) { #fileChange(ev) {
let file = ev.target.files[0] let file = ev.target.files[0]
ev.target.value = '' ev.target.value = ''
this.#initPanel(URL.createObjectURL(file)) this.#initPanel(file, true)
} }
#fetchUpload(file) { #fetchUpload(file) {
@ -463,7 +537,18 @@ class Uploader extends Component {
this.#cropData.y = y this.#cropData.y = y
elem.style.cssText = `left:${x}px;top:${y}px;width:${w}px;height:${h}px;` elem.style.cssText = `left:${x}px;top:${y}px;width:${w}px;height:${h}px;`
}) })
bind(DOC, 'mouseup', _ => unbind(DOC, 'mousemove', callback), EV_OPTION) bind(
DOC,
'mouseup',
_ => {
unbind(DOC, 'mousemove', callback)
this.#export().then(file => {
this.#cropData.size = file.size
this.$requestUpdate()
})
},
EV_OPTION
)
} }
#changeCropRatio(ev) { #changeCropRatio(ev) {
@ -493,34 +578,29 @@ class Uploader extends Component {
this.#cropData = { w: rw, h: rh, x, y } this.#cropData = { w: rw, h: rh, x, y }
this.$refs.crop.style.cssText = `left:${x}px;top:${y}px;width:${rw}px;height:${rh}px;` this.$refs.crop.style.cssText = `left:${x}px;top:${y}px;width:${rw}px;height:${rh}px;`
this.#export().then(file => {
this.#cropData.size = file.size
this.$requestUpdate()
})
} }
} }
#rotate() { async #rotate() {
let ctx = this.#ctx this.#cache.rotate(90)
let { x, y, w, h, rw, rh } = this.#origin let file = await this.#cache.export()
// ctx.save() this.#initPanel(file)
ctx.clearRect(0, 0, WORKSPACE_WIDTH, WORKSPACE_HEIGHT)
ctx.translate(WORKSPACE_WIDTH / 2, WORKSPACE_HEIGHT / 2)
ctx.rotate((90 * Math.PI) / 180)
ctx.drawImage(this.#img, x - w / 2, y - h / 2, rw, rh)
// ctx.restore()
console.log('<><><><>')
}
#scale() {}
#filter(type) {
if (type === 'blur') {
this.#stat.blur = !this.#stat.blur
let { x, y, rw, rh } = this.#origin
this.#ctx.filter = this.#stat.blur ? 'blur(6px)' : 'none'
// this.#ctx.clearRect(0, 0, WORKSPACE_WIDTH, WORKSPACE_HEIGHT)
this.#ctx.drawImage(this.#img, x, y, rw, rh)
}
} }
#initPanel(url) { async #scale(axis) {
this.#cache.scale(axis === 'x' ? -1 : 1, axis === 'y' ? -1 : 1)
let file = await this.#cache.export()
this.#initPanel(file)
}
#initPanel(file, force) {
let img = new Image() let img = new Image()
this.#img = img this.#img = img
@ -547,13 +627,18 @@ class Uploader extends Component {
} }
let x = (WORKSPACE_WIDTH - rw) / 2 let x = (WORKSPACE_WIDTH - rw) / 2
let y = (WORKSPACE_HEIGHT - rh) / 2 let y = (WORKSPACE_HEIGHT - rh) / 2
this.#origin = { x, y, w, h, rw, rh, scale }
if (force) {
this.#cache = new ImageTool(img, w, h)
}
this.#origin = { x, y, w, h, rw, rh, scale, size: file.size }
this.#ctx.drawImage(img, x, y, rw, rh) this.#ctx.drawImage(img, x, y, rw, rh)
this.#changeCropRatio({ target: { dataset: { ratio: 1 } } }) this.#changeCropRatio({ target: { dataset: { ratio: 1 } } })
this.#panelShow = true this.#panelShow = true
this.$requestUpdate() this.$requestUpdate()
} }
img.src = url img.src = URL.createObjectURL(file)
} }
mounted() { mounted() {
@ -561,12 +646,19 @@ class Uploader extends Component {
fetch(this.value) fetch(this.value)
.then(r => r.blob()) .then(r => r.blob())
.then(b => { .then(b => {
this.#initPanel(URL.createObjectURL(b)) this.#initPanel(b, true)
}) })
} }
render() { render() {
let { disabled } = this let { disabled } = this
let { w: ww, h: hh, scale, size } = this.#origin
let { w, h, size: s } = this.#cropData
w /= scale
h /= scale
w = ~~w
h = ~~h
return html` return html`
<main class="container"> <main class="container">
@ -596,7 +688,7 @@ class Uploader extends Component {
`} `}
<div class="edit-panel ${this.#panelShow ? 'show' : ''}"> <div class="edit-panel ${this.#panelShow ? 'show' : ''}">
<div class="workspace"> <div class="workspace alpha-bg">
<canvas ref="can" width="640" height="480"></canvas> <canvas ref="can" width="640" height="480"></canvas>
<div <div
ref="crop" ref="crop"
@ -620,19 +712,19 @@ class Uploader extends Component {
</div> </div>
<fieldset> <fieldset>
<legend>原尺寸</legend> <legend>原尺寸</legend>
<span>666x666</span> <span>${ww} x ${hh}</span>
</fieldset> </fieldset>
<fieldset> <fieldset>
<legend>原体积</legend> <legend>原体积</legend>
<span>2.33MB</span> <span>${parseSize(size)}</span>
</fieldset> </fieldset>
<fieldset> <fieldset>
<legend>现尺寸</legend> <legend class="blue">现尺寸</legend>
<span>666x666</span> <span>${w} x ${h}</span>
</fieldset> </fieldset>
<fieldset> <fieldset>
<legend>现体积</legend> <legend class="blue">现体积</legend>
<span>800KB</span> <span>${parseSize(s)}</span>
</fieldset> </fieldset>
<span class="button" @click=${this.#cancel} <span class="button" @click=${this.#cancel}
@ -648,18 +740,15 @@ class Uploader extends Component {
<footer class="toolbar"> <footer class="toolbar">
<span class="button" @click=${this.#rotate}>旋转90度</span> <span class="button" @click=${this.#rotate}>旋转90度</span>
<span class="button" @click=${this.#scale}>左右翻转</span> <span class="button" @click=${_ => this.#scale('x')}>左右翻转</span>
<span class="button" @click=${this.#scale}>上下翻转</span> <span class="button" @click=${_ => this.#scale('y')}>上下翻转</span>
<span class="button" @click=${_ => this.#filter('greyscale')}
>去色</span
>
<span class="button">增加水印</span> <span class="button">增加水印</span>
</footer> </footer>
</div> </div>
</main> </main>
<div class="preview" ref="view" @click.self=${this.#closePreview}> <div class="preview" ref="view" @click.self=${this.#closePreview}>
<img /> <img class="alpha-bg" />
</div> </div>
` `
} }