Last active
November 13, 2015 13:43
-
-
Save ivan-kleshnin/e84ec367fc1b77c778fe to your computer and use it in GitHub Desktop.
Lwip basic wrapper demo
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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