Skip to content

Instantly share code, notes, and snippets.

@ivan-kleshnin
Last active November 13, 2015 13:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ivan-kleshnin/e84ec367fc1b77c778fe to your computer and use it in GitHub Desktop.
Save ivan-kleshnin/e84ec367fc1b77c778fe to your computer and use it in GitHub Desktop.
Lwip basic wrapper demo
/*
This wrapper
1. Reduces boilerplate. Lwip functions like .resize() are very low-level. See the code to get what I mean.
2. Adds sharpen to downsampling.
3. Has promise-based API
4. Operates on file buffers instead of image buffers to make operations immutable.
Use Lwip directly if you experience bottleneck and need to batch operations for performance.
Use this wrapper (it's forks) in other cases.
*/
import BlueBird from "bluebird";
import LwipCb from "lwip";
import FsCb from "fs";
let Fs = BlueBird.promisifyAll(FsCb);
let Lwip = BlueBird.promisifyAll(LwipCb);
// (Buffer -> String) -> Object
function ImageMaker(buffer, format) {
// Calculate resolution by width (get width <= maxWidth, keeping proportions)
function calculateDimensionsByWidth(maxWidth, actualDimensions) {
var actualWidth = actualDimensions[0];
var actualHeight = actualDimensions[1];
if (actualWidth > maxWidth) {
var scale = actualWidth / maxWidth;
return [maxWidth, Math.round(actualHeight / scale)];
} else {
return actualDimensions;
}
}
// Calculate resolution by height (get height <= maxHeight, keeping proportions)
function calculateDimensionsByHeight(maxHeight, actualDimensions) {
var actualWidth = actualDimensions[0];
var actualHeight = actualDimensions[1];
if (actualHeight > maxHeight) {
var scale = actualHeight / maxHeight;
return [Math.round(actualWidth / scale), maxHeight];
} else {
return actualDimensions;
}
}
// Calculate resolution by width and height (get width <= maxWidth, height <= maxHeight, keeping proportions)
function calculateDimensions(maxDimensions, actualDimensions) {
if (actualDimensions[0] >= actualDimensions[1]) {
// width >= height
if (actualDimensions[0] > maxDimensions[0]) {
return calculateDimensionsByWidth(maxDimensions[0], actualDimensions);
} else {
return actualDimensions;
}
} else {
// height > width
if (actualDimensions[1] > maxDimensions[1]) {
return calculateDimensionsByHeight(maxDimensions[1], actualDimensions);
} else {
return actualDimensions;
}
}
}
// (Integer -> Integer -> String) -> Promise<Buffer>
function resize(maxWidth, maxHeight, interpolation="lanczos") {
return new Promise(function (resolve, reject) {
Lwip
.openAsync(buffer, format)
.then(image => {
let [width, height] = calculateDimensions([maxWidth, maxHeight], [image.width(), image.height()]);
image
.resize(
width, height,
interpolation, // one of: "nearest-neighbor", "moving-average", "linear", "grid", "cubic", "lanczos"
function (err, image) {
if (err) return reject(err);
else {
return image.batch()
.sharpen(25) // TODO how to get the best approximate value?
.toBuffer(format, {}, function (err, buffer) {
if (err) return reject(err);
else return resolve(buffer);
});
}
}
);
})
.catch(reject);
});
}
// (Integer -> String) -> Promise<Buffer>
function resizeToHeight(maxHeight, interpolation="lanczos") {
return new Promise(function (resolve, reject) {
Lwip
.openAsync(buffer, format)
.then(image => {
let [width, height] = calculateDimensionsByHeight(maxHeight, [image.width(), image.height()]);
image
.resize(
width, height,
interpolation, // one of: "nearest-neighbor", "moving-average", "linear", "grid", "cubic", "lanczos"
function (err, image) {
if (err) return reject(err);
else {
return image.batch()
.sharpen(25) // TODO how to get the best approximate value?
.toBuffer(format, {}, function (err, buffer) {
if (err) return reject(err);
else return resolve(buffer);
});
}
}
);
})
.catch(reject);
});
}
// (Integer -> String) -> Promise<Buffer>
function resizeToWidth(maxWidth, interpolation="lanczos") {
return new Promise(function (resolve, reject) {
Lwip
.openAsync(buffer, format)
.then(image => {
let [width, height] = calculateDimensionsByWidth(maxWidth, [image.width(), image.height()]);
image
.resize(
width, height,
interpolation, // one of: "nearest-neighbor", "moving-average", "linear", "grid", "cubic", "lanczos"
function (err, image) {
if (err) return reject(err);
else {
return image.batch()
.sharpen(25) // TODO how to get the best approximate value?
.toBuffer(format, {}, function (err, buffer) {
if (err) return reject(err);
else return resolve(buffer);
});
}
}
);
})
.catch(reject);
});
}
// (Float -> Float -> String) -> Promise<Buffer>
function scale(wRatio, hRatio, interpolation="lanczos") {
return new Promise(function (resolve, reject) {
Lwip
.openAsync(buffer, format)
.then(image => {
image
.scale(
wRatio, hRatio,
interpolation, // one of: "nearest-neighbor", "moving-average", "linear", "grid", "cubic", "lanczos"
function (err, image) {
if (err) return reject(err);
else {
return image.batch()
.sharpen(25) // TODO how to get the best approximate value?
.toBuffer(format, {}, function (err, buffer) {
if (err) return reject(err);
else return resolve(buffer);
});
}
}
);
})
.catch(reject);
});
}
return Object.freeze({
resize, resizeToHeight, resizeToWidth,
scale,
});
}
Fs
.readFileAsync("image.original.jpg")
.then(buffer => {
let imageMaker = ImageMaker(buffer, "jpg");
let resized$ = imageMaker.resize(100, 100);
let resizedToHeight$ = imageMaker.resizeToHeight(100);
let scaled$ = imageMaker.scale(0.5, 0.5);
return Promise
.all([resized$, resizedToHeight$, scaled$])
.then(([resized, resizedToHeight, scaled]) => {
return Promise
.all([
Fs.writeFileAsync("image.resized.jpg", resized),
Fs.writeFileAsync("image.resized-to-height.jpg", resizedToHeight),
Fs.writeFileAsync("image.scaled.jpg", scaled),
])
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment