image-js 1.4.0 → 1.6.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.
Files changed (57) hide show
  1. package/dist/image-js.esm.js +323 -6
  2. package/dist/image-js.esm.js.map +1 -1
  3. package/dist/image-js.esm.min.js +2 -2
  4. package/dist/image-js.esm.min.js.map +1 -1
  5. package/dist/image-js.umd.js +325 -10
  6. package/dist/image-js.umd.js.map +1 -1
  7. package/dist/image-js.umd.min.js +2 -2
  8. package/dist/image-js.umd.min.js.map +1 -1
  9. package/dist-types/image-js.d.ts +23 -2
  10. package/lib/Mask.d.ts +8 -0
  11. package/lib/Mask.d.ts.map +1 -1
  12. package/lib/Mask.js +10 -0
  13. package/lib/Mask.js.map +1 -1
  14. package/lib/maskAnalysis/getContourMoore.d.ts +10 -0
  15. package/lib/maskAnalysis/getContourMoore.d.ts.map +1 -0
  16. package/lib/maskAnalysis/getContourMoore.js +107 -0
  17. package/lib/maskAnalysis/getContourMoore.js.map +1 -0
  18. package/lib/maskAnalysis/getContourPavlidis.d.ts +11 -0
  19. package/lib/maskAnalysis/getContourPavlidis.d.ts.map +1 -0
  20. package/lib/maskAnalysis/getContourPavlidis.js +90 -0
  21. package/lib/maskAnalysis/getContourPavlidis.js.map +1 -0
  22. package/lib/maskAnalysis/getExternalContour.d.ts +11 -0
  23. package/lib/maskAnalysis/getExternalContour.d.ts.map +1 -0
  24. package/lib/maskAnalysis/getExternalContour.js +18 -0
  25. package/lib/maskAnalysis/getExternalContour.js.map +1 -0
  26. package/lib/maskAnalysis/maskAnalysis.types.d.ts +8 -2
  27. package/lib/maskAnalysis/maskAnalysis.types.d.ts.map +1 -1
  28. package/lib/maskAnalysis/utils/getExtendedBorderPoints.d.ts.map +1 -1
  29. package/lib/maskAnalysis/utils/getExtendedBorderPoints.js +2 -4
  30. package/lib/maskAnalysis/utils/getExtendedBorderPoints.js.map +1 -1
  31. package/lib/maskAnalysis/utils/getExternalContourUtils.d.ts +17 -0
  32. package/lib/maskAnalysis/utils/getExternalContourUtils.d.ts.map +1 -0
  33. package/lib/maskAnalysis/utils/getExternalContourUtils.js +32 -0
  34. package/lib/maskAnalysis/utils/getExternalContourUtils.js.map +1 -0
  35. package/lib/roi/Roi.d.ts +7 -0
  36. package/lib/roi/Roi.d.ts.map +1 -1
  37. package/lib/roi/Roi.js +9 -0
  38. package/lib/roi/Roi.js.map +1 -1
  39. package/lib/roi/getExternalContour.d.ts +10 -0
  40. package/lib/roi/getExternalContour.d.ts.map +1 -0
  41. package/lib/roi/getExternalContour.js +11 -0
  42. package/lib/roi/getExternalContour.js.map +1 -0
  43. package/lib/utils/cross_platform.d.ts +1 -0
  44. package/lib/utils/cross_platform.d.ts.map +1 -1
  45. package/lib/utils/cross_platform.js +9 -1
  46. package/lib/utils/cross_platform.js.map +1 -1
  47. package/package.json +4 -2
  48. package/src/Mask.ts +12 -0
  49. package/src/maskAnalysis/getContourMoore.ts +143 -0
  50. package/src/maskAnalysis/getContourPavlidis.ts +102 -0
  51. package/src/maskAnalysis/getExternalContour.ts +27 -0
  52. package/src/maskAnalysis/maskAnalysis.types.ts +8 -2
  53. package/src/maskAnalysis/utils/getExtendedBorderPoints.ts +2 -4
  54. package/src/maskAnalysis/utils/getExternalContourUtils.ts +33 -0
  55. package/src/roi/Roi.ts +11 -0
  56. package/src/roi/getExternalContour.ts +17 -0
  57. package/src/utils/cross_platform.ts +11 -1
@@ -1,10 +1,12 @@
1
1
  /*
2
- * image-js v1.4.0
2
+ * image-js v1.6.0
3
3
  * Image processing and manipulation in JavaScript
4
4
  * https://github.com/image-js/image-js#readme
5
5
  *
6
6
  * Licensed under the MIT license.
7
7
  */
8
+ import assert$1 from 'node:assert';
9
+
8
10
  function getDefaultExportFromCjs (x) {
9
11
  return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
10
12
  }
@@ -13959,6 +13961,287 @@ function getPolygonArea(points) {
13959
13961
  return Math.abs(area / 2);
13960
13962
  }
13961
13963
 
13964
+ /**
13965
+ * Gets point's row and column from their index and mask width.
13966
+ * @param index - point's index.
13967
+ * @param maskWidth - mask width.
13968
+ * @returns array of point's row and column.
13969
+ */
13970
+ function getCoordsFromIndex(index, maskWidth) {
13971
+ return [Math.floor(index / maskWidth), index % maskWidth];
13972
+ }
13973
+ /**
13974
+ * Safely get the bit value at the given column and row in the mask, returning 0 if out of bounds.
13975
+ * @param mask - Mask to get the bit from.
13976
+ * @param col - Column index.
13977
+ * @param row - Row index.
13978
+ * @returns the bit value.
13979
+ */
13980
+ function getBitSafe(mask, col, row) {
13981
+ if (!isInBounds(mask, col, row)) return 0;
13982
+ return mask.getBit(col, row);
13983
+ }
13984
+ /**
13985
+ * Checks if the given column and row are within the bounds of the mask.
13986
+ * @param mask - Mask to check against.
13987
+ * @param col - Column index.
13988
+ * @param row - Row index.
13989
+ * @returns whether the given column and row are within the bounds of the mask.
13990
+ */
13991
+ function isInBounds(mask, col, row) {
13992
+ return col >= 0 && col < mask.width && row >= 0 && row < mask.height;
13993
+ }
13994
+
13995
+ /**
13996
+ * Possible directions.
13997
+ */
13998
+ const directions = [{
13999
+ dc: -1,
14000
+ dr: 0
14001
+ },
14002
+ // left
14003
+ {
14004
+ dc: -1,
14005
+ dr: -1
14006
+ },
14007
+ // up-left
14008
+ {
14009
+ dc: 0,
14010
+ dr: -1
14011
+ },
14012
+ // up
14013
+ {
14014
+ dc: 1,
14015
+ dr: -1
14016
+ },
14017
+ // up-right
14018
+ {
14019
+ dc: 1,
14020
+ dr: 0
14021
+ },
14022
+ // right
14023
+ {
14024
+ dc: 1,
14025
+ dr: 1
14026
+ },
14027
+ // down-right
14028
+ {
14029
+ dc: 0,
14030
+ dr: 1
14031
+ },
14032
+ // down
14033
+ {
14034
+ dc: -1,
14035
+ dr: 1
14036
+ } // down-left
14037
+ ];
14038
+ // Lookup table for directions' index.
14039
+ const directionIndexLookup = new Int8Array(9).fill(-1);
14040
+ for (let i = 0; i < directions.length; i++) {
14041
+ const {
14042
+ dc,
14043
+ dr
14044
+ } = directions[i];
14045
+ directionIndexLookup[(dr + 1) * 3 + (dc + 1)] = i;
14046
+ }
14047
+ /**
14048
+ * Return an array with the coordinates of the pixels that are on the border of the mask using Moore's tracing algorithm.
14049
+ * The reference is the top-left corner of the ROI.
14050
+ * @param mask - Mask to process.
14051
+ * @returns The array of border pixels.
14052
+ */
14053
+ function getContourMoore(mask) {
14054
+ let index = 0;
14055
+ const contour = [];
14056
+ const visited = new Uint8Array(mask.width * mask.height);
14057
+ while (mask.getBitByIndex(index) !== 1 && index !== mask.size) {
14058
+ index++;
14059
+ }
14060
+ if (index === mask.size) return contour;
14061
+ const [row, col] = getCoordsFromIndex(index, mask.width);
14062
+ const startingPoint = {
14063
+ column: col,
14064
+ row
14065
+ };
14066
+ let currentPoint = startingPoint;
14067
+ visited[index] = 1;
14068
+ contour.push(currentPoint);
14069
+ const firstNext = findNextPoint(mask, currentPoint, {
14070
+ column: col - 1,
14071
+ row
14072
+ });
14073
+ if (!firstNext) {
14074
+ return contour;
14075
+ }
14076
+ let startingBacktrackPoint = firstNext.prevPoint;
14077
+ let backtrackPoint = firstNext.prevPoint;
14078
+ currentPoint = firstNext.currPoint;
14079
+ while (true) {
14080
+ index = currentPoint.row * mask.width + currentPoint.column;
14081
+ if (!visited[index]) {
14082
+ visited[index] = 1;
14083
+ contour.push(currentPoint);
14084
+ }
14085
+ const next = findNextPoint(mask, currentPoint, backtrackPoint);
14086
+ assert$1.ok(next, 'Next border point is undefined.');
14087
+ backtrackPoint = next.prevPoint;
14088
+ currentPoint = next.currPoint;
14089
+ if (currentPoint.column === startingPoint.column && currentPoint.row === startingPoint.row) {
14090
+ if (backtrackPoint.column === startingBacktrackPoint.column && backtrackPoint.row === startingBacktrackPoint.row) {
14091
+ break;
14092
+ } else {
14093
+ startingBacktrackPoint = backtrackPoint;
14094
+ }
14095
+ }
14096
+ }
14097
+ return contour;
14098
+ }
14099
+ /**
14100
+ * Finds next point to trace.
14101
+ * @param mask - Mask in check.
14102
+ * @param current - Current border point.
14103
+ * @param previous - Last non-border point to start search from.
14104
+ * @returns Object with new border point and last non-border point.
14105
+ */
14106
+ function findNextPoint(mask, current, previous) {
14107
+ let prevPoint = previous;
14108
+ // Vector from current back to previous
14109
+ const backDc = previous.column - current.column;
14110
+ const backDr = previous.row - current.row;
14111
+ const startIndex = directionIndexLookup[(backDr + 1) * 3 + (backDc + 1)];
14112
+ // Scan clockwise starting from that index.
14113
+ for (let i = 0; i < directions.length; i++) {
14114
+ const {
14115
+ dc,
14116
+ dr
14117
+ } = directions.at((startIndex + i) % directions.length);
14118
+ const newCol = current.column + dc;
14119
+ const newRow = current.row + dr;
14120
+ if (getBitSafe(mask, newCol, newRow) === 1) {
14121
+ // Stores found border point and last non-border point that
14122
+ // was found. Will be used as a starting point in the next
14123
+ // iteration.
14124
+ return {
14125
+ prevPoint,
14126
+ currPoint: {
14127
+ column: newCol,
14128
+ row: newRow
14129
+ }
14130
+ };
14131
+ } else {
14132
+ prevPoint = {
14133
+ column: newCol,
14134
+ row: newRow
14135
+ };
14136
+ }
14137
+ }
14138
+ // Is triggered only if the point has no neighbours. So it is undefined
14139
+ // only if mask is made of one pixel.
14140
+ return undefined;
14141
+ }
14142
+
14143
+ const DR = [-1, 0, 1, 0];
14144
+ const DC = [0, 1, 0, -1];
14145
+ function turnLeft(d) {
14146
+ return (d + 3) % 4;
14147
+ }
14148
+ function turnRight(d) {
14149
+ return (d + 1) % 4;
14150
+ }
14151
+ /**
14152
+ * Return an array with the coordinates of the pixels that are on the
14153
+ * external border of the mask using Theo Pavlidis's tracing algorithm.
14154
+ * The reference is the top-left corner of the ROI.
14155
+ * @param mask - Mask to process.
14156
+ * @returns The array of external border pixels.
14157
+ */
14158
+ function getContourPavlidis(mask) {
14159
+ let index = 0;
14160
+ while (index < mask.size && mask.getBitByIndex(index) !== 1) {
14161
+ index++;
14162
+ }
14163
+ if (index === mask.size) return [];
14164
+ const [row, col] = getCoordsFromIndex(index, mask.width);
14165
+ return traceFrom(mask, col, row);
14166
+ }
14167
+ /**
14168
+ * Traces contour using Pavlidis algorithm.
14169
+ * @param mask - Mask in check.
14170
+ * @param startCol - Starting column.
14171
+ * @param startRow - Starting row.
14172
+ * @returns Array of contour points.
14173
+ */
14174
+ function traceFrom(mask, startCol, startRow) {
14175
+ let row = startRow;
14176
+ let column = startCol;
14177
+ let dir = 2;
14178
+ let inPlaceTurns = 0;
14179
+ const seen = new Uint8Array(mask.width * mask.height);
14180
+ const contour = [];
14181
+ let visits = 0;
14182
+ while (true) {
14183
+ const idx = row * mask.width + column;
14184
+ if (!seen[idx]) {
14185
+ seen[idx] = 1;
14186
+ contour.push({
14187
+ row,
14188
+ column
14189
+ });
14190
+ } else if (row === startRow && column === startCol) {
14191
+ if (visits === 2) {
14192
+ break;
14193
+ } else {
14194
+ visits++;
14195
+ }
14196
+ }
14197
+ const left = turnLeft(dir);
14198
+ const right = turnRight(dir);
14199
+ const c1r = row + DR[left];
14200
+ const c1c = column + DC[left];
14201
+ const c2r = row + DR[dir];
14202
+ const c2c = column + DC[dir];
14203
+ const c3r = row + DR[right];
14204
+ const c3c = column + DC[right];
14205
+ if (getBitSafe(mask, c1c, c1r) === 1) {
14206
+ dir = left;
14207
+ row = c1r;
14208
+ column = c1c;
14209
+ inPlaceTurns = 0;
14210
+ } else if (getBitSafe(mask, c2c, c2r) === 1) {
14211
+ row = c2r;
14212
+ column = c2c;
14213
+ inPlaceTurns = 0;
14214
+ } else if (getBitSafe(mask, c3c, c3r) === 1) {
14215
+ dir = right;
14216
+ row = c3r;
14217
+ column = c3c;
14218
+ inPlaceTurns = 0;
14219
+ } else {
14220
+ dir = right;
14221
+ inPlaceTurns++;
14222
+ if (inPlaceTurns >= 4) break;
14223
+ }
14224
+ }
14225
+ return contour;
14226
+ }
14227
+
14228
+ /**
14229
+ * Finds external contour of the mask.
14230
+ * @param mask - Mask to find contours from.
14231
+ * @param options - GetExternalContourOptions.
14232
+ * @returns Array of contour points.
14233
+ */
14234
+ function getExternalContour$1(mask, options = {}) {
14235
+ const {
14236
+ allowCorners = false
14237
+ } = options;
14238
+ if (allowCorners) {
14239
+ return getContourPavlidis(mask);
14240
+ } else {
14241
+ return getContourMoore(mask);
14242
+ }
14243
+ }
14244
+
13962
14245
  /**
13963
14246
  * Get the pixels that surround an ROI. The pixels include the top and left borders,
13964
14247
  * but extend the right and bottom one by one pixel.
@@ -13968,10 +14251,7 @@ function getPolygonArea(points) {
13968
14251
  * @returns - The array of points.
13969
14252
  */
13970
14253
  function getExtendedBorderPoints(mask) {
13971
- const borderPoints = mask.getBorderPoints({
13972
- allowCorners: true,
13973
- innerBorders: false
13974
- });
14254
+ const borderPoints = getExternalContour$1(mask);
13975
14255
  const result = [];
13976
14256
  for (const point of borderPoints) {
13977
14257
  result.push(point, {
@@ -15601,6 +15881,15 @@ class Mask {
15601
15881
  getBorderPoints(options) {
15602
15882
  return getBorderPoints$1(this, options);
15603
15883
  }
15884
+ /**
15885
+ * Returns external contour of the mask. Unlike border points, returned points
15886
+ * follow the shape of the mask.
15887
+ * @param options - Get external contour options.
15888
+ * @returns Array of contour points.
15889
+ */
15890
+ getExternalContour(options) {
15891
+ return getExternalContour$1(this, options);
15892
+ }
15604
15893
  /**
15605
15894
  * Get the vertices of the convex Hull polygon of a mask.
15606
15895
  * @returns Array of the vertices of the convex Hull in clockwise order.
@@ -23204,13 +23493,22 @@ let CanvasCtorBrowser;
23204
23493
  let CanvasCtorNode;
23205
23494
  /**
23206
23495
  * Returns a 2D canvas context for rendering on the browser or Node.js.
23496
+ * On Node.js this requires the optional `skia-canvas` package to be installed.
23207
23497
  * @param width - Width of the canvas.
23208
23498
  * @param height - Height of the canvas.
23209
23499
  * @returns The initialised canvas context.
23210
23500
  */
23211
23501
  function getCanvasContext(width, height) {
23212
23502
  if (isNode()) {
23213
- CanvasCtorNode ??= getRequireFn()('skia-canvas').Canvas;
23503
+ if (!CanvasCtorNode) {
23504
+ try {
23505
+ CanvasCtorNode = getRequireFn()('skia-canvas').Canvas;
23506
+ } catch (error) {
23507
+ throw new Error('drawText on Node.js requires the optional "skia-canvas" package. Install it with: npm install skia-canvas', {
23508
+ cause: error
23509
+ });
23510
+ }
23511
+ }
23214
23512
  return new CanvasCtorNode(width, height).getContext('2d');
23215
23513
  } else {
23216
23514
  CanvasCtorBrowser ??= globalThis.OffscreenCanvas;
@@ -26550,6 +26848,17 @@ function getBorderPoints(roi, options = {}) {
26550
26848
  return mask.getBorderPoints(options);
26551
26849
  }
26552
26850
 
26851
+ /**
26852
+ * Finds external contour of the roi from its mask.
26853
+ * @param roi - roi to find contour from.
26854
+ * @param options - GetExternalContourOptions.
26855
+ * @returns Array of contour points.
26856
+ */
26857
+ function getExternalContour(roi, options) {
26858
+ const mask = roi.getMask();
26859
+ return mask.getExternalContour(options);
26860
+ }
26861
+
26553
26862
  /**
26554
26863
  * Generate a mask of an ROI.
26555
26864
  * @param roi - The ROI to generate a mask for.
@@ -26753,6 +27062,14 @@ class Roi {
26753
27062
  getBorderPoints(options) {
26754
27063
  return getBorderPoints(this, options);
26755
27064
  }
27065
+ /**
27066
+ * Finds external contour of the roi from its mask.
27067
+ * @param options - GetExternalContourOptions.
27068
+ * @returns Array of contour points.
27069
+ */
27070
+ getExternalContour(options) {
27071
+ return getExternalContour(this, options);
27072
+ }
26756
27073
  /**
26757
27074
  * Returns an array of ROIs IDs that are included in the current ROI.
26758
27075
  * This will be useful to know if there are some holes in the ROI.