UNPKG

alinea

Version:
525 lines (522 loc) 19.3 kB
import { rgbaToThumbHash, thumbHashToAverageRGBA } from "../../chunks/chunk-GWZU2AWQ.js"; import { I, m } from "../../chunks/chunk-76QF5YTJ.js"; import { __commonJS, __toESM } from "../../chunks/chunk-NZLE2WMY.js"; // node_modules/smartcrop/smartcrop.js var require_smartcrop = __commonJS({ "node_modules/smartcrop/smartcrop.js"(exports, module) { (function() { "use strict"; var smartcrop2 = {}; function NoPromises() { throw new Error("No native promises and smartcrop.Promise not set."); } smartcrop2.Promise = typeof Promise !== "undefined" ? Promise : NoPromises; smartcrop2.DEFAULTS = { width: 0, height: 0, aspect: 0, cropWidth: 0, cropHeight: 0, detailWeight: 0.2, skinColor: [0.78, 0.57, 0.44], skinBias: 0.01, skinBrightnessMin: 0.2, skinBrightnessMax: 1, skinThreshold: 0.8, skinWeight: 1.8, saturationBrightnessMin: 0.05, saturationBrightnessMax: 0.9, saturationThreshold: 0.4, saturationBias: 0.2, saturationWeight: 0.1, // Step * minscale rounded down to the next power of two should be good scoreDownSample: 8, step: 8, scaleStep: 0.1, minScale: 1, maxScale: 1, edgeRadius: 0.4, edgeWeight: -20, outsideImportance: -0.5, boostWeight: 100, ruleOfThirds: true, prescale: true, imageOperations: null, canvasFactory: defaultCanvasFactory, // Factory: defaultFactories, debug: false }; smartcrop2.crop = function(inputImage, options_, callback) { var options = extend({}, smartcrop2.DEFAULTS, options_); if (options.aspect) { options.width = options.aspect; options.height = 1; } if (options.imageOperations === null) { options.imageOperations = canvasImageOperations(options.canvasFactory); } var iop = options.imageOperations; var scale = 1; var prescale = 1; return iop.open(inputImage, options.input).then(function(image) { if (options.width && options.height) { scale = min( image.width / options.width, image.height / options.height ); options.cropWidth = ~~(options.width * scale); options.cropHeight = ~~(options.height * scale); options.minScale = min( options.maxScale, max(1 / scale, options.minScale) ); if (options.prescale !== false) { prescale = min(max(256 / image.width, 256 / image.height), 1); if (prescale < 1) { image = iop.resample( image, image.width * prescale, image.height * prescale ); options.cropWidth = ~~(options.cropWidth * prescale); options.cropHeight = ~~(options.cropHeight * prescale); if (options.boost) { options.boost = options.boost.map(function(boost) { return { x: ~~(boost.x * prescale), y: ~~(boost.y * prescale), width: ~~(boost.width * prescale), height: ~~(boost.height * prescale), weight: boost.weight }; }); } } else { prescale = 1; } } } return image; }).then(function(image) { return iop.getData(image).then(function(data) { var result = analyse(options, data); var crops = result.crops || [result.topCrop]; for (var i = 0, iLen = crops.length; i < iLen; i++) { var crop = crops[i]; crop.x = ~~(crop.x / prescale); crop.y = ~~(crop.y / prescale); crop.width = ~~(crop.width / prescale); crop.height = ~~(crop.height / prescale); } if (callback) callback(result); return result; }); }); }; smartcrop2.isAvailable = function(options) { if (!smartcrop2.Promise) return false; var canvasFactory = options ? options.canvasFactory : defaultCanvasFactory; if (canvasFactory === defaultCanvasFactory) { var c = document.createElement("canvas"); if (!c.getContext("2d")) { return false; } } return true; }; function edgeDetect(i, o) { var id = i.data; var od = o.data; var w = i.width; var h = i.height; for (var y = 0; y < h; y++) { for (var x = 0; x < w; x++) { var p = (y * w + x) * 4; var lightness; if (x === 0 || x >= w - 1 || y === 0 || y >= h - 1) { lightness = sample(id, p); } else { lightness = sample(id, p) * 4 - sample(id, p - w * 4) - sample(id, p - 4) - sample(id, p + 4) - sample(id, p + w * 4); } od[p + 1] = lightness; } } } function skinDetect(options, i, o) { var id = i.data; var od = o.data; var w = i.width; var h = i.height; for (var y = 0; y < h; y++) { for (var x = 0; x < w; x++) { var p = (y * w + x) * 4; var lightness = cie(id[p], id[p + 1], id[p + 2]) / 255; var skin = skinColor(options, id[p], id[p + 1], id[p + 2]); var isSkinColor = skin > options.skinThreshold; var isSkinBrightness = lightness >= options.skinBrightnessMin && lightness <= options.skinBrightnessMax; if (isSkinColor && isSkinBrightness) { od[p] = (skin - options.skinThreshold) * (255 / (1 - options.skinThreshold)); } else { od[p] = 0; } } } } function saturationDetect(options, i, o) { var id = i.data; var od = o.data; var w = i.width; var h = i.height; for (var y = 0; y < h; y++) { for (var x = 0; x < w; x++) { var p = (y * w + x) * 4; var lightness = cie(id[p], id[p + 1], id[p + 2]) / 255; var sat = saturation(id[p], id[p + 1], id[p + 2]); var acceptableSaturation = sat > options.saturationThreshold; var acceptableLightness = lightness >= options.saturationBrightnessMin && lightness <= options.saturationBrightnessMax; if (acceptableLightness && acceptableSaturation) { od[p + 2] = (sat - options.saturationThreshold) * (255 / (1 - options.saturationThreshold)); } else { od[p + 2] = 0; } } } } function applyBoosts(options, output) { if (!options.boost) return; var od = output.data; for (var i = 0; i < output.width; i += 4) { od[i + 3] = 0; } for (i = 0; i < options.boost.length; i++) { applyBoost(options.boost[i], options, output); } } function applyBoost(boost, options, output) { var od = output.data; var w = output.width; var x0 = ~~boost.x; var x1 = ~~(boost.x + boost.width); var y0 = ~~boost.y; var y1 = ~~(boost.y + boost.height); var weight = boost.weight * 255; for (var y = y0; y < y1; y++) { for (var x = x0; x < x1; x++) { var i = (y * w + x) * 4; od[i + 3] += weight; } } } function generateCrops(options, width, height) { var results = []; var minDimension = min(width, height); var cropWidth = options.cropWidth || minDimension; var cropHeight = options.cropHeight || minDimension; for (var scale = options.maxScale; scale >= options.minScale; scale -= options.scaleStep) { for (var y = 0; y + cropHeight * scale <= height; y += options.step) { for (var x = 0; x + cropWidth * scale <= width; x += options.step) { results.push({ x, y, width: cropWidth * scale, height: cropHeight * scale }); } } } return results; } function score(options, output, crop) { var result = { detail: 0, saturation: 0, skin: 0, boost: 0, total: 0 }; var od = output.data; var downSample2 = options.scoreDownSample; var invDownSample = 1 / downSample2; var outputHeightDownSample = output.height * downSample2; var outputWidthDownSample = output.width * downSample2; var outputWidth = output.width; for (var y = 0; y < outputHeightDownSample; y += downSample2) { for (var x = 0; x < outputWidthDownSample; x += downSample2) { var p = (~~(y * invDownSample) * outputWidth + ~~(x * invDownSample)) * 4; var i = importance(options, crop, x, y); var detail = od[p + 1] / 255; result.skin += od[p] / 255 * (detail + options.skinBias) * i; result.detail += detail * i; result.saturation += od[p + 2] / 255 * (detail + options.saturationBias) * i; result.boost += od[p + 3] / 255 * i; } } result.total = (result.detail * options.detailWeight + result.skin * options.skinWeight + result.saturation * options.saturationWeight + result.boost * options.boostWeight) / (crop.width * crop.height); return result; } function importance(options, crop, x, y) { if (crop.x > x || x >= crop.x + crop.width || crop.y > y || y >= crop.y + crop.height) { return options.outsideImportance; } x = (x - crop.x) / crop.width; y = (y - crop.y) / crop.height; var px = abs(0.5 - x) * 2; var py = abs(0.5 - y) * 2; var dx = Math.max(px - 1 + options.edgeRadius, 0); var dy = Math.max(py - 1 + options.edgeRadius, 0); var d = (dx * dx + dy * dy) * options.edgeWeight; var s = 1.41 - sqrt(px * px + py * py); if (options.ruleOfThirds) { s += Math.max(0, s + d + 0.5) * 1.2 * (thirds(px) + thirds(py)); } return s + d; } smartcrop2.importance = importance; function skinColor(options, r, g, b) { var mag = sqrt(r * r + g * g + b * b); var rd = r / mag - options.skinColor[0]; var gd = g / mag - options.skinColor[1]; var bd = b / mag - options.skinColor[2]; var d = sqrt(rd * rd + gd * gd + bd * bd); return 1 - d; } function analyse(options, input) { var result = {}; var output = new ImgData(input.width, input.height); edgeDetect(input, output); skinDetect(options, input, output); saturationDetect(options, input, output); applyBoosts(options, output); var scoreOutput = downSample(output, options.scoreDownSample); var topScore = -Infinity; var topCrop = null; var crops = generateCrops(options, input.width, input.height); for (var i = 0, iLen = crops.length; i < iLen; i++) { var crop = crops[i]; crop.score = score(options, scoreOutput, crop); if (crop.score.total > topScore) { topCrop = crop; topScore = crop.score.total; } } result.topCrop = topCrop; if (options.debug && topCrop) { result.crops = crops; result.debugOutput = output; result.debugOptions = options; result.debugTopCrop = extend({}, result.topCrop); } return result; } function ImgData(width, height, data) { this.width = width; this.height = height; if (data) { this.data = new Uint8ClampedArray(data); } else { this.data = new Uint8ClampedArray(width * height * 4); } } smartcrop2.ImgData = ImgData; function downSample(input, factor) { var idata = input.data; var iwidth = input.width; var width = Math.floor(input.width / factor); var height = Math.floor(input.height / factor); var output = new ImgData(width, height); var data = output.data; var ifactor2 = 1 / (factor * factor); for (var y = 0; y < height; y++) { for (var x = 0; x < width; x++) { var i = (y * width + x) * 4; var r = 0; var g = 0; var b = 0; var a = 0; var mr = 0; var mg = 0; for (var v = 0; v < factor; v++) { for (var u = 0; u < factor; u++) { var j = ((y * factor + v) * iwidth + (x * factor + u)) * 4; r += idata[j]; g += idata[j + 1]; b += idata[j + 2]; a += idata[j + 3]; mr = Math.max(mr, idata[j]); mg = Math.max(mg, idata[j + 1]); } } data[i] = r * ifactor2 * 0.5 + mr * 0.5; data[i + 1] = g * ifactor2 * 0.7 + mg * 0.3; data[i + 2] = b * ifactor2; data[i + 3] = a * ifactor2; } } return output; } smartcrop2._downSample = downSample; function defaultCanvasFactory(w, h) { var c = document.createElement("canvas"); c.width = w; c.height = h; return c; } function canvasImageOperations(canvasFactory) { return { // Takes imageInput as argument // returns an object which has at least // {width: n, height: n} open: function(image) { var w = image.naturalWidth || image.width; var h = image.naturalHeight || image.height; var c = canvasFactory(w, h); var ctx = c.getContext("2d"); if (image.naturalWidth && (image.naturalWidth != image.width || image.naturalHeight != image.height)) { c.width = image.naturalWidth; c.height = image.naturalHeight; } else { c.width = image.width; c.height = image.height; } ctx.drawImage(image, 0, 0); return smartcrop2.Promise.resolve(c); }, // Takes an image (as returned by open), and changes it's size by resampling resample: function(image, width, height) { return Promise.resolve(image).then(function(image2) { var c = canvasFactory(~~width, ~~height); var ctx = c.getContext("2d"); ctx.drawImage( image2, 0, 0, image2.width, image2.height, 0, 0, c.width, c.height ); return smartcrop2.Promise.resolve(c); }); }, getData: function(image) { return Promise.resolve(image).then(function(c) { var ctx = c.getContext("2d"); var id = ctx.getImageData(0, 0, c.width, c.height); return new ImgData(c.width, c.height, id.data); }); } }; } smartcrop2._canvasImageOperations = canvasImageOperations; var min = Math.min; var max = Math.max; var abs = Math.abs; var sqrt = Math.sqrt; function extend(o) { for (var i = 1, iLen = arguments.length; i < iLen; i++) { var arg = arguments[i]; if (arg) { for (var name in arg) { o[name] = arg[name]; } } } return o; } function thirds(x) { x = ((x - 1 / 3 + 1) % 2 * 0.5 - 0.5) * 16; return Math.max(1 - x * x, 0); } function cie(r, g, b) { return 0.5126 * b + 0.7152 * g + 0.0722 * r; } function sample(id, p) { return cie(id[p], id[p + 1], id[p + 2]); } function saturation(r, g, b) { var maximum = max(r / 255, g / 255, b / 255); var minimum = min(r / 255, g / 255, b / 255); if (maximum === minimum) { return 0; } var l = (maximum + minimum) / 2; var d = maximum - minimum; return l > 0.5 ? d / (2 - maximum - minimum) : d / (maximum + minimum); } if (typeof define !== "undefined" && define.amd) define(function() { return smartcrop2; }); if (typeof exports !== "undefined") exports.smartcrop = smartcrop2; else if (typeof navigator !== "undefined") window.SmartCrop = window.smartcrop = smartcrop2; if (typeof module !== "undefined") { module.exports = smartcrop2; } })(); } }); // src/core/media/CreatePreview.browser.ts var import_smartcrop = __toESM(require_smartcrop(), 1); import { base64 } from "../util/Encoding.js"; async function createPreview(blob) { const url = URL.createObjectURL(blob); const image = await new Promise((resolve, reject) => { const image2 = new Image(); image2.onload = () => resolve(image2); image2.onerror = (err) => reject(err); image2.src = url; }).finally(() => URL.revokeObjectURL(url)); const size = Math.max(image.width, image.height); const thumbW = Math.round(100 * image.width / size); const thumbH = Math.round(100 * image.height / size); const thumbCanvas = document.createElement("canvas"); const thumbContext = thumbCanvas.getContext("2d"); thumbCanvas.width = thumbW; thumbCanvas.height = thumbH; thumbContext.drawImage(image, 0, 0, thumbW, thumbH); const pixels = thumbContext.getImageData(0, 0, thumbW, thumbH); const thumbHash = rgbaToThumbHash(thumbW, thumbH, pixels.data); const { r, g, b } = thumbHashToAverageRGBA(thumbHash); const averageColor = I(m(r * 255, g * 255, b * 255, 1)); const previewW = Math.min(Math.round(160 * image.width / size), image.width); const previewH = Math.min( Math.round(160 * image.height / size), image.height ); const previewCanvas = document.createElement("canvas"); const previewContext = previewCanvas.getContext("2d"); previewContext.imageSmoothingEnabled = true; previewContext.imageSmoothingQuality = "high"; previewCanvas.width = previewW; previewCanvas.height = previewH; previewContext.drawImage(image, 0, 0, previewW, previewH); const preview = previewCanvas.toDataURL("image/webp"); const crop = await import_smartcrop.default.crop(image, { width: 100, height: 100 }); const focus = { x: (crop.topCrop.x + crop.topCrop.width / 2) / image.width, y: (crop.topCrop.y + crop.topCrop.height / 2) / image.height }; return { preview, averageColor, focus, thumbHash: base64.stringify(thumbHash), width: image.naturalWidth, height: image.naturalHeight }; } export { createPreview };