image-js 0.34.1 → 0.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.d.ts +1 -1
- package/lib/image/transform/crop.js +50 -11
- package/lib/image/transform/rotate.js +90 -31
- package/lib/image/transform/rotateFree.js +74 -13
- package/package.json +1 -1
- package/src/image/transform/crop.js +50 -9
- package/src/image/transform/rotate.js +108 -35
- package/src/image/transform/rotateFree.js +153 -36
package/index.d.ts
CHANGED
|
@@ -32,8 +32,8 @@ function crop(options = {}) {
|
|
|
32
32
|
width = this.width - x,
|
|
33
33
|
height = this.height - y
|
|
34
34
|
} = options;
|
|
35
|
-
this.checkProcessable('
|
|
36
|
-
bitDepth: [8, 16]
|
|
35
|
+
this.checkProcessable('crop', {
|
|
36
|
+
bitDepth: [1, 8, 16]
|
|
37
37
|
});
|
|
38
38
|
x = Math.round(x);
|
|
39
39
|
y = Math.round(y);
|
|
@@ -56,24 +56,63 @@ function crop(options = {}) {
|
|
|
56
56
|
throw new RangeError(`crop: (x: ${x}, y:${y}, width:${width}, height:${height}) size is out of range`);
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
let
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
59
|
+
let result = this;
|
|
60
|
+
|
|
61
|
+
if (this.bitDepth === 1) {
|
|
62
|
+
const newImage = new _Image.default(width, height, {
|
|
63
|
+
kind: 'BINARY',
|
|
64
|
+
parent: this
|
|
65
|
+
});
|
|
66
|
+
result = cropBinary(this, newImage, x, y, width, height);
|
|
67
|
+
} else {
|
|
68
|
+
const newImage = _Image.default.createFrom(this, {
|
|
69
|
+
width,
|
|
70
|
+
height,
|
|
71
|
+
position: [x, y]
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
result = cropDefault(this, newImage, x, y, width, height);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
64
79
|
|
|
65
|
-
|
|
80
|
+
function cropDefault(img, newImage, x, y, width, height) {
|
|
81
|
+
let xWidth = width * img.channels;
|
|
66
82
|
let y1 = y + height;
|
|
67
83
|
let ptr = 0; // pointer for new array
|
|
68
84
|
|
|
69
|
-
let jLeft = x *
|
|
85
|
+
let jLeft = x * img.channels;
|
|
70
86
|
|
|
71
87
|
for (let i = y; i < y1; i++) {
|
|
72
|
-
let j = i *
|
|
88
|
+
let j = i * img.width * img.channels + jLeft;
|
|
73
89
|
let jL = j + xWidth;
|
|
74
90
|
|
|
75
91
|
for (; j < jL; j++) {
|
|
76
|
-
newImage.data[ptr++] =
|
|
92
|
+
newImage.data[ptr++] = img.data[j];
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return newImage;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function cropBinary(img, newImage, x, y, width, height) {
|
|
100
|
+
let xWidth = width * img.channels;
|
|
101
|
+
let y1 = y + height;
|
|
102
|
+
let ptr = 0; // pointer for new array
|
|
103
|
+
|
|
104
|
+
let jLeft = x * img.channels;
|
|
105
|
+
|
|
106
|
+
for (let i = y; i < y1; i++) {
|
|
107
|
+
let j = i * img.width * img.channels + jLeft;
|
|
108
|
+
let jL = j + xWidth;
|
|
109
|
+
|
|
110
|
+
for (; j < jL; j++) {
|
|
111
|
+
if (img.getBit(j)) {
|
|
112
|
+
newImage.setBit(ptr);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
++ptr;
|
|
77
116
|
}
|
|
78
117
|
}
|
|
79
118
|
|
|
@@ -23,6 +23,10 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
|
|
|
23
23
|
* @return {Image} The new rotated image
|
|
24
24
|
*/
|
|
25
25
|
function rotate(angle, options) {
|
|
26
|
+
this.checkProcessable('rotate', {
|
|
27
|
+
bitDepth: [1, 8, 16]
|
|
28
|
+
});
|
|
29
|
+
|
|
26
30
|
if (typeof angle !== 'number') {
|
|
27
31
|
throw new TypeError('angle must be a number');
|
|
28
32
|
}
|
|
@@ -57,22 +61,40 @@ function rotate(angle, options) {
|
|
|
57
61
|
|
|
58
62
|
|
|
59
63
|
function rotateLeft() {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
+
if (this.bitDepth === 1) {
|
|
65
|
+
const newImage = new _Image.default(this.height, this.width, {
|
|
66
|
+
kind: 'BINARY',
|
|
67
|
+
parent: this
|
|
68
|
+
});
|
|
69
|
+
const newMaxHeight = newImage.height - 1;
|
|
70
|
+
|
|
71
|
+
for (let i = 0; i < this.height; i++) {
|
|
72
|
+
for (let j = 0; j < this.width; j++) {
|
|
73
|
+
if (this.getBitXY(j, i)) {
|
|
74
|
+
newImage.setBitXY(i, newMaxHeight - j);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
64
78
|
|
|
65
|
-
|
|
79
|
+
return newImage;
|
|
80
|
+
} else {
|
|
81
|
+
const newImage = _Image.default.createFrom(this, {
|
|
82
|
+
width: this.height,
|
|
83
|
+
height: this.width
|
|
84
|
+
});
|
|
66
85
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
86
|
+
const newMaxHeight = newImage.height - 1;
|
|
87
|
+
|
|
88
|
+
for (let i = 0; i < this.height; i++) {
|
|
89
|
+
for (let j = 0; j < this.width; j++) {
|
|
90
|
+
for (let k = 0; k < this.channels; k++) {
|
|
91
|
+
newImage.setValueXY(i, newMaxHeight - j, k, this.getValueXY(j, i, k));
|
|
92
|
+
}
|
|
71
93
|
}
|
|
72
94
|
}
|
|
73
|
-
}
|
|
74
95
|
|
|
75
|
-
|
|
96
|
+
return newImage;
|
|
97
|
+
}
|
|
76
98
|
}
|
|
77
99
|
/**
|
|
78
100
|
* Rotates an image clockwise
|
|
@@ -83,37 +105,74 @@ function rotateLeft() {
|
|
|
83
105
|
|
|
84
106
|
|
|
85
107
|
function rotateRight() {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
108
|
+
if (this.bitDepth === 1) {
|
|
109
|
+
const newImage = new _Image.default(this.height, this.width, {
|
|
110
|
+
kind: 'BINARY',
|
|
111
|
+
parent: this
|
|
112
|
+
});
|
|
113
|
+
const newMaxWidth = newImage.width - 1;
|
|
114
|
+
|
|
115
|
+
for (let i = 0; i < this.height; i++) {
|
|
116
|
+
for (let j = 0; j < this.width; j++) {
|
|
117
|
+
if (this.getBitXY(j, i)) {
|
|
118
|
+
newImage.setBitXY(newMaxWidth - i, j);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return newImage;
|
|
124
|
+
} else {
|
|
125
|
+
const newImage = _Image.default.createFrom(this, {
|
|
126
|
+
width: this.height,
|
|
127
|
+
height: this.width
|
|
128
|
+
});
|
|
90
129
|
|
|
91
|
-
|
|
130
|
+
const newMaxWidth = newImage.width - 1;
|
|
92
131
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
132
|
+
for (let i = 0; i < this.height; i++) {
|
|
133
|
+
for (let j = 0; j < this.width; j++) {
|
|
134
|
+
for (let k = 0; k < this.channels; k++) {
|
|
135
|
+
newImage.setValueXY(newMaxWidth - i, j, k, this.getValueXY(j, i, k));
|
|
136
|
+
}
|
|
97
137
|
}
|
|
98
138
|
}
|
|
99
|
-
}
|
|
100
139
|
|
|
101
|
-
|
|
140
|
+
return newImage;
|
|
141
|
+
}
|
|
102
142
|
}
|
|
103
143
|
|
|
104
144
|
function rotate180() {
|
|
105
|
-
|
|
145
|
+
if (this.bitDepth === 1) {
|
|
146
|
+
const newImage = new _Image.default(this.width, this.height, {
|
|
147
|
+
kind: 'BINARY',
|
|
148
|
+
parent: this
|
|
149
|
+
});
|
|
150
|
+
const newMaxWidth = newImage.width - 1;
|
|
151
|
+
const newMaxHeight = newImage.height - 1;
|
|
152
|
+
|
|
153
|
+
for (let i = 0; i < this.height; i++) {
|
|
154
|
+
for (let j = 0; j < this.width; j++) {
|
|
155
|
+
if (this.getBitXY(j, i)) {
|
|
156
|
+
newImage.setBitXY(newMaxWidth - j, newMaxHeight - i);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return newImage;
|
|
162
|
+
} else {
|
|
163
|
+
const newImage = _Image.default.createFrom(this);
|
|
106
164
|
|
|
107
|
-
|
|
108
|
-
|
|
165
|
+
const newMaxWidth = newImage.width - 1;
|
|
166
|
+
const newMaxHeight = newImage.height - 1;
|
|
109
167
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
168
|
+
for (let i = 0; i < this.height; i++) {
|
|
169
|
+
for (let j = 0; j < this.width; j++) {
|
|
170
|
+
for (let k = 0; k < this.channels; k++) {
|
|
171
|
+
newImage.setValueXY(newMaxWidth - j, newMaxHeight - i, k, this.getValueXY(j, i, k));
|
|
172
|
+
}
|
|
114
173
|
}
|
|
115
174
|
}
|
|
116
|
-
}
|
|
117
175
|
|
|
118
|
-
|
|
176
|
+
return newImage;
|
|
177
|
+
}
|
|
119
178
|
}
|
|
@@ -26,12 +26,6 @@ function rotateFree(degrees, options = {}) {
|
|
|
26
26
|
const radians = degrees * Math.PI / 180;
|
|
27
27
|
const newWidth = Math.floor(Math.abs(width * Math.cos(radians)) + Math.abs(height * Math.sin(radians)));
|
|
28
28
|
const newHeight = Math.floor(Math.abs(height * Math.cos(radians)) + Math.abs(width * Math.sin(radians)));
|
|
29
|
-
|
|
30
|
-
const newImage = _Image.default.createFrom(this, {
|
|
31
|
-
width: newWidth,
|
|
32
|
-
height: newHeight
|
|
33
|
-
});
|
|
34
|
-
|
|
35
29
|
const cos = Math.cos(-radians);
|
|
36
30
|
const sin = Math.sin(-radians);
|
|
37
31
|
let x0 = newWidth / 2;
|
|
@@ -58,15 +52,38 @@ function rotateFree(degrees, options = {}) {
|
|
|
58
52
|
const incrementX = Math.floor(width / 2 - x0);
|
|
59
53
|
const incrementY = Math.floor(height / 2 - y0);
|
|
60
54
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
55
|
+
if (this.bitDepth === 1) {
|
|
56
|
+
const newImage = new _Image.default(newWidth, newHeight, {
|
|
57
|
+
kind: 'BINARY',
|
|
58
|
+
parent: this
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
switch (interpolationToUse) {
|
|
62
|
+
case _checks.validInterpolations.nearestneighbor:
|
|
63
|
+
return rotateBinaryNearestNeighbor(this, newImage, incrementX, incrementY, x0, y0, cos, sin);
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
case _checks.validInterpolations.bilinear:
|
|
66
|
+
return rotateBinaryBilinear(this, newImage, incrementX, incrementY, x0, y0, cos, sin);
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
default:
|
|
69
|
+
throw new Error(`unsupported rotate interpolation: ${interpolationToUse}`);
|
|
70
|
+
}
|
|
71
|
+
} else {
|
|
72
|
+
const newImage = _Image.default.createFrom(this, {
|
|
73
|
+
width: newWidth,
|
|
74
|
+
height: newHeight
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
switch (interpolationToUse) {
|
|
78
|
+
case _checks.validInterpolations.nearestneighbor:
|
|
79
|
+
return rotateNearestNeighbor(this, newImage, incrementX, incrementY, x0, y0, cos, sin);
|
|
80
|
+
|
|
81
|
+
case _checks.validInterpolations.bilinear:
|
|
82
|
+
return rotateBilinear(this, newImage, incrementX, incrementY, x0, y0, cos, sin);
|
|
83
|
+
|
|
84
|
+
default:
|
|
85
|
+
throw new Error(`unsupported rotate interpolation: ${interpolationToUse}`);
|
|
86
|
+
}
|
|
70
87
|
}
|
|
71
88
|
}
|
|
72
89
|
|
|
@@ -93,6 +110,21 @@ function rotateNearestNeighbor(thisImage, newImage, incrementX, incrementY, x0,
|
|
|
93
110
|
return newImage;
|
|
94
111
|
}
|
|
95
112
|
|
|
113
|
+
function rotateBinaryNearestNeighbor(thisImage, newImage, incrementX, incrementY, x0, y0, cos, sin) {
|
|
114
|
+
for (let i = 0; i < newImage.width; i += 1) {
|
|
115
|
+
for (let j = 0; j < newImage.height; j += 1) {
|
|
116
|
+
let x = Math.round((i - x0) * cos - (j - y0) * sin + x0) + incrementX;
|
|
117
|
+
let y = Math.round((j - y0) * cos + (i - x0) * sin + y0) + incrementY;
|
|
118
|
+
|
|
119
|
+
if (x < 0 || x >= thisImage.width || y < 0 || y >= thisImage.height || thisImage.getBitXY(x, y)) {
|
|
120
|
+
newImage.setBitXY(i, j);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return newImage;
|
|
126
|
+
}
|
|
127
|
+
|
|
96
128
|
function rotateBilinear(thisImage, newImage, incrementX, incrementY, x0, y0, cos, sin) {
|
|
97
129
|
let stride = thisImage.width * thisImage.channels;
|
|
98
130
|
|
|
@@ -125,5 +157,34 @@ function rotateBilinear(thisImage, newImage, incrementX, incrementY, x0, y0, cos
|
|
|
125
157
|
}
|
|
126
158
|
}
|
|
127
159
|
|
|
160
|
+
return newImage;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function rotateBinaryBilinear(thisImage, newImage, incrementX, incrementY, x0, y0, cos, sin) {
|
|
164
|
+
let stride = thisImage.width;
|
|
165
|
+
|
|
166
|
+
for (let j = 0; j < newImage.height; j++) {
|
|
167
|
+
for (let i = 0; i < newImage.width; i++) {
|
|
168
|
+
let x = (i - x0) * cos - (j - y0) * sin + x0 + incrementX;
|
|
169
|
+
let y = (j - y0) * cos + (i - x0) * sin + y0 + incrementY;
|
|
170
|
+
let x1 = x | 0;
|
|
171
|
+
let y1 = y | 0;
|
|
172
|
+
let xDiff = x - x1;
|
|
173
|
+
let yDiff = y - y1;
|
|
174
|
+
|
|
175
|
+
if (x < 0 || x >= thisImage.width || y < 0 || y >= thisImage.height) {
|
|
176
|
+
newImage.setBitXY(i, j);
|
|
177
|
+
} else {
|
|
178
|
+
let index = y1 * thisImage.width + x1;
|
|
179
|
+
let A = thisImage.getBit(index);
|
|
180
|
+
let B = thisImage.getBit(index + 1);
|
|
181
|
+
let C = thisImage.getBit(index + stride);
|
|
182
|
+
let D = thisImage.getBit(index + 1 + stride);
|
|
183
|
+
let result = A | xDiff & B - A | yDiff & C - A | xDiff & yDiff & A - B - C + D;
|
|
184
|
+
if (result > 0) newImage.setBitXY(i, j);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
128
189
|
return newImage;
|
|
129
190
|
}
|
package/package.json
CHANGED
|
@@ -23,11 +23,9 @@ export default function crop(options = {}) {
|
|
|
23
23
|
width = this.width - x,
|
|
24
24
|
height = this.height - y,
|
|
25
25
|
} = options;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
bitDepth: [8, 16],
|
|
26
|
+
this.checkProcessable('crop', {
|
|
27
|
+
bitDepth: [1, 8, 16],
|
|
29
28
|
});
|
|
30
|
-
|
|
31
29
|
x = Math.round(x);
|
|
32
30
|
y = Math.round(y);
|
|
33
31
|
width = Math.round(width);
|
|
@@ -40,36 +38,79 @@ export default function crop(options = {}) {
|
|
|
40
38
|
})`,
|
|
41
39
|
);
|
|
42
40
|
}
|
|
41
|
+
|
|
43
42
|
if (width <= 0 || height <= 0) {
|
|
44
43
|
throw new RangeError(
|
|
45
44
|
`crop: width and height (width:${width}; height:${height}) must be positive numbers`,
|
|
46
45
|
);
|
|
47
46
|
}
|
|
47
|
+
|
|
48
48
|
if (x < 0 || y < 0) {
|
|
49
49
|
throw new RangeError(
|
|
50
50
|
`crop: x and y (x:${x}, y:${y}) must be positive numbers`,
|
|
51
51
|
);
|
|
52
52
|
}
|
|
53
|
+
|
|
53
54
|
if (width > this.width - x || height > this.height - y) {
|
|
54
55
|
throw new RangeError(
|
|
55
56
|
`crop: (x: ${x}, y:${y}, width:${width}, height:${height}) size is out of range`,
|
|
56
57
|
);
|
|
57
58
|
}
|
|
58
59
|
|
|
59
|
-
let
|
|
60
|
+
let result = this;
|
|
61
|
+
if (this.bitDepth === 1) {
|
|
62
|
+
const newImage = new Image(width, height, {
|
|
63
|
+
kind: 'BINARY',
|
|
64
|
+
parent: this,
|
|
65
|
+
});
|
|
66
|
+
result = cropBinary(this, newImage, x, y, width, height);
|
|
67
|
+
} else {
|
|
68
|
+
const newImage = Image.createFrom(this, {
|
|
69
|
+
width,
|
|
70
|
+
height,
|
|
71
|
+
position: [x, y],
|
|
72
|
+
});
|
|
73
|
+
result = cropDefault(this, newImage, x, y, width, height);
|
|
74
|
+
}
|
|
60
75
|
|
|
61
|
-
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function cropDefault(img, newImage, x, y, width, height) {
|
|
80
|
+
let xWidth = width * img.channels;
|
|
62
81
|
let y1 = y + height;
|
|
82
|
+
let ptr = 0; // pointer for new array
|
|
83
|
+
|
|
84
|
+
let jLeft = x * img.channels;
|
|
85
|
+
|
|
86
|
+
for (let i = y; i < y1; i++) {
|
|
87
|
+
let j = i * img.width * img.channels + jLeft;
|
|
88
|
+
let jL = j + xWidth;
|
|
89
|
+
|
|
90
|
+
for (; j < jL; j++) {
|
|
91
|
+
newImage.data[ptr++] = img.data[j];
|
|
92
|
+
}
|
|
93
|
+
}
|
|
63
94
|
|
|
95
|
+
return newImage;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function cropBinary(img, newImage, x, y, width, height) {
|
|
99
|
+
let xWidth = width * img.channels;
|
|
100
|
+
let y1 = y + height;
|
|
64
101
|
let ptr = 0; // pointer for new array
|
|
65
102
|
|
|
66
|
-
let jLeft = x *
|
|
103
|
+
let jLeft = x * img.channels;
|
|
67
104
|
|
|
68
105
|
for (let i = y; i < y1; i++) {
|
|
69
|
-
let j = i *
|
|
106
|
+
let j = i * img.width * img.channels + jLeft;
|
|
70
107
|
let jL = j + xWidth;
|
|
108
|
+
|
|
71
109
|
for (; j < jL; j++) {
|
|
72
|
-
|
|
110
|
+
if (img.getBit(j)) {
|
|
111
|
+
newImage.setBit(ptr);
|
|
112
|
+
}
|
|
113
|
+
++ptr;
|
|
73
114
|
}
|
|
74
115
|
}
|
|
75
116
|
|
|
@@ -12,6 +12,9 @@ import rotateFree from './rotateFree';
|
|
|
12
12
|
* @return {Image} The new rotated image
|
|
13
13
|
*/
|
|
14
14
|
export function rotate(angle, options) {
|
|
15
|
+
this.checkProcessable('rotate', {
|
|
16
|
+
bitDepth: [1, 8, 16],
|
|
17
|
+
});
|
|
15
18
|
if (typeof angle !== 'number') {
|
|
16
19
|
throw new TypeError('angle must be a number');
|
|
17
20
|
}
|
|
@@ -23,39 +26,64 @@ export function rotate(angle, options) {
|
|
|
23
26
|
switch (angle % 360) {
|
|
24
27
|
case 0:
|
|
25
28
|
return this.clone();
|
|
29
|
+
|
|
26
30
|
case 90:
|
|
27
31
|
return rotateRight.call(this);
|
|
32
|
+
|
|
28
33
|
case 180:
|
|
29
34
|
return rotate180.call(this);
|
|
35
|
+
|
|
30
36
|
case 270:
|
|
31
37
|
return rotateLeft.call(this);
|
|
38
|
+
|
|
32
39
|
default:
|
|
33
40
|
return rotateFree.call(this, angle, options);
|
|
34
41
|
}
|
|
35
42
|
}
|
|
36
|
-
|
|
37
43
|
/**
|
|
38
44
|
* Rotates an image counter-clockwise
|
|
39
45
|
* @memberof Image
|
|
40
46
|
* @instance
|
|
41
47
|
* @return {Image} The new rotated image
|
|
42
48
|
*/
|
|
49
|
+
|
|
43
50
|
export function rotateLeft() {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
if (this.bitDepth === 1) {
|
|
52
|
+
const newImage = new Image(this.height, this.width, {
|
|
53
|
+
kind: 'BINARY',
|
|
54
|
+
parent: this,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const newMaxHeight = newImage.height - 1;
|
|
58
|
+
|
|
59
|
+
for (let i = 0; i < this.height; i++) {
|
|
60
|
+
for (let j = 0; j < this.width; j++) {
|
|
61
|
+
if (this.getBitXY(j, i)) {
|
|
62
|
+
newImage.setBitXY(i, newMaxHeight - j);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return newImage;
|
|
68
|
+
} else {
|
|
69
|
+
const newImage = Image.createFrom(this, {
|
|
70
|
+
width: this.height,
|
|
71
|
+
height: this.width,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const newMaxHeight = newImage.height - 1;
|
|
75
|
+
|
|
76
|
+
for (let i = 0; i < this.height; i++) {
|
|
77
|
+
for (let j = 0; j < this.width; j++) {
|
|
78
|
+
for (let k = 0; k < this.channels; k++) {
|
|
79
|
+
newImage.setValueXY(i, newMaxHeight - j, k, this.getValueXY(j, i, k));
|
|
80
|
+
}
|
|
53
81
|
}
|
|
54
82
|
}
|
|
83
|
+
|
|
84
|
+
return newImage;
|
|
55
85
|
}
|
|
56
|
-
return newImage;
|
|
57
86
|
}
|
|
58
|
-
|
|
59
87
|
/**
|
|
60
88
|
* Rotates an image clockwise
|
|
61
89
|
* @memberof Image
|
|
@@ -64,36 +92,81 @@ export function rotateLeft() {
|
|
|
64
92
|
*/
|
|
65
93
|
|
|
66
94
|
export function rotateRight() {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
95
|
+
if (this.bitDepth === 1) {
|
|
96
|
+
const newImage = new Image(this.height, this.width, {
|
|
97
|
+
kind: 'BINARY',
|
|
98
|
+
parent: this,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const newMaxWidth = newImage.width - 1;
|
|
102
|
+
|
|
103
|
+
for (let i = 0; i < this.height; i++) {
|
|
104
|
+
for (let j = 0; j < this.width; j++) {
|
|
105
|
+
if (this.getBitXY(j, i)) {
|
|
106
|
+
newImage.setBitXY(newMaxWidth - i, j);
|
|
107
|
+
}
|
|
76
108
|
}
|
|
77
109
|
}
|
|
110
|
+
|
|
111
|
+
return newImage;
|
|
112
|
+
} else {
|
|
113
|
+
const newImage = Image.createFrom(this, {
|
|
114
|
+
width: this.height,
|
|
115
|
+
height: this.width,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const newMaxWidth = newImage.width - 1;
|
|
119
|
+
|
|
120
|
+
for (let i = 0; i < this.height; i++) {
|
|
121
|
+
for (let j = 0; j < this.width; j++) {
|
|
122
|
+
for (let k = 0; k < this.channels; k++) {
|
|
123
|
+
newImage.setValueXY(newMaxWidth - i, j, k, this.getValueXY(j, i, k));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return newImage;
|
|
78
129
|
}
|
|
79
|
-
return newImage;
|
|
80
130
|
}
|
|
81
131
|
|
|
82
132
|
function rotate180() {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
)
|
|
133
|
+
if (this.bitDepth === 1) {
|
|
134
|
+
const newImage = new Image(this.width, this.height, {
|
|
135
|
+
kind: 'BINARY',
|
|
136
|
+
parent: this,
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
const newMaxWidth = newImage.width - 1;
|
|
140
|
+
const newMaxHeight = newImage.height - 1;
|
|
141
|
+
|
|
142
|
+
for (let i = 0; i < this.height; i++) {
|
|
143
|
+
for (let j = 0; j < this.width; j++) {
|
|
144
|
+
if (this.getBitXY(j, i)) {
|
|
145
|
+
newImage.setBitXY(newMaxWidth - j, newMaxHeight - i);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return newImage;
|
|
151
|
+
} else {
|
|
152
|
+
const newImage = Image.createFrom(this);
|
|
153
|
+
|
|
154
|
+
const newMaxWidth = newImage.width - 1;
|
|
155
|
+
const newMaxHeight = newImage.height - 1;
|
|
156
|
+
|
|
157
|
+
for (let i = 0; i < this.height; i++) {
|
|
158
|
+
for (let j = 0; j < this.width; j++) {
|
|
159
|
+
for (let k = 0; k < this.channels; k++) {
|
|
160
|
+
newImage.setValueXY(
|
|
161
|
+
newMaxWidth - j,
|
|
162
|
+
newMaxHeight - i,
|
|
163
|
+
k,
|
|
164
|
+
this.getValueXY(j, i, k),
|
|
165
|
+
);
|
|
166
|
+
}
|
|
95
167
|
}
|
|
96
168
|
}
|
|
169
|
+
|
|
170
|
+
return newImage;
|
|
97
171
|
}
|
|
98
|
-
return newImage;
|
|
99
172
|
}
|
|
@@ -11,8 +11,8 @@ export default function rotateFree(degrees, options = {}) {
|
|
|
11
11
|
if (typeof degrees !== 'number') {
|
|
12
12
|
throw new TypeError('degrees must be a number');
|
|
13
13
|
}
|
|
14
|
-
const interpolationToUse = checkInterpolation(interpolation);
|
|
15
14
|
|
|
15
|
+
const interpolationToUse = (0, checkInterpolation)(interpolation);
|
|
16
16
|
const radians = (degrees * Math.PI) / 180;
|
|
17
17
|
const newWidth = Math.floor(
|
|
18
18
|
Math.abs(width * Math.cos(radians)) + Math.abs(height * Math.sin(radians)),
|
|
@@ -20,17 +20,15 @@ export default function rotateFree(degrees, options = {}) {
|
|
|
20
20
|
const newHeight = Math.floor(
|
|
21
21
|
Math.abs(height * Math.cos(radians)) + Math.abs(width * Math.sin(radians)),
|
|
22
22
|
);
|
|
23
|
-
|
|
24
|
-
width: newWidth,
|
|
25
|
-
height: newHeight,
|
|
26
|
-
});
|
|
23
|
+
|
|
27
24
|
const cos = Math.cos(-radians);
|
|
28
25
|
const sin = Math.sin(-radians);
|
|
29
|
-
|
|
30
26
|
let x0 = newWidth / 2;
|
|
31
27
|
let y0 = newHeight / 2;
|
|
28
|
+
|
|
32
29
|
if (newWidth % 2 === 0) {
|
|
33
30
|
x0 = x0 - 0.5;
|
|
31
|
+
|
|
34
32
|
if (newHeight % 2 === 0) {
|
|
35
33
|
y0 = y0 - 0.5;
|
|
36
34
|
} else {
|
|
@@ -38,6 +36,7 @@ export default function rotateFree(degrees, options = {}) {
|
|
|
38
36
|
}
|
|
39
37
|
} else {
|
|
40
38
|
x0 = Math.floor(x0);
|
|
39
|
+
|
|
41
40
|
if (newHeight % 2 === 0) {
|
|
42
41
|
y0 = y0 - 0.5;
|
|
43
42
|
} else {
|
|
@@ -48,33 +47,78 @@ export default function rotateFree(degrees, options = {}) {
|
|
|
48
47
|
const incrementX = Math.floor(width / 2 - x0);
|
|
49
48
|
const incrementY = Math.floor(height / 2 - y0);
|
|
50
49
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
50
|
+
if (this.bitDepth === 1) {
|
|
51
|
+
const newImage = new Image(newWidth, newHeight, {
|
|
52
|
+
kind: 'BINARY',
|
|
53
|
+
parent: this,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
switch (interpolationToUse) {
|
|
57
|
+
case validInterpolations.nearestneighbor:
|
|
58
|
+
return rotateBinaryNearestNeighbor(
|
|
59
|
+
this,
|
|
60
|
+
newImage,
|
|
61
|
+
incrementX,
|
|
62
|
+
incrementY,
|
|
63
|
+
x0,
|
|
64
|
+
y0,
|
|
65
|
+
cos,
|
|
66
|
+
sin,
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
case validInterpolations.bilinear:
|
|
70
|
+
return rotateBinaryBilinear(
|
|
71
|
+
this,
|
|
72
|
+
newImage,
|
|
73
|
+
incrementX,
|
|
74
|
+
incrementY,
|
|
75
|
+
x0,
|
|
76
|
+
y0,
|
|
77
|
+
cos,
|
|
78
|
+
sin,
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
default:
|
|
82
|
+
throw new Error(
|
|
83
|
+
`unsupported rotate interpolation: ${interpolationToUse}`,
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
} else {
|
|
87
|
+
const newImage = Image.createFrom(this, {
|
|
88
|
+
width: newWidth,
|
|
89
|
+
height: newHeight,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
switch (interpolationToUse) {
|
|
93
|
+
case validInterpolations.nearestneighbor:
|
|
94
|
+
return rotateNearestNeighbor(
|
|
95
|
+
this,
|
|
96
|
+
newImage,
|
|
97
|
+
incrementX,
|
|
98
|
+
incrementY,
|
|
99
|
+
x0,
|
|
100
|
+
y0,
|
|
101
|
+
cos,
|
|
102
|
+
sin,
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
case validInterpolations.bilinear:
|
|
106
|
+
return rotateBilinear(
|
|
107
|
+
this,
|
|
108
|
+
newImage,
|
|
109
|
+
incrementX,
|
|
110
|
+
incrementY,
|
|
111
|
+
x0,
|
|
112
|
+
y0,
|
|
113
|
+
cos,
|
|
114
|
+
sin,
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
default:
|
|
118
|
+
throw new Error(
|
|
119
|
+
`unsupported rotate interpolation: ${interpolationToUse}`,
|
|
120
|
+
);
|
|
121
|
+
}
|
|
78
122
|
}
|
|
79
123
|
}
|
|
80
124
|
|
|
@@ -106,6 +150,37 @@ function rotateNearestNeighbor(
|
|
|
106
150
|
}
|
|
107
151
|
}
|
|
108
152
|
}
|
|
153
|
+
|
|
154
|
+
return newImage;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function rotateBinaryNearestNeighbor(
|
|
158
|
+
thisImage,
|
|
159
|
+
newImage,
|
|
160
|
+
incrementX,
|
|
161
|
+
incrementY,
|
|
162
|
+
x0,
|
|
163
|
+
y0,
|
|
164
|
+
cos,
|
|
165
|
+
sin,
|
|
166
|
+
) {
|
|
167
|
+
for (let i = 0; i < newImage.width; i += 1) {
|
|
168
|
+
for (let j = 0; j < newImage.height; j += 1) {
|
|
169
|
+
let x = Math.round((i - x0) * cos - (j - y0) * sin + x0) + incrementX;
|
|
170
|
+
let y = Math.round((j - y0) * cos + (i - x0) * sin + y0) + incrementY;
|
|
171
|
+
|
|
172
|
+
if (
|
|
173
|
+
x < 0 ||
|
|
174
|
+
x >= thisImage.width ||
|
|
175
|
+
y < 0 ||
|
|
176
|
+
y >= thisImage.height ||
|
|
177
|
+
thisImage.getBitXY(x, y)
|
|
178
|
+
) {
|
|
179
|
+
newImage.setBitXY(i, j);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
109
184
|
return newImage;
|
|
110
185
|
}
|
|
111
186
|
|
|
@@ -120,6 +195,7 @@ function rotateBilinear(
|
|
|
120
195
|
sin,
|
|
121
196
|
) {
|
|
122
197
|
let stride = thisImage.width * thisImage.channels;
|
|
198
|
+
|
|
123
199
|
for (let j = 0; j < newImage.height; j++) {
|
|
124
200
|
for (let i = 0; i < newImage.width; i++) {
|
|
125
201
|
let x = (i - x0) * cos - (j - y0) * sin + x0 + incrementX;
|
|
@@ -128,6 +204,7 @@ function rotateBilinear(
|
|
|
128
204
|
let y1 = y | 0;
|
|
129
205
|
let xDiff = x - x1;
|
|
130
206
|
let yDiff = y - y1;
|
|
207
|
+
|
|
131
208
|
for (let c = 0; c < thisImage.channels; c++) {
|
|
132
209
|
if (x < 0 || x >= thisImage.width || y < 0 || y >= thisImage.height) {
|
|
133
210
|
if (thisImage.alpha === 1 && c === thisImage.channels - 1) {
|
|
@@ -137,23 +214,63 @@ function rotateBilinear(
|
|
|
137
214
|
}
|
|
138
215
|
} else {
|
|
139
216
|
let index = (y1 * thisImage.width + x1) * thisImage.channels + c;
|
|
140
|
-
|
|
141
217
|
let A = thisImage.data[index];
|
|
142
218
|
let B = thisImage.data[index + thisImage.channels];
|
|
143
219
|
let C = thisImage.data[index + stride];
|
|
144
220
|
let D = thisImage.data[index + stride + thisImage.channels];
|
|
145
|
-
|
|
146
221
|
let result =
|
|
147
222
|
(A +
|
|
148
223
|
xDiff * (B - A) +
|
|
149
224
|
yDiff * (C - A) +
|
|
150
225
|
xDiff * yDiff * (A - B - C + D)) |
|
|
151
226
|
0;
|
|
152
|
-
|
|
153
227
|
newImage.setValueXY(i, j, c, result);
|
|
154
228
|
}
|
|
155
229
|
}
|
|
156
230
|
}
|
|
157
231
|
}
|
|
232
|
+
|
|
233
|
+
return newImage;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function rotateBinaryBilinear(
|
|
237
|
+
thisImage,
|
|
238
|
+
newImage,
|
|
239
|
+
incrementX,
|
|
240
|
+
incrementY,
|
|
241
|
+
x0,
|
|
242
|
+
y0,
|
|
243
|
+
cos,
|
|
244
|
+
sin,
|
|
245
|
+
) {
|
|
246
|
+
let stride = thisImage.width;
|
|
247
|
+
|
|
248
|
+
for (let j = 0; j < newImage.height; j++) {
|
|
249
|
+
for (let i = 0; i < newImage.width; i++) {
|
|
250
|
+
let x = (i - x0) * cos - (j - y0) * sin + x0 + incrementX;
|
|
251
|
+
let y = (j - y0) * cos + (i - x0) * sin + y0 + incrementY;
|
|
252
|
+
let x1 = x | 0;
|
|
253
|
+
let y1 = y | 0;
|
|
254
|
+
let xDiff = x - x1;
|
|
255
|
+
let yDiff = y - y1;
|
|
256
|
+
|
|
257
|
+
if (x < 0 || x >= thisImage.width || y < 0 || y >= thisImage.height) {
|
|
258
|
+
newImage.setBitXY(i, j);
|
|
259
|
+
} else {
|
|
260
|
+
let index = y1 * thisImage.width + x1;
|
|
261
|
+
let A = thisImage.getBit(index);
|
|
262
|
+
let B = thisImage.getBit(index + 1);
|
|
263
|
+
let C = thisImage.getBit(index + stride);
|
|
264
|
+
let D = thisImage.getBit(index + 1 + stride);
|
|
265
|
+
let result =
|
|
266
|
+
A |
|
|
267
|
+
(xDiff & (B - A)) |
|
|
268
|
+
(yDiff & (C - A)) |
|
|
269
|
+
(xDiff & yDiff & (A - B - C + D));
|
|
270
|
+
if (result > 0) newImage.setBitXY(i, j);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
158
275
|
return newImage;
|
|
159
276
|
}
|