Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add 'cropSymmetric' to AutoCrop #461

Merged
merged 5 commits into from
Sep 5, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/jimp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ image.scaleToFit( w, h[, mode] ); // scale the image to the largest size that fi

/* Crop */
image.autocrop([tolerance, frames]); // automatically crop same-color borders from image (if any), frames must be a Boolean
image.autocrop(options); // automatically crop same-color borders from image (if any), options may contain tolerance, cropOnlyFrames, cropSymmetric
image.crop( x, y, w, h ); // crop to the given region

/* Composing */
Expand Down
8 changes: 8 additions & 0 deletions packages/jimp/jimp.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,14 @@ declare namespace Jimp {
cropOnlyFrames?: boolean,
cb?: Jimp.ImageCallback
): this;
autocrop(
options: {
tolerance?: number;
cropOnlyFrames?: boolean;
cropSymmetric: boolean;
},
cb?: Jimp.ImageCallback
): this;

// Text methods
print(
Expand Down
8 changes: 8 additions & 0 deletions packages/plugin-crop/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,21 @@ AutoCrop same color borders from this image
_ @param {number} tolerance (optional): a percent value of tolerance for pixels color difference (default: 0.0002%)
_ @param {boolean} cropOnlyFrames (optional): flag to crop only real frames: all 4 sides of the image must have some border (default: true)

or

- @param {object} options object
- tolerance (optional): a percent value of tolerance for pixels color difference (default: 0.0002%)
- cropOnlyFrames (optional): flag to crop only real frames: all 4 sides of the image must have some border (default: true)
- cropSymetric (optional): flag to force cropping top be symmetric. north and south / east and west are cropped by the same value

```js
import jimp from 'jimp';

async function main() {
const image = await jimp.read('test/image.png');

image.autocrop();
image.autocrop({ cropOnlyFrames: false, cropSymmetric: true });
}

main();
Expand Down
51 changes: 41 additions & 10 deletions packages/plugin-crop/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ export default function pluginCrop(event) {
let tolerance = 0.0002; // percent of color difference tolerance (default value)
let cropOnlyFrames = true; // flag to force cropping only if the image has a real "frame"
// 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

// parse arguments
for (let a = 0, len = args.length; a < len; a++) {
Expand All @@ -88,6 +90,23 @@ export default function pluginCrop(event) {
// callback value passed
cb = args[a];
}

if (typeof args[a] === 'object') {
// config object passed
const config = args[a];

if (typeof config.tolerance !== 'undefined') {
({ tolerance } = config);
}

if (typeof config.cropOnlyFrames !== 'undefined') {
({ cropOnlyFrames } = config);
}

if (typeof config.cropSymmetric !== 'undefined') {
({ cropSymmetric } = config);
}
}
}

/**
Expand All @@ -98,7 +117,7 @@ export default function pluginCrop(event) {
*/

// scan each side for same color borders
const colorTarget = this.getPixelColor(0, 0); // top left pixel color is the target color
let colorTarget = this.getPixelColor(0, 0); // top left pixel color is the target color
const rgba1 = this.constructor.intToRGBA(colorTarget);

// for north and east sides
Expand All @@ -108,6 +127,7 @@ export default function pluginCrop(event) {
let westPixelsToCrop = 0;

// 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);
Expand All @@ -123,6 +143,7 @@ export default function pluginCrop(event) {
}

// 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);
Expand All @@ -138,6 +159,7 @@ export default function pluginCrop(event) {
}

// south side (scan rows from south to north)
colorTarget = this.getPixelColor(0, h);
south: for (
let y = h - 1;
y >= northPixelsToCrop + minPixelsPerSide;
Expand All @@ -157,6 +179,7 @@ export default function pluginCrop(event) {
}

// west side (scan columns from west to east)
colorTarget = this.getPixelColor(w, h);
west: for (
let x = w - 1;
x >= 0 + eastPixelsToCrop + minPixelsPerSide;
Expand All @@ -175,16 +198,24 @@ export default function pluginCrop(event) {
westPixelsToCrop++;
}

// safety checks
const widthOfPixelsToCrop = w - (westPixelsToCrop + eastPixelsToCrop);
// widthOfPixelsToCrop >= 0 ? widthOfPixelsToCrop : 0;
const heightOfPixelsToCrop =
h - (southPixelsToCrop + northPixelsToCrop);
// heightOfPixelsToCrop >= 0 ? heightOfPixelsToCrop : 0;

// decide if a crop is needed
let doCrop = false;

if (cropSymmetric) {
const horizontal = Math.min(eastPixelsToCrop, westPixelsToCrop);
const vertical = Math.min(northPixelsToCrop, southPixelsToCrop);
westPixelsToCrop = horizontal;
eastPixelsToCrop = horizontal;
northPixelsToCrop = vertical;
southPixelsToCrop = vertical;
}

// safety checks
const widthOfRemainingPixels =
w - (westPixelsToCrop + eastPixelsToCrop);
const heightOfRemainingPixels =
h - (southPixelsToCrop + northPixelsToCrop);

if (cropOnlyFrames) {
// crop image if all sides should be cropped
doCrop =
Expand All @@ -206,8 +237,8 @@ export default function pluginCrop(event) {
this.crop(
eastPixelsToCrop,
northPixelsToCrop,
widthOfPixelsToCrop,
heightOfPixelsToCrop
widthOfRemainingPixels,
heightOfRemainingPixels
);
}

Expand Down
139 changes: 139 additions & 0 deletions packages/plugin-crop/test/autocrop.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,4 +110,143 @@ describe('Autocrop', () => {
mkJGD(' ◆◆ ', ' ◆▦▦◆ ', ' ◆▦▦▦▦◆ ', ' ◆▦▦◆ ', ' ◆◆ ')
);
});

it('image border with small variation configured by options', async () => {
const imgSrc = await Jimp.read(
mkJGD(
'323232323232',
'232323232323',
'32 ◆◆ 32',
'23 ◆▦▦◆ 23',
'32 ◆▦▦▦▦◆ 32',
'23 ◆▦▦◆ 23',
'32 ◆◆ 32',
'232323232323',
'323232323232'
)
);
imgSrc
.clone()
.autocrop()
.getJGDSync()
.should.be.sameJGD(
mkJGD(
'323232323232',
'232323232323',
'32 ◆◆ 32',
'23 ◆▦▦◆ 23',
'32 ◆▦▦▦▦◆ 32',
'23 ◆▦▦◆ 23',
'32 ◆◆ 32',
'232323232323',
'323232323232'
)
);
imgSrc
.clone()
.autocrop({ tolerance: 0.005 })
.getJGDSync()
.should.be.sameJGD(
mkJGD(' ◆◆ ', ' ◆▦▦◆ ', ' ◆▦▦▦▦◆ ', ' ◆▦▦◆ ', ' ◆◆ ')
);
});

it('image without frame', async () => {
const imgSrc = await Jimp.read(
mkJGD(
'▥▥ ◆◆ ',
'▥▥ ◆▦▦◆ ',
'▥▥ ◆▦▦▦▦◆ ',
'▥▥ ◆▦▦◆ ',
'▥▥ ◆◆ ',
'▥▥▥▥▥▥▥▥▥▥',
'▥▥▥▥▥▥▥▥▥▥'
)
);

imgSrc
.autocrop(false)
.getJGDSync()
.should.be.sameJGD(
mkJGD(' ◆◆ ', ' ◆▦▦◆ ', ' ◆▦▦▦▦◆ ', ' ◆▦▦◆ ', ' ◆◆ ')
);
});

it('image without frame configured by options', async () => {
const imgSrc = await Jimp.read(
mkJGD(
'▥▥ ◆◆ ',
'▥▥ ◆▦▦◆ ',
'▥▥ ◆▦▦▦▦◆ ',
'▥▥ ◆▦▦◆ ',
'▥▥ ◆◆ ',
'▥▥▥▥▥▥▥▥▥▥',
'▥▥▥▥▥▥▥▥▥▥'
)
);

imgSrc
.autocrop({ cropOnlyFrames: false })
.getJGDSync()
.should.be.sameJGD(
mkJGD(' ◆◆ ', ' ◆▦▦◆ ', ' ◆▦▦▦▦◆ ', ' ◆▦▦◆ ', ' ◆◆ ')
);
});

it('image with symmetric border configured by options', async () => {
const imgSrc = await Jimp.read(
mkJGD(
'▥▥▥▥▥▥▥▥▥▥▥▥▥▥',
'▥▥ ◆◆ ▥▥▥▥',
'▥▥ ◆▦▦◆ ▥▥▥▥',
'▥▥ ◆▦▦▦▦◆ ▥▥▥▥',
'▥▥ ◆▦▦◆ ▥▥▥▥',
'▥▥ ◆◆ ▥▥▥▥',
'▥▥▥▥▥▥▥▥▥▥▥▥▥▥',
'▥▥▥▥▥▥▥▥▥▥▥▥▥▥'
)
);

imgSrc
.autocrop({ cropSymmetric: true })
.getJGDSync()
.should.be.sameJGD(
mkJGD(
' ◆◆ ▥▥',
' ◆▦▦◆ ▥▥',
' ◆▦▦▦▦◆ ▥▥',
' ◆▦▦◆ ▥▥',
' ◆◆ ▥▥',
'▥▥▥▥▥▥▥▥▥▥'
)
);
});

it('image wihtout frame and with symmetric border configured by options', async () => {
const imgSrc = await Jimp.read(
mkJGD(
'▥▥ ◆◆ ▥▥▥▥',
'▥▥ ◆▦▦◆ ▥▥▥▥',
'▥▥ ◆▦▦▦▦◆ ▥▥▥▥',
'▥▥ ◆▦▦◆ ▥▥▥▥',
'▥▥ ◆◆ ▥▥▥▥',
'▥▥▥▥▥▥▥▥▥▥▥▥▥▥',
'▥▥▥▥▥▥▥▥▥▥▥▥▥▥'
)
);
imgSrc
.autocrop({ cropSymmetric: true, cropOnlyFrames: false })
.getJGDSync()
.should.be.sameJGD(
mkJGD(
' ◆◆ ▥▥',
' ◆▦▦◆ ▥▥',
' ◆▦▦▦▦◆ ▥▥',
' ◆▦▦◆ ▥▥',
' ◆◆ ▥▥',
'▥▥▥▥▥▥▥▥▥▥',
'▥▥▥▥▥▥▥▥▥▥'
)
);
});
});