@archvisioninc/canvas 3.1.0 → 3.1.1

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.
@@ -4,6 +4,62 @@ import { deleteSelected } from '../actions';
4
4
  import { TRANSPARENCY_MODES } from '../constants';
5
5
  import * as BABYLON from 'babylonjs';
6
6
  import _ from 'lodash';
7
+ const removeChannelFromTexture = (image, channel) => {
8
+ const imageHeight = image?.naturalHeight ?? 0;
9
+ const imageWidth = image?.naturalWidth ?? 0;
10
+ let imageData;
11
+ const maxHeight = Math.max(...[imageHeight, 1024]);
12
+ const maxWidth = Math.max(...[imageWidth, 1024]);
13
+ const canvas = document.createElement('canvas');
14
+ const ctx = canvas.getContext('2d');
15
+ canvas.width = maxWidth;
16
+ canvas.height = maxHeight;
17
+ if (image) {
18
+ ctx.drawImage(image, 0, 0, maxWidth, maxHeight);
19
+ imageData = ctx.getImageData(0, 0, maxWidth, maxHeight).data;
20
+ }
21
+ const combinedImageData = ctx.createImageData(maxWidth, maxHeight);
22
+ const combinedData = combinedImageData.data;
23
+ for (let i = 0; i < combinedData.length; i += 4) {
24
+ combinedData[i] = channel === 'R' ? 255 : imageData[i];
25
+ combinedData[i + 1] = channel === 'G' ? 255 : imageData[i + 1];
26
+ combinedData[i + 2] = channel === 'B' ? 255 : imageData[i + 2];
27
+ combinedData[i + 3] = channel === 'A' ? 255 : imageData[i + 3];
28
+ }
29
+ ctx.putImageData(combinedImageData, 0, 0);
30
+ return canvas.toDataURL('image/png');
31
+ };
32
+ const textureToImage = texture => {
33
+ return new Promise(resolve => {
34
+ if (!texture.readPixels()) {
35
+ resolve(null);
36
+ return;
37
+ }
38
+ texture.readPixels().then(pixels => {
39
+ const canvas = document.createElement('canvas');
40
+ const ctx = canvas.getContext('2d');
41
+ const {
42
+ width,
43
+ height
44
+ } = texture.getSize();
45
+ canvas.width = width;
46
+ canvas.height = height;
47
+ const img = new Image();
48
+ const imageData = ctx.createImageData(width, height);
49
+ imageData.data.set(new Uint8ClampedArray(pixels));
50
+ ctx.putImageData(imageData, 0, 0);
51
+ img.onload = () => {
52
+ resolve(img);
53
+ };
54
+ img.onerror = () => {
55
+ resolve(null);
56
+ };
57
+ img.src = canvas.toDataURL();
58
+ }).catch(() => {
59
+ resolve(null);
60
+ });
61
+ });
62
+ };
7
63
  export const removeFromMaterial = inboundData => {
8
64
  const {
9
65
  payload
@@ -15,7 +71,9 @@ export const removeFromMaterial = inboundData => {
15
71
  removeMicroSurfaceTexture,
16
72
  removeEmissiveTexture,
17
73
  removeBumpTexture,
18
- removeOpacityTexture
74
+ removeOpacityTexture,
75
+ removeRoughnessTexture,
76
+ removeAmbientTexture
19
77
  } = payload;
20
78
  const material = scene.getMaterialByName(id, true);
21
79
  if (material) {
@@ -32,8 +90,16 @@ export const removeFromMaterial = inboundData => {
32
90
  material.albedoTexture = null;
33
91
  }
34
92
  if (removeMetallicTexture) {
35
- material.metallicTexture?.dispose();
36
- material.metallicTexture = null;
93
+ if (material.metallicTexture) {
94
+ textureToImage(material.metallicTexture).then(data => {
95
+ const updatedTexture = removeChannelFromTexture(data, 'B');
96
+ material.metallicTexture.updateURL(updatedTexture);
97
+ newMetaDataEntry('materials', buildMaterialsArray());
98
+ newMetaDataEntry('selectedMaterials', buildSelectedMaterialArray());
99
+ }).catch(() => {
100
+ console.log('Failed to remove metallic texture');
101
+ });
102
+ }
37
103
  }
38
104
  if (removeMicroSurfaceTexture) {
39
105
  material.microSurfaceTexture?.dispose();
@@ -51,6 +117,22 @@ export const removeFromMaterial = inboundData => {
51
117
  material.opacityTexture?.dispose();
52
118
  material.opacityTexture = null;
53
119
  }
120
+ if (removeAmbientTexture) {
121
+ material.ambientTexture?.dispose();
122
+ material.opacityTexture = null;
123
+ }
124
+ if (removeRoughnessTexture) {
125
+ if (material.metallicTexture) {
126
+ textureToImage(material.metallicTexture).then(data => {
127
+ const updatedTexture = removeChannelFromTexture(data, 'G');
128
+ material.metallicTexture.updateURL(updatedTexture);
129
+ newMetaDataEntry('materials', buildMaterialsArray());
130
+ newMetaDataEntry('selectedMaterials', buildSelectedMaterialArray());
131
+ }).catch(() => {
132
+ console.log('Failed to remove roughness texture');
133
+ });
134
+ }
135
+ }
54
136
  newMetaDataEntry('materials', buildMaterialsArray());
55
137
  newMetaDataEntry('selectedMaterials', buildSelectedMaterialArray());
56
138
  }
@@ -141,34 +141,42 @@ const applyUVSettings = args => {
141
141
  };
142
142
 
143
143
  // TODO: Future uses should support red, green, blue, and alpha channels with defaults for each
144
- const combineMetallicRoughnessTextures = (metallicImage, roughnessImage) => {
145
- const roughnessHeight = roughnessImage?.naturalHeight ?? 0;
146
- const roughnessWidth = roughnessImage?.naturalWidth ?? 0;
147
- const metallicHeight = metallicImage?.naturalHeight ?? 0;
148
- const metallicWidth = metallicImage?.naturalWidth ?? 0;
149
- let roughnessData;
150
- let metallicData;
151
- const maxHeight = Math.max(...[roughnessHeight, metallicHeight, 1024]);
152
- const maxWidth = Math.max(...[roughnessWidth, metallicWidth, 1024]);
144
+ const updateTextureChannel = (imageToMaintain, newImage, channelToUpdate) => {
145
+ const maintainHeight = imageToMaintain?.naturalHeight ?? 0;
146
+ const maintainWidth = imageToMaintain?.naturalWidth ?? 0;
147
+ const newHeight = newImage?.naturalHeight ?? 0;
148
+ const newWidth = newImage?.naturalWidth ?? 0;
149
+ let maintainData;
150
+ let newData;
151
+ const maxHeight = Math.max(...[maintainHeight, newHeight, 1024]);
152
+ const maxWidth = Math.max(...[maintainWidth, newWidth, 1024]);
153
153
  const canvas = document.createElement('canvas');
154
154
  const ctx = canvas.getContext('2d');
155
155
  canvas.width = maxWidth;
156
156
  canvas.height = maxHeight;
157
- if (roughnessImage) {
158
- ctx.drawImage(roughnessImage, 0, 0, maxWidth, maxHeight);
159
- roughnessData = ctx.getImageData(0, 0, maxWidth, maxHeight).data;
157
+ if (newImage) {
158
+ ctx.drawImage(newImage, 0, 0, maxWidth, maxHeight);
159
+ newData = ctx.getImageData(0, 0, maxWidth, maxHeight).data;
160
160
  }
161
- if (metallicImage) {
162
- ctx.drawImage(metallicImage, 0, 0, maxWidth, maxHeight);
163
- metallicData = ctx.getImageData(0, 0, maxWidth, maxHeight).data;
161
+ if (imageToMaintain) {
162
+ ctx.drawImage(imageToMaintain, 0, 0, maxWidth, maxHeight);
163
+ maintainData = ctx.getImageData(0, 0, maxWidth, maxHeight).data;
164
164
  }
165
165
  const combinedImageData = ctx.createImageData(maxWidth, maxHeight);
166
166
  const combinedData = combinedImageData.data;
167
167
  for (let i = 0; i < combinedData.length; i += 4) {
168
- combinedData[i] = 255;
169
- combinedData[i + 1] = roughnessData ? roughnessData[i + 1] : 255;
170
- combinedData[i + 2] = metallicData ? metallicData[i + 2] : 255;
171
- combinedData[i + 3] = 255;
168
+ const maintainRed = maintainData ? maintainData[i] : 255;
169
+ const maintainGreen = maintainData ? maintainData[i + 1] : 255;
170
+ const maintainBlue = maintainData ? maintainData[i + 2] : 255;
171
+ const maintainAlpha = maintainData ? maintainData[i + 3] : 255;
172
+ const newRed = newData ? newData[i] : 255;
173
+ const newGreen = newData ? newData[i + 1] : 255;
174
+ const newBlue = newData ? newData[i + 2] : 255;
175
+ const newAlpha = newData ? newData[i + 3] : 255;
176
+ combinedData[i] = channelToUpdate === 'R' ? newRed : maintainRed;
177
+ combinedData[i + 1] = channelToUpdate === 'G' ? newGreen : maintainGreen;
178
+ combinedData[i + 2] = channelToUpdate === 'B' ? newBlue : maintainBlue;
179
+ combinedData[i + 3] = channelToUpdate === 'A' ? newAlpha : maintainAlpha;
172
180
  }
173
181
  ctx.putImageData(combinedImageData, 0, 0);
174
182
  return canvas.toDataURL('image/png');
@@ -385,7 +393,7 @@ export const updateMaterial = inboundData => {
385
393
  const metallicBlob = dataUrlToBlob(texture.url);
386
394
  Promise.all([loadImage(metallicBlob), textureToImage(currentTexture)]).then(data => {
387
395
  const [metallicImage, currentImage] = data;
388
- const combinedTexture = combineMetallicRoughnessTextures(metallicImage, currentImage);
396
+ const combinedTexture = updateTextureChannel(currentImage, metallicImage, 'B');
389
397
  currentTexture.updateURL(combinedTexture);
390
398
  texture.dispose();
391
399
  applyUVSettings({
@@ -424,7 +432,7 @@ export const updateMaterial = inboundData => {
424
432
  const roughnessBlob = dataUrlToBlob(texture.url);
425
433
  Promise.all([loadImage(roughnessBlob), textureToImage(currentTexture)]).then(data => {
426
434
  const [roughnessImage, metallicImage] = data;
427
- const combinedTexture = combineMetallicRoughnessTextures(metallicImage, roughnessImage);
435
+ const combinedTexture = updateTextureChannel(metallicImage, roughnessImage, 'G');
428
436
  currentTexture.updateURL(combinedTexture);
429
437
  texture.dispose();
430
438
  applyUVSettings({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@archvisioninc/canvas",
3
- "version": "3.1.0",
3
+ "version": "3.1.1",
4
4
  "private": false,
5
5
  "main": "dist/Canvas.js",
6
6
  "module": "dist/Canvas.js",
@@ -13,6 +13,82 @@ import { TRANSPARENCY_MODES } from '../constants';
13
13
  import * as BABYLON from 'babylonjs';
14
14
  import _ from 'lodash';
15
15
 
16
+ const removeChannelFromTexture = (image, channel) => {
17
+ const imageHeight = image?.naturalHeight ?? 0;
18
+ const imageWidth = image?.naturalWidth ?? 0;
19
+
20
+ let imageData;
21
+
22
+ const maxHeight = Math.max(...[ imageHeight, 1024 ]);
23
+ const maxWidth = Math.max(...[ imageWidth, 1024 ]);
24
+
25
+ const canvas = document.createElement('canvas');
26
+
27
+ const ctx = canvas.getContext('2d');
28
+
29
+ canvas.width = maxWidth;
30
+ canvas.height = maxHeight;
31
+
32
+ if (image) {
33
+ ctx.drawImage(image, 0, 0, maxWidth, maxHeight);
34
+ imageData = ctx.getImageData(0, 0, maxWidth, maxHeight).data;
35
+ }
36
+
37
+ const combinedImageData = ctx.createImageData(maxWidth, maxHeight);
38
+ const combinedData = combinedImageData.data;
39
+
40
+ for (let i = 0; i < combinedData.length; i += 4) {
41
+ combinedData[i] = channel === 'R' ? 255 : imageData[i];
42
+ combinedData[i + 1] = channel === 'G' ? 255 : imageData[i + 1];
43
+ combinedData[i + 2] = channel === 'B' ? 255 : imageData[i + 2];
44
+ combinedData[i + 3] = channel === 'A' ? 255 : imageData[i + 3];
45
+ }
46
+
47
+ ctx.putImageData(combinedImageData, 0, 0);
48
+
49
+ return canvas.toDataURL('image/png');
50
+
51
+ };
52
+
53
+ const textureToImage = texture => {
54
+ return new Promise(resolve => {
55
+ if (!texture.readPixels()) {
56
+ resolve(null);
57
+ return;
58
+ }
59
+ texture.readPixels()
60
+ .then(pixels => {
61
+ const canvas = document.createElement('canvas');
62
+ const ctx = canvas.getContext('2d');
63
+ const { width, height } = texture.getSize();
64
+
65
+ canvas.width = width;
66
+ canvas.height = height;
67
+
68
+ const img = new Image();
69
+ const imageData = ctx.createImageData(width, height);
70
+ imageData.data.set(new Uint8ClampedArray(pixels));
71
+
72
+ ctx.putImageData(imageData, 0, 0);
73
+
74
+ img.onload = () => {
75
+ resolve(img);
76
+ };
77
+
78
+ img.onerror = () => {
79
+ resolve(null);
80
+ };
81
+
82
+
83
+ img.src = canvas.toDataURL();
84
+ })
85
+ .catch(() => {
86
+ resolve(null);
87
+ });
88
+
89
+ });
90
+ };
91
+
16
92
  export const removeFromMaterial = inboundData => {
17
93
  const { payload } = inboundData;
18
94
  const {
@@ -23,6 +99,8 @@ export const removeFromMaterial = inboundData => {
23
99
  removeEmissiveTexture,
24
100
  removeBumpTexture,
25
101
  removeOpacityTexture,
102
+ removeRoughnessTexture,
103
+ removeAmbientTexture,
26
104
  } = payload;
27
105
 
28
106
  const material = scene.getMaterialByName(id, true);
@@ -44,8 +122,18 @@ export const removeFromMaterial = inboundData => {
44
122
  }
45
123
 
46
124
  if (removeMetallicTexture) {
47
- material.metallicTexture?.dispose();
48
- material.metallicTexture = null;
125
+ if (material.metallicTexture) {
126
+ textureToImage(material.metallicTexture)
127
+ .then(data => {
128
+ const updatedTexture = removeChannelFromTexture(data, 'B');
129
+ material.metallicTexture.updateURL(updatedTexture);
130
+ newMetaDataEntry('materials', buildMaterialsArray());
131
+ newMetaDataEntry('selectedMaterials', buildSelectedMaterialArray());
132
+ })
133
+ .catch(() => {
134
+ console.log('Failed to remove metallic texture');
135
+ });
136
+ }
49
137
  }
50
138
 
51
139
  if (removeMicroSurfaceTexture) {
@@ -67,6 +155,24 @@ export const removeFromMaterial = inboundData => {
67
155
  material.opacityTexture?.dispose();
68
156
  material.opacityTexture = null;
69
157
  }
158
+ if (removeAmbientTexture) {
159
+ material.ambientTexture?.dispose();
160
+ material.opacityTexture = null;
161
+ }
162
+ if (removeRoughnessTexture) {
163
+ if (material.metallicTexture) {
164
+ textureToImage(material.metallicTexture)
165
+ .then(data => {
166
+ const updatedTexture = removeChannelFromTexture(data, 'G');
167
+ material.metallicTexture.updateURL(updatedTexture);
168
+ newMetaDataEntry('materials', buildMaterialsArray());
169
+ newMetaDataEntry('selectedMaterials', buildSelectedMaterialArray());
170
+ })
171
+ .catch(() => {
172
+ console.log('Failed to remove roughness texture');
173
+ });
174
+ }
175
+ }
70
176
 
71
177
  newMetaDataEntry('materials', buildMaterialsArray());
72
178
  newMetaDataEntry('selectedMaterials', buildSelectedMaterialArray());
@@ -161,4 +267,4 @@ export const removeFromViewport = inboundData => {
161
267
 
162
268
  props.setConfirmMessage?.(confirmConfig);
163
269
  }
164
- };
270
+ };
@@ -178,17 +178,17 @@ const applyUVSettings = args => {
178
178
  };
179
179
 
180
180
  // TODO: Future uses should support red, green, blue, and alpha channels with defaults for each
181
- const combineMetallicRoughnessTextures = (metallicImage, roughnessImage) => {
182
- const roughnessHeight = roughnessImage?.naturalHeight ?? 0;
183
- const roughnessWidth = roughnessImage?.naturalWidth ?? 0;
184
- const metallicHeight = metallicImage?.naturalHeight ?? 0;
185
- const metallicWidth = metallicImage?.naturalWidth ?? 0;
181
+ const updateTextureChannel = (imageToMaintain, newImage, channelToUpdate) => {
182
+ const maintainHeight = imageToMaintain?.naturalHeight ?? 0;
183
+ const maintainWidth = imageToMaintain?.naturalWidth ?? 0;
184
+ const newHeight = newImage?.naturalHeight ?? 0;
185
+ const newWidth = newImage?.naturalWidth ?? 0;
186
186
 
187
- let roughnessData;
188
- let metallicData;
187
+ let maintainData;
188
+ let newData;
189
189
 
190
- const maxHeight = Math.max(...[ roughnessHeight, metallicHeight, 1024 ]);
191
- const maxWidth = Math.max(...[ roughnessWidth, metallicWidth, 1024 ]);
190
+ const maxHeight = Math.max(...[ maintainHeight, newHeight, 1024 ]);
191
+ const maxWidth = Math.max(...[ maintainWidth, newWidth, 1024 ]);
192
192
 
193
193
  const canvas = document.createElement('canvas');
194
194
 
@@ -197,24 +197,34 @@ const combineMetallicRoughnessTextures = (metallicImage, roughnessImage) => {
197
197
  canvas.width = maxWidth;
198
198
  canvas.height = maxHeight;
199
199
 
200
- if (roughnessImage) {
201
- ctx.drawImage(roughnessImage, 0, 0, maxWidth, maxHeight);
202
- roughnessData = ctx.getImageData(0, 0, maxWidth, maxHeight).data;
200
+ if (newImage) {
201
+ ctx.drawImage(newImage, 0, 0, maxWidth, maxHeight);
202
+ newData = ctx.getImageData(0, 0, maxWidth, maxHeight).data;
203
203
  }
204
204
 
205
- if (metallicImage) {
206
- ctx.drawImage(metallicImage, 0, 0, maxWidth, maxHeight);
207
- metallicData = ctx.getImageData(0, 0, maxWidth, maxHeight).data;
205
+ if (imageToMaintain) {
206
+ ctx.drawImage(imageToMaintain, 0, 0, maxWidth, maxHeight);
207
+ maintainData = ctx.getImageData(0, 0, maxWidth, maxHeight).data;
208
208
  }
209
209
 
210
210
  const combinedImageData = ctx.createImageData(maxWidth, maxHeight);
211
211
  const combinedData = combinedImageData.data;
212
212
 
213
213
  for (let i = 0; i < combinedData.length; i += 4) {
214
- combinedData[i] = 255;
215
- combinedData[i + 1] = roughnessData ? roughnessData[i + 1] : 255;
216
- combinedData[i + 2] = metallicData ? metallicData[i + 2] : 255;
217
- combinedData[i + 3] = 255;
214
+ const maintainRed = maintainData ? maintainData[i] : 255;
215
+ const maintainGreen = maintainData ? maintainData[i + 1] : 255;
216
+ const maintainBlue = maintainData ? maintainData[i + 2] : 255;
217
+ const maintainAlpha = maintainData ? maintainData[i + 3] : 255;
218
+
219
+ const newRed = newData ? newData[i] : 255;
220
+ const newGreen = newData ? newData[i + 1] : 255;
221
+ const newBlue = newData ? newData[i + 2] : 255;
222
+ const newAlpha = newData ? newData[i + 3] : 255;
223
+
224
+ combinedData[i] = channelToUpdate === 'R' ? newRed : maintainRed;
225
+ combinedData[i + 1] = channelToUpdate === 'G' ? newGreen : maintainGreen;
226
+ combinedData[i + 2] = channelToUpdate === 'B' ? newBlue : maintainBlue;
227
+ combinedData[i + 3] = channelToUpdate === 'A' ? newAlpha : maintainAlpha;
218
228
  }
219
229
 
220
230
  ctx.putImageData(combinedImageData, 0, 0);
@@ -442,7 +452,7 @@ export const updateMaterial = inboundData => {
442
452
  ])
443
453
  .then(data => {
444
454
  const [ metallicImage, currentImage ] = data;
445
- const combinedTexture = combineMetallicRoughnessTextures(metallicImage, currentImage);
455
+ const combinedTexture = updateTextureChannel(currentImage, metallicImage, 'B');
446
456
  currentTexture.updateURL(combinedTexture);
447
457
 
448
458
  texture.dispose();
@@ -480,7 +490,7 @@ export const updateMaterial = inboundData => {
480
490
  Promise.all([ loadImage(roughnessBlob), textureToImage(currentTexture) ])
481
491
  .then(data => {
482
492
  const [ roughnessImage, metallicImage ] = data;
483
- const combinedTexture = combineMetallicRoughnessTextures(metallicImage, roughnessImage);
493
+ const combinedTexture = updateTextureChannel(metallicImage, roughnessImage, 'G');
484
494
  currentTexture.updateURL(combinedTexture);
485
495
 
486
496
  texture.dispose();
@@ -5,7 +5,7 @@ import { AppContainer, DebugButtons, Button } from './styles';
5
5
  import { theme } from 'static/theme';
6
6
  import { fetchDownloadURL } from 'helpers/fetchHelpers';
7
7
  import { ENVIRONMENTS } from 'constants';
8
- import { updateMaterial, scene } from 'package/helpers';
8
+ import { updateMaterial, scene, removeFromMaterial } from 'package/helpers';
9
9
  import Canvas from 'package/Canvas';
10
10
  import _ from 'lodash';
11
11
 
@@ -198,16 +198,15 @@ const App = () => {
198
198
  onClick={() => {
199
199
  const inboundData = {
200
200
  payload: {
201
- id: 'material',
202
- ...serializedResponseData,
201
+ id: 'Material Metal PBR Rich',
202
+ removeRoughnessTexture: true,
203
203
  },
204
204
  };
205
205
 
206
- updateMaterial(inboundData);
207
- console.log({ metadata: scene.metadata });
206
+ removeFromMaterial(inboundData);
208
207
  }}
209
208
  >
210
- Import material
209
+ Remove Roughness Texture
211
210
  </Button>
212
211
 
213
212
  <input