alinea
Version:
Headless git-based CMS
525 lines (522 loc) • 19.3 kB
JavaScript
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
};