image-js 1.0.0 → 1.2.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 (140) hide show
  1. package/dist/image-js.esm.js +1041 -245
  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 +1009 -211
  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 +4 -5
  10. package/lib/Mask.d.ts +1 -1
  11. package/lib/Mask.js +1 -1
  12. package/lib/draw/drawLineOnImage.d.ts.map +1 -1
  13. package/lib/draw/drawLineOnImage.js.map +1 -1
  14. package/lib/extra/draw/draw_text.d.ts +47 -0
  15. package/lib/extra/draw/draw_text.d.ts.map +1 -0
  16. package/lib/extra/draw/draw_text.js +108 -0
  17. package/lib/extra/draw/draw_text.js.map +1 -0
  18. package/lib/extra/draw/index.d.ts +3 -0
  19. package/lib/extra/draw/index.d.ts.map +1 -0
  20. package/lib/extra/draw/index.js +3 -0
  21. package/lib/extra/draw/index.js.map +1 -0
  22. package/lib/{ssim → extra/ssim}/compute_dssim.d.ts +2 -2
  23. package/lib/extra/ssim/compute_dssim.d.ts.map +1 -0
  24. package/lib/{ssim → extra/ssim}/compute_dssim.js +1 -1
  25. package/lib/extra/ssim/compute_dssim.js.map +1 -0
  26. package/lib/{ssim → extra/ssim}/compute_ssim.d.ts +1 -1
  27. package/lib/extra/ssim/compute_ssim.d.ts.map +1 -0
  28. package/lib/{ssim → extra/ssim}/compute_ssim.js +2 -2
  29. package/lib/extra/ssim/compute_ssim.js.map +1 -0
  30. package/lib/extra/ssim/index.d.ts +3 -0
  31. package/lib/extra/ssim/index.d.ts.map +1 -0
  32. package/lib/extra/ssim/index.js +3 -0
  33. package/lib/extra/ssim/index.js.map +1 -0
  34. package/lib/featureMatching/matching/filterEuclideanDistance.d.ts.map +1 -1
  35. package/lib/featureMatching/matching/filterEuclideanDistance.js +6 -7
  36. package/lib/featureMatching/matching/filterEuclideanDistance.js.map +1 -1
  37. package/lib/featureMatching/matching/filterSmallestDistanceMatches.d.ts.map +1 -1
  38. package/lib/featureMatching/matching/filterSmallestDistanceMatches.js +6 -3
  39. package/lib/featureMatching/matching/filterSmallestDistanceMatches.js.map +1 -1
  40. package/lib/featureMatching/utils/sortByDistance.d.ts.map +1 -1
  41. package/lib/featureMatching/utils/sortByDistance.js +3 -1
  42. package/lib/featureMatching/utils/sortByDistance.js.map +1 -1
  43. package/lib/featureMatching/utils/sortBySourceDest.d.ts.map +1 -1
  44. package/lib/featureMatching/utils/sortBySourceDest.js +6 -2
  45. package/lib/featureMatching/utils/sortBySourceDest.js.map +1 -1
  46. package/lib/index_browser.d.ts +3 -0
  47. package/lib/index_browser.d.ts.map +1 -0
  48. package/lib/index_browser.js +5 -0
  49. package/lib/index_browser.js.map +1 -0
  50. package/lib/index_full.d.ts +4 -0
  51. package/lib/index_full.d.ts.map +1 -0
  52. package/lib/index_full.js +5 -0
  53. package/lib/index_full.js.map +1 -0
  54. package/lib/load/decode.d.ts.map +1 -1
  55. package/lib/load/decode.js +2 -0
  56. package/lib/load/decode.js.map +1 -1
  57. package/lib/load/decodeBmp.d.ts +8 -0
  58. package/lib/load/decodeBmp.d.ts.map +1 -0
  59. package/lib/load/decodeBmp.js +36 -0
  60. package/lib/load/decodeBmp.js.map +1 -0
  61. package/lib/load/decodePng.d.ts.map +1 -1
  62. package/lib/load/decodePng.js +29 -5
  63. package/lib/load/decodePng.js.map +1 -1
  64. package/lib/load/decodeTiff.d.ts.map +1 -1
  65. package/lib/load/decodeTiff.js +44 -21
  66. package/lib/load/decodeTiff.js.map +1 -1
  67. package/lib/load/read.d.ts +1 -1
  68. package/lib/load/read.js +1 -1
  69. package/lib/maskAnalysis/utils/getMbrAngle.d.ts.map +1 -1
  70. package/lib/maskAnalysis/utils/getMbrAngle.js +2 -1
  71. package/lib/maskAnalysis/utils/getMbrAngle.js.map +1 -1
  72. package/lib/maskAnalysis/utils/monotoneChainConvexHull.d.ts.map +1 -1
  73. package/lib/maskAnalysis/utils/monotoneChainConvexHull.js +2 -1
  74. package/lib/maskAnalysis/utils/monotoneChainConvexHull.js.map +1 -1
  75. package/lib/save/encodeBmp.d.ts +4 -4
  76. package/lib/save/encodeBmp.d.ts.map +1 -1
  77. package/lib/save/encodeBmp.js +22 -14
  78. package/lib/save/encodeBmp.js.map +1 -1
  79. package/lib/save/write.d.ts +1 -1
  80. package/lib/save/write.js +1 -1
  81. package/lib/stack/load/decodeApng.d.ts +8 -0
  82. package/lib/stack/load/decodeApng.d.ts.map +1 -0
  83. package/lib/stack/load/decodeApng.js +60 -0
  84. package/lib/stack/load/decodeApng.js.map +1 -0
  85. package/lib/stack/load/decodeStack.d.ts.map +1 -1
  86. package/lib/stack/load/decodeStack.js +2 -0
  87. package/lib/stack/load/decodeStack.js.map +1 -1
  88. package/lib/utils/cross_platform.d.ts +8 -0
  89. package/lib/utils/cross_platform.d.ts.map +1 -1
  90. package/lib/utils/cross_platform.js +34 -2
  91. package/lib/utils/cross_platform.js.map +1 -1
  92. package/lib/utils/geometry/points.d.ts.map +1 -1
  93. package/lib/utils/geometry/points.js +3 -1
  94. package/lib/utils/geometry/points.js.map +1 -1
  95. package/lib/utils/geometry/removeClosePoints.d.ts.map +1 -1
  96. package/lib/utils/geometry/removeClosePoints.js +2 -1
  97. package/lib/utils/geometry/removeClosePoints.js.map +1 -1
  98. package/lib/utils/setBlendedPixel.d.ts +1 -1
  99. package/lib/utils/setBlendedPixel.js +1 -1
  100. package/lib/utils/setBlendedVisiblePixel.d.ts +1 -1
  101. package/lib/utils/setBlendedVisiblePixel.js +1 -1
  102. package/package.json +43 -29
  103. package/src/Mask.ts +1 -1
  104. package/src/draw/drawLineOnImage.ts +0 -1
  105. package/src/extra/draw/draw_text.ts +194 -0
  106. package/src/extra/draw/index.ts +2 -0
  107. package/src/{ssim → extra/ssim}/compute_dssim.ts +3 -3
  108. package/src/{ssim → extra/ssim}/compute_ssim.ts +3 -3
  109. package/src/extra/ssim/index.ts +2 -0
  110. package/src/featureMatching/matching/filterEuclideanDistance.ts +6 -13
  111. package/src/featureMatching/matching/filterSmallestDistanceMatches.ts +6 -9
  112. package/src/featureMatching/utils/sortByDistance.ts +3 -1
  113. package/src/featureMatching/utils/sortBySourceDest.ts +6 -2
  114. package/src/index_browser.ts +4 -0
  115. package/src/index_full.ts +4 -0
  116. package/src/load/decode.ts +2 -1
  117. package/src/load/decodeBmp.ts +37 -0
  118. package/src/load/decodePng.ts +39 -7
  119. package/src/load/decodeTiff.ts +53 -22
  120. package/src/load/read.ts +1 -1
  121. package/src/maskAnalysis/utils/getMbrAngle.ts +2 -1
  122. package/src/maskAnalysis/utils/monotoneChainConvexHull.ts +2 -1
  123. package/src/save/encodeBmp.ts +22 -15
  124. package/src/save/write.ts +1 -1
  125. package/src/stack/load/decodeApng.ts +71 -0
  126. package/src/stack/load/decodeStack.ts +2 -0
  127. package/src/utils/cross_platform.ts +47 -4
  128. package/src/utils/geometry/points.ts +3 -1
  129. package/src/utils/geometry/removeClosePoints.ts +2 -1
  130. package/src/utils/setBlendedPixel.ts +1 -1
  131. package/src/utils/setBlendedVisiblePixel.ts +1 -1
  132. package/lib/ssim/compute_dssim.d.ts.map +0 -1
  133. package/lib/ssim/compute_dssim.js.map +0 -1
  134. package/lib/ssim/compute_ssim.d.ts.map +0 -1
  135. package/lib/ssim/compute_ssim.js.map +0 -1
  136. package/lib/ssim/index.d.ts +0 -3
  137. package/lib/ssim/index.d.ts.map +0 -1
  138. package/lib/ssim/index.js +0 -3
  139. package/lib/ssim/index.js.map +0 -1
  140. package/src/ssim/index.ts +0 -2
@@ -1 +1 @@
1
- {"version":3,"file":"removeClosePoints.js","sourceRoot":"","sources":["../../../src/utils/geometry/removeClosePoints.ts"],"names":[],"mappings":"AAqBA;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAC/B,MAAe,EACf,KAAY,EACZ,OAAiC;IAEjC,MAAM,QAAQ,GAAG,OAAO,EAAE,QAAQ,IAAI,CAAC,CAAC;IACxC,MAAM,IAAI,GAAG,OAAO,EAAE,IAAI,IAAI,SAAS,CAAC;IAExC,IAAI,OAAO,EAAE,OAAO,KAAK,SAAS,IAAI,KAAK,CAAC,QAAQ,GAAG,CAAC,EAAE,CAAC;QACzD,MAAM,IAAI,KAAK,CACb,qEAAqE,CACtE,CAAC;IACJ,CAAC;IACD,MAAM,OAAO,GAAG,OAAO,EAAE,OAAO,IAAI,CAAC,CAAC;IACtC,MAAM,KAAK,GAAG,IAAI,KAAK,SAAS,CAAC;IAEjC,MAAM,YAAY,GAAG,MAAM,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC;IAEzE,IAAI,QAAQ,GAAG,CAAC,EAAE,CAAC;QACjB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,YAAY,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YAC7C,KAAK,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,YAAY,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBACjD,IACE,IAAI,CAAC,KAAK,CACR,YAAY,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC,MAAM,EAC/C,YAAY,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC,GAAG,CAC1C,GAAG,QAAQ,EACZ,CAAC;oBACD,YAAY,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;oBAC1B,CAAC,EAAE,CAAC;gBACN,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,YAAY,CAAC;AACtB,CAAC;AAED,SAAS,OAAO,CAAC,KAAY,EAAE,OAAe,EAAE,YAAqB;IACnE,IAAI,YAAY,EAAE,CAAC;QACjB,OAAO,SAAS,cAAc,CAAC,CAAQ,EAAE,CAAQ;YAC/C,OAAO,CACL,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,EAAE,OAAO,CAAC;gBACxC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,EAAE,OAAO,CAAC,CACzC,CAAC;QACJ,CAAC,CAAC;IACJ,CAAC;SAAM,CAAC;QACN,OAAO,SAAS,aAAa,CAAC,CAAQ,EAAE,CAAQ;YAC9C,OAAO,CACL,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,EAAE,OAAO,CAAC;gBACxC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,EAAE,OAAO,CAAC,CACzC,CAAC;QACJ,CAAC,CAAC;IACJ,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"removeClosePoints.js","sourceRoot":"","sources":["../../../src/utils/geometry/removeClosePoints.ts"],"names":[],"mappings":"AAqBA;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAC/B,MAAe,EACf,KAAY,EACZ,OAAiC;IAEjC,MAAM,QAAQ,GAAG,OAAO,EAAE,QAAQ,IAAI,CAAC,CAAC;IACxC,MAAM,IAAI,GAAG,OAAO,EAAE,IAAI,IAAI,SAAS,CAAC;IAExC,IAAI,OAAO,EAAE,OAAO,KAAK,SAAS,IAAI,KAAK,CAAC,QAAQ,GAAG,CAAC,EAAE,CAAC;QACzD,MAAM,IAAI,KAAK,CACb,qEAAqE,CACtE,CAAC;IACJ,CAAC;IACD,MAAM,OAAO,GAAG,OAAO,EAAE,OAAO,IAAI,CAAC,CAAC;IACtC,MAAM,KAAK,GAAG,IAAI,KAAK,SAAS,CAAC;IAEjC,MAAM,YAAY,GAAG,MAAM,CAAC,KAAK,EAAE,CAAC;IACpC,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC;IAElD,IAAI,QAAQ,GAAG,CAAC,EAAE,CAAC;QACjB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,YAAY,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YAC7C,KAAK,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,YAAY,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBACjD,IACE,IAAI,CAAC,KAAK,CACR,YAAY,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC,MAAM,EAC/C,YAAY,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC,GAAG,CAC1C,GAAG,QAAQ,EACZ,CAAC;oBACD,YAAY,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;oBAC1B,CAAC,EAAE,CAAC;gBACN,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,YAAY,CAAC;AACtB,CAAC;AAED,SAAS,OAAO,CAAC,KAAY,EAAE,OAAe,EAAE,YAAqB;IACnE,IAAI,YAAY,EAAE,CAAC;QACjB,OAAO,SAAS,cAAc,CAAC,CAAQ,EAAE,CAAQ;YAC/C,OAAO,CACL,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,EAAE,OAAO,CAAC;gBACxC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,EAAE,OAAO,CAAC,CACzC,CAAC;QACJ,CAAC,CAAC;IACJ,CAAC;SAAM,CAAC;QACN,OAAO,SAAS,aAAa,CAAC,CAAQ,EAAE,CAAQ;YAC9C,OAAO,CACL,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,EAAE,OAAO,CAAC;gBACxC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,EAAE,OAAO,CAAC,CACzC,CAAC;QACJ,CAAC,CAAC;IACJ,CAAC;AACH,CAAC"}
@@ -5,7 +5,7 @@ import type { Mask } from '../Mask.js';
5
5
  * @param image - The image with which to blend.
6
6
  * @param column - Column of the target pixel.
7
7
  * @param row - Row of the target pixel.
8
- * @param color - Color with which to blend the image pixel. @default `'Opaque black'`.
8
+ * @param color - Color with which to blend the image pixel. Default: `Opaque black`.
9
9
  */
10
10
  export declare function setBlendedPixel(image: Image | Mask, column: number, row: number, color?: number[]): void;
11
11
  //# sourceMappingURL=setBlendedPixel.d.ts.map
@@ -6,7 +6,7 @@ import { assert } from './validators/assert.js';
6
6
  * @param image - The image with which to blend.
7
7
  * @param column - Column of the target pixel.
8
8
  * @param row - Row of the target pixel.
9
- * @param color - Color with which to blend the image pixel. @default `'Opaque black'`.
9
+ * @param color - Color with which to blend the image pixel. Default: `Opaque black`.
10
10
  */
11
11
  export function setBlendedPixel(image, column, row, color) {
12
12
  color = color ?? getDefaultColor(image);
@@ -5,7 +5,7 @@ import type { Mask } from '../Mask.js';
5
5
  * @param image - The image with which to blend.
6
6
  * @param column - Column of the target pixel.
7
7
  * @param row - Row of the target pixel.
8
- * @param color - Color with which to blend the image pixel. @default `'Opaque black'`.
8
+ * @param color - Color with which to blend the image pixel. Default: `Opaque black`.
9
9
  */
10
10
  export declare function setBlendedVisiblePixel(image: Image | Mask, column: number, row: number, color?: number[]): void;
11
11
  //# sourceMappingURL=setBlendedVisiblePixel.d.ts.map
@@ -4,7 +4,7 @@ import { setBlendedPixel } from './setBlendedPixel.js';
4
4
  * @param image - The image with which to blend.
5
5
  * @param column - Column of the target pixel.
6
6
  * @param row - Row of the target pixel.
7
- * @param color - Color with which to blend the image pixel. @default `'Opaque black'`.
7
+ * @param color - Color with which to blend the image pixel. Default: `Opaque black`.
8
8
  */
9
9
  export function setBlendedVisiblePixel(image, column, row, color) {
10
10
  if (column >= 0 && column < image.width && row >= 0 && row < image.height) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "image-js",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "license": "MIT",
5
5
  "description": "Image processing and manipulation in JavaScript",
6
6
  "author": "Michaël Zasso",
@@ -12,15 +12,18 @@
12
12
  "lib",
13
13
  "src"
14
14
  ],
15
+ "types": "./lib/index.d.ts",
15
16
  "exports": {
16
17
  ".": "./lib/index.js",
17
- "./ssim": "./lib/ssim/index.js"
18
+ "./full": "./lib/index_full.js",
19
+ "./draw": "./lib/extra/draw/index.js",
20
+ "./ssim": "./lib/extra/ssim/index.js"
18
21
  },
19
22
  "scripts": {
20
23
  "api-extractor": "rimraf dist-types && api-extractor run --local",
21
24
  "build": "npm run tsc && npm run cheminfo-build",
22
25
  "check-types": "tsc --noEmit",
23
- "cheminfo-build": "cheminfo-build --entry lib/index.js --root IJS",
26
+ "cheminfo-build": "cheminfo-build --entry lib/index_browser.js --root IJS",
24
27
  "clean": "rimraf coverage dist dist-types lib",
25
28
  "demo": "vite --open",
26
29
  "eslint": "eslint . --cache",
@@ -34,7 +37,7 @@
34
37
  "tsc-build": "tsc --project tsconfig.build.json"
35
38
  },
36
39
  "dependencies": {
37
- "bresenham-zingl": "^0.2.0",
40
+ "bresenham-zingl": "^0.2.5",
38
41
  "colord": "^2.9.3",
39
42
  "fast-bmp": "^4.0.1",
40
43
  "fast-jpeg": "^3.0.1",
@@ -49,47 +52,58 @@
49
52
  "ml-ransac": "^1.0.0",
50
53
  "ml-regression-multivariate-linear": "^2.0.4",
51
54
  "ml-regression-polynomial-2d": "^1.0.0",
52
- "ml-spectra-processing": "^14.17.0",
55
+ "ml-spectra-processing": "^14.18.0",
53
56
  "robust-point-in-polygon": "^1.0.3",
57
+ "skia-canvas": "^3.0.8",
54
58
  "ssim.js": "^3.5.0",
55
- "tiff": "^7.1.1",
56
- "ts-pattern": "^5.8.0",
59
+ "tiff": "^7.1.2",
60
+ "ts-pattern": "^5.9.0",
57
61
  "uint8-base64": "^1.0.0"
58
62
  },
63
+ "lavamoat": {
64
+ "allowScripts": {
65
+ "@lavamoat/preinstall-always-fail": false,
66
+ "skia-canvas": true,
67
+ "vite>esbuild": false
68
+ }
69
+ },
59
70
  "devDependencies": {
60
- "@microsoft/api-extractor": "^7.52.10",
71
+ "@lavamoat/allow-scripts": "^3.4.0",
72
+ "@lavamoat/preinstall-always-fail": "^2.1.1",
73
+ "@microsoft/api-extractor": "^7.53.3",
61
74
  "@tailwindcss/forms": "^0.5.10",
62
- "@tailwindcss/vite": "^4.1.12",
75
+ "@tailwindcss/vite": "^4.1.16",
63
76
  "@types/jest-image-snapshot": "^6.4.0",
64
77
  "@types/js-priority-queue": "^0.0.9",
65
- "@types/node": "^24.3.0",
66
- "@types/react": "^19.1.10",
67
- "@types/react-dom": "^19.1.7",
78
+ "@types/node": "^24.9.2",
79
+ "@types/react": "^19.2.2",
80
+ "@types/react-dom": "^19.2.2",
68
81
  "@types/robust-point-in-polygon": "^1.0.4",
69
- "@vitejs/plugin-react": "^5.0.1",
70
- "@vitest/coverage-v8": "^3.2.4",
71
- "@vitest/expect": "^3.2.4",
82
+ "@types/skia-canvas": "^0.9.2",
83
+ "@vitejs/plugin-react": "^5.1.0",
84
+ "@vitest/coverage-v8": "^4.0.6",
85
+ "@vitest/expect": "^4.0.6",
72
86
  "@zakodium/tsconfig": "^1.0.2",
73
87
  "autoprefixer": "^10.4.21",
74
- "cheminfo-build": "^1.3.0",
88
+ "cheminfo-build": "^1.3.1",
75
89
  "clsx": "^2.1.1",
76
- "cross-env": "^10.0.0",
77
- "eslint": "^9.33.0",
78
- "eslint-config-cheminfo-react": "^17.0.1",
79
- "eslint-config-cheminfo-typescript": "^19.0.0",
80
- "immer": "^10.1.1",
90
+ "cross-env": "^10.1.0",
91
+ "eslint": "^9.38.0",
92
+ "eslint-config-cheminfo-react": "^18.0.1",
93
+ "eslint-config-cheminfo-typescript": "^20.0.0",
94
+ "immer": "^10.2.0",
81
95
  "jest-image-snapshot": "^6.5.1",
82
96
  "jest-matcher-deep-close-to": "^3.0.2",
83
97
  "postcss": "^8.5.6",
84
98
  "prettier": "^3.6.2",
85
- "react": "^19.1.1",
86
- "react-dom": "^19.1.1",
87
- "react-router-dom": "^7.8.1",
88
- "rimraf": "^6.0.1",
89
- "tailwindcss": "^4.1.12",
90
- "typescript": "~5.9.2",
91
- "vite": "^7.1.3",
92
- "vitest": "^3.2.4"
99
+ "react": "^19.2.0",
100
+ "react-dom": "^19.2.0",
101
+ "react-router-dom": "^7.9.5",
102
+ "rimraf": "^6.1.0",
103
+ "tailwindcss": "^4.1.16",
104
+ "typescript": "~5.9.3",
105
+ "vite": "^7.1.12",
106
+ "vitest": "^4.0.6"
93
107
  },
94
108
  "repository": {
95
109
  "type": "git",
package/src/Mask.ts CHANGED
@@ -328,7 +328,7 @@ export class Mask {
328
328
 
329
329
  /**
330
330
  * Get the number of pixels that do not have the value 0.
331
- * @returns
331
+ * @returns The number of non-zero pixels.
332
332
  */
333
333
  public getNbNonZeroPixels(): number {
334
334
  let count = 0;
@@ -50,7 +50,6 @@ export function drawLineOnImage(
50
50
  checkProcessable(newImage, {
51
51
  bitDepth: [8, 16],
52
52
  });
53
-
54
53
  line(
55
54
  Math.round(origin.column + from.column),
56
55
  Math.round(origin.row + from.row),
@@ -0,0 +1,194 @@
1
+ import type { CanvasRenderingContext2D as SkiaCanvasRenderingContext2D } from 'skia-canvas';
2
+
3
+ import type { Image, ImageDataArray } from '../../Image.ts';
4
+ import type { Point } from '../../geometry/index.ts';
5
+ import { getCanvasContext } from '../../utils/cross_platform.ts';
6
+ import { getOutputImage } from '../../utils/getOutputImage.ts';
7
+ import { validateValues } from '../../utils/validators/validators.ts';
8
+
9
+ export interface DrawTextLabel {
10
+ /**
11
+ * Text of the label.
12
+ */
13
+ content: number | string;
14
+ /**
15
+ * Position to draw the label at.
16
+ */
17
+ position: Point;
18
+ /**
19
+ * Size and type of font.
20
+ * If specified, this overrides the general font from DrawTextOptions.
21
+ */
22
+ font?: string;
23
+ /**
24
+ * Font color. Should be in rgba8 format.
25
+ * If specified, this overrides the general fontColor from DrawTextOptions.
26
+ */
27
+ fontColor?: number[];
28
+ }
29
+
30
+ export interface DrawTextOptions {
31
+ /**
32
+ * General size and type of font.
33
+ * Used as the default when font is not specified in individual text labels.
34
+ */
35
+ font?: string;
36
+ /**
37
+ * General font color.
38
+ * Used as the default when fontColor is not specified in individual text labels.
39
+ */
40
+ fontColor?: number[];
41
+ /**
42
+ * Image to which the resulting image has to be put.
43
+ */
44
+ out?: Image;
45
+ }
46
+
47
+ /**
48
+ * Draws text on an image.
49
+ * @param image - Image to write text on.
50
+ * @param text - Text to write on the image.
51
+ * @param options - Out Options
52
+ * @returns Image with drawn text.
53
+ */
54
+ export function drawText(
55
+ image: Image,
56
+ text: DrawTextLabel | DrawTextLabel[],
57
+ options?: DrawTextOptions,
58
+ ) {
59
+ if (Array.isArray(text) && text.length === 0) {
60
+ throw new Error('At least one text element must be provided');
61
+ }
62
+
63
+ const newImage = getOutputImage(image, options, { clone: true });
64
+ const defaultFont = options?.font ?? '12px Helvetica';
65
+ const defaultColor = options?.fontColor ?? [255, 255, 255, 255];
66
+
67
+ const ctx = getCanvasContext(image.width, image.height);
68
+ if (!Array.isArray(text)) {
69
+ drawTextElement(image, ctx, text, defaultFont, defaultColor);
70
+ } else {
71
+ for (const label of text) {
72
+ drawTextElement(image, ctx, label, defaultFont, defaultColor);
73
+ }
74
+ }
75
+
76
+ layerCanvas(
77
+ newImage.getRawImage().data,
78
+ ctx.getImageData(0, 0, image.width, image.height).data,
79
+ image.channels,
80
+ image.bitDepth,
81
+ );
82
+
83
+ return newImage;
84
+ }
85
+
86
+ function drawTextElement(
87
+ image: Image,
88
+ ctx: OffscreenCanvasRenderingContext2D | SkiaCanvasRenderingContext2D,
89
+ text: DrawTextLabel,
90
+ defaultFont: string,
91
+ defaultColor: number[],
92
+ ) {
93
+ ctx.font = text.font ?? defaultFont;
94
+ const fontColor = text.fontColor ?? defaultColor;
95
+ validateValues(fontColor, image);
96
+ const alpha = fontColor[3] ? fontColor[3] / 255 : 1;
97
+ const normalizedColor = [
98
+ fontColor[0] ?? 0,
99
+ fontColor[1] ?? 0,
100
+ fontColor[2] ?? 0,
101
+ alpha,
102
+ ];
103
+ ctx.fillStyle = `rgba(${normalizedColor.join(',')})`;
104
+ ctx.fillText(String(text.content), text.position.column, text.position.row);
105
+ }
106
+
107
+ /**
108
+ * Draws labels on the image data from canvas.
109
+ * @param imageData - Image data to draw text on.
110
+ * @param canvasData - Canvas data to draw on the image.
111
+ * @param numberOfChannels - Number of channels of the initial image.
112
+ * @param bitDepth - Bit depth of the initial image.
113
+ */
114
+ function layerCanvas(
115
+ imageData: ImageDataArray,
116
+ canvasData: Uint8ClampedArray,
117
+ numberOfChannels: number,
118
+ bitDepth: number,
119
+ ) {
120
+ const config = CHANNEL_CONFIGS[numberOfChannels] || CHANNEL_CONFIGS[1];
121
+ const pixelCount = canvasData.length >>> 2;
122
+ const bitShift = bitDepth - 8;
123
+
124
+ let imageIndex = 0;
125
+ let canvasIndex = 0;
126
+
127
+ for (let pixel = 0; pixel < pixelCount; pixel++) {
128
+ const canvasAlpha = canvasData[canvasIndex + 3] / 255;
129
+
130
+ // Skip transparent canvas pixels completely
131
+ if (canvasAlpha === 0) {
132
+ imageIndex += numberOfChannels;
133
+ canvasIndex += 4;
134
+ continue;
135
+ }
136
+
137
+ const invAlpha = 1 - canvasAlpha;
138
+
139
+ for (const channel of config.channelOffsets) {
140
+ const targetIndex = imageIndex + channel;
141
+ imageData[targetIndex] =
142
+ Math.round(
143
+ canvasData[canvasIndex + channel] * canvasAlpha +
144
+ imageData[targetIndex] * invAlpha,
145
+ ) << bitShift;
146
+ }
147
+ if (config.hasAlpha) {
148
+ const alphaIndex = imageIndex + (config.alphaOffset as number);
149
+ const imageAlpha = (imageData[alphaIndex] >>> bitShift) / 255;
150
+
151
+ const newAlpha = canvasAlpha + imageAlpha * (1 - canvasAlpha);
152
+ imageData[alphaIndex] = Math.round(newAlpha * 255) << bitShift;
153
+ }
154
+
155
+ imageIndex += numberOfChannels;
156
+ canvasIndex += 4;
157
+ }
158
+ }
159
+
160
+ /**
161
+ * Configuration for different channel numbers when layering canvas with onto destination image.
162
+ */
163
+ interface ChannelConfig {
164
+ channelOffsets: number[];
165
+ hasAlpha: boolean;
166
+ alphaOffset: number | undefined; // Required when hasAlpha is true
167
+ }
168
+
169
+ const CHANNEL_CONFIGS: Record<number, ChannelConfig> = {
170
+ // GREY
171
+ 1: {
172
+ channelOffsets: [0],
173
+ hasAlpha: false,
174
+ alphaOffset: undefined,
175
+ },
176
+ // GREYA
177
+ 2: {
178
+ channelOffsets: [0],
179
+ hasAlpha: true,
180
+ alphaOffset: 1,
181
+ },
182
+ 3: {
183
+ // RGB
184
+ channelOffsets: [0, 1, 2],
185
+ hasAlpha: false,
186
+ alphaOffset: undefined,
187
+ },
188
+ 4: {
189
+ // RGBA
190
+ channelOffsets: [0, 1, 2],
191
+ hasAlpha: true,
192
+ alphaOffset: 3,
193
+ },
194
+ };
@@ -0,0 +1,2 @@
1
+ export * from './draw_text.ts';
2
+ export * from '../../draw/index.ts';
@@ -1,7 +1,7 @@
1
- import type { Image } from '../Image.js';
1
+ import type { Image } from '../../Image.ts';
2
2
 
3
- import type { SsimOptions } from './compute_ssim.js';
4
- import { computeSsim } from './compute_ssim.js';
3
+ import type { SsimOptions } from './compute_ssim.ts';
4
+ import { computeSsim } from './compute_ssim.ts';
5
5
 
6
6
  /**
7
7
  * Compute the Structural Dissimilarity (DSSIM) of two GREY images.
@@ -1,8 +1,8 @@
1
1
  import { ssim as bufferSsim } from 'ssim.js';
2
2
 
3
- import type { Image } from '../Image.js';
4
- import checkProcessable from '../utils/validators/checkProcessable.js';
5
- import { validateForComparison } from '../utils/validators/validators.js';
3
+ import type { Image } from '../../Image.ts';
4
+ import checkProcessable from '../../utils/validators/checkProcessable.ts';
5
+ import { validateForComparison } from '../../utils/validators/validators.ts';
6
6
 
7
7
  export interface SsimOptions {
8
8
  /**
@@ -0,0 +1,2 @@
1
+ export * from './compute_dssim.ts';
2
+ export * from './compute_ssim.ts';
@@ -28,9 +28,8 @@ export function filterEuclideanDistance(
28
28
  ): Match[] {
29
29
  const { origin = { column: 0, row: 0 } } = options;
30
30
 
31
- const sorted = matches.sort(
32
- (a, b) => a.destinationIndex - b.destinationIndex,
33
- );
31
+ const sorted = matches.slice();
32
+ sorted.sort((a, b) => a.destinationIndex - b.destinationIndex);
34
33
 
35
34
  const result: Match[] = [];
36
35
  let sameDestMatches: Match[] = [];
@@ -39,21 +38,15 @@ export function filterEuclideanDistance(
39
38
  if (match.destinationIndex === currentIndex) {
40
39
  sameDestMatches.push(match);
41
40
  } else {
42
- result.push(
43
- ...sameDestMatches
44
- .sort((a, b) => distanceSquared(a) - distanceSquared(b))
45
- .slice(0, 1),
46
- );
41
+ sameDestMatches.sort((a, b) => distanceSquared(a) - distanceSquared(b));
42
+ result.push(...sameDestMatches.slice(0, 1));
47
43
 
48
44
  currentIndex = match.destinationIndex;
49
45
  sameDestMatches = [match];
50
46
  }
51
47
  }
52
- result.push(
53
- ...sameDestMatches
54
- .sort((a, b) => distanceSquared(a) - distanceSquared(b))
55
- .slice(0, 1),
56
- );
48
+ sameDestMatches.sort((a, b) => distanceSquared(a) - distanceSquared(b));
49
+ result.push(...sameDestMatches.slice(0, 1));
57
50
  return result;
58
51
 
59
52
  function distanceSquared(match: Match): number {
@@ -7,9 +7,8 @@ import type { Match } from './bruteForceMatch.js';
7
7
  * @returns Only the matches from source to destination with the smallest distance.
8
8
  */
9
9
  export function filterSmallestDistanceMatches(matches: Match[]): Match[] {
10
- const sorted = matches.sort(
11
- (a, b) => a.destinationIndex - b.destinationIndex,
12
- );
10
+ const sorted = matches.slice();
11
+ sorted.sort((a, b) => a.destinationIndex - b.destinationIndex);
13
12
 
14
13
  const result: Match[] = [];
15
14
  let sameDestMatches: Match[] = [];
@@ -18,16 +17,14 @@ export function filterSmallestDistanceMatches(matches: Match[]): Match[] {
18
17
  if (match.destinationIndex === currentIndex) {
19
18
  sameDestMatches.push(match);
20
19
  } else {
21
- result.push(
22
- ...sameDestMatches.sort((a, b) => a.distance - b.distance).slice(0, 1),
23
- );
20
+ sameDestMatches.sort((a, b) => a.distance - b.distance);
21
+ result.push(...sameDestMatches.slice(0, 1));
24
22
 
25
23
  currentIndex = match.destinationIndex;
26
24
  sameDestMatches = [match];
27
25
  }
28
26
  }
29
- result.push(
30
- ...sameDestMatches.sort((a, b) => a.distance - b.distance).slice(0, 1),
31
- );
27
+ sameDestMatches.sort((a, b) => a.distance - b.distance);
28
+ result.push(...sameDestMatches.slice(0, 1));
32
29
  return result;
33
30
  }
@@ -6,7 +6,9 @@ import type { Match } from '../matching/bruteForceMatch.js';
6
6
  * @returns Sorted copy of the array of matches.
7
7
  */
8
8
  export function sortByDistance(matches: Match[]): Match[] {
9
- return matches.slice().sort((match1, match2) => {
9
+ const sorted = matches.slice();
10
+ sorted.sort((match1, match2) => {
10
11
  return match1.distance - match2.distance;
11
12
  });
13
+ return sorted;
12
14
  }
@@ -6,11 +6,13 @@ import type { Match } from '../matching/bruteForceMatch.js';
6
6
  * @returns Sorted copy of the array of matches.
7
7
  */
8
8
  export function sortBySourceDest(matches: Match[]): Match[] {
9
- return matches.slice().sort((match1, match2) => {
9
+ const sorted = matches.slice();
10
+ sorted.sort((match1, match2) => {
10
11
  if (match1.sourceIndex < match2.sourceIndex) return -1;
11
12
  if (match1.sourceIndex > match2.sourceIndex) return 1;
12
13
  return match1.destinationIndex - match2.destinationIndex;
13
14
  });
15
+ return sorted;
14
16
  }
15
17
 
16
18
  /**
@@ -19,9 +21,11 @@ export function sortBySourceDest(matches: Match[]): Match[] {
19
21
  * @returns Sorted copy of the array of matches.
20
22
  */
21
23
  export function sortByDestSource(matches: Match[]): Match[] {
22
- return matches.slice().sort((match1, match2) => {
24
+ const sorted = matches.slice();
25
+ sorted.sort((match1, match2) => {
23
26
  if (match1.destinationIndex < match2.destinationIndex) return -1;
24
27
  if (match1.destinationIndex > match2.destinationIndex) return 1;
25
28
  return match1.sourceIndex - match2.sourceIndex;
26
29
  });
30
+ return sorted;
27
31
  }
@@ -0,0 +1,4 @@
1
+ // API for the browser build.
2
+ // TODO: figure out how to bundle the SSIM library. Then we can just use `index_full.ts` instead.
3
+ export * from './index.ts';
4
+ export * from './extra/draw/index.ts';
@@ -0,0 +1,4 @@
1
+ // Full API.
2
+ export * from './index.ts';
3
+ export * from './extra/draw/index.ts';
4
+ export * from './extra/ssim/index.ts';
@@ -3,10 +3,10 @@ import { match } from 'ts-pattern';
3
3
 
4
4
  import type { Image } from '../Image.js';
5
5
 
6
+ import { decodeBmp } from './decodeBmp.ts';
6
7
  import { decodeJpeg } from './decodeJpeg.js';
7
8
  import { decodePng } from './decodePng.js';
8
9
  import { decodeTiff } from './decodeTiff.js';
9
-
10
10
  /**
11
11
  * Decode input data. Data format is automatically detected.
12
12
  * Possible formats: png, jpeg and tiff.
@@ -24,6 +24,7 @@ export function decode(data: ArrayBufferView): Image {
24
24
  .with({ mime: 'image/png' }, () => decodePng(typedArray))
25
25
  .with({ mime: 'image/jpeg' }, () => decodeJpeg(typedArray))
26
26
  .with({ mime: 'image/tiff' }, () => decodeTiff(typedArray))
27
+ .with({ mime: 'image/bmp' }, () => decodeBmp(typedArray))
27
28
  .otherwise(() => {
28
29
  throw new RangeError(`invalid data format: ${type?.mime}`);
29
30
  });
@@ -0,0 +1,37 @@
1
+ import { decode } from 'fast-bmp';
2
+
3
+ import { Image } from '../Image.ts';
4
+ import { Mask } from '../Mask.ts';
5
+ import type { ImageColorModel } from '../utils/constants/colorModels.ts';
6
+
7
+ /**
8
+ * Decode a BMP. See the fast-bmp npm module.
9
+ * @param data - The data to decode.
10
+ * @returns The decoded image or mask.
11
+ */
12
+ export function decodeBmp(data: Uint8Array): Image {
13
+ const decodedData = decode(data);
14
+ if (decodedData.bitsPerPixel === 1) {
15
+ const mask = new Mask(decodedData.width, decodedData.height, {
16
+ data: decodedData.data as Uint8Array,
17
+ });
18
+ return mask.convertColor('GREY');
19
+ } else {
20
+ let colorModel: ImageColorModel;
21
+ switch (decodedData.channels) {
22
+ case 1:
23
+ colorModel = 'GREY';
24
+ break;
25
+ case 3:
26
+ colorModel = 'RGB';
27
+ break;
28
+ default:
29
+ colorModel = 'RGBA';
30
+ break;
31
+ }
32
+ return new Image(decodedData.width, decodedData.height, {
33
+ colorModel,
34
+ data: decodedData.data as Uint8Array,
35
+ });
36
+ }
37
+ }