diff --git a/packages/plugin-crop/index.d.ts b/packages/plugin-crop/index.d.ts index c5c4cf2f9..5dc5c83e7 100644 --- a/packages/plugin-crop/index.d.ts +++ b/packages/plugin-crop/index.d.ts @@ -22,6 +22,12 @@ interface CropClass { cropOnlyFrames?: boolean; cropSymmetric?: boolean; leaveBorder?: number; + ignoreSides?: { + north: boolean; + south: boolean; + east: boolean; + west: boolean; + } }, cb?: ImageCallback ): this; diff --git a/packages/plugin-crop/src/index.js b/packages/plugin-crop/src/index.js index 2ab8ca957..4d1f78adb 100644 --- a/packages/plugin-crop/src/index.js +++ b/packages/plugin-crop/src/index.js @@ -74,6 +74,12 @@ export default function pluginCrop(event) { // i.e. all 4 sides have some border (default value) let cropSymmetric = false; // flag to force cropping top be symmetric. // i.e. north and south / east and west are cropped by the same value + let ignoreSides = { + north: false, + south: false, + east: false, + west: false + }; // parse arguments for (let a = 0, len = args.length; a < len; a++) { @@ -111,6 +117,10 @@ export default function pluginCrop(event) { if (typeof config.leaveBorder !== 'undefined') { ({ leaveBorder } = config); } + + if (typeof config.ignoreSides !== 'undefined') { + ({ ignoreSides } = config); + } } } @@ -133,78 +143,87 @@ export default function pluginCrop(event) { // north side (scan rows from north to south) colorTarget = this.getPixelColor(0, 0); - north: for (let y = 0; y < h - minPixelsPerSide; y++) { - for (let x = 0; x < w; x++) { - const colorXY = this.getPixelColor(x, y); - const rgba2 = this.constructor.intToRGBA(colorXY); - - if (this.constructor.colorDiff(rgba1, rgba2) > tolerance) { - // this pixel is too distant from the first one: abort this side scan - break north; + if (!ignoreSides.north) { + north: for (let y = 0; y < h - minPixelsPerSide; y++) { + for (let x = 0; x < w; x++) { + const colorXY = this.getPixelColor(x, y); + const rgba2 = this.constructor.intToRGBA(colorXY); + + if (this.constructor.colorDiff(rgba1, rgba2) > tolerance) { + // this pixel is too distant from the first one: abort this side scan + break north; + } } - } - // this row contains all pixels with the same color: increment this side pixels to crop - northPixelsToCrop++; + // this row contains all pixels with the same color: increment this side pixels to crop + northPixelsToCrop++; + } } // east side (scan columns from east to west) colorTarget = this.getPixelColor(w, 0); - east: for (let x = 0; x < w - minPixelsPerSide; x++) { - for (let y = 0 + northPixelsToCrop; y < h; y++) { - const colorXY = this.getPixelColor(x, y); - const rgba2 = this.constructor.intToRGBA(colorXY); - - if (this.constructor.colorDiff(rgba1, rgba2) > tolerance) { - // this pixel is too distant from the first one: abort this side scan - break east; + if (!ignoreSides.east) { + east: for (let x = 0; x < w - minPixelsPerSide; x++) { + for (let y = 0 + northPixelsToCrop; y < h; y++) { + const colorXY = this.getPixelColor(x, y); + const rgba2 = this.constructor.intToRGBA(colorXY); + + if (this.constructor.colorDiff(rgba1, rgba2) > tolerance) { + // this pixel is too distant from the first one: abort this side scan + break east; + } } - } - // this column contains all pixels with the same color: increment this side pixels to crop - eastPixelsToCrop++; + // this column contains all pixels with the same color: increment this side pixels to crop + eastPixelsToCrop++; + } } // south side (scan rows from south to north) colorTarget = this.getPixelColor(0, h); - south: for ( - let y = h - 1; - y >= northPixelsToCrop + minPixelsPerSide; - y-- - ) { - for (let x = w - eastPixelsToCrop - 1; x >= 0; x--) { - const colorXY = this.getPixelColor(x, y); - const rgba2 = this.constructor.intToRGBA(colorXY); - - if (this.constructor.colorDiff(rgba1, rgba2) > tolerance) { - // this pixel is too distant from the first one: abort this side scan - break south; + + if (!ignoreSides.south) { + south: for ( + let y = h - 1; + y >= northPixelsToCrop + minPixelsPerSide; + y-- + ) { + for (let x = w - eastPixelsToCrop - 1; x >= 0; x--) { + const colorXY = this.getPixelColor(x, y); + const rgba2 = this.constructor.intToRGBA(colorXY); + + if (this.constructor.colorDiff(rgba1, rgba2) > tolerance) { + // this pixel is too distant from the first one: abort this side scan + break south; + } } - } - // this row contains all pixels with the same color: increment this side pixels to crop - southPixelsToCrop++; + // this row contains all pixels with the same color: increment this side pixels to crop + southPixelsToCrop++; + } } // west side (scan columns from west to east) colorTarget = this.getPixelColor(w, h); - west: for ( - let x = w - 1; - x >= 0 + eastPixelsToCrop + minPixelsPerSide; - x-- - ) { - for (let y = h - 1; y >= 0 + northPixelsToCrop; y--) { - const colorXY = this.getPixelColor(x, y); - const rgba2 = this.constructor.intToRGBA(colorXY); - - if (this.constructor.colorDiff(rgba1, rgba2) > tolerance) { - // this pixel is too distant from the first one: abort this side scan - break west; + if (!ignoreSides.west) { + west: for ( + let x = w - 1; + x >= 0 + eastPixelsToCrop + minPixelsPerSide; + x-- + ) { + for (let y = h - 1; y >= 0 + northPixelsToCrop; y--) { + const colorXY = this.getPixelColor(x, y); + const rgba2 = this.constructor.intToRGBA(colorXY); + + if (this.constructor.colorDiff(rgba1, rgba2) > tolerance) { + // this pixel is too distant from the first one: abort this side scan + break west; + } } - } - // this column contains all pixels with the same color: increment this side pixels to crop - westPixelsToCrop++; + // this column contains all pixels with the same color: increment this side pixels to crop + westPixelsToCrop++; + } } // decide if a crop is needed diff --git a/packages/plugin-crop/test/autocrop.test.js b/packages/plugin-crop/test/autocrop.test.js index 78dac64bf..395af91e1 100644 --- a/packages/plugin-crop/test/autocrop.test.js +++ b/packages/plugin-crop/test/autocrop.test.js @@ -353,4 +353,77 @@ describe('Autocrop', () => { ) ); }); + + it('ignore sides north', async () => { + const imgSrc = await jimp.read( + mkJGD( + ' ', + ' ◆◆ ', + ' ◆▦▦◆ ', + ' ◆▦▦▦▦◆ ', + ' ◆▦▦◆ ', + ' ◆◆ ', + ' ' + ) + ); + + imgSrc + .autocrop({ cropOnlyFrames: false, ignoreSides: { north: true } }) + .getJGDSync() + .should.be.sameJGD( + mkJGD(' ', ' ◆◆ ', ' ◆▦▦◆ ', '◆▦▦▦▦◆', ' ◆▦▦◆ ', ' ◆◆ ') + ); + }); + + it('ignore sides south and west', async () => { + const imgSrc = await jimp.read( + mkJGD( + ' ', + ' ◆◆ ', + ' ◆▦▦◆ ', + ' ◆▦▦▦▦◆ ', + ' ◆▦▦◆ ', + ' ◆◆ ', + ' ' + ) + ); + + imgSrc + .autocrop({ + cropOnlyFrames: false, + ignoreSides: { west: true, south: true } + }) + .getJGDSync() + .should.be.sameJGD( + mkJGD( + ' ◆◆ ', + ' ◆▦▦◆ ', + '◆▦▦▦▦◆ ', + ' ◆▦▦◆ ', + ' ◆◆ ', + ' ' + ) + ); + }); + + it('ignore sides east', async () => { + const imgSrc = await jimp.read( + mkJGD( + ' ', + ' ◆◆ ', + ' ◆▦▦◆ ', + ' ◆▦▦▦▦◆ ', + ' ◆▦▦◆ ', + ' ◆◆ ', + ' ' + ) + ); + + imgSrc + .autocrop({ cropOnlyFrames: false, ignoreSides: { east: true } }) + .getJGDSync() + .should.be.sameJGD( + mkJGD(' ◆◆ ', ' ◆▦▦◆ ', ' ◆▦▦▦▦◆', ' ◆▦▦◆ ', ' ◆◆ ') + ); + }); });