-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.mjs
93 lines (70 loc) · 2.82 KB
/
index.mjs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#!/usr/bin/env node
import sharp from "sharp";
import fs from "fs";
import path from "path";
import exif from "exif-reader";
const outputExposure = 1 / Number(/^(?:1\/)?(.*)$/.exec(process.argv[2])[1]);
if (!Number.isFinite(outputExposure)) {
process.stdout.write(await fs.promises.readFile(new URL("help.txt", import.meta.url)));
process.exit(1);
}
console.log(`Output Exposure: ${Math.round(outputExposure * 1000) / 1000} s (1/${1 / outputExposure} s)`);
console.log();
const dirents = await fs.promises.readdir(process.cwd(), { withFileTypes: true });
console.log("Processing...");
const result = {};
for (const dirent of dirents) {
if (!dirent.isFile()) continue;
if (!/.(?:jpe?g|png|webp|tiff?|avif)$/i.test(dirent.name)) continue;
const file = path.join(process.cwd(), dirent.name);
const image = sharp(file, { failOnError: false }).removeAlpha().raw({ depth: "float" });
const metadata = await image.metadata();
let exposureTime;
const labelMatch = /\be_*(\d+)(?:_+(\d+))?\b/i.exec(dirent.name);
if (labelMatch) {
const [, dividend, divisor = 1] = labelMatch;
exposureTime = dividend / divisor;
}
if (!exposureTime && metadata.exif) {
exposureTime = Number(exif(metadata.exif).exif["ExposureTime"]);
}
if (!exposureTime) {
console.log(`Skipping (${file}): Missing or invalid exposure time label.`);
continue;
}
const { data, info } = await image.toBuffer({ resolveWithObject: true });
const bpp = info.size / info.width / info.height / info.channels;
result.metadata ??= metadata;
result.pixels ??= Array(data.length / bpp).fill(0);
result.info ??= info;
result.ref ??= file;
if (info.width != result.info.width || info.height != result.info.height) {
console.log(`Skipping (${file}): Width and height not matching (${result.ref}).`);
continue;
}
for (let i = 0; i < info.size / bpp; i++) {
let v = data.readFloatLE(i * bpp) / 0xff;
v = v <= 0.01 ? 0 : (v - 0.01) / 0.99;
v = v <= 0.04045 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4 / exposureTime;
result.pixels[i] = Math.max(result.pixels[i], v);
}
console.log(" + " + dirent.name);
}
console.log();
console.log("Saving...");
if (result.pixels) {
const data = new Float32Array(result.pixels.length);
for (let i = 0; i < result.pixels.length; i++) {
data[i] = result.pixels[i] * outputExposure;
}
const out = sharp(data, {
raw: {
channels: 3,
width: result.info.width,
height: result.info.height,
},
}).withMetadata({ orientation: result.metadata.orientation });
const fileOut = path.join(process.cwd(), "out.tiff");
await out.tiff({ compression: "deflate", predictor: "float" }).toFile(fileOut);
console.log(">> out.tiff");
}