redhotmagma-visualization 1.57.5 → 1.57.7

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/Three/Three2D.js CHANGED
@@ -34,7 +34,7 @@ function _getPrototypeOf(t) { return _getPrototypeOf = Object.setPrototypeOf ? O
34
34
  function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
35
35
  function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
36
36
  function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
37
- import { BackSide, Box3, CylinderGeometry, Group, ImageLoader, Mesh, MeshBasicMaterial, NearestFilter, LinearFilter, OrthographicCamera, PlaneGeometry, Texture, Vector2, Vector3, WebGLRenderer, BufferGeometry, Float32BufferAttribute, DoubleSide, LinearSRGBColorSpace } from "three";
37
+ import { BackSide, Box3, CylinderGeometry, Group, ImageLoader, Matrix3, Mesh, MeshBasicMaterial, NearestFilter, LinearFilter, OrthographicCamera, PlaneGeometry, Texture, Vector2, Vector3, WebGLRenderer, BufferGeometry, Float32BufferAttribute, DoubleSide, LinearSRGBColorSpace } from "three";
38
38
  import TWEEN from "@tweenjs/tween.js";
39
39
  import get from "lodash/get";
40
40
  import first from "lodash/first";
@@ -50,6 +50,8 @@ import { createMouseWheelEventListener, createPointerMoveEventListener } from ".
50
50
  import { EventTypes, Numeric } from "../Constants";
51
51
  import { createTextureBuilder, optimizeSizeToTexture } from "../Utils/TextureBuilder/createTextureBuilder";
52
52
  import { getDesignAreaCanvasSize } from "../Utils/DesignArea/getDesignAreaCanvasSize";
53
+ import { sampleRingAverage } from "../Utils/DesignArea/sampleBorderRingColor";
54
+ import { buildVisiblePaths, getUvBoundaryIndices } from "../Utils/DesignArea/getUvBoundaryPaths";
53
55
  import { disposeMaterial } from "../Utils/disposeMaterial";
54
56
  import { createImageRepository } from "../Utils/createImageRepository";
55
57
  import { createCache } from "../Utils/createCache";
@@ -76,6 +78,7 @@ export var Three2D = /*#__PURE__*/function (_ThreeBase) {
76
78
  _classCallCheck(this, Three2D);
77
79
  _this = _super.call(this, cfg);
78
80
  _defineProperty(_assertThisInitialized(_this), "_materialMap", new Map());
81
+ _defineProperty(_assertThisInitialized(_this), "_underlyingColorCache", new Map());
79
82
  _defineProperty(_assertThisInitialized(_this), "_designAreaList", []);
80
83
  _defineProperty(_assertThisInitialized(_this), "_currentDesignView", null);
81
84
  _defineProperty(_assertThisInitialized(_this), "_currentDesignArea", null);
@@ -808,6 +811,105 @@ export var Three2D = /*#__PURE__*/function (_ThreeBase) {
808
811
  width: width,
809
812
  height: height
810
813
  };
814
+ // the sampled colors belong to the previous product image
815
+ this._underlyingColorCache.clear();
816
+ }
817
+
818
+ /**
819
+ * Reads the average color of the product image underneath the border of the given design area.
820
+ * It is used to derive a border color that stays visible on the current product image.
821
+ * Returns null when it cannot be determined, so the caller can fall back to a static color.
822
+ * @param {string} designAreaIdentifier
823
+ * @returns {{r: number, g: number, b: number}|null}
824
+ */
825
+ }, {
826
+ key: "getDesignAreaUnderlyingColor",
827
+ value: function getDesignAreaUnderlyingColor(designAreaIdentifier) {
828
+ var _this$_model2;
829
+ var image = (_this$_model2 = this._model) === null || _this$_model2 === void 0 || (_this$_model2 = _this$_model2.material) === null || _this$_model2 === void 0 || (_this$_model2 = _this$_model2.map) === null || _this$_model2 === void 0 ? void 0 : _this$_model2.image;
830
+ if (!image || !this._size.width || !this._size.height) {
831
+ return null;
832
+ }
833
+ try {
834
+ // getModelPath() covers the view and the material variant, so every product image
835
+ // that can be shown gets its own entry
836
+ var cacheKey = "".concat(this.getModelPath(), "|").concat(designAreaIdentifier);
837
+ if (this._underlyingColorCache.has(cacheKey)) {
838
+ return this._underlyingColorCache.get(cacheKey);
839
+ }
840
+ var color = this._sampleDesignAreaUnderlyingColor(designAreaIdentifier, image);
841
+ this._underlyingColorCache.set(cacheKey, color);
842
+ return color;
843
+ } catch (e) {
844
+ // this runs while drawing, so never let it break the render
845
+ // eslint-disable-next-line no-console
846
+ console.warn("Could not determine the color underneath the design area border.", e);
847
+ return null;
848
+ }
849
+ }
850
+ }, {
851
+ key: "_sampleDesignAreaUnderlyingColor",
852
+ value: function _sampleDesignAreaUnderlyingColor(designAreaIdentifier, image) {
853
+ var _geometry$attributes,
854
+ _this7 = this;
855
+ var mesh = this.findAreaObjectByName(designAreaIdentifier);
856
+ var geometry = mesh === null || mesh === void 0 ? void 0 : mesh.geometry;
857
+ if (!(geometry !== null && geometry !== void 0 && (_geometry$attributes = geometry.attributes) !== null && _geometry$attributes !== void 0 && _geometry$attributes.position)) {
858
+ return null;
859
+ }
860
+ var indices = getUvBoundaryIndices(geometry);
861
+ if (!indices.length) {
862
+ return null;
863
+ }
864
+ mesh.updateWorldMatrix(true, false);
865
+ var position = geometry.attributes.position;
866
+ var normal = geometry.attributes.normal;
867
+
868
+ // with optimizeProductImage the texture holds a resized copy, while the scene still uses the
869
+ // original image size, so the mesh has to be projected into the pixel space of the source
870
+ var scaling = image.width / this._size.width;
871
+ var point = new Vector3();
872
+ var project = function project(index) {
873
+ point.fromBufferAttribute(position, index).applyMatrix4(mesh.matrixWorld);
874
+ return {
875
+ // the product plane is centered in the scene and three.js has the y axis pointing up,
876
+ // while the image pixels are counted downwards from the top left corner
877
+ x: (_this7._size.width / 2 + point.x) * scaling,
878
+ y: (_this7._size.height / 2 - point.y) * scaling
879
+ };
880
+ };
881
+ var paths = buildVisiblePaths(indices, this._createVisibilityCheck(mesh, normal), project);
882
+ return sampleRingAverage(image, paths);
883
+ }
884
+
885
+ /**
886
+ * Back facing parts of a design area are culled by the renderer, so the border is not drawn there
887
+ * and must not be sampled either. A rotated cylinder that spans a wide angle turns part of its
888
+ * own border away from the camera.
889
+ * @param {import("three").Mesh} mesh
890
+ * @param {import("three").BufferAttribute} normal
891
+ * @returns {function(number): boolean}
892
+ */
893
+ }, {
894
+ key: "_createVisibilityCheck",
895
+ value: function _createVisibilityCheck(mesh, normal) {
896
+ var _mesh$material, _mesh$material2;
897
+ // planes are rendered with DoubleSide, so both of their faces are visible
898
+ if (!normal || ((_mesh$material = mesh.material) === null || _mesh$material === void 0 ? void 0 : _mesh$material.side) === DoubleSide) {
899
+ return function () {
900
+ return true;
901
+ };
902
+ }
903
+
904
+ // the camera looks along -z, so a face pointing towards +z faces the camera,
905
+ // unless the material was flipped to the back side by the "inside" flag
906
+ var towardsCamera = ((_mesh$material2 = mesh.material) === null || _mesh$material2 === void 0 ? void 0 : _mesh$material2.side) === BackSide ? -1 : 1;
907
+ var normalMatrix = new Matrix3().getNormalMatrix(mesh.matrixWorld);
908
+ var direction = new Vector3();
909
+ return function (index) {
910
+ direction.fromBufferAttribute(normal, index).applyMatrix3(normalMatrix);
911
+ return direction.z * towardsCamera > 0;
912
+ };
811
913
  }
812
914
  }, {
813
915
  key: "getOffset",
@@ -866,7 +968,7 @@ export var Three2D = /*#__PURE__*/function (_ThreeBase) {
866
968
  }, {
867
969
  key: "moveCamera",
868
970
  value: function moveCamera(position, zoom) {
869
- var _this7 = this;
971
+ var _this8 = this;
870
972
  var duration = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
871
973
  var viewOffset = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;
872
974
  this.cleanUpCameraAnimations();
@@ -875,7 +977,7 @@ export var Three2D = /*#__PURE__*/function (_ThreeBase) {
875
977
  new TWEEN.Tween(this.camera, this.cameraTweenGroup).to({
876
978
  zoom: zoom
877
979
  }, duration.zoom || 1000).easing(TWEEN.Easing.Quadratic.Out).onUpdate(function () {
878
- return _this7.camera.updateProjectionMatrix();
980
+ return _this8.camera.updateProjectionMatrix();
879
981
  }).onComplete(animationDef.createResolver()).start();
880
982
  }
881
983
  if (position) {
@@ -892,7 +994,7 @@ export var Three2D = /*#__PURE__*/function (_ThreeBase) {
892
994
  new TWEEN.Tween(currentViewOffset, this.cameraTweenGroup).to(viewOffset, duration.viewOffset || 1000).easing(TWEEN.Easing.Quadratic.Out).onComplete(animationDef.createResolver()).start();
893
995
  }
894
996
  animationDef.then(function () {
895
- return _this7.stopAnimation("camera");
997
+ return _this8.stopAnimation("camera");
896
998
  });
897
999
  this.startAnimation("camera");
898
1000
  }
@@ -921,8 +1023,11 @@ export var Three2D = /*#__PURE__*/function (_ThreeBase) {
921
1023
  }, {
922
1024
  key: "createObjectsForDesignAreas",
923
1025
  value: function createObjectsForDesignAreas(designAreas) {
924
- var _this8 = this;
1026
+ var _this9 = this;
925
1027
  var containerGroup = new Group();
1028
+
1029
+ // the design area geometry is rebuilt here, so previously sampled colors may be outdated
1030
+ this._underlyingColorCache.clear();
926
1031
  var EPS = 10;
927
1032
  var index = 1;
928
1033
  designAreas.forEach(function (designArea) {
@@ -931,7 +1036,7 @@ export var Three2D = /*#__PURE__*/function (_ThreeBase) {
931
1036
  try {
932
1037
  geometry = createBaseShapeGeometry(designArea);
933
1038
  } catch (e) {
934
- _this8.observable.dispatch(EventTypes.Error, e);
1039
+ _this9.observable.dispatch(EventTypes.Error, e);
935
1040
  throw e;
936
1041
  }
937
1042
  var _x$y$z$rotation$baseS = _objectSpread({
@@ -948,7 +1053,7 @@ export var Three2D = /*#__PURE__*/function (_ThreeBase) {
948
1053
  y = _x$y$z$rotation$baseS.y,
949
1054
  z = _x$y$z$rotation$baseS.z,
950
1055
  rotation = _x$y$z$rotation$baseS.rotation;
951
- var mat = _this8.createCanvasMaterial(designArea);
1056
+ var mat = _this9.createCanvasMaterial(designArea);
952
1057
  var obj = new Mesh(geometry, mat);
953
1058
  Object.assign(obj.rotation, rotation);
954
1059
  obj.position.x = x;
@@ -1227,7 +1332,7 @@ export var Three2D = /*#__PURE__*/function (_ThreeBase) {
1227
1332
  key: "getScreenshots",
1228
1333
  value: function () {
1229
1334
  var _getScreenshots = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee14(callback) {
1230
- var _this9 = this;
1335
+ var _this0 = this;
1231
1336
  var _ref0,
1232
1337
  designViewList,
1233
1338
  areaList,
@@ -1283,16 +1388,16 @@ export var Three2D = /*#__PURE__*/function (_ThreeBase) {
1283
1388
  return _regeneratorRuntime().wrap(function _callee12$(_context12) {
1284
1389
  while (1) switch (_context12.prev = _context12.next) {
1285
1390
  case 0:
1286
- _this9._screenshotCamera = null;
1287
- _this9._screenshottingInProgress = false;
1288
- _this9._materialVariantPath = origVariantPath;
1289
- _this9._currentDesignView = null;
1391
+ _this0._screenshotCamera = null;
1392
+ _this0._screenshottingInProgress = false;
1393
+ _this0._materialVariantPath = origVariantPath;
1394
+ _this0._currentDesignView = null;
1290
1395
  _context12.next = 6;
1291
- return _this9.setView(origDesignView);
1396
+ return _this0.setView(origDesignView);
1292
1397
  case 6:
1293
- _this9.renderer.setSize(origSize.width, origSize.height);
1294
- _this9.renderer.setPixelRatio(origPixelRatio);
1295
- _this9.render();
1398
+ _this0.renderer.setSize(origSize.width, origSize.height);
1399
+ _this0.renderer.setPixelRatio(origPixelRatio);
1400
+ _this0.render();
1296
1401
  removeTempOverlay();
1297
1402
  case 10:
1298
1403
  case "end":
@@ -1319,8 +1424,8 @@ export var Three2D = /*#__PURE__*/function (_ThreeBase) {
1319
1424
  case 0:
1320
1425
  variant = _step.value;
1321
1426
  data = {};
1322
- _this9.setMaterialVariantPath(variant);
1323
- _this9._currentDesignView = null;
1427
+ _this0.setMaterialVariantPath(variant);
1428
+ _this0._currentDesignView = null;
1324
1429
  _iterator2 = _createForOfIteratorHelper(views);
1325
1430
  _context15.prev = 5;
1326
1431
  _loop2 = /*#__PURE__*/_regeneratorRuntime().mark(function _loop2() {
@@ -1329,7 +1434,7 @@ export var Three2D = /*#__PURE__*/function (_ThreeBase) {
1329
1434
  while (1) switch (_context14.prev = _context14.next) {
1330
1435
  case 0:
1331
1436
  view = _step2.value;
1332
- if (!_this9._abortScreenshotting) {
1437
+ if (!_this0._abortScreenshotting) {
1333
1438
  _context14.next = 7;
1334
1439
  break;
1335
1440
  }
@@ -1348,35 +1453,35 @@ export var Three2D = /*#__PURE__*/function (_ThreeBase) {
1348
1453
  _context14.next = 10;
1349
1454
  return new Promise(function (resolve) {
1350
1455
  window.setTimeout( /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee13() {
1351
- var _this9$getScreenshotS, width, height;
1456
+ var _this0$getScreenshotS, width, height;
1352
1457
  return _regeneratorRuntime().wrap(function _callee13$(_context13) {
1353
1458
  while (1) switch (_context13.prev = _context13.next) {
1354
1459
  case 0:
1355
1460
  _context13.next = 2;
1356
- return _this9.updateView(view, {
1461
+ return _this0.updateView(view, {
1357
1462
  forceUpdate: false,
1358
1463
  updateMaterial: false
1359
1464
  });
1360
1465
  case 2:
1361
- _this9$getScreenshotS = _this9.getScreenshotSize(size), width = _this9$getScreenshotS.width, height = _this9$getScreenshotS.height;
1466
+ _this0$getScreenshotS = _this0.getScreenshotSize(size), width = _this0$getScreenshotS.width, height = _this0$getScreenshotS.height;
1362
1467
  setOrthographicCameraSize(camera, width, height);
1363
- _this9.renderer.setSize(width, height);
1364
- _this9.renderer.setPixelRatio(1);
1365
- camera.zoom = _this9.getZoomForSize({
1468
+ _this0.renderer.setSize(width, height);
1469
+ _this0.renderer.setPixelRatio(1);
1470
+ camera.zoom = _this0.getZoomForSize({
1366
1471
  width: width,
1367
1472
  height: height
1368
1473
  });
1369
1474
  camera.updateProjectionMatrix();
1370
1475
  _context13.next = 10;
1371
- return _this9.dispatchMaterialUpdateEvent({
1476
+ return _this0.dispatchMaterialUpdateEvent({
1372
1477
  screenshot: true
1373
1478
  });
1374
1479
  case 10:
1375
1480
  // set size and pixel ratio right before rendering, to make sure they are correct
1376
- _this9.renderer.setSize(width, height);
1377
- _this9.renderer.setPixelRatio(1);
1378
- _this9.renderer.render(_this9.scene, camera);
1379
- data[view.identifier] = _this9.renderer.domElement.toDataURL();
1481
+ _this0.renderer.setSize(width, height);
1482
+ _this0.renderer.setPixelRatio(1);
1483
+ _this0.renderer.render(_this0.scene, camera);
1484
+ data[view.identifier] = _this0.renderer.domElement.toDataURL();
1380
1485
  resolve();
1381
1486
  case 15:
1382
1487
  case "end":
@@ -0,0 +1,125 @@
1
+ function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); }
2
+ function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
3
+ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
4
+ function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); }
5
+ function _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(r); }
6
+ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
7
+ var EPSILON = 1e-4;
8
+
9
+ /**
10
+ * Collects the vertices on the outer edge of a uv mapped geometry, in perimeter order.
11
+ * The design area border is stroked along the edge of the texture, so these are the vertices the
12
+ * border runs through. It works for any geometry whose uv attribute spans the full 0..1 range,
13
+ * which covers the plane, the control point grid and the open ended cylinder.
14
+ * @param {import("three").BufferGeometry} geometry
15
+ * @returns {number[]} Vertex indices, without a repeated closing vertex
16
+ */
17
+ export var getUvBoundaryIndices = function getUvBoundaryIndices(geometry) {
18
+ var _geometry$attributes;
19
+ var uv = geometry === null || geometry === void 0 || (_geometry$attributes = geometry.attributes) === null || _geometry$attributes === void 0 ? void 0 : _geometry$attributes.uv;
20
+ if (!uv || !uv.count) {
21
+ return [];
22
+ }
23
+ var us = [];
24
+ var vs = [];
25
+ var uMin = Infinity;
26
+ var uMax = -Infinity;
27
+ var vMin = Infinity;
28
+ var vMax = -Infinity;
29
+ // a cylinder gets one segment per pixel of arc length, so this can be a few thousand vertices,
30
+ // which is why the extremes are collected in a loop rather than by spreading into Math.min
31
+ for (var i = 0; i < uv.count; i++) {
32
+ var u = uv.getX(i);
33
+ var v = uv.getY(i);
34
+ us.push(u);
35
+ vs.push(v);
36
+ uMin = Math.min(uMin, u);
37
+ uMax = Math.max(uMax, u);
38
+ vMin = Math.min(vMin, v);
39
+ vMax = Math.max(vMax, v);
40
+ }
41
+
42
+ // a geometry without an extent in one direction has no perimeter to walk
43
+ if (uMax - uMin < EPSILON || vMax - vMin < EPSILON) {
44
+ return [];
45
+ }
46
+ var on = function on(value, target) {
47
+ return Math.abs(value - target) < EPSILON;
48
+ };
49
+ var collect = function collect(isOnEdge, sortBy, descending) {
50
+ return us.map(function (u, i) {
51
+ return i;
52
+ }).filter(function (i) {
53
+ return isOnEdge(us[i], vs[i]);
54
+ }).sort(function (a, b) {
55
+ return descending ? sortBy[b] - sortBy[a] : sortBy[a] - sortBy[b];
56
+ });
57
+ };
58
+
59
+ // walk the four edges so that the result is a continuous ring
60
+ var ring = [].concat(_toConsumableArray(collect(function (u, v) {
61
+ return on(v, vMin);
62
+ }, us, false)), _toConsumableArray(collect(function (u) {
63
+ return on(u, uMax);
64
+ }, vs, false)), _toConsumableArray(collect(function (u, v) {
65
+ return on(v, vMax);
66
+ }, us, true)), _toConsumableArray(collect(function (u) {
67
+ return on(u, uMin);
68
+ }, vs, true)));
69
+
70
+ // the corners are part of two edges each, and the last vertex closes onto the first one
71
+ var deduped = ring.filter(function (index, i) {
72
+ return index !== ring[i - 1];
73
+ });
74
+ while (deduped.length > 1 && deduped[deduped.length - 1] === deduped[0]) {
75
+ deduped.pop();
76
+ }
77
+ return deduped;
78
+ };
79
+
80
+ /**
81
+ * Turns the boundary ring into the paths that are actually drawn on screen.
82
+ * Vertices facing away from the camera are culled by the renderer, so the border is not visible
83
+ * there and those parts must not be sampled either. The ring is treated as cyclic, so a visible
84
+ * run may wrap around its end.
85
+ * @param {number[]} indices The boundary ring, as returned by getUvBoundaryIndices
86
+ * @param {function(number): boolean} isVisible
87
+ * @param {function(number): {x: number, y: number}} project Maps a vertex index to a 2D point
88
+ * @returns {Array<Array<{x: number, y: number}>>} Open polylines, or one closed loop
89
+ */
90
+ export var buildVisiblePaths = function buildVisiblePaths(indices, isVisible, project) {
91
+ if (indices.length < 2) {
92
+ return [];
93
+ }
94
+ var visible = indices.map(isVisible);
95
+ if (visible.every(Boolean)) {
96
+ // nothing is culled, so the border is a closed loop
97
+ var loop = indices.map(project);
98
+ return [[].concat(_toConsumableArray(loop), [loop[0]])];
99
+ }
100
+ if (!visible.some(Boolean)) {
101
+ return [];
102
+ }
103
+
104
+ // start right after a culled vertex, so that no visible run is split across the array end
105
+ var offset = visible.indexOf(false);
106
+ var paths = [];
107
+ var current = null;
108
+ for (var i = 1; i <= indices.length; i++) {
109
+ var at = (offset + i) % indices.length;
110
+ if (!visible[at]) {
111
+ current = null;
112
+ continue;
113
+ }
114
+ if (!current) {
115
+ current = [];
116
+ paths.push(current);
117
+ }
118
+ current.push(project(indices[at]));
119
+ }
120
+
121
+ // a single point has no segment to sample along
122
+ return paths.filter(function (path) {
123
+ return path.length > 1;
124
+ });
125
+ };
@@ -0,0 +1,251 @@
1
+ function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }
2
+ function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
3
+ function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
4
+ function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
5
+ function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; }
6
+ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
7
+ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
8
+ import { createWorkingCanvas } from "../createCanvas";
9
+
10
+ /**
11
+ * Default size of the sampled band, relative to the shorter side of the sampled paths.
12
+ * @type {number}
13
+ */
14
+ var RING_WIDTH_RATIO = 0.03;
15
+ var RING_WIDTH_MIN = 4;
16
+ var SAMPLE_STEP = 2;
17
+ /**
18
+ * If the sampled band is mostly transparent there is no meaningful "underlying color",
19
+ * so the caller should keep its static color instead.
20
+ * @type {number}
21
+ */
22
+ var MIN_MEAN_ALPHA = 0.1;
23
+
24
+ /**
25
+ * @param {Array<Array<{x: number, y: number}>>} paths
26
+ * @returns {{x: number, y: number, width: number, height: number}|null}
27
+ */
28
+ var getPathsBounds = function getPathsBounds(paths) {
29
+ var minX = Infinity;
30
+ var minY = Infinity;
31
+ var maxX = -Infinity;
32
+ var maxY = -Infinity;
33
+ var _iterator = _createForOfIteratorHelper(paths),
34
+ _step;
35
+ try {
36
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
37
+ var path = _step.value;
38
+ var _iterator2 = _createForOfIteratorHelper(path),
39
+ _step2;
40
+ try {
41
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
42
+ var point = _step2.value;
43
+ minX = Math.min(minX, point.x);
44
+ minY = Math.min(minY, point.y);
45
+ maxX = Math.max(maxX, point.x);
46
+ maxY = Math.max(maxY, point.y);
47
+ }
48
+ } catch (err) {
49
+ _iterator2.e(err);
50
+ } finally {
51
+ _iterator2.f();
52
+ }
53
+ }
54
+ } catch (err) {
55
+ _iterator.e(err);
56
+ } finally {
57
+ _iterator.f();
58
+ }
59
+ if (minX > maxX) {
60
+ return null;
61
+ }
62
+ return {
63
+ x: minX,
64
+ y: minY,
65
+ width: maxX - minX,
66
+ height: maxY - minY
67
+ };
68
+ };
69
+ var getRingWidth = function getRingWidth(paths, ringWidth) {
70
+ if (ringWidth) {
71
+ return ringWidth;
72
+ }
73
+ var bounds = getPathsBounds(paths);
74
+ var shorterSide = bounds ? Math.min(bounds.width, bounds.height) : 0;
75
+ return Math.max(RING_WIDTH_MIN, RING_WIDTH_RATIO * shorterSide);
76
+ };
77
+
78
+ /**
79
+ * Walks every segment of the given paths and calls back with the sample points of the band that is
80
+ * centered on it, so the callback sees the pixels just inside and just outside the border.
81
+ * The band is symmetric around the segment, which is why the winding direction does not matter.
82
+ * @param {Array<Array<{x: number, y: number}>>} paths
83
+ * @param {number} ringWidth
84
+ * @param {number} sampleStep
85
+ * @param {function(number, number): void} cb Called with the x/y of every sample point
86
+ */
87
+ var forEachRingPoint = function forEachRingPoint(paths, ringWidth, sampleStep, cb) {
88
+ var half = ringWidth / 2;
89
+ // the offsets across the band, from the inner to the outer edge
90
+ var offsets = [];
91
+ for (var o = -half; o <= half; o += sampleStep) {
92
+ offsets.push(o);
93
+ }
94
+ if (!offsets.length) {
95
+ offsets.push(0);
96
+ }
97
+ var _iterator3 = _createForOfIteratorHelper(paths),
98
+ _step3;
99
+ try {
100
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
101
+ var path = _step3.value;
102
+ for (var i = 1; i < path.length; i++) {
103
+ var from = path[i - 1];
104
+ var to = path[i];
105
+ var dx = to.x - from.x;
106
+ var dy = to.y - from.y;
107
+ var length = Math.sqrt(dx * dx + dy * dy);
108
+ if (!length) {
109
+ continue;
110
+ }
111
+ // the unit normal of the segment, the direction the band extends into
112
+ var nx = -dy / length;
113
+ var ny = dx / length;
114
+ var steps = Math.max(1, Math.ceil(length / sampleStep));
115
+ for (var s = 0; s <= steps; s++) {
116
+ var t = s / steps;
117
+ var x = from.x + dx * t;
118
+ var y = from.y + dy * t;
119
+ var _iterator4 = _createForOfIteratorHelper(offsets),
120
+ _step4;
121
+ try {
122
+ for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
123
+ var _o = _step4.value;
124
+ cb(x + nx * _o, y + ny * _o);
125
+ }
126
+ } catch (err) {
127
+ _iterator4.e(err);
128
+ } finally {
129
+ _iterator4.f();
130
+ }
131
+ }
132
+ }
133
+ }
134
+ } catch (err) {
135
+ _iterator3.e(err);
136
+ } finally {
137
+ _iterator3.f();
138
+ }
139
+ };
140
+
141
+ /**
142
+ * Averages the pixels of the border band over the given image data.
143
+ * The channels are weighted by alpha, so semi transparent pixels contribute proportionally.
144
+ * This is kept free of any canvas access so it can be tested without a DOM canvas implementation.
145
+ * @param {{width: number, height: number, data: Uint8ClampedArray|number[]}} imageData
146
+ * @param {{x: number, y: number}} origin Position of the image data within the source image
147
+ * @param {Array<Array<{x: number, y: number}>>} paths The border, in source image pixels
148
+ * @param {{[ringWidth]: number, [sampleStep]: number, [minAlpha]: number}} [options]
149
+ * @returns {{r: number, g: number, b: number}|null} null if there is nothing meaningful to sample
150
+ */
151
+ export var averageRingColorFromImageData = function averageRingColorFromImageData(imageData, origin, paths) {
152
+ var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
153
+ var _options$sampleStep = options.sampleStep,
154
+ sampleStep = _options$sampleStep === void 0 ? SAMPLE_STEP : _options$sampleStep,
155
+ _options$minAlpha = options.minAlpha,
156
+ minAlpha = _options$minAlpha === void 0 ? MIN_MEAN_ALPHA : _options$minAlpha;
157
+ var ringWidth = getRingWidth(paths, options.ringWidth);
158
+ var width = imageData.width,
159
+ height = imageData.height,
160
+ data = imageData.data;
161
+ var wr = 0;
162
+ var wg = 0;
163
+ var wb = 0;
164
+ var wa = 0;
165
+ var count = 0;
166
+ forEachRingPoint(paths, ringWidth, sampleStep, function (px, py) {
167
+ var x = Math.round(px - origin.x);
168
+ var y = Math.round(py - origin.y);
169
+ if (x < 0 || y < 0 || x >= width || y >= height) {
170
+ return;
171
+ }
172
+ var i = (y * width + x) * 4;
173
+ var a = data[i + 3];
174
+ wr += data[i] * a;
175
+ wg += data[i + 1] * a;
176
+ wb += data[i + 2] * a;
177
+ wa += a;
178
+ count++;
179
+ });
180
+ if (!count || wa / (count * 255) < minAlpha) {
181
+ return null;
182
+ }
183
+ return {
184
+ r: Math.round(wr / wa),
185
+ g: Math.round(wg / wa),
186
+ b: Math.round(wb / wa)
187
+ };
188
+ };
189
+
190
+ /**
191
+ * Bounding box of the given paths, grown by half the band width and clamped to the source.
192
+ * @param {{width: number, height: number}} source
193
+ * @param {Array<Array<{x: number, y: number}>>} paths
194
+ * @param {number} ringWidth
195
+ * @returns {{x: number, y: number, width: number, height: number}}
196
+ */
197
+ export var getSampleBounds = function getSampleBounds(source, paths, ringWidth) {
198
+ var bounds = getPathsBounds(paths);
199
+ if (!bounds) {
200
+ return {
201
+ x: 0,
202
+ y: 0,
203
+ width: 0,
204
+ height: 0
205
+ };
206
+ }
207
+ var half = ringWidth / 2;
208
+ var x = Math.max(0, Math.floor(bounds.x - half));
209
+ var y = Math.max(0, Math.floor(bounds.y - half));
210
+ var right = Math.min(source.width, Math.ceil(bounds.x + bounds.width + half));
211
+ var bottom = Math.min(source.height, Math.ceil(bounds.y + bounds.height + half));
212
+ return {
213
+ x: x,
214
+ y: y,
215
+ width: right - x,
216
+ height: bottom - y
217
+ };
218
+ };
219
+
220
+ /**
221
+ * Reads the average color of the band that the design area border is drawn on.
222
+ * @param {HTMLImageElement|HTMLCanvasElement} source The product image, in its own pixel space
223
+ * @param {Array<Array<{x: number, y: number}>>} paths The border, in the pixel space of the source.
224
+ * Every path is a polyline, a closed one repeats its first point at the end.
225
+ * @param {{[ringWidth]: number, [sampleStep]: number, [minAlpha]: number}} [options]
226
+ * @returns {{r: number, g: number, b: number}|null} null if the color could not be determined
227
+ */
228
+ export var sampleRingAverage = function sampleRingAverage(source, paths) {
229
+ var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
230
+ if (!source || !source.width || !source.height || !paths || !paths.length) {
231
+ return null;
232
+ }
233
+ var bounds = getSampleBounds(source, paths, getRingWidth(paths, options.ringWidth));
234
+ if (bounds.width <= 0 || bounds.height <= 0) {
235
+ return null;
236
+ }
237
+ var _createWorkingCanvas = createWorkingCanvas(source, true),
238
+ _createWorkingCanvas2 = _slicedToArray(_createWorkingCanvas, 2),
239
+ ctx = _createWorkingCanvas2[1];
240
+ var imageData;
241
+ try {
242
+ imageData = ctx.getImageData(bounds.x, bounds.y, bounds.width, bounds.height);
243
+ } catch (e) {
244
+ // a cross origin image without the proper headers taints the canvas, in that case the
245
+ // caller has to fall back to its static color
246
+ // eslint-disable-next-line no-console
247
+ console.warn("Could not read the product image pixels to derive the border color.", e);
248
+ return null;
249
+ }
250
+ return averageRingColorFromImageData(imageData, bounds, paths, options);
251
+ };