196 lines
5.5 KiB
JavaScript
196 lines
5.5 KiB
JavaScript
const fs = require("fs");
|
|
const util = require("util");
|
|
const path = require("path");
|
|
const { read } = require("fast-exif");
|
|
const iptc = require("node-iptc");
|
|
const Vibrant = require("node-vibrant");
|
|
const chroma = require("chroma-js");
|
|
const chalk = require("chalk");
|
|
const R = require("ramda");
|
|
|
|
const readFile = util.promisify(fs.readFile);
|
|
|
|
const badContrast = (color1, color2) => chroma.contrast(color1, color2) < 4.5;
|
|
|
|
const logColorsWithContrast = (color1, color2, text) => {
|
|
const c1hex = color1.hex();
|
|
const c2hex = color2.hex();
|
|
console.log(
|
|
chalk.hex(c1hex).bgHex(c2hex)(
|
|
`${text} ${c1hex}/${c2hex} ${chroma.contrast(color1, color2)}`
|
|
)
|
|
);
|
|
};
|
|
|
|
function processColors(vibrantData, imagePath) {
|
|
let Vibrant = chroma(vibrantData.Vibrant.getRgb());
|
|
let DarkVibrant = chroma(vibrantData.DarkVibrant.getRgb());
|
|
let LightVibrant = chroma(vibrantData.LightVibrant.getRgb());
|
|
let Muted = chroma(vibrantData.Muted.getRgb());
|
|
let DarkMuted = chroma(vibrantData.DarkMuted.getRgb());
|
|
let LightMuted = chroma(vibrantData.LightMuted.getRgb());
|
|
|
|
// first pass - darken bg and lighten relevant fg colors
|
|
if (
|
|
badContrast(DarkVibrant, Vibrant) ||
|
|
badContrast(DarkVibrant, LightMuted)
|
|
) {
|
|
DarkVibrant = DarkVibrant.darken();
|
|
if (badContrast(DarkVibrant, Vibrant)) {
|
|
Vibrant = Vibrant.brighten();
|
|
}
|
|
if (badContrast(DarkVibrant, Vibrant)) {
|
|
Vibrant = Vibrant.brighten();
|
|
}
|
|
}
|
|
// second pass - first doesn't always do enough
|
|
if (
|
|
badContrast(DarkVibrant, Vibrant) ||
|
|
badContrast(DarkVibrant, LightMuted)
|
|
) {
|
|
// DarkVibrant = DarkVibrant.darken();
|
|
if (badContrast(DarkVibrant, Vibrant)) {
|
|
Vibrant = Vibrant.brighten(2);
|
|
}
|
|
if (badContrast(DarkVibrant, LightMuted)) {
|
|
LightMuted = LightMuted.brighten(2);
|
|
}
|
|
}
|
|
|
|
if (badContrast(DarkVibrant, Vibrant)) {
|
|
console.warn("contrast still too low", imagePath);
|
|
logColorsWithContrast(Vibrant, DarkVibrant, "V-DV");
|
|
}
|
|
if (badContrast(DarkVibrant, LightMuted)) {
|
|
console.warn("contrast still too low", imagePath);
|
|
logColorsWithContrast(LightMuted, DarkVibrant, "LM-DV");
|
|
}
|
|
|
|
return {
|
|
Vibrant: Vibrant.rgb(),
|
|
DarkVibrant: DarkVibrant.rgb(),
|
|
LightVibrant: LightVibrant.rgb(),
|
|
Muted: Muted.rgb(),
|
|
DarkMuted: DarkMuted.rgb(),
|
|
LightMuted: LightMuted.rgb(),
|
|
};
|
|
}
|
|
|
|
function convertDMSToDD(dms, positiveDirection) {
|
|
const res = dms
|
|
.map((item, i) => {
|
|
return item / Math.pow(60, i);
|
|
})
|
|
.reduce((a, b) => a + b);
|
|
return positiveDirection ? res : -res;
|
|
}
|
|
|
|
function transformMetaToNodeData(exifData, iptcData, vibrantData, imagePath) {
|
|
const gps = { longitude: null, latitude: null };
|
|
|
|
if (exifData) {
|
|
if (exifData.gps && exifData.gps.GPSLongitude && exifData.gps.GPSLatitude) {
|
|
gps.longitude = convertDMSToDD(
|
|
exifData.gps.GPSLongitude,
|
|
exifData.gps.GPSLongitudeRef === "E"
|
|
);
|
|
gps.latitude = convertDMSToDD(
|
|
exifData.gps.GPSLatitude,
|
|
exifData.gps.GPSLatitudeRef === "N"
|
|
);
|
|
}
|
|
}
|
|
|
|
const vibrant = vibrantData ? processColors(vibrantData, imagePath) : null;
|
|
|
|
return {
|
|
exif: exifData?.exif,
|
|
gps,
|
|
dateTaken: exifData?.exif?.DateTimeOriginal,
|
|
iptc: iptcData || undefined,
|
|
vibrant,
|
|
};
|
|
}
|
|
|
|
exports.onCreateNode = async function ({ node, actions }) {
|
|
const { createNodeField } = actions;
|
|
if (node.internal.type === "File" && node.sourceInstanceName === "gallery") {
|
|
const file = await readFile(node.absolutePath);
|
|
const iptcData = iptc(file);
|
|
const exifData = await read(node.absolutePath);
|
|
const vibrantData = await Vibrant.from(node.absolutePath)
|
|
.quality(3)
|
|
.getPalette();
|
|
|
|
createNodeField({
|
|
node,
|
|
name: "imageMeta",
|
|
value: transformMetaToNodeData(
|
|
exifData,
|
|
iptcData,
|
|
vibrantData,
|
|
node.absolutePath
|
|
),
|
|
});
|
|
}
|
|
};
|
|
|
|
// Implement the Gatsby API “createPages”. This is called once the
|
|
// data layer is bootstrapped to let plugins create pages from data.
|
|
exports.createPages = async ({ graphql, actions, reporter }) => {
|
|
const { createPage } = actions;
|
|
// get all images
|
|
const galleryImages = await graphql(`
|
|
{
|
|
allFile(filter: { sourceInstanceName: { eq: "gallery" } }) {
|
|
edges {
|
|
node {
|
|
relativePath
|
|
base
|
|
fields {
|
|
imageMeta {
|
|
dateTaken
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
`);
|
|
// Handle errors
|
|
if (galleryImages.errors) {
|
|
reporter.panicOnBuild("Error while running GraphQL query.");
|
|
return;
|
|
}
|
|
// Create pages for each markdown file.
|
|
const galleryImageTemplate = path.resolve(
|
|
"src/components/GalleryImage/GalleryImage.js"
|
|
);
|
|
// const diffDate = (a, b) =>
|
|
// new Date(R.path(['node', 'childImageSharp', 'fields', 'imageMeta', 'dateTaken'], a)).getTime() - new Date(R.path(['node', 'childImageSharp', 'fields', 'imageMeta', 'dateTaken'],b)).getTime();
|
|
|
|
const edges = R.sort(
|
|
R.descend(
|
|
(edge) =>
|
|
new Date(R.path(["node", "fields", "imageMeta", "dateTaken"], edge))
|
|
),
|
|
galleryImages.data.allFile.edges
|
|
);
|
|
|
|
edges.forEach(({ node }, index) => {
|
|
const nextImage =
|
|
index === edges.length - 1 ? null : edges[index + 1].node.base;
|
|
const prevImage = index === 0 ? null : edges[index - 1].node.base;
|
|
const page = {
|
|
path: `photogallery/${node.base}`,
|
|
component: galleryImageTemplate,
|
|
context: {
|
|
imageFilename: node.base,
|
|
nextImage,
|
|
prevImage,
|
|
},
|
|
};
|
|
createPage(page);
|
|
});
|
|
};
|