Skip to content

Commit

Permalink
add: crop options, remove yarn lock and remove mime_type
Browse files Browse the repository at this point in the history
  • Loading branch information
Sigit Prabowo committed Jun 3, 2020
1 parent c848b62 commit 1f61131
Show file tree
Hide file tree
Showing 5 changed files with 203 additions and 2,588 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ node_modules
test/img/
sample/img/
sample/.cache/
package-lock.json
package-lock.json
yarn.lock
10 changes: 3 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,9 @@ Defaults values are shown:
// Optional: use falsy value to fall back to native image size
widths: [null],

// Array of heights
// Optional: use falsy value to fall back to native image size
heights: [null],

// to crop image use array pair from widths and heights
// widths: [1600, 160, 80],
// heights: [900, 90, 45]
// Array of crops
// Optional: use falsy value to skip cropping
crops: null, // ["1600x900", "160x90"] or [ { width: 1600, height: 900 }, { width: 160, height: 90 } ]

// Pass any format supported by sharp
formats: ["webp", "jpeg"], //"png"
Expand Down
187 changes: 121 additions & 66 deletions img.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ const CacheAsset = require("@11ty/eleventy-cache-assets");
const globalOptions = {
src: null,
widths: [null],
heights: [null],
formats: ["webp", "jpeg"], //"png"
crops: null,
formats: ["webp", "jpeg"], // "png"
concurrency: 10,
urlPath: "/img/",
outputDir: "img/",
Expand All @@ -25,7 +25,6 @@ const globalOptions = {
};

const MIME_TYPES = {
"jpg": "image/jpeg",
"jpeg": "image/jpeg",
"webp": "image/webp",
"png": "image/png"
Expand Down Expand Up @@ -109,62 +108,113 @@ async function resizeImage(src, options = {}) {
let formats = getFormatsArray(options.formats);
for(let format of formats) {
let hasAtLeastOneValidMaxWidth = false;
for(let widthKey in options.widths) {
let width = options.widths[widthKey];
let hasWidth = !!width;
// heights length should be same length with widths
if (options.heights && options.heights[0] != null && options.heights.length != options.widths.length) {
throw new Error("if `heights` is set. it should has same with length of width.");
}
let height = options.heights[widthKey];
// Set format
let imageFormat = sharpImage.clone();
if(metadata.format !== format) {
imageFormat.toFormat(format);
if (options.crops) {
let crops = _normalizeCrop(options.crops);
if (crops.length == 0) {
throw new Error(`Expected options.crops should be ["1600x900", "160x90"] or ${JSON.stringify([{ width: 1600, height: 900 }, { width: 160, height: 90 }], null, 2)}`);
}
for (let [width, height] of crops) {
let hasWidth = !!width;
// Set format
let imageFormat = sharpImage.clone();
if(metadata.format !== format) {
imageFormat.toFormat(format);
}

if(width > metadata.width || height > metadata.height) {
continue;
}

imageFormat.resize({
width: width,
height: height,
withoutEnlargement: true
});

let outputFilename = getFilename(src, width + 'x' + height, format);
let outputPath = path.join(options.outputDir, outputFilename);
outputFilePromises.push(imageFormat.toFile(outputPath).then(data => {
let stats = getStats(src, format, options.urlPath, data.width, data.height, hasWidth);
stats.outputPath = outputPath;
stats.size = data.size;

return stats;
}));

// skip this width because it’s larger than the original and we already
// have at least one output image size that works
if(hasAtLeastOneValidMaxWidth && (!width || width > metadata.width)) {
continue;
debug( "Writing %o", outputPath );
}
} else {
for(let width of options.widths) {
let hasWidth = !!width;
// Set format
let imageFormat = sharpImage.clone();
if(metadata.format !== format) {
imageFormat.toFormat(format);
}

// skip this width because it’s larger than the original and we already
// have at least one output image size that works
if(hasAtLeastOneValidMaxWidth && (!width || width > metadata.width)) {
continue;
}

// Resize the image
if(!width) {
hasAtLeastOneValidMaxWidth = true;
} else {
if(width >= metadata.width) {
// don’t reassign width if it’s falsy
width = null;
hasWidth = false;
// Resize the image
if(!width) {
hasAtLeastOneValidMaxWidth = true;
} else {
imageFormat.resize({
width: width,
height: height,
withoutEnlargement: true
});
if(width >= metadata.width) {
// don’t reassign width if it’s falsy
width = null;
hasWidth = false;
hasAtLeastOneValidMaxWidth = true;
} else {
imageFormat.resize({
width: width,
withoutEnlargement: true
});
}
}
}


let outputFilename = getFilename(src, width, format);
let outputPath = path.join(options.outputDir, outputFilename);
outputFilePromises.push(imageFormat.toFile(outputPath).then(data => {
let stats = getStats(src, format, options.urlPath, data.width, data.height, hasWidth);
stats.outputPath = outputPath;
stats.size = data.size;
let outputFilename = getFilename(src, width, format);
let outputPath = path.join(options.outputDir, outputFilename);
outputFilePromises.push(imageFormat.toFile(outputPath).then(data => {
let stats = getStats(src, format, options.urlPath, data.width, data.height, hasWidth);
stats.outputPath = outputPath;
stats.size = data.size;

return stats;
}));
return stats;
}));

debug( "Writing %o", outputPath );
debug( "Writing %o", outputPath );
}
}
}

return Promise.all(outputFilePromises).then(files => transformRawFiles(files));
}

function _normalizeCrop(options) {
if (options == null) return null;
let filteredOptions = options.map(function(config) {
if (typeof(config) == 'string') {
let val = config.split('x')
if (config.split('x').length != 2) {
return false;
}
return [parseInt(val[0]), parseInt(val[1])]
} else if (typeof(config) == 'object') {
let width = config.hasOwnProperty('width');
let height = config.hasOwnProperty('height');
if (width && height) {
return [parseInt(config.width), parseInt(config.height)];
}
return false;
}
})
return filteredOptions.filter(Boolean);
}

function isFullUrl(url) {
try {
new URL(url);
Expand Down Expand Up @@ -240,34 +290,39 @@ function _statsSync(src, originalWidth, originalHeight, opts) {

for(let format of formats) {
let hasAtLeastOneValidMaxWidth = false;
for(let widthKey in options.widths) {
let width = options.widths[widthKey];
let hasWidth = !!width;
// heights length should be same length with widths
if (options.heights && options.heights[0] != null && options.heights.length != options.widths.length) {
throw new Error("if `heights` is set. it should has same with length of width.");
}
let height;

if(hasAtLeastOneValidMaxWidth && (!width || width > originalWidth)) {
continue;
if (options.crops) {
let crops = _normalizeCrop(options.crops);
for (let [width, height] of crops) {
let hasWidth = !!width
if(width > originalWidth || height > originalWidth) {
continue;
}
results.push(getStats(src, format, options.urlPath, width, height, hasWidth));
}

if(!width) {
width = originalWidth;
height = originalHeight;
hasAtLeastOneValidMaxWidth = true;
} else {
if(width >= originalWidth) {
} else {
for(let width of options.widths) {
let hasWidth = !!width;
let height;

if(hasAtLeastOneValidMaxWidth && (!width || width > originalWidth)) {
continue;
}

if(!width) {
width = originalWidth;
hasWidth = false;
height = originalHeight;
hasAtLeastOneValidMaxWidth = true;
} else {
if(width >= originalWidth) {
width = originalWidth;
hasWidth = false;
hasAtLeastOneValidMaxWidth = true;
}
height = Math.floor(width * originalHeight / originalWidth);
}
height = options.heights[widthKey] || Math.floor(width * originalHeight / originalWidth);

results.push(getStats(src, format, options.urlPath, width, height, hasWidth));
}


results.push(getStats(src, format, options.urlPath, width, height, hasWidth));
}
}

Expand All @@ -284,4 +339,4 @@ function statsByDimensionsSync(src, width, height, opts) {
}

module.exports.statsSync = statsSync;
module.exports.statsByDimensionsSync = statsByDimensionsSync;
module.exports.statsByDimensionsSync = statsByDimensionsSync;
107 changes: 77 additions & 30 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -167,49 +167,96 @@ test("Use exact same width as original (statsSync)", t => {
t.is(stats.jpeg[0].width, 1280);
});

test("Try to add mime type jpg", async t => {
test("Use crop feature case 1", async t => {
let stats = await eleventyImage("./test/bio-2017.jpg", {
widths: [225, 100],
formats: ["jpg"],
crops: ["160x90"],
formats: ["jpeg"],
outputDir: "./test/img/"
});
t.is(stats.jpg.length, 2);
t.is(stats.jpg[0].outputPath, "test/img/97854483-100.jpg");
t.is(stats.jpg[1].outputPath, "test/img/97854483-225.jpg");
t.is(stats.jpeg.length, 1);
});

test("Try to crop with widths and heights options", async t => {
test("Use crop feature case 2 (ignore image larger than original)", async t => {
let stats = await eleventyImage("./test/bio-2017.jpg", {
widths: [225, 100],
heights: [400, 200],
crops: ["1600x900", "160x90"],
formats: ["jpeg"],
outputDir: "./test/img/"
});
t.is(stats.jpeg.length, 2);
t.is(stats.jpeg[0].outputPath, "test/img/97854483-100.jpeg");
t.is(stats.jpeg[0].width, 100);
t.is(stats.jpeg[0].height, 200);
t.is(stats.jpeg[1].outputPath, "test/img/97854483-225.jpeg");
t.is(stats.jpeg[1].width, 225);
t.is(stats.jpeg[1].height, 400);
});

test("Try to crop with widths and heights are not balance scenario 1", async t => {
let stats = await t.throwsAsync(() => eleventyImage("./test/bio-2017.jpg", {
widths: [225, 100],
heights: [400],
t.is(stats.jpeg.length, 1);
t.is(stats.jpeg[0].outputPath, "test/img/97854483-160x90.jpeg"); // no width in filename
t.is(stats.jpeg[0].width, 160);
t.is(stats.jpeg[0].height, 90);
});

test("Use crop feature case 3 (ignore image larger than original)", async t => {
let stats = await eleventyImage("./test/bio-2017.jpg", {
crops: [{
width: 1600,
height: 900
}, {
width: 160,
height: 90
}],
formats: ["jpeg"],
outputDir: "./test/img/"
}));
t.is(stats.message, 'if `heights` is set. it should has same with length of width.');
});
t.is(stats.jpeg.length, 1);
t.is(stats.jpeg[0].outputPath, "test/img/97854483-160x90.jpeg"); // no width in filename
t.is(stats.jpeg[0].width, 160);
t.is(stats.jpeg[0].height, 90);
});

test("Try to crop with widths and heights are not balance scenario 2", async t => {
let stats = await t.throwsAsync(() => eleventyImage("./test/bio-2017.jpg", {
widths: [225, 100],
heights: [400, 20, 30],
test("Use crop feature case 4", async t => {
let stats = await eleventyImage("./test/bio-2017.jpg", {
crops: [{
width: 800,
height: 600
}, {
width: 160,
height: 90
}],
formats: ["jpeg"],
outputDir: "./test/img/"
}));
t.is(stats.message, 'if `heights` is set. it should has same with length of width.');
});
t.is(stats.jpeg.length, 2);
t.is(stats.jpeg[0].outputPath, "test/img/97854483-160x90.jpeg"); // no width in filename
t.is(stats.jpeg[0].width, 160);
t.is(stats.jpeg[0].height, 90);
t.is(stats.jpeg[1].outputPath, "test/img/97854483-800x600.jpeg"); // no width in filename
t.is(stats.jpeg[1].width, 800);
t.is(stats.jpeg[1].height, 600);
});

test("Sync with crop feature case 1", t => {
let stats = eleventyImage.statsSync("./test/bio-2017.jpg", {
crops: [{
width: 800,
height: 600
}, {
width: 160,
height: 90
}]
});
t.is(stats.webp.length, 2);
t.is(stats.webp[0].width, 160);
t.is(stats.webp[0].height, 90);
t.is(stats.webp[1].width, 800);
t.is(stats.webp[1].height, 600);
t.is(stats.jpeg.length, 2);
t.is(stats.jpeg[0].width, 160);
t.is(stats.jpeg[0].height, 90);
t.is(stats.jpeg[1].width, 800);
t.is(stats.jpeg[1].height, 600);
});

test("Sync with crop feature case 2 (ignore image larger than original)", t => {
let stats = eleventyImage.statsSync("./test/bio-2017.jpg", {
crops: ["1600x900", "160x90"]
});
t.is(stats.webp.length, 1);
t.is(stats.webp[0].width, 160);
t.is(stats.webp[0].height, 90);
t.is(stats.jpeg.length, 1);
t.is(stats.jpeg[0].width, 160);
t.is(stats.jpeg[0].height, 90);
});
Loading

0 comments on commit 1f61131

Please sign in to comment.