@craftily/image
Version:
A lightweight image filter utility for canvas using TypeScript.
1 lines • 18.9 kB
Source Map (JSON)
{"version":3,"file":"ImageEditor.cjs","sources":["../src/constants/index.ts","../src/utils/ImageUtils.ts","../src/components/ImageEditorStyles.ts","../src/components/ImageEditor.ts"],"sourcesContent":["import { DefaultControlProps, FilterOptions, MimeTypes } from '../types';\n\nexport const DEFAULT_CONTROL_VALUES: FilterOptions = {\n brightness: 100,\n contrast: 100,\n saturate: 100,\n grayscale: 0,\n opacity: 100,\n sepia: 0,\n hueRotate: 0,\n blur: 0,\n color: undefined,\n};\nexport const DEFAULT_CONTROL_OPTIONS: DefaultControlProps = {\n brightness: { defaultValue: 100, min: 0, max: 200 },\n contrast: { defaultValue: 100, min: 0, max: 200 },\n saturate: { defaultValue: 100, min: 0, max: 300 },\n grayscale: { defaultValue: 0, min: 0, max: 1 },\n opacity: { defaultValue: 100, min: 0, max: 100 },\n sepia: { defaultValue: 0, min: 0, max: 100 },\n hueRotate: { defaultValue: 0, min: 0, max: 360 },\n blur: { defaultValue: 0, min: 0, max: 10 },\n color: { defaultValue: undefined },\n};\nexport const MIME_TYPES: MimeTypes = {\n png: 'image/png',\n jpeg: 'image/jpeg',\n jpg: 'image/jpg',\n bmp: 'image/bmp',\n webp: 'image/webp',\n};\n\nexport const EVENT_TYPE_CONTROL_CHANGE = 'onControlChange';\nexport const EVENT_TYPE_RESET = 'image-reset';\n","import { DEFAULT_CONTROL_VALUES, MIME_TYPES } from '../constants';\nimport { FilterOptions, SupportedFileFormat } from '../types';\n\nclass ImageUtils {\n canvas: HTMLCanvasElement | null = null;\n applyImageFilters(\n ctx: CanvasRenderingContext2D,\n image: HTMLImageElement,\n canvas: HTMLCanvasElement,\n options: FilterOptions = DEFAULT_CONTROL_VALUES\n ): void {\n if (!ctx || !image || !canvas) return;\n\n this.canvas = canvas;\n // Clear canvas\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n\n // Apply filters and draw image\n ctx.filter = `\n brightness(${options.brightness}%) \n contrast(${options.contrast}%) \n grayscale(${options.grayscale}) \n saturate(${options.saturate}%) \n sepia(${options.sepia}) \n hue-rotate(${parseFloat(options.hueRotate as string) * 100}deg)\n blur(${options.blur}px)\n opacity(${options.opacity}%) \n `.trim();\n\n ctx.drawImage(image, 0, 0, canvas.width, canvas.height);\n\n // Apply color tint\n if (options.color) {\n ctx.globalCompositeOperation = 'source-atop';\n ctx.fillStyle = options.color as string;\n ctx.fillRect(0, 0, canvas.width, canvas.height);\n ctx.globalCompositeOperation = 'source-over';\n }\n }\n\n toBlob(type: SupportedFileFormat = 'png', quality = 1.0): Promise<Blob> {\n const format = MIME_TYPES[type];\n if (!format) {\n throw new Error(`Unsupported format: ${type}`);\n }\n return new Promise((resolve, reject) => {\n this.canvas.toBlob(\n (blob) => {\n if (!blob) {\n reject(new Error('Failed to create blob'));\n return;\n }\n resolve(blob);\n },\n format,\n quality\n );\n });\n }\n\n toPng(quality = 1.0): Promise<Blob> {\n return this.toBlob('png', quality);\n }\n\n toJpeg(quality = 1.0): Promise<Blob> {\n return this.toBlob('jpeg', quality);\n }\n\n toJpg(quality = 1.0): Promise<Blob> {\n return this.toBlob('jpg', quality);\n }\n\n toBmp(quality = 1.0): Promise<Blob> {\n return this.toBlob('bmp', quality);\n }\n\n toWebp(quality = 1.0): Promise<Blob> {\n return this.toBlob('webp', quality);\n }\n\n toDataURL(type: SupportedFileFormat = 'png', quality = 1.0): string {\n const format = MIME_TYPES[type];\n if (!format) {\n throw new Error(`Unsupported format: ${type}`);\n }\n return this.canvas.toDataURL(format, quality);\n }\n\n downloadImage(format: SupportedFileFormat = 'png', quality = 1.0) {\n const dataUrl = this.toDataURL(format, quality);\n const a = document.createElement('a');\n a.href = dataUrl;\n a.download = 'image';\n a.click();\n }\n\n download(format: SupportedFileFormat = 'png', quality = 1.0) {\n this.downloadImage(format, quality);\n }\n}\nconst imageUtils = new ImageUtils();\nexport { imageUtils };\n","import { css } from 'lit';\n\nexport default css`\n :host {\n display: block;\n font-family: sans-serif;\n max-width: 100%;\n justify-self: center;\n }\n canvas {\n max-width: 100%;\n display: block;\n margin-bottom: 1rem;\n outline: 1px solid;\n justify-self: center;\n }\n .toolbar {\n display: inline-flex;\n gap: 1rem;\n padding: 1rem;\n flex-direction: column;\n width: 100%;\n }\n .control {\n display: grid;\n grid-template-columns: 110px 1fr 50px;\n }\n input[type='color'] {\n margin-left: auto;\n margin-right: auto;\n width: 100%;\n }\n .preview {\n margin-top: 1rem;\n }\n .preview img {\n max-width: 100%;\n display: block;\n }\n .upload {\n margin-bottom: 1rem;\n }\n .actions {\n display: grid;\n grid-template-columns: 1fr 20px 50px;\n gap: 1rem;\n justify-content: end;\n justify-items: start;\n }\n .actions.hide-download {\n grid-template-columns: 1fr 50px;\n }\n .actions button {\n background: none;\n border: none;\n cursor: pointer;\n font-size: 1.5rem;\n display: inline-block;\n line-height: 1;\n }\n .actions button:hover {\n opacity: 0.8;\n }\n .actions .icon {\n display: inline-block;\n line-height: 1;\n }\n .actions .divider {\n background: #dddddd;\n width: 100%;\n }\n`;\n","import { LitElement, html } from 'lit';\nimport { property, state, query } from 'lit/decorators.js';\nimport { imageUtils } from '../utils';\nimport {\n Property,\n SupportedFileFormat,\n MimeTypes,\n InputControlProps,\n DefaultControlProps,\n ImageEditorEventDetail,\n} from '../types';\nimport {\n DEFAULT_CONTROL_OPTIONS,\n EVENT_TYPE_CONTROL_CHANGE,\n EVENT_TYPE_RESET,\n MIME_TYPES,\n} from '../constants';\nimport editorStyles from './ImageEditorStyles';\n\nexport class ImageEditor extends LitElement {\n static styles = editorStyles;\n\n @property({ type: String }) src = '';\n\n // Toggle which controls to display\n @property({ type: Object }) controls: Record<Property, InputControlProps> = undefined;\n\n /**\n * Dimensions (optional)\n */\n @property({ type: Number }) width?: number;\n @property({ type: Number }) height?: number;\n @property({ type: Boolean }) showUpload?: boolean;\n\n @state() defaultState: DefaultControlProps = DEFAULT_CONTROL_OPTIONS;\n\n @state() format: SupportedFileFormat = 'png';\n\n @query('canvas') canvas!: HTMLCanvasElement;\n @query('img') imgEl!: HTMLImageElement;\n\n @state() mimeTypes: MimeTypes = MIME_TYPES;\n\n connectedCallback() {\n super.connectedCallback();\n this.defaultState = Object.entries(DEFAULT_CONTROL_OPTIONS).reduce((acc, [key, control]) => {\n acc[key] = {\n defaultValue: this.controls?.[key]?.value || control.defaultValue,\n min: control?.min,\n max: control?.max,\n };\n return acc;\n }, {} as DefaultControlProps);\n }\n\n render() {\n return html`\n ${this.showUpload\n ? html`<div class=\"upload\" part=\"upload\">\n <label>Select Image:</label>\n <input type=\"file\" part=\"file-input\" accept=\"image/*\" @change=${this.onFileChange} />\n </div>`\n : ''}\n <canvas part=\"canvas\"></canvas>\n <img src=${this.src} crossorigin=\"anonymous\" @load=${this.draw} style=\"display:none;\" />\n\n <div class=\"toolbar\" part=\"toolbar\">\n ${Object.entries(this.controls || {}).map(([key, control]) =>\n this.inputControl(key, control)\n )}\n </div>\n `;\n }\n\n private inputControl(key: string, control: InputControlProps) {\n if (!this.src) return;\n\n if (key === 'color') {\n return html`<div class=\"control\" part=\"control\">\n <label>${control.label}</label>\n <input\n name=${key}\n type=\"color\"\n part=\"color-picker\"\n value=\"${control.value || this.defaultState[key].defaultValue}\"\n @input=${(e: Event) => this.updateValue(key, (e.target as HTMLInputElement).value)}\n />\n </div>`;\n }\n return html`<div class=\"control\" part=\"control\">\n <label>${control.label}</label>\n <input\n name=${key}\n type=\"range\"\n part=\"input-range\"\n min=\"${this.defaultState[key].min}\"\n max=\"${this.defaultState[key].max}\"\n step=\"0.1\"\n value=\"${control.value || this.defaultState[key].defaultValue}\"\n @input=${(e: Event) => this.updateValue(key, (e.target as HTMLInputElement).value)}\n />\n </div>`;\n }\n\n private updateValue(key: string, value: number | string) {\n this.controls[key].value = value;\n this.draw();\n this.onControlChange(`${key}Changed`);\n }\n\n private draw() {\n const ctx = this.canvas.getContext('2d');\n const img = this.imgEl;\n if (!ctx || !img) return;\n\n const width = this.width || img.naturalWidth;\n const height = this.height || img.naturalHeight;\n this.canvas.width = width;\n this.canvas.height = height;\n\n imageUtils.applyImageFilters(ctx, img, this.canvas, {\n brightness: this.controls?.brightness?.value || this.defaultState.brightness.defaultValue,\n contrast: this.controls?.contrast?.value || this.defaultState.contrast.defaultValue,\n saturate: this.controls?.saturate?.value || this.defaultState.saturate.defaultValue,\n grayscale: this.controls?.grayscale?.value || this.defaultState.grayscale.defaultValue,\n opacity: this.controls?.opacity?.value || this.defaultState.opacity.defaultValue,\n sepia: this.controls?.sepia?.value || this.defaultState.sepia.defaultValue,\n hueRotate: this.controls?.hueRotate?.value || this.defaultState.hueRotate.defaultValue,\n blur: this.controls?.blur?.value || this.defaultState.blur.defaultValue,\n color: this.controls?.color?.value || this.defaultState.color.defaultValue,\n });\n }\n\n public download(format: SupportedFileFormat = 'png', quality = 1.0) {\n imageUtils.download(format, quality);\n }\n private onFileChange(e: Event) {\n const input = e.target as HTMLInputElement;\n const file = input.files?.[0];\n if (file) {\n this.src = URL.createObjectURL(file);\n }\n }\n\n /**\n * Reset all filters to default values and redraw image\n */\n public reset() {\n this.controls = Object.entries(this.controls || {}).reduce(\n (acc, [key, control]) => {\n const input: HTMLInputElement = this.renderRoot.querySelector(`input[name=\"${key}\"]`);\n if (input && control.value !== this.defaultState[key].defaultValue) {\n input.value = this.defaultState[key]?.defaultValue;\n }\n acc[key] = {\n ...control,\n value: this.defaultState[key].defaultValue,\n };\n return acc;\n },\n {} as Record<Property, InputControlProps>\n );\n this.draw();\n this.onControlChange(EVENT_TYPE_RESET);\n }\n\n private onControlChange(type: string) {\n const event = new CustomEvent<ImageEditorEventDetail>(EVENT_TYPE_CONTROL_CHANGE, {\n detail: {\n toDataURL: (type: SupportedFileFormat = 'png', quality = 1.0) =>\n imageUtils.toDataURL(type, quality),\n toBlob: (type: SupportedFileFormat = 'png', quality = 1.0) =>\n imageUtils.toBlob(type, quality),\n download: (type: SupportedFileFormat = 'png', quality = 1.0) =>\n imageUtils.download(type, quality),\n metadata: {\n canvas: this.canvas,\n controls: this.controls,\n eventType: type,\n },\n },\n bubbles: true,\n composed: true,\n });\n this.dispatchEvent(event);\n }\n}\n\nif (!customElements.get('craftily-image-editor')) {\n customElements.define('craftily-image-editor', ImageEditor);\n}\n"],"names":["DEFAULT_CONTROL_VALUES","brightness","contrast","saturate","grayscale","opacity","sepia","hueRotate","blur","color","undefined","DEFAULT_CONTROL_OPTIONS","defaultValue","min","max","MIME_TYPES","png","jpeg","jpg","bmp","webp","imageUtils","constructor","this","canvas","applyImageFilters","ctx","image","options","clearRect","width","height","filter","parseFloat","trim","drawImage","globalCompositeOperation","fillStyle","fillRect","toBlob","type","quality","format","Error","Promise","resolve","reject","blob","toPng","toJpeg","toJpg","toBmp","toWebp","toDataURL","downloadImage","dataUrl","a","document","createElement","href","download","click","editorStyles","css","ImageEditor","LitElement","src","controls","defaultState","mimeTypes","connectedCallback","super","Object","entries","reduce","acc","key","control","value","render","html","showUpload","onFileChange","draw","map","inputControl","label","e","updateValue","target","onControlChange","getContext","img","imgEl","naturalWidth","naturalHeight","input","file","files","URL","createObjectURL","reset","renderRoot","querySelector","event","CustomEvent","detail","metadata","eventType","bubbles","composed","dispatchEvent","styles","__decorate","property","String","prototype","Number","Boolean","state","query","HTMLCanvasElement","HTMLImageElement","customElements","get","define"],"mappings":"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"}