Skip to content

Commit 1f61131

Browse files
author
Sigit Prabowo
committed
add: crop options, remove yarn lock and remove mime_type
1 parent c848b62 commit 1f61131

File tree

5 files changed

+203
-2588
lines changed

5 files changed

+203
-2588
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ node_modules
22
test/img/
33
sample/img/
44
sample/.cache/
5-
package-lock.json
5+
package-lock.json
6+
yarn.lock

README.md

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,9 @@ Defaults values are shown:
3838
// Optional: use falsy value to fall back to native image size
3939
widths: [null],
4040

41-
// Array of heights
42-
// Optional: use falsy value to fall back to native image size
43-
heights: [null],
44-
45-
// to crop image use array pair from widths and heights
46-
// widths: [1600, 160, 80],
47-
// heights: [900, 90, 45]
41+
// Array of crops
42+
// Optional: use falsy value to skip cropping
43+
crops: null, // ["1600x900", "160x90"] or [ { width: 1600, height: 900 }, { width: 160, height: 90 } ]
4844

4945
// Pass any format supported by sharp
5046
formats: ["webp", "jpeg"], //"png"

img.js

Lines changed: 121 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ const CacheAsset = require("@11ty/eleventy-cache-assets");
1515
const globalOptions = {
1616
src: null,
1717
widths: [null],
18-
heights: [null],
19-
formats: ["webp", "jpeg"], //"png"
18+
crops: null,
19+
formats: ["webp", "jpeg"], // "png"
2020
concurrency: 10,
2121
urlPath: "/img/",
2222
outputDir: "img/",
@@ -25,7 +25,6 @@ const globalOptions = {
2525
};
2626

2727
const MIME_TYPES = {
28-
"jpg": "image/jpeg",
2928
"jpeg": "image/jpeg",
3029
"webp": "image/webp",
3130
"png": "image/png"
@@ -109,62 +108,113 @@ async function resizeImage(src, options = {}) {
109108
let formats = getFormatsArray(options.formats);
110109
for(let format of formats) {
111110
let hasAtLeastOneValidMaxWidth = false;
112-
for(let widthKey in options.widths) {
113-
let width = options.widths[widthKey];
114-
let hasWidth = !!width;
115-
// heights length should be same length with widths
116-
if (options.heights && options.heights[0] != null && options.heights.length != options.widths.length) {
117-
throw new Error("if `heights` is set. it should has same with length of width.");
118-
}
119-
let height = options.heights[widthKey];
120-
// Set format
121-
let imageFormat = sharpImage.clone();
122-
if(metadata.format !== format) {
123-
imageFormat.toFormat(format);
111+
if (options.crops) {
112+
let crops = _normalizeCrop(options.crops);
113+
if (crops.length == 0) {
114+
throw new Error(`Expected options.crops should be ["1600x900", "160x90"] or ${JSON.stringify([{ width: 1600, height: 900 }, { width: 160, height: 90 }], null, 2)}`);
124115
}
116+
for (let [width, height] of crops) {
117+
let hasWidth = !!width;
118+
// Set format
119+
let imageFormat = sharpImage.clone();
120+
if(metadata.format !== format) {
121+
imageFormat.toFormat(format);
122+
}
123+
124+
if(width > metadata.width || height > metadata.height) {
125+
continue;
126+
}
127+
128+
imageFormat.resize({
129+
width: width,
130+
height: height,
131+
withoutEnlargement: true
132+
});
133+
134+
let outputFilename = getFilename(src, width + 'x' + height, format);
135+
let outputPath = path.join(options.outputDir, outputFilename);
136+
outputFilePromises.push(imageFormat.toFile(outputPath).then(data => {
137+
let stats = getStats(src, format, options.urlPath, data.width, data.height, hasWidth);
138+
stats.outputPath = outputPath;
139+
stats.size = data.size;
140+
141+
return stats;
142+
}));
125143

126-
// skip this width because it’s larger than the original and we already
127-
// have at least one output image size that works
128-
if(hasAtLeastOneValidMaxWidth && (!width || width > metadata.width)) {
129-
continue;
144+
debug( "Writing %o", outputPath );
130145
}
146+
} else {
147+
for(let width of options.widths) {
148+
let hasWidth = !!width;
149+
// Set format
150+
let imageFormat = sharpImage.clone();
151+
if(metadata.format !== format) {
152+
imageFormat.toFormat(format);
153+
}
154+
155+
// skip this width because it’s larger than the original and we already
156+
// have at least one output image size that works
157+
if(hasAtLeastOneValidMaxWidth && (!width || width > metadata.width)) {
158+
continue;
159+
}
131160

132-
// Resize the image
133-
if(!width) {
134-
hasAtLeastOneValidMaxWidth = true;
135-
} else {
136-
if(width >= metadata.width) {
137-
// don’t reassign width if it’s falsy
138-
width = null;
139-
hasWidth = false;
161+
// Resize the image
162+
if(!width) {
140163
hasAtLeastOneValidMaxWidth = true;
141164
} else {
142-
imageFormat.resize({
143-
width: width,
144-
height: height,
145-
withoutEnlargement: true
146-
});
165+
if(width >= metadata.width) {
166+
// don’t reassign width if it’s falsy
167+
width = null;
168+
hasWidth = false;
169+
hasAtLeastOneValidMaxWidth = true;
170+
} else {
171+
imageFormat.resize({
172+
width: width,
173+
withoutEnlargement: true
174+
});
175+
}
147176
}
148-
}
149177

150178

151-
let outputFilename = getFilename(src, width, format);
152-
let outputPath = path.join(options.outputDir, outputFilename);
153-
outputFilePromises.push(imageFormat.toFile(outputPath).then(data => {
154-
let stats = getStats(src, format, options.urlPath, data.width, data.height, hasWidth);
155-
stats.outputPath = outputPath;
156-
stats.size = data.size;
179+
let outputFilename = getFilename(src, width, format);
180+
let outputPath = path.join(options.outputDir, outputFilename);
181+
outputFilePromises.push(imageFormat.toFile(outputPath).then(data => {
182+
let stats = getStats(src, format, options.urlPath, data.width, data.height, hasWidth);
183+
stats.outputPath = outputPath;
184+
stats.size = data.size;
157185

158-
return stats;
159-
}));
186+
return stats;
187+
}));
160188

161-
debug( "Writing %o", outputPath );
189+
debug( "Writing %o", outputPath );
190+
}
162191
}
163192
}
164193

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

197+
function _normalizeCrop(options) {
198+
if (options == null) return null;
199+
let filteredOptions = options.map(function(config) {
200+
if (typeof(config) == 'string') {
201+
let val = config.split('x')
202+
if (config.split('x').length != 2) {
203+
return false;
204+
}
205+
return [parseInt(val[0]), parseInt(val[1])]
206+
} else if (typeof(config) == 'object') {
207+
let width = config.hasOwnProperty('width');
208+
let height = config.hasOwnProperty('height');
209+
if (width && height) {
210+
return [parseInt(config.width), parseInt(config.height)];
211+
}
212+
return false;
213+
}
214+
})
215+
return filteredOptions.filter(Boolean);
216+
}
217+
168218
function isFullUrl(url) {
169219
try {
170220
new URL(url);
@@ -240,34 +290,39 @@ function _statsSync(src, originalWidth, originalHeight, opts) {
240290

241291
for(let format of formats) {
242292
let hasAtLeastOneValidMaxWidth = false;
243-
for(let widthKey in options.widths) {
244-
let width = options.widths[widthKey];
245-
let hasWidth = !!width;
246-
// heights length should be same length with widths
247-
if (options.heights && options.heights[0] != null && options.heights.length != options.widths.length) {
248-
throw new Error("if `heights` is set. it should has same with length of width.");
249-
}
250-
let height;
251-
252-
if(hasAtLeastOneValidMaxWidth && (!width || width > originalWidth)) {
253-
continue;
293+
if (options.crops) {
294+
let crops = _normalizeCrop(options.crops);
295+
for (let [width, height] of crops) {
296+
let hasWidth = !!width
297+
if(width > originalWidth || height > originalWidth) {
298+
continue;
299+
}
300+
results.push(getStats(src, format, options.urlPath, width, height, hasWidth));
254301
}
255-
256-
if(!width) {
257-
width = originalWidth;
258-
height = originalHeight;
259-
hasAtLeastOneValidMaxWidth = true;
260-
} else {
261-
if(width >= originalWidth) {
302+
} else {
303+
for(let width of options.widths) {
304+
let hasWidth = !!width;
305+
let height;
306+
307+
if(hasAtLeastOneValidMaxWidth && (!width || width > originalWidth)) {
308+
continue;
309+
}
310+
311+
if(!width) {
262312
width = originalWidth;
263-
hasWidth = false;
313+
height = originalHeight;
264314
hasAtLeastOneValidMaxWidth = true;
315+
} else {
316+
if(width >= originalWidth) {
317+
width = originalWidth;
318+
hasWidth = false;
319+
hasAtLeastOneValidMaxWidth = true;
320+
}
321+
height = Math.floor(width * originalHeight / originalWidth);
265322
}
266-
height = options.heights[widthKey] || Math.floor(width * originalHeight / originalWidth);
323+
324+
results.push(getStats(src, format, options.urlPath, width, height, hasWidth));
267325
}
268-
269-
270-
results.push(getStats(src, format, options.urlPath, width, height, hasWidth));
271326
}
272327
}
273328

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

286341
module.exports.statsSync = statsSync;
287-
module.exports.statsByDimensionsSync = statsByDimensionsSync;
342+
module.exports.statsByDimensionsSync = statsByDimensionsSync;

test/test.js

Lines changed: 77 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -167,49 +167,96 @@ test("Use exact same width as original (statsSync)", t => {
167167
t.is(stats.jpeg[0].width, 1280);
168168
});
169169

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

181-
test("Try to crop with widths and heights options", async t => {
179+
test("Use crop feature case 2 (ignore image larger than original)", async t => {
182180
let stats = await eleventyImage("./test/bio-2017.jpg", {
183-
widths: [225, 100],
184-
heights: [400, 200],
181+
crops: ["1600x900", "160x90"],
185182
formats: ["jpeg"],
186183
outputDir: "./test/img/"
187184
});
188-
t.is(stats.jpeg.length, 2);
189-
t.is(stats.jpeg[0].outputPath, "test/img/97854483-100.jpeg");
190-
t.is(stats.jpeg[0].width, 100);
191-
t.is(stats.jpeg[0].height, 200);
192-
t.is(stats.jpeg[1].outputPath, "test/img/97854483-225.jpeg");
193-
t.is(stats.jpeg[1].width, 225);
194-
t.is(stats.jpeg[1].height, 400);
195-
});
196-
197-
test("Try to crop with widths and heights are not balance scenario 1", async t => {
198-
let stats = await t.throwsAsync(() => eleventyImage("./test/bio-2017.jpg", {
199-
widths: [225, 100],
200-
heights: [400],
185+
t.is(stats.jpeg.length, 1);
186+
t.is(stats.jpeg[0].outputPath, "test/img/97854483-160x90.jpeg"); // no width in filename
187+
t.is(stats.jpeg[0].width, 160);
188+
t.is(stats.jpeg[0].height, 90);
189+
});
190+
191+
test("Use crop feature case 3 (ignore image larger than original)", async t => {
192+
let stats = await eleventyImage("./test/bio-2017.jpg", {
193+
crops: [{
194+
width: 1600,
195+
height: 900
196+
}, {
197+
width: 160,
198+
height: 90
199+
}],
201200
formats: ["jpeg"],
202201
outputDir: "./test/img/"
203-
}));
204-
t.is(stats.message, 'if `heights` is set. it should has same with length of width.');
202+
});
203+
t.is(stats.jpeg.length, 1);
204+
t.is(stats.jpeg[0].outputPath, "test/img/97854483-160x90.jpeg"); // no width in filename
205+
t.is(stats.jpeg[0].width, 160);
206+
t.is(stats.jpeg[0].height, 90);
205207
});
206208

207-
test("Try to crop with widths and heights are not balance scenario 2", async t => {
208-
let stats = await t.throwsAsync(() => eleventyImage("./test/bio-2017.jpg", {
209-
widths: [225, 100],
210-
heights: [400, 20, 30],
209+
test("Use crop feature case 4", async t => {
210+
let stats = await eleventyImage("./test/bio-2017.jpg", {
211+
crops: [{
212+
width: 800,
213+
height: 600
214+
}, {
215+
width: 160,
216+
height: 90
217+
}],
211218
formats: ["jpeg"],
212219
outputDir: "./test/img/"
213-
}));
214-
t.is(stats.message, 'if `heights` is set. it should has same with length of width.');
220+
});
221+
t.is(stats.jpeg.length, 2);
222+
t.is(stats.jpeg[0].outputPath, "test/img/97854483-160x90.jpeg"); // no width in filename
223+
t.is(stats.jpeg[0].width, 160);
224+
t.is(stats.jpeg[0].height, 90);
225+
t.is(stats.jpeg[1].outputPath, "test/img/97854483-800x600.jpeg"); // no width in filename
226+
t.is(stats.jpeg[1].width, 800);
227+
t.is(stats.jpeg[1].height, 600);
228+
});
229+
230+
test("Sync with crop feature case 1", t => {
231+
let stats = eleventyImage.statsSync("./test/bio-2017.jpg", {
232+
crops: [{
233+
width: 800,
234+
height: 600
235+
}, {
236+
width: 160,
237+
height: 90
238+
}]
239+
});
240+
t.is(stats.webp.length, 2);
241+
t.is(stats.webp[0].width, 160);
242+
t.is(stats.webp[0].height, 90);
243+
t.is(stats.webp[1].width, 800);
244+
t.is(stats.webp[1].height, 600);
245+
t.is(stats.jpeg.length, 2);
246+
t.is(stats.jpeg[0].width, 160);
247+
t.is(stats.jpeg[0].height, 90);
248+
t.is(stats.jpeg[1].width, 800);
249+
t.is(stats.jpeg[1].height, 600);
250+
});
251+
252+
test("Sync with crop feature case 2 (ignore image larger than original)", t => {
253+
let stats = eleventyImage.statsSync("./test/bio-2017.jpg", {
254+
crops: ["1600x900", "160x90"]
255+
});
256+
t.is(stats.webp.length, 1);
257+
t.is(stats.webp[0].width, 160);
258+
t.is(stats.webp[0].height, 90);
259+
t.is(stats.jpeg.length, 1);
260+
t.is(stats.jpeg[0].width, 160);
261+
t.is(stats.jpeg[0].height, 90);
215262
});

0 commit comments

Comments
 (0)