UNPKG

@craftily/image

Version:

A lightweight image filter utility for canvas using TypeScript.

104 lines (102 loc) 7.8 kB
"use strict";var t=require("tslib"),e=require("lit"),a=require("lit/decorators.js");const o={brightness:100,contrast:100,saturate:100,grayscale:0,opacity:100,sepia:0,hueRotate:0,blur:0,color:void 0},l={brightness:{defaultValue:100,min:0,max:200},contrast:{defaultValue:100,min:0,max:200},saturate:{defaultValue:100,min:0,max:300},grayscale:{defaultValue:0,min:0,max:1},opacity:{defaultValue:100,min:0,max:100},sepia:{defaultValue:0,min:0,max:100},hueRotate:{defaultValue:0,min:0,max:360},blur:{defaultValue:0,min:0,max:10},color:{defaultValue:void 0}},i={png:"image/png",jpeg:"image/jpeg",jpg:"image/jpg",bmp:"image/bmp",webp:"image/webp"};const r=new class{constructor(){this.canvas=null}applyImageFilters(t,e,a,l=o){t&&e&&a&&(this.canvas=a,t.clearRect(0,0,a.width,a.height),t.filter=`\n brightness(${l.brightness}%) \n contrast(${l.contrast}%) \n grayscale(${l.grayscale}) \n saturate(${l.saturate}%) \n sepia(${l.sepia}) \n hue-rotate(${100*parseFloat(l.hueRotate)}deg)\n blur(${l.blur}px)\n opacity(${l.opacity}%) \n `.trim(),t.drawImage(e,0,0,a.width,a.height),l.color&&(t.globalCompositeOperation="source-atop",t.fillStyle=l.color,t.fillRect(0,0,a.width,a.height),t.globalCompositeOperation="source-over"))}toBlob(t="png",e=1){const a=i[t];if(!a)throw new Error(`Unsupported format: ${t}`);return new Promise(((t,o)=>{this.canvas.toBlob((e=>{e?t(e):o(new Error("Failed to create blob"))}),a,e)}))}toPng(t=1){return this.toBlob("png",t)}toJpeg(t=1){return this.toBlob("jpeg",t)}toJpg(t=1){return this.toBlob("jpg",t)}toBmp(t=1){return this.toBlob("bmp",t)}toWebp(t=1){return this.toBlob("webp",t)}toDataURL(t="png",e=1){const a=i[t];if(!a)throw new Error(`Unsupported format: ${t}`);return this.canvas.toDataURL(a,e)}downloadImage(t="png",e=1){const a=this.toDataURL(t,e),o=document.createElement("a");o.href=a,o.download="image",o.click()}download(t="png",e=1){this.downloadImage(t,e)}};var s=e.css` :host { display: block; font-family: sans-serif; max-width: 100%; justify-self: center; } canvas { max-width: 100%; display: block; margin-bottom: 1rem; outline: 1px solid; justify-self: center; } .toolbar { display: inline-flex; gap: 1rem; padding: 1rem; flex-direction: column; width: 100%; } .control { display: grid; grid-template-columns: 110px 1fr 50px; } input[type='color'] { margin-left: auto; margin-right: auto; width: 100%; } .preview { margin-top: 1rem; } .preview img { max-width: 100%; display: block; } .upload { margin-bottom: 1rem; } .actions { display: grid; grid-template-columns: 1fr 20px 50px; gap: 1rem; justify-content: end; justify-items: start; } .actions.hide-download { grid-template-columns: 1fr 50px; } .actions button { background: none; border: none; cursor: pointer; font-size: 1.5rem; display: inline-block; line-height: 1; } .actions button:hover { opacity: 0.8; } .actions .icon { display: inline-block; line-height: 1; } .actions .divider { background: #dddddd; width: 100%; } `;class n extends e.LitElement{constructor(){super(...arguments),this.src="",this.controls=void 0,this.defaultState=l,this.format="png",this.mimeTypes=i}connectedCallback(){super.connectedCallback(),this.defaultState=Object.entries(l).reduce(((t,[e,a])=>(t[e]={defaultValue:this.controls?.[e]?.value||a.defaultValue,min:a?.min,max:a?.max},t)),{})}render(){return e.html` ${this.showUpload?e.html`<div class="upload" part="upload"> <label>Select Image:</label> <input type="file" part="file-input" accept="image/*" @change=${this.onFileChange} /> </div>`:""} <canvas part="canvas"></canvas> <img src=${this.src} crossorigin="anonymous" @load=${this.draw} style="display:none;" /> <div class="toolbar" part="toolbar"> ${Object.entries(this.controls||{}).map((([t,e])=>this.inputControl(t,e)))} </div> `}inputControl(t,a){if(this.src)return"color"===t?e.html`<div class="control" part="control"> <label>${a.label}</label> <input name=${t} type="color" part="color-picker" value="${a.value||this.defaultState[t].defaultValue}" @input=${e=>this.updateValue(t,e.target.value)} /> </div>`:e.html`<div class="control" part="control"> <label>${a.label}</label> <input name=${t} type="range" part="input-range" min="${this.defaultState[t].min}" max="${this.defaultState[t].max}" step="0.1" value="${a.value||this.defaultState[t].defaultValue}" @input=${e=>this.updateValue(t,e.target.value)} /> </div>`}updateValue(t,e){this.controls[t].value=e,this.draw(),this.onControlChange(`${t}Changed`)}draw(){const t=this.canvas.getContext("2d"),e=this.imgEl;if(!t||!e)return;const a=this.width||e.naturalWidth,o=this.height||e.naturalHeight;this.canvas.width=a,this.canvas.height=o,r.applyImageFilters(t,e,this.canvas,{brightness:this.controls?.brightness?.value||this.defaultState.brightness.defaultValue,contrast:this.controls?.contrast?.value||this.defaultState.contrast.defaultValue,saturate:this.controls?.saturate?.value||this.defaultState.saturate.defaultValue,grayscale:this.controls?.grayscale?.value||this.defaultState.grayscale.defaultValue,opacity:this.controls?.opacity?.value||this.defaultState.opacity.defaultValue,sepia:this.controls?.sepia?.value||this.defaultState.sepia.defaultValue,hueRotate:this.controls?.hueRotate?.value||this.defaultState.hueRotate.defaultValue,blur:this.controls?.blur?.value||this.defaultState.blur.defaultValue,color:this.controls?.color?.value||this.defaultState.color.defaultValue})}download(t="png",e=1){r.download(t,e)}onFileChange(t){const e=t.target,a=e.files?.[0];a&&(this.src=URL.createObjectURL(a))}reset(){this.controls=Object.entries(this.controls||{}).reduce(((t,[e,a])=>{const o=this.renderRoot.querySelector(`input[name="${e}"]`);return o&&a.value!==this.defaultState[e].defaultValue&&(o.value=this.defaultState[e]?.defaultValue),t[e]={...a,value:this.defaultState[e].defaultValue},t}),{}),this.draw(),this.onControlChange("image-reset")}onControlChange(t){const e=new CustomEvent("onControlChange",{detail:{toDataURL:(t="png",e=1)=>r.toDataURL(t,e),toBlob:(t="png",e=1)=>r.toBlob(t,e),download:(t="png",e=1)=>r.download(t,e),metadata:{canvas:this.canvas,controls:this.controls,eventType:t}},bubbles:!0,composed:!0});this.dispatchEvent(e)}}n.styles=s,t.__decorate([a.property({type:String}),t.__metadata("design:type",Object)],n.prototype,"src",void 0),t.__decorate([a.property({type:Object}),t.__metadata("design:type",Object)],n.prototype,"controls",void 0),t.__decorate([a.property({type:Number}),t.__metadata("design:type",Number)],n.prototype,"width",void 0),t.__decorate([a.property({type:Number}),t.__metadata("design:type",Number)],n.prototype,"height",void 0),t.__decorate([a.property({type:Boolean}),t.__metadata("design:type",Boolean)],n.prototype,"showUpload",void 0),t.__decorate([a.state(),t.__metadata("design:type",Object)],n.prototype,"defaultState",void 0),t.__decorate([a.state(),t.__metadata("design:type",String)],n.prototype,"format",void 0),t.__decorate([a.query("canvas"),t.__metadata("design:type",HTMLCanvasElement)],n.prototype,"canvas",void 0),t.__decorate([a.query("img"),t.__metadata("design:type",HTMLImageElement)],n.prototype,"imgEl",void 0),t.__decorate([a.state(),t.__metadata("design:type",Object)],n.prototype,"mimeTypes",void 0),customElements.get("craftily-image-editor")||customElements.define("craftily-image-editor",n); //# sourceMappingURL=ImageEditor.cjs.map