@alegendstale/holly-components
Version:
Reusable UI components created using lit
171 lines (168 loc) • 6.13 kB
JavaScript
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'
=${() => 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 };