Path: blob/master/node_modules/@jimp/plugin-crop/src/index.js
1126 views
/* eslint-disable no-labels */12import { throwError, isNodePattern } from '@jimp/utils';34export default function pluginCrop(event) {5/**6* Crops the image at a given point to a give size7* @param {number} x the x coordinate to crop form8* @param {number} y the y coordinate to crop form9* @param w the width of the crop region10* @param h the height of the crop region11* @param {function(Error, Jimp)} cb (optional) a callback for when complete12* @returns {Jimp} this for chaining of methods13*/14event('crop', function(x, y, w, h, cb) {15if (typeof x !== 'number' || typeof y !== 'number')16return throwError.call(this, 'x and y must be numbers', cb);17if (typeof w !== 'number' || typeof h !== 'number')18return throwError.call(this, 'w and h must be numbers', cb);1920// round input21x = Math.round(x);22y = Math.round(y);23w = Math.round(w);24h = Math.round(h);2526if (x === 0 && w === this.bitmap.width) {27// shortcut28const start = (w * y + x) << 2;29const end = (start + h * w) << 2;3031this.bitmap.data = this.bitmap.data.slice(start, end);32} else {33const bitmap = Buffer.allocUnsafe(w * h * 4);34let offset = 0;3536this.scanQuiet(x, y, w, h, function(x, y, idx) {37const data = this.bitmap.data.readUInt32BE(idx, true);38bitmap.writeUInt32BE(data, offset, true);39offset += 4;40});4142this.bitmap.data = bitmap;43}4445this.bitmap.width = w;46this.bitmap.height = h;4748if (isNodePattern(cb)) {49cb.call(this, null, this);50}5152return this;53});5455return {56class: {57/**58* Autocrop same color borders from this image59* @param {number} tolerance (optional): a percent value of tolerance for pixels color difference (default: 0.0002%)60* @param {boolean} cropOnlyFrames (optional): flag to crop only real frames: all 4 sides of the image must have some border (default: true)61* @param {function(Error, Jimp)} cb (optional): a callback for when complete (default: no callback)62* @returns {Jimp} this for chaining of methods63*/64autocrop(...args) {65const w = this.bitmap.width;66const h = this.bitmap.height;67const minPixelsPerSide = 1; // to avoid cropping completely the image, resulting in an invalid 0 sized image6869let cb; // callback70let leaveBorder = 0; // Amount of pixels in border to leave71let tolerance = 0.0002; // percent of color difference tolerance (default value)72let cropOnlyFrames = true; // flag to force cropping only if the image has a real "frame"73// i.e. all 4 sides have some border (default value)74let cropSymmetric = false; // flag to force cropping top be symmetric.75// i.e. north and south / east and west are cropped by the same value76let ignoreSides = {77north: false,78south: false,79east: false,80west: false81};8283// parse arguments84for (let a = 0, len = args.length; a < len; a++) {85if (typeof args[a] === 'number') {86// tolerance value passed87tolerance = args[a];88}8990if (typeof args[a] === 'boolean') {91// cropOnlyFrames value passed92cropOnlyFrames = args[a];93}9495if (typeof args[a] === 'function') {96// callback value passed97cb = args[a];98}99100if (typeof args[a] === 'object') {101// config object passed102const config = args[a];103104if (typeof config.tolerance !== 'undefined') {105({ tolerance } = config);106}107108if (typeof config.cropOnlyFrames !== 'undefined') {109({ cropOnlyFrames } = config);110}111112if (typeof config.cropSymmetric !== 'undefined') {113({ cropSymmetric } = config);114}115116if (typeof config.leaveBorder !== 'undefined') {117({ leaveBorder } = config);118}119120if (typeof config.ignoreSides !== 'undefined') {121({ ignoreSides } = config);122}123}124}125126/**127* All borders must be of the same color as the top left pixel, to be cropped.128* It should be possible to crop borders each with a different color,129* but since there are many ways for corners to intersect, it would130* introduce unnecessary complexity to the algorithm.131*/132133// scan each side for same color borders134let colorTarget = this.getPixelColor(0, 0); // top left pixel color is the target color135const rgba1 = this.constructor.intToRGBA(colorTarget);136137// for north and east sides138let northPixelsToCrop = 0;139let eastPixelsToCrop = 0;140let southPixelsToCrop = 0;141let westPixelsToCrop = 0;142143// north side (scan rows from north to south)144colorTarget = this.getPixelColor(0, 0);145if (!ignoreSides.north) {146north: for (let y = 0; y < h - minPixelsPerSide; y++) {147for (let x = 0; x < w; x++) {148const colorXY = this.getPixelColor(x, y);149const rgba2 = this.constructor.intToRGBA(colorXY);150151if (this.constructor.colorDiff(rgba1, rgba2) > tolerance) {152// this pixel is too distant from the first one: abort this side scan153break north;154}155}156157// this row contains all pixels with the same color: increment this side pixels to crop158northPixelsToCrop++;159}160}161162// east side (scan columns from east to west)163colorTarget = this.getPixelColor(w, 0);164if (!ignoreSides.east) {165east: for (let x = 0; x < w - minPixelsPerSide; x++) {166for (let y = 0 + northPixelsToCrop; y < h; y++) {167const colorXY = this.getPixelColor(x, y);168const rgba2 = this.constructor.intToRGBA(colorXY);169170if (this.constructor.colorDiff(rgba1, rgba2) > tolerance) {171// this pixel is too distant from the first one: abort this side scan172break east;173}174}175176// this column contains all pixels with the same color: increment this side pixels to crop177eastPixelsToCrop++;178}179}180181// south side (scan rows from south to north)182colorTarget = this.getPixelColor(0, h);183184if (!ignoreSides.south) {185south: for (186let y = h - 1;187y >= northPixelsToCrop + minPixelsPerSide;188y--189) {190for (let x = w - eastPixelsToCrop - 1; x >= 0; x--) {191const colorXY = this.getPixelColor(x, y);192const rgba2 = this.constructor.intToRGBA(colorXY);193194if (this.constructor.colorDiff(rgba1, rgba2) > tolerance) {195// this pixel is too distant from the first one: abort this side scan196break south;197}198}199200// this row contains all pixels with the same color: increment this side pixels to crop201southPixelsToCrop++;202}203}204205// west side (scan columns from west to east)206colorTarget = this.getPixelColor(w, h);207if (!ignoreSides.west) {208west: for (209let x = w - 1;210x >= 0 + eastPixelsToCrop + minPixelsPerSide;211x--212) {213for (let y = h - 1; y >= 0 + northPixelsToCrop; y--) {214const colorXY = this.getPixelColor(x, y);215const rgba2 = this.constructor.intToRGBA(colorXY);216217if (this.constructor.colorDiff(rgba1, rgba2) > tolerance) {218// this pixel is too distant from the first one: abort this side scan219break west;220}221}222223// this column contains all pixels with the same color: increment this side pixels to crop224westPixelsToCrop++;225}226}227228// decide if a crop is needed229let doCrop = false;230231// apply leaveBorder232westPixelsToCrop -= leaveBorder;233eastPixelsToCrop -= leaveBorder;234northPixelsToCrop -= leaveBorder;235southPixelsToCrop -= leaveBorder;236237if (cropSymmetric) {238const horizontal = Math.min(eastPixelsToCrop, westPixelsToCrop);239const vertical = Math.min(northPixelsToCrop, southPixelsToCrop);240westPixelsToCrop = horizontal;241eastPixelsToCrop = horizontal;242northPixelsToCrop = vertical;243southPixelsToCrop = vertical;244}245246// make sure that crops are >= 0247westPixelsToCrop = westPixelsToCrop >= 0 ? westPixelsToCrop : 0;248eastPixelsToCrop = eastPixelsToCrop >= 0 ? eastPixelsToCrop : 0;249northPixelsToCrop = northPixelsToCrop >= 0 ? northPixelsToCrop : 0;250southPixelsToCrop = southPixelsToCrop >= 0 ? southPixelsToCrop : 0;251252// safety checks253const widthOfRemainingPixels =254w - (westPixelsToCrop + eastPixelsToCrop);255const heightOfRemainingPixels =256h - (southPixelsToCrop + northPixelsToCrop);257258if (cropOnlyFrames) {259// crop image if all sides should be cropped260doCrop =261eastPixelsToCrop !== 0 &&262northPixelsToCrop !== 0 &&263westPixelsToCrop !== 0 &&264southPixelsToCrop !== 0;265} else {266// crop image if at least one side should be cropped267doCrop =268eastPixelsToCrop !== 0 ||269northPixelsToCrop !== 0 ||270westPixelsToCrop !== 0 ||271southPixelsToCrop !== 0;272}273274if (doCrop) {275// do the real crop276this.crop(277eastPixelsToCrop,278northPixelsToCrop,279widthOfRemainingPixels,280heightOfRemainingPixels281);282}283284if (isNodePattern(cb)) {285cb.call(this, null, this);286}287288return this;289}290}291};292}293294295