Skip to content

Commit 1b8fcda

Browse files
authored
Normalise rotate and tile angle modulo 360 to avoid 32-bit truncation (#4637)
1 parent adae0a0 commit 1b8fcda

4 files changed

Lines changed: 32 additions & 2 deletions

File tree

‎lib/operation.mjs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ function rotate (angle, options) {
6262
this.options.rotationAngle = 0;
6363
}
6464
if (is.integer(angle) && !(angle % 90)) {
65-
this.options.angle = angle;
65+
this.options.angle = angle % 360;
6666
} else if (is.number(angle)) {
6767
this.options.rotationAngle = angle;
6868
if (is.object(options) && options.background) {

‎lib/output.mjs‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1518,7 +1518,7 @@ function tile (options) {
15181518
// Angle of rotation,
15191519
if (is.defined(options.angle)) {
15201520
if (is.integer(options.angle) && !(options.angle % 90)) {
1521-
this.options.tileAngle = options.angle;
1521+
this.options.tileAngle = options.angle % 360;
15221522
} else {
15231523
throw is.invalidParameterError('angle', 'positive/negative multiple of 90', options.angle);
15241524
}

‎test/unit/rotate.js‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,17 @@ suite('Rotation', () => {
183183
});
184184
});
185185

186+
// A 90-multiple angle larger than the 32-bit integer range is normalised
187+
// modulo 360 rather than silently truncated when read natively
188+
[90 * 47721859, 90 * 47721861, -90 * 47721859].forEach((angle) => {
189+
test(`Rotate by a 90-multiple angle beyond 32-bit range (${angle}deg)`, async (t) => {
190+
t.plan(2);
191+
const { info } = await bufferWithInfo(sharp(fixtures.inputJpg320x240).rotate(angle));
192+
t.assert.strictEqual(240, info.width);
193+
t.assert.strictEqual(320, info.height);
194+
});
195+
});
196+
186197
[-3750, -510, -150, 30, 390, 3630].forEach((angle) => {
187198
test(`Rotate by any 30-multiple angle (${angle}deg)`, async (t) => {
188199
t.plan(2);

‎test/unit/tile.js‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,25 @@ suite('Tile', () => {
411411
t.assert.strictEqual(true, metadata.height === 170);
412412
});
413413

414+
test('Deep Zoom layout with angle beyond 32-bit integer range', async (t) => {
415+
const directory = fixtures.path('output.512_largeangle.dzi_files');
416+
await fs.rm(directory, { recursive: true, force: true });
417+
// 2147483970 is a multiple of 90 equal to 90 modulo 360 but larger than
418+
// the signed 32-bit range, so it must rotate as 90 rather than truncate
419+
const info = await sharp(fixtures.inputJpg)
420+
.tile({
421+
size: 512,
422+
angle: 2147483970
423+
})
424+
.toFile(fixtures.path('output.512_largeangle.dzi'));
425+
t.plan(3);
426+
t.assert.strictEqual('dz', info.format);
427+
const tile = path.join(directory, '10', '0_1.jpeg');
428+
const metadata = await sharp(tile).metadata();
429+
t.assert.strictEqual(true, metadata.width === 512);
430+
t.assert.strictEqual(true, metadata.height === 170);
431+
});
432+
414433
test('Deep Zoom layout with depth of one', async (t) => {
415434
const directory = fixtures.path('output.512_depth_one.dzi_files');
416435
await fs.rm(directory, { recursive: true, force: true });

0 commit comments

Comments
 (0)