From e4a629da4188da76357d6494a0155263d67f6899 Mon Sep 17 00:00:00 2001 From: Andrew Lisowski Date: Sun, 5 Aug 2018 08:54:15 -0700 Subject: [PATCH] Performance improvements (#512) --- src/image-manipulation/shape.js | 66 +++++++++++++++++++++++---------- src/image-manipulation/text.js | 12 ++++-- src/index.js | 39 +++++++++---------- 3 files changed, 73 insertions(+), 44 deletions(-) diff --git a/src/image-manipulation/shape.js b/src/image-manipulation/shape.js index 2bc9d0236..c40e9da50 100644 --- a/src/image-manipulation/shape.js +++ b/src/image-manipulation/shape.js @@ -6,36 +6,57 @@ import Resize2 from '../modules/resize2'; import { isNodePattern, throwError } from '../utils/error-checking'; import * as constants from '../constants'; +function rotate90degrees(bitmap, dstBuffer, clockwise) { + const dstOffsetStep = clockwise ? -4 : 4; + let dstOffset = clockwise ? dstBuffer.length - 4 : 0; + + let tmp; + let x; + let y; + let srcOffset; + + for (x = 0; x < bitmap.width; x++) { + for (y = bitmap.height - 1; y >= 0; y--) { + srcOffset = (bitmap.width * y + x) << 2; + tmp = bitmap.data.readUInt32BE(srcOffset, true); + dstBuffer.writeUInt32BE(tmp, dstOffset, true); + dstOffset += dstOffsetStep; + } + } +} + /** * Rotates an image clockwise by a number of degrees rounded to the nearest 90 degrees. NB: 'this' must be a Jimp object. * @param {number} deg the number of degrees to rotate the image by */ function simpleRotate(deg) { - let i = Math.round(deg / 90) % 4; - while (i < 0) i += 4; - - while (i > 0) { - // https://github.com/ekulabuhov/jimp/commit/9a0c7cff88292d88c32a424b11256c76f1e20e46 - const dstBuffer = Buffer.alloc(this.bitmap.data.length); - let dstOffset = 0; - - for (let x = this.bitmap.width - 1; x >= 0; x--) { - for (let y = 0; y < this.bitmap.height; y++) { - const srcOffset = (this.bitmap.width * y + x) << 2; - const data = this.bitmap.data.readUInt32BE(srcOffset); - dstBuffer.writeUInt32BE(data, dstOffset); - dstOffset += 4; - } - } + let steps = Math.round(deg / 90) % 4; + steps += steps < 0 ? 4 : 0; + + if (steps === 0) return; + + const srcBuffer = this.bitmap.data; + const len = srcBuffer.length; + const dstBuffer = Buffer.allocUnsafe(len); - this.bitmap.data = Buffer.from(dstBuffer); + let tmp; - const tmp = this.bitmap.width; + if (steps === 2) { + // Upside-down + for (let srcOffset = 0; srcOffset < len; srcOffset += 4) { + tmp = srcBuffer.readUInt32BE(srcOffset, true); + dstBuffer.writeUInt32BE(tmp, len - srcOffset - 4, true); + } + } else { + // Clockwise or counter-clockwise rotation by 90 degree + rotate90degrees(this.bitmap, dstBuffer, steps === 1); + + tmp = this.bitmap.width; this.bitmap.width = this.bitmap.height; this.bitmap.height = tmp; - - i--; } + + this.bitmap.data = dstBuffer; } /** @@ -208,6 +229,11 @@ function flipFn(horizontal, vertical, cb) { cb ); + if (horizontal && vertical) { + // shortcut + return this.rotate(180, true, cb); + } + const bitmap = Buffer.alloc(this.bitmap.data.length); this.scanQuiet(0, 0, this.bitmap.width, this.bitmap.height, function( x, diff --git a/src/image-manipulation/text.js b/src/image-manipulation/text.js index b49dd5b2c..73653b763 100644 --- a/src/image-manipulation/text.js +++ b/src/image-manipulation/text.js @@ -52,9 +52,15 @@ function xOffsetBasedOnAlignment(font, line, maxWidth, alignment) { function drawCharacter(image, font, x, y, char) { if (char.width > 0 && char.height > 0) { - const imageChar = font.pages[char.page] - .cloneQuiet() - .crop(char.x, char.y, char.width, char.height); + let imageChar = char.image; + + if (!imageChar) { + imageChar = font.pages[char.page] + .cloneQuiet() + .crop(char.x, char.y, char.width, char.height); + char.image = imageChar; + } + return image.composite(imageChar, x + char.xoffset, y + char.yoffset); } diff --git a/src/index.js b/src/index.js index 48b25b55e..76eb75099 100755 --- a/src/index.js +++ b/src/index.js @@ -235,20 +235,8 @@ class Jimp extends EventEmitter { return throwError.call(this, 'cb must be a function', finish); } - const bitmap = Buffer.alloc(original.bitmap.data.length); - original.scanQuiet( - 0, - 0, - original.bitmap.width, - original.bitmap.height, - (x, y, idx) => { - const data = original.bitmap.data.readUInt32BE(idx); - bitmap.writeUInt32BE(data, idx); - } - ); - this.bitmap = { - data: bitmap, + data: Buffer.from(original.bitmap.data), width: original.bitmap.width, height: original.bitmap.height }; @@ -1280,16 +1268,25 @@ jimpEvChange('crop', function(x, y, w, h, cb) { w = Math.round(w); h = Math.round(h); - const bitmap = Buffer.alloc(this.bitmap.data.length); - let offset = 0; + if (x === 0 && w === this.bitmap.width) { + // shortcut + const start = (w * y + x) << 2; + const end = (start + h * w) << (2 + 1); - this.scanQuiet(x, y, w, h, function(x, y, idx) { - const data = this.bitmap.data.readUInt32BE(idx); - bitmap.writeUInt32BE(data, offset); - offset += 4; - }); + 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.data = Buffer.from(bitmap); this.bitmap.width = w; this.bitmap.height = h;