|
|
|
|
|
|
|
|
import { throwError, isNodePattern } from "@jimp/utils"; |
|
|
|
|
|
export default function pluginCrop(event) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
event("crop", function (x, y, w, h, cb) { |
|
|
if (typeof x !== "number" || typeof y !== "number") |
|
|
return throwError.call(this, "x and y must be numbers", cb); |
|
|
if (typeof w !== "number" || typeof h !== "number") |
|
|
return throwError.call(this, "w and h must be numbers", cb); |
|
|
|
|
|
|
|
|
x = Math.round(x); |
|
|
y = Math.round(y); |
|
|
w = Math.round(w); |
|
|
h = Math.round(h); |
|
|
|
|
|
if (x === 0 && w === this.bitmap.width) { |
|
|
|
|
|
const start = (w * y + x) << 2; |
|
|
const end = start + ((h * w) << 2); |
|
|
|
|
|
this.bitmap.data = this.bitmap.data.slice(start, end); |
|
|
} else { |
|
|
const bitmap = Buffer.allocUnsafe(w * h * 4); |
|
|
let offset = 0; |
|
|
|
|
|
this.scanQuiet(x, y, w, h, function (x, y, idx) { |
|
|
const data = this.bitmap.data.readUInt32BE(idx, true); |
|
|
bitmap.writeUInt32BE(data, offset, true); |
|
|
offset += 4; |
|
|
}); |
|
|
|
|
|
this.bitmap.data = bitmap; |
|
|
} |
|
|
|
|
|
this.bitmap.width = w; |
|
|
this.bitmap.height = h; |
|
|
|
|
|
if (isNodePattern(cb)) { |
|
|
cb.call(this, null, this); |
|
|
} |
|
|
|
|
|
return this; |
|
|
}); |
|
|
|
|
|
return { |
|
|
class: { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
autocrop(...args) { |
|
|
const w = this.bitmap.width; |
|
|
const h = this.bitmap.height; |
|
|
const minPixelsPerSide = 1; |
|
|
|
|
|
let cb; |
|
|
let leaveBorder = 0; |
|
|
let tolerance = 0.0002; |
|
|
let cropOnlyFrames = true; |
|
|
|
|
|
let cropSymmetric = false; |
|
|
|
|
|
let ignoreSides = { |
|
|
north: false, |
|
|
south: false, |
|
|
east: false, |
|
|
west: false, |
|
|
}; |
|
|
|
|
|
|
|
|
for (let a = 0, len = args.length; a < len; a++) { |
|
|
if (typeof args[a] === "number") { |
|
|
|
|
|
tolerance = args[a]; |
|
|
} |
|
|
|
|
|
if (typeof args[a] === "boolean") { |
|
|
|
|
|
cropOnlyFrames = args[a]; |
|
|
} |
|
|
|
|
|
if (typeof args[a] === "function") { |
|
|
|
|
|
cb = args[a]; |
|
|
} |
|
|
|
|
|
if (typeof args[a] === "object") { |
|
|
|
|
|
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); |
|
|
} |
|
|
|
|
|
if (typeof config.leaveBorder !== "undefined") { |
|
|
({ leaveBorder } = config); |
|
|
} |
|
|
|
|
|
if (typeof config.ignoreSides !== "undefined") { |
|
|
({ ignoreSides } = config); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let colorTarget = this.getPixelColor(0, 0); |
|
|
const rgba1 = this.constructor.intToRGBA(colorTarget); |
|
|
|
|
|
|
|
|
let northPixelsToCrop = 0; |
|
|
let eastPixelsToCrop = 0; |
|
|
let southPixelsToCrop = 0; |
|
|
let westPixelsToCrop = 0; |
|
|
|
|
|
|
|
|
colorTarget = this.getPixelColor(0, 0); |
|
|
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) { |
|
|
|
|
|
break north; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
northPixelsToCrop++; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
colorTarget = this.getPixelColor(w, 0); |
|
|
if (!ignoreSides.west) { |
|
|
west: 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) { |
|
|
|
|
|
break west; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
westPixelsToCrop++; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
colorTarget = this.getPixelColor(0, h); |
|
|
|
|
|
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) { |
|
|
|
|
|
break south; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
southPixelsToCrop++; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
colorTarget = this.getPixelColor(w, h); |
|
|
if (!ignoreSides.east) { |
|
|
east: for ( |
|
|
let x = w - 1; |
|
|
x >= 0 + westPixelsToCrop + 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) { |
|
|
|
|
|
break east; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
eastPixelsToCrop++; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
let doCrop = false; |
|
|
|
|
|
|
|
|
westPixelsToCrop -= leaveBorder; |
|
|
eastPixelsToCrop -= leaveBorder; |
|
|
northPixelsToCrop -= leaveBorder; |
|
|
southPixelsToCrop -= leaveBorder; |
|
|
|
|
|
if (cropSymmetric) { |
|
|
const horizontal = Math.min(eastPixelsToCrop, westPixelsToCrop); |
|
|
const vertical = Math.min(northPixelsToCrop, southPixelsToCrop); |
|
|
westPixelsToCrop = horizontal; |
|
|
eastPixelsToCrop = horizontal; |
|
|
northPixelsToCrop = vertical; |
|
|
southPixelsToCrop = vertical; |
|
|
} |
|
|
|
|
|
|
|
|
westPixelsToCrop = westPixelsToCrop >= 0 ? westPixelsToCrop : 0; |
|
|
eastPixelsToCrop = eastPixelsToCrop >= 0 ? eastPixelsToCrop : 0; |
|
|
northPixelsToCrop = northPixelsToCrop >= 0 ? northPixelsToCrop : 0; |
|
|
southPixelsToCrop = southPixelsToCrop >= 0 ? southPixelsToCrop : 0; |
|
|
|
|
|
|
|
|
const widthOfRemainingPixels = |
|
|
w - (westPixelsToCrop + eastPixelsToCrop); |
|
|
const heightOfRemainingPixels = |
|
|
h - (southPixelsToCrop + northPixelsToCrop); |
|
|
|
|
|
if (cropOnlyFrames) { |
|
|
|
|
|
doCrop = |
|
|
eastPixelsToCrop !== 0 && |
|
|
northPixelsToCrop !== 0 && |
|
|
westPixelsToCrop !== 0 && |
|
|
southPixelsToCrop !== 0; |
|
|
} else { |
|
|
|
|
|
doCrop = |
|
|
eastPixelsToCrop !== 0 || |
|
|
northPixelsToCrop !== 0 || |
|
|
westPixelsToCrop !== 0 || |
|
|
southPixelsToCrop !== 0; |
|
|
} |
|
|
|
|
|
if (doCrop) { |
|
|
|
|
|
this.crop( |
|
|
westPixelsToCrop, |
|
|
northPixelsToCrop, |
|
|
widthOfRemainingPixels, |
|
|
heightOfRemainingPixels |
|
|
); |
|
|
} |
|
|
|
|
|
if (isNodePattern(cb)) { |
|
|
cb.call(this, null, this); |
|
|
} |
|
|
|
|
|
return this; |
|
|
}, |
|
|
}, |
|
|
}; |
|
|
} |
|
|
|