UNPKG

@alegendstale/holly-components

Version:

Reusable UI components created using lit

171 lines (168 loc) 6.13 kB
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; import { html, css } from 'lit'; import { property, query } from 'lit/decorators.js'; import quantize from 'quantize'; import { CanvasBase } from './canvas-base.js'; import { condCustomElement } from '../../decorators/condCustomElement.js'; let CanvasImage = class CanvasImage extends CanvasBase { constructor() { super(...arguments); this.imageURL = ''; this.smoothing = false; this.loading = true; this.width = 0; this.height = 0; /** * Waits for loading variable to equal true */ this.waitForLoading = () => new Promise((resolve) => { const checkLoading = setInterval(() => { if (!this.loading) { clearInterval(checkLoading); resolve(true); } }, 100); }); } firstUpdated(_changedProperties) { super.firstUpdated(_changedProperties); this.canvas.classList.add('image'); this.context.imageSmoothingEnabled = this.smoothing; } render() { // Allows CORS requests for images from different domains return html ` ${super.render()} <img src=${this.imageURL} crossorigin='anonymous' @load=${() => this.loading = false} > `; } /** * Updates & loads the canvas image. * Attempts to preserve aspect ratio based on width. * @param width The canvas width * @param height The canvas height */ updateImage(imageURL, width, height) { if (!this.image) return; // Set URL this.image.src = imageURL; // Wait for image to load before calculating dimensions & drawing image to canvas this.waitForLoading().then(() => { if (!this.image) return; // Calculate the new height based on the aspect ratio const aspectRatio = this.image.naturalHeight / this.image.naturalWidth; let newWidth = width; let newHeight = newWidth * aspectRatio; // Ensure the new height fits within the canvas height if (newHeight > height) { // Adjust width if height exceeds canvas height newWidth = height / aspectRatio; newHeight = height; } this.width = this.canvas.width = newWidth; this.height = this.canvas.height = newHeight; this.context.drawImage(this.image, 0, 0, newWidth, newHeight); }); } /** * Gets the most frequent colors in an image * @param numColors Number of colors to return * @param quality Artificially reduce number of pixels (higher = less accurate but faster) * @returns Most frequent colors */ async getPalette(numColors = 7, quality = 10) { /* Quantize has an issue with number of colors which has been addressed here https://github.com/olivierlesnicki/quantize/issues/9 `nColors` is a simple fix for this. */ const nColors = numColors <= 7 ? numColors : numColors + 1; // Wait for image to load await this.waitForLoading(); // Get an array of pixels const pixels = await this.createPixelArray(quality); if (!pixels) return null; // Reduce pixels array to a small number of the most common colors const colorMap = quantize(pixels, nColors); // Return palette return colorMap ? colorMap.palette() : []; } /** * Creates an array of pixels from the image * Inspired by colorthief * @param quality Artificially reduce number of pixels (higher = less accurate but faster) * @returns */ async createPixelArray(quality) { await this.waitForLoading(); const pixelArray = []; const imageData = (await this.getImageData())?.data; if (!imageData) return null; // Get number of pixels in image const pixelCount = this.height * this.width; for (let i = 0; i < pixelCount; i += quality) { // Offset to correct starting position of each pixel const offset = i * 4; const [r, g, b, a] = [imageData[offset + 0], imageData[offset + 1], imageData[offset + 2], imageData[offset + 3]]; // If pixel is mostly opaque and not white if (typeof a === 'undefined' || a >= 125) { if (!(r > 250 && g > 250 && b > 250)) { pixelArray.push([r, g, b]); } } } return pixelArray; } /** * Gets the image data from the canvas */ async getImageData(x = 0, y = 0) { try { await this.waitForLoading(); return this.context.getImageData(x, y, this.width, this.height); } catch (e) { throw new Error('Failed to get image data.'); } } }; CanvasImage.styles = [ ...CanvasBase.styles, css ` :host { display: block; } /* Hide when width hasn't been set */ canvas:not([width]) { display: none; } img { display: none; } `, ]; __decorate([ query('img') ], CanvasImage.prototype, "image", void 0); __decorate([ property({ type: String }) ], CanvasImage.prototype, "imageURL", void 0); __decorate([ property({ type: Boolean }) ], CanvasImage.prototype, "smoothing", void 0); CanvasImage = __decorate([ condCustomElement('canvas-image') ], CanvasImage); export { CanvasImage };