image-js 0.33.2 → 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 CHANGED
@@ -32,7 +32,7 @@ export declare class Image {
32
32
  static createFrom(other: Image, options: ImageConstructorOptions): Image;
33
33
  static load(
34
34
  image: string | ArrayBuffer | Uint8Array,
35
- options?: RequestInit,
35
+ options?: RequestInit & { ignorePalette: boolean },
36
36
  ): Promise<Image>;
37
37
 
38
38
  getRoiManager(): RoiManager;
@@ -135,7 +135,7 @@ export declare class Image {
135
135
  // split
136
136
  // getChannel
137
137
  // combineChannels
138
- // setChannel
138
+ setChannel(channel: any, image: Image): this;
139
139
  // getSimilarity
140
140
  // getPixelsGrid
141
141
  // getBestMatch
@@ -145,14 +145,30 @@ export declare class Image {
145
145
  // extract
146
146
  // floodFill
147
147
  // paintLabels
148
- // paintMasks
148
+ paintMasks(
149
+ masks: Image | Array<Image>,
150
+ options?: {
151
+ color?: Array<number> | string;
152
+ colors?: Array<Array<number>> | Array<string>;
153
+ randomColors?: boolean;
154
+ distinctColors?: boolean;
155
+ alpha?: number;
156
+ labels?: Array<string>;
157
+ labelsPosition?: Array<Array<number>>;
158
+ labelColor?: string;
159
+ labelFont?: string;
160
+ },
161
+ ): Image;
149
162
  // paintPoints
150
163
  // paintPolyline
151
164
  // paintPolylines
152
- paintPolygon(points: Array<Array<number>>, options?: {
153
- color?: Array<number>;
154
- filled?: boolean;
155
- }): Image;
165
+ paintPolygon(
166
+ points: Array<Array<number>>,
167
+ options?: {
168
+ color?: Array<number>;
169
+ filled?: boolean;
170
+ },
171
+ ): Image;
156
172
 
157
173
  // paintPolygons
158
174
 
@@ -34,7 +34,10 @@ const isDataURL = /^data:[a-z]+\/(?:[a-z]+);base64,/;
34
34
  * @static
35
35
  * @param {string|ArrayBuffer|Buffer|Uint8Array} image - URL of the image (browser, can be a dataURL) or path (Node.js)
36
36
  * or buffer containing the binary data
37
- * @param {object} [options] - In the browser, the options object is passed to the underlying `fetch` call.
37
+ * @param {object} [options] - In the browser, the options object is passed to the underlying `fetch` call, along with
38
+ * the data URL. For binary data, the option specify decoding options.
39
+ * @param {boolean} [options.ignorePalette] - When set to true and loading a tiff from binary data, if the tiff is of
40
+ * type 3 (palette), load as single channel greyscale rather than as a pseudo-colored RGB.
38
41
  * @return {Promise<Image>}
39
42
  * @example
40
43
  * const image = await Image.load('https://example.com/image.png');
@@ -44,15 +47,15 @@ function load(image, options) {
44
47
  if (typeof image === 'string') {
45
48
  return loadURL(image, options);
46
49
  } else if (image instanceof ArrayBuffer) {
47
- return Promise.resolve(loadBinary(new Uint8Array(image)));
50
+ return Promise.resolve(loadBinary(new Uint8Array(image), undefined, options && options.ignorePalette));
48
51
  } else if (image.buffer) {
49
- return Promise.resolve(loadBinary(image));
52
+ return Promise.resolve(loadBinary(image, undefined, options && options.ignorePalette));
50
53
  } else {
51
54
  throw new Error('argument to "load" must be a string or buffer.');
52
55
  }
53
56
  }
54
57
 
55
- function loadBinary(image, base64Url) {
58
+ function loadBinary(image, base64Url, ignorePalette) {
56
59
  const type = (0, _imageType.default)(image);
57
60
 
58
61
  if (type) {
@@ -64,7 +67,7 @@ function loadBinary(image, base64Url) {
64
67
  return loadJPEG(image);
65
68
 
66
69
  case 'image/tiff':
67
- return loadTIFF(image);
70
+ return loadTIFF(image, ignorePalette);
68
71
 
69
72
  default:
70
73
  return loadGeneric(getBase64(type.mime));
@@ -94,7 +97,7 @@ function loadURL(url, options) {
94
97
 
95
98
  return binaryDataP.then(binaryData => {
96
99
  const uint8 = new Uint8Array(binaryData);
97
- return loadBinary(uint8, dataURL ? url : undefined);
100
+ return loadBinary(uint8, dataURL ? url : undefined, options && options.ignorePalette);
98
101
  });
99
102
  }
100
103
 
@@ -195,13 +198,15 @@ function loadJPEG(data) {
195
198
  return image;
196
199
  }
197
200
 
198
- function loadTIFF(data) {
201
+ function loadTIFF(data, ignorePalette) {
199
202
  let result = (0, _tiff.decode)(data);
200
203
 
201
204
  if (result.length === 1) {
202
- return getImageFromIFD(result[0]);
205
+ return getImageFromIFD(result[0], ignorePalette);
203
206
  } else {
204
- return new _Stack.default(result.map(getImageFromIFD));
207
+ return new _Stack.default(result.map(function (image) {
208
+ return getImageFromIFD(image, ignorePalette);
209
+ }));
205
210
  }
206
211
  }
207
212
 
@@ -224,8 +229,8 @@ function getMetadata(image) {
224
229
  return metadata;
225
230
  }
226
231
 
227
- function getImageFromIFD(image) {
228
- if (image.type === 3) {
232
+ function getImageFromIFD(image, ignorePalette) {
233
+ if (!ignorePalette && image.type === 3) {
229
234
  // Palette
230
235
  const data = new Uint16Array(3 * image.width * image.height);
231
236
  const palette = image.palette;
@@ -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('max', {
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 newImage = _Image.default.createFrom(this, {
60
- width,
61
- height,
62
- position: [x, y]
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
- let xWidth = width * this.channels;
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 * this.channels;
85
+ let jLeft = x * img.channels;
70
86
 
71
87
  for (let i = y; i < y1; i++) {
72
- let j = i * this.width * this.channels + jLeft;
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++] = this.data[j];
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
- const newImage = _Image.default.createFrom(this, {
61
- width: this.height,
62
- height: this.width
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
- const newMaxHeight = newImage.height - 1;
79
+ return newImage;
80
+ } else {
81
+ const newImage = _Image.default.createFrom(this, {
82
+ width: this.height,
83
+ height: this.width
84
+ });
66
85
 
67
- for (let i = 0; i < this.height; i++) {
68
- for (let j = 0; j < this.width; j++) {
69
- for (let k = 0; k < this.channels; k++) {
70
- newImage.setValueXY(i, newMaxHeight - j, k, this.getValueXY(j, i, k));
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
- return newImage;
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
- const newImage = _Image.default.createFrom(this, {
87
- width: this.height,
88
- height: this.width
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
- const newMaxWidth = newImage.width - 1;
130
+ const newMaxWidth = newImage.width - 1;
92
131
 
93
- for (let i = 0; i < this.height; i++) {
94
- for (let j = 0; j < this.width; j++) {
95
- for (let k = 0; k < this.channels; k++) {
96
- newImage.setValueXY(newMaxWidth - i, j, k, this.getValueXY(j, i, k));
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
- return newImage;
140
+ return newImage;
141
+ }
102
142
  }
103
143
 
104
144
  function rotate180() {
105
- const newImage = _Image.default.createFrom(this);
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
- const newMaxWidth = newImage.width - 1;
108
- const newMaxHeight = newImage.height - 1;
165
+ const newMaxWidth = newImage.width - 1;
166
+ const newMaxHeight = newImage.height - 1;
109
167
 
110
- for (let i = 0; i < this.height; i++) {
111
- for (let j = 0; j < this.width; j++) {
112
- for (let k = 0; k < this.channels; k++) {
113
- newImage.setValueXY(newMaxWidth - j, newMaxHeight - i, k, this.getValueXY(j, i, k));
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
- return newImage;
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
- switch (interpolationToUse) {
62
- case _checks.validInterpolations.nearestneighbor:
63
- return rotateNearestNeighbor(this, newImage, incrementX, incrementY, x0, y0, cos, sin);
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
- case _checks.validInterpolations.bilinear:
66
- return rotateBilinear(this, newImage, incrementX, incrementY, x0, y0, cos, sin);
65
+ case _checks.validInterpolations.bilinear:
66
+ return rotateBinaryBilinear(this, newImage, incrementX, incrementY, x0, y0, cos, sin);
67
67
 
68
- default:
69
- throw new Error(`unsupported rotate interpolation: ${interpolationToUse}`);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "image-js",
3
- "version": "0.33.2",
3
+ "version": "0.35.0",
4
4
  "description": "Image processing and manipulation in JavaScript",
5
5
  "keywords": [
6
6
  "image",
@@ -26,6 +26,7 @@ class Worker {
26
26
  let manager;
27
27
  let url;
28
28
  let runner = {};
29
+
29
30
  function run(...args) {
30
31
  if (!manager) {
31
32
  this.checkUrl();
@@ -35,6 +36,7 @@ class Worker {
35
36
  }
36
37
  return method.run.call(runner, ...args);
37
38
  }
39
+
38
40
  run.reset = function () {
39
41
  if (manager) {
40
42
  manager.terminate();
@@ -125,10 +125,12 @@ const exportMethods = {
125
125
  }
126
126
  const { useCanvas = false, encoder: encoderOptions = undefined } = options;
127
127
  type = getType(type);
128
+
128
129
  function dataUrl(encoder, ctx) {
129
130
  const u8 = encoder(ctx, encoderOptions);
130
131
  return toBase64URL(u8, type);
131
132
  }
133
+
132
134
  if (type === 'image/bmp') {
133
135
  return dataUrl(encodeBmp, this);
134
136
  } else if (type === 'image/png' && !useCanvas) {
@@ -19,7 +19,10 @@ const isDataURL = /^data:[a-z]+\/(?:[a-z]+);base64,/;
19
19
  * @static
20
20
  * @param {string|ArrayBuffer|Buffer|Uint8Array} image - URL of the image (browser, can be a dataURL) or path (Node.js)
21
21
  * or buffer containing the binary data
22
- * @param {object} [options] - In the browser, the options object is passed to the underlying `fetch` call.
22
+ * @param {object} [options] - In the browser, the options object is passed to the underlying `fetch` call, along with
23
+ * the data URL. For binary data, the option specify decoding options.
24
+ * @param {boolean} [options.ignorePalette] - When set to true and loading a tiff from binary data, if the tiff is of
25
+ * type 3 (palette), load as single channel greyscale rather than as a pseudo-colored RGB.
23
26
  * @return {Promise<Image>}
24
27
  * @example
25
28
  * const image = await Image.load('https://example.com/image.png');
@@ -28,15 +31,23 @@ export default function load(image, options) {
28
31
  if (typeof image === 'string') {
29
32
  return loadURL(image, options);
30
33
  } else if (image instanceof ArrayBuffer) {
31
- return Promise.resolve(loadBinary(new Uint8Array(image)));
34
+ return Promise.resolve(
35
+ loadBinary(
36
+ new Uint8Array(image),
37
+ undefined,
38
+ options && options.ignorePalette,
39
+ ),
40
+ );
32
41
  } else if (image.buffer) {
33
- return Promise.resolve(loadBinary(image));
42
+ return Promise.resolve(
43
+ loadBinary(image, undefined, options && options.ignorePalette),
44
+ );
34
45
  } else {
35
46
  throw new Error('argument to "load" must be a string or buffer.');
36
47
  }
37
48
  }
38
49
 
39
- function loadBinary(image, base64Url) {
50
+ function loadBinary(image, base64Url, ignorePalette) {
40
51
  const type = imageType(image);
41
52
  if (type) {
42
53
  switch (type.mime) {
@@ -45,12 +56,13 @@ function loadBinary(image, base64Url) {
45
56
  case 'image/jpeg':
46
57
  return loadJPEG(image);
47
58
  case 'image/tiff':
48
- return loadTIFF(image);
59
+ return loadTIFF(image, ignorePalette);
49
60
  default:
50
61
  return loadGeneric(getBase64(type.mime));
51
62
  }
52
63
  }
53
64
  return loadGeneric(getBase64('application/octet-stream'));
65
+
54
66
  function getBase64(type) {
55
67
  if (base64Url) {
56
68
  return base64Url;
@@ -70,7 +82,11 @@ function loadURL(url, options) {
70
82
  }
71
83
  return binaryDataP.then((binaryData) => {
72
84
  const uint8 = new Uint8Array(binaryData);
73
- return loadBinary(uint8, dataURL ? url : undefined);
85
+ return loadBinary(
86
+ uint8,
87
+ dataURL ? url : undefined,
88
+ options && options.ignorePalette,
89
+ );
74
90
  });
75
91
  }
76
92
 
@@ -159,12 +175,16 @@ function loadJPEG(data) {
159
175
  return image;
160
176
  }
161
177
 
162
- function loadTIFF(data) {
178
+ function loadTIFF(data, ignorePalette) {
163
179
  let result = decodeTiff(data);
164
180
  if (result.length === 1) {
165
- return getImageFromIFD(result[0]);
181
+ return getImageFromIFD(result[0], ignorePalette);
166
182
  } else {
167
- return new Stack(result.map(getImageFromIFD));
183
+ return new Stack(
184
+ result.map(function (image) {
185
+ return getImageFromIFD(image, ignorePalette);
186
+ }),
187
+ );
168
188
  }
169
189
  }
170
190
 
@@ -184,8 +204,8 @@ function getMetadata(image) {
184
204
  return metadata;
185
205
  }
186
206
 
187
- function getImageFromIFD(image) {
188
- if (image.type === 3) {
207
+ function getImageFromIFD(image, ignorePalette) {
208
+ if (!ignorePalette && image.type === 3) {
189
209
  // Palette
190
210
  const data = new Uint16Array(3 * image.width * image.height);
191
211
  const palette = image.palette;
@@ -151,5 +151,6 @@ export default function fromMask(mask, options = {}) {
151
151
  }
152
152
  }
153
153
  }
154
+
154
155
  return new RoiMap(mask, data);
155
156
  }
@@ -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
- this.checkProcessable('max', {
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 newImage = Image.createFrom(this, { width, height, position: [x, y] });
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
- let xWidth = width * this.channels;
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 * this.channels;
103
+ let jLeft = x * img.channels;
67
104
 
68
105
  for (let i = y; i < y1; i++) {
69
- let j = i * this.width * this.channels + jLeft;
106
+ let j = i * img.width * img.channels + jLeft;
70
107
  let jL = j + xWidth;
108
+
71
109
  for (; j < jL; j++) {
72
- newImage.data[ptr++] = this.data[j];
110
+ if (img.getBit(j)) {
111
+ newImage.setBit(ptr);
112
+ }
113
+ ++ptr;
73
114
  }
74
115
  }
75
116
 
@@ -31,6 +31,7 @@ export default function minimum(histogram) {
31
31
  threshold = minimumBetweenPeeks(histogramCopy, max);
32
32
  return threshold;
33
33
  }
34
+
34
35
  function smoothed(histogram) {
35
36
  // Smooth with a 3 point running mean filter
36
37
  let auHistogram = new Array(histogram.length); // a copy of the histograma for the smoothing process
@@ -42,6 +43,7 @@ function smoothed(histogram) {
42
43
  (histogram[histogram.length - 2] + histogram[histogram.length - 1]) / 3;
43
44
  return auHistogram;
44
45
  }
46
+
45
47
  function minimumBetweenPeeks(histogramBimodal, max) {
46
48
  let threshold;
47
49
  for (let i = 1; i < max; i++) {
@@ -55,6 +57,7 @@ function minimumBetweenPeeks(histogramBimodal, max) {
55
57
  }
56
58
  return threshold;
57
59
  }
60
+
58
61
  function bimodalTest(histogram) {
59
62
  // It is responsible for determining if a histogram is bimodal
60
63
  let len = histogram.length;
@@ -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
- const newImage = Image.createFrom(this, {
45
- width: this.height,
46
- height: this.width,
47
- });
48
- const newMaxHeight = newImage.height - 1;
49
- for (let i = 0; i < this.height; i++) {
50
- for (let j = 0; j < this.width; j++) {
51
- for (let k = 0; k < this.channels; k++) {
52
- newImage.setValueXY(i, newMaxHeight - j, k, this.getValueXY(j, i, k));
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
- const newImage = Image.createFrom(this, {
68
- width: this.height,
69
- height: this.width,
70
- });
71
- const newMaxWidth = newImage.width - 1;
72
- for (let i = 0; i < this.height; i++) {
73
- for (let j = 0; j < this.width; j++) {
74
- for (let k = 0; k < this.channels; k++) {
75
- newImage.setValueXY(newMaxWidth - i, j, k, this.getValueXY(j, i, k));
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
- const newImage = Image.createFrom(this);
84
- const newMaxWidth = newImage.width - 1;
85
- const newMaxHeight = newImage.height - 1;
86
- for (let i = 0; i < this.height; i++) {
87
- for (let j = 0; j < this.width; j++) {
88
- for (let k = 0; k < this.channels; k++) {
89
- newImage.setValueXY(
90
- newMaxWidth - j,
91
- newMaxHeight - i,
92
- k,
93
- this.getValueXY(j, i, k),
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
- const newImage = Image.createFrom(this, {
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
- switch (interpolationToUse) {
52
- case validInterpolations.nearestneighbor:
53
- return rotateNearestNeighbor(
54
- this,
55
- newImage,
56
- incrementX,
57
- incrementY,
58
- x0,
59
- y0,
60
- cos,
61
- sin,
62
- );
63
- case validInterpolations.bilinear:
64
- return rotateBilinear(
65
- this,
66
- newImage,
67
- incrementX,
68
- incrementY,
69
- x0,
70
- y0,
71
- cos,
72
- sin,
73
- );
74
- default:
75
- throw new Error(
76
- `unsupported rotate interpolation: ${interpolationToUse}`,
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
  }