189 lines
5.5 KiB
JavaScript
189 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);
|
|
});
|
|
};
|