Weird behaviour with fabric.util.loadImage() when looping through objects loaded via loadFromJSON() - json

In order to try and get around the odd issue in having with CORS (here) I am attempting to reload any images loaded via canvas.loadFromJSON()
But, I am experiencing weird issues. Sometimes only one image is replaced, other times I get duplicates of one image.
Here is my code:
canvas.loadFromJSON(<?php echo json_encode($objects); ?>, function() {
var objArray = canvas.getObjects();
for (var i = 0; i < objArray.length; i++) {
canvas.setActiveObject(objArray[i]);
var activeObject = canvas.getActiveObject();
if(activeObject.type === 'image') {
fabric.util.loadImage(activeObject.src, function(img) {
var object = new fabric.Image(img);
object.hasControls = true;
object.lockUniScaling = true;
object.scaleX = activeObject.scaleX;
object.scaleY = activeObject.scaleY;
object.originX = activeObject.originX;
object.originY = activeObject.originY;
object.centeredRotation = true;
object.centeredScaling = true;
canvas.add(object);
}, null, {crossOrigin: 'Anonymous'});
canvas.remove(activeObject);
}
activeObject.setCoords();
}
canvas.deactivateAll();
canvas.renderAll();
canvas.calcOffset();
});
Any ideas why I'm getting these weird issues?

First glance at your code I don't see anything wrong... But I'm also thinking the code might be a bit inefficient? Is there a need to create a new image instance?
I believe you should be able to just set the crossOrigin property on the image object.
This code is untested, but I'd try something like this:
canvas.loadFromJSON(<?php echo json_encode($objects); ?>, function() {
var objArray = canvas.getObjects();
for (var i = 0; i < objArray.length; i++) {
canvas.setActiveObject(objArray[i]);
var activeObject = canvas.getActiveObject();
if(activeObject.type === 'image') {
activeObject.crossOrigin = 'Anonymous';
}
}
canvas.deactivateAll();
canvas.renderAll();
canvas.calcOffset();
});

I had the same problem and overcome it downloading again the image then reassign it to object._element once each fabric object was created using loadFromJSON.
export const getImage = url => {
return new Promise((resolve, reject) => {
let img = new Image();
img.onload = () => resolve(img);
img.onerror = reject;
img.setAttribute('crossOrigin', 'anonymous');
img.src = url;
});
}
canvas.loadFromJSON(json, canvas.renderAll.bind(canvas), async (o, object) => {
if (object.type === "image") {
let imagecore = await getImage(object.src);
object._element = imagecore;
}
});

Related

Why am I getting the updated json field converted info ZoneAwarePromise data

I am trying to get an "image URL field" convert to a base64 blob data, however the updated field console log data is showing as ZoneAwarePromise {__zone_symbol__state: null, __zone_symbol__value: Array(0)}
this.http.get(this.api).subscribe(data => {
this.mainContact = data;
for (var a = 0; a < this.mainContact.length; a++) {
for (var j = 0; j < this.mainContact[a].acf.project_gallery.length; j++) {
var mainUrl = 'https://cdnwebapp.azureedge.net'+this.mainContact[a].acf.project_gallery[j].url;
//Below updates the all field value to the converted blob
this.mainContact[a].acf.project_gallery[j].url = this.convertToDataURLviaCanvas(mainUrl, "image/jpeg", 1)
.then(base64Img => {
return base64Img;
// console.log(base64Img); *As Expected blob*
})
//console.log(this.mainContact[a].acf.project_gallery[j].url) * The update field data console log is showing as ZoneAwarePromise
}
}
});
convertToDataURLviaCanvas(url, outputFormat, osize){
return new Promise( (resolve, reject) => {
let img = new Image();
img.crossOrigin = 'Anonymous';
img.onload = function(){
let canvas = <HTMLCanvasElement> document.createElement('CANVAS'),
ctx = canvas.getContext('2d'),
dataURL;
canvas.height = 959;
canvas.width = 1365;
ctx.drawImage(img, 0, 0);
dataURL = canvas.toDataURL(outputFormat, osize);
//callback(dataURL);
canvas = null;
resolve(dataURL);
};
img.src = url;
});
}
I think you have to assign in on the .then.
Try:
this.convertToDataURLviaCanvas(mainUrl, "image/jpeg", 1)
.then(base64Img => {
this.mainContact[a].acf.project_gallery[j].url = base64img
console.log(base64Img);
console.log(this.mainContact[a].acf.project_gallery[j].url);
})

why i can't get full image size from convertToDataURLviaCanvas method in ionic 3?

I want original image size in offline mode also as shown in online mode in image 2.
My problem is that when i gone in app without internet connection, i loss full quality and size of images, because of i am using convertToDataURLviaCanvas().
so, please give here solution as soon as possible.
My code is:
I am using this function to convert all images:
convertToDataURLviaCanvas(url, outputFormat) {
return new Promise((resolve, reject) =>
{
let img = new Image();
img.crossOrigin = 'Anonymous';
img.onload = function () {
let canvas = <HTMLCanvasElement>document.createElement('CANVAS'),
ctx = canvas.getContext('2d'),
dataURL;
canvas.height = 1000;
canvas.width = 1000;
ctx.drawImage(img, 0, 0);
dataURL = canvas.toDataURL();
//callback(dataURL);
canvas = null;
resolve(dataURL);
};
img.src = url;
});
}
getTravelAdviceData() {
if(this.restProvider.getNetworkType() == 'none') {
// this.onlineGrid = false;
// this.offlineGrid = true;
this.storage.get('companyLogoOffline').then((data) => {
// Do something with latitude value
// console.log("offline data", data);
this.getcompanyLogo = data;
});
this.storage.get('travelTips64Image').then((data) => {
// Do something with latitude value
// console.log("offline data", data);
this.adviceArray = data;
// console.log("offline this.adviceArray", this.adviceArray);
});
} else {
// this.offlineGrid = false;
// this.onlineGrid = true;
this.restProvider.getTravelAdvice()
.then(data => {
let serviceData : any = data['consejosviaje'];
// this.adviceArray = serviceData;
let base64Image;
for (let i in serviceData) {
this.imagen = serviceData[i].imagen;
this.convertToDataURLviaCanvas(this.imagen, "image/jpeg").then(base64 => {
base64Image = base64;
this.texto = serviceData[i].texto;
this.adviceArrays64.push({'texto': this.texto, 'imagen': base64Image});
this.storage.set("travelTips64Image", this.adviceArrays64);
this.adviceArray = this.adviceArrays64;
});
}
});
}
}

Viewer automatically change material to default

I have a problem with changes of material of some elements
when geometry is loaded:
_this.viewer.addEventListener(Autodesk.Viewing.GEOMETRY_LOADED_EVENT, () => {
changeModelMaterial()
});
...
const changeModelMaterial = () => {
const grey = new THREE.Color(0.5, 0.5, 0.5);
let dbIds = getDbIds()
changeAllElementsMaterial(grey)
setMaterialOfDbIds(dbIds)
}
code that i`m using to change material:
const changeAllElementsMaterial = (color) => {
const fragmentList = _this.viewer.model.getFragmentList();
for (let materialId of fragmentList.materialids) {
if (fragmentList.materialmap[materialId]) {
fragmentList.materialmap[materialId].map = null
fragmentList.materialmap[materialId].color = color
fragmentList.materialmap[materialId].needsUpdate = true;
}
}
_this.viewer.impl.invalidate(true);
}
const setMaterialOfDbIds = (dbIds) => {
var color_diffuse = 0xAB00EE;
var color_specular = 0xEEABEE;
var colorM = new THREE.MeshPhongMaterial({
color: color_diffuse,
specular: color_specular
});
_this.viewer.impl.matman().addMaterial(
'ADN-Material-' +
"common color material", // or a GUID
colorM,
true);
for (let dbId of dbIds) {
_this.viewer.model.getData().instanceTree.enumNodeFragments(dbId, function (fragId) {
_this.viewer.model.getFragmentList().setMaterial(fragId, colorM);
});
}
_this.viewer.impl.invalidate(true);
}
It works, because I see that materials of model are changed, but the problem is that materials back to default after ~1-2 sec.
After this I cannot change material even with run this code manually.
Question is why Viewer is locking material change after this 2 sec, how to prevent it
And maybe you will be able to tell me what i can do better with material changes, eg. maybe something better that running my code after GEOMETRY_LOAD. The best would be change material before first render of model
........
hint:
when change event from GEOMETRY_LOADED_EVENT to OBJECT_TREE_CREATED_EVENT "sometimes" but only sometimes it works well (materials stay to the end of working with model), but mostly when i run my method after OBJECT_TREE_CREATED it not working (even not working by run it manually, materials are in some way locked). So I suspect that problem is between time of GEOMETRY_LOAD and OBJECT_TREE_CREATED
I will be grateful for any help
==============================full code==============================
index.html
<div id="main">
<div id="MyViewerDiv"></div>
<button id="open-nav-button" onClick="showDocInfo()">test</button>
</div>
<script src="https://developer.api.autodesk.com/derivativeservice/v2/viewers/three.min.js"></script>
<script src="https://developer.api.autodesk.com/derivativeservice/v2/viewers/viewer3D.min.js"></script>
<script type="text/javascript" src="lib/jquery.min.js"></script>
<script src="js/autodesk-viewer.js"></script>
<script src="js/extension/test-extension.js"></script>
<script>
const autodeskViewer = new AutodeskViewer()
const showDocInfo = () => {
autodeskViewer.showDocInfo()
}
</script>
autodesk-viewer.js
var AutodeskViewer = (function () {
function AutodeskViewer() {
var _this = this;
this.urn = 'urn:dXJuOmFkc2sub2JqZWN0czpvcy5vYmplY3Q6Zm9yZ2UtamF2YS1zYW1wbGUtYXBwLTFzcGduazdqcWpxdjhmYXV0YmNzd2R0cGdvN3VtNWY1L1BPQy1Gb3JnZS1JVCUyMDIwMTclMjBSdWNoXzEwMDUxNy5ud2Q';
this.initializeViewer = function (containerId, documentId) {
_this.viewerApp = new Autodesk.Viewing.ViewingApplication(containerId);
var config = {
extensions: ['TestExtension']
};
_this.viewerApp.registerViewer(_this.viewerApp.k3D, Autodesk.Viewing.Private.GuiViewer3D, config);
_this.viewerApp.loadDocument(documentId, _this.onDocumentLoadSuccess, _this.onDocumentLoadFailure);
}
this.onDocumentLoadSuccess = function (doc) {
const viewables = _this.viewerApp.bubble.search(av.BubbleNode.MODEL_NODE);
if (viewables.length === 0) {
return;
}
_this.viewerApp.selectItem(viewables[0].data, _this.onItemLoadSuccess, _this.onItemLoadFail);
_this.viewer3d = _this.viewerApp.getCurrentViewer();
}
this.onDocumentLoadFailure = (viewerErrorCode) => {}
this.onItemLoadSuccess = (viewer) => {
_this.viewer = viewer
}
this.onItemLoadFail = (errorCode) => {}
this.initialize = () => {
var options = {
env: 'AutodeskProduction',
getAccessToken: _this.getToken,
refreshToken: _this.getToken
};
Autodesk.Viewing.Initializer(options, _this.initCallback);
};
this.initCallback = function () {
_this.initializeViewer('MyViewerDiv', _this.urn, '3d');
};
this.getToken = function (onGetAccessToken) {
$.get("forge/oauth/token")
.done(function (data) {
token = data
onGetAccessToken(token, 60 * 60);
})
.fail(function (error) {
console.log('ERROR', error);
});
};
this.showDocInfo = function () {};
this.initialize();
}
return AutodeskViewer;
}());
test-extension.js
var _self;
var _viewer;
var _tempValue = 0;
function TestExtension(viewer, options) {
Autodesk.Viewing.Extension.call(this, viewer, options);
_self = this;
_viewer = viewer;
}
const changeModelMaterial = () => {
// _tempValue++;
// if (_tempValue == 2) {
const elements = [4340, 4342, 4344, 4346, 4348, 4367, 4371, 4375, 4380, 4452, 4468, 4488, 4503, 4517, 4520, 4522, 4524, 4526, 4528, 4530]
changeAllElementsMaterial(new THREE.Color(0.5, 0.5, 0.5))
setMaterialOfDbIds(elements)
_tempValue = 0
// }
}
const changeAllElementsMaterial = (color) => {
var fragmentList = _viewer.model.getFragmentList();
for (let materialId of fragmentList.materialids) {
if (fragmentList.materialmap[materialId]) {
fragmentList.materialmap[materialId].map = null
fragmentList.materialmap[materialId].color = color
fragmentList.materialmap[materialId].needsUpdate = true;
}
}
_viewer.impl.invalidate(true);
}
const setMaterialOfDbIds = (dbIds) => {
var colorM = new THREE.MeshPhongMaterial({
color: new THREE.Color(0xAB00EE)
});
for (let dbId of dbIds) {
_viewer.model.getData().instanceTree.enumNodeFragments(dbId, function (fragId) {
_viewer.model.getFragmentList().setMaterial(fragId, colorM);
});
}
_viewer.impl.invalidate(true);
}
TestExtension.prototype = Object.create(Autodesk.Viewing.Extension.prototype);
TestExtension.prototype.constructor = TestExtension;
TestExtension.prototype.load = function () {
_viewer.addEventListener(Autodesk.Viewing.OBJECT_TREE_CREATED_EVENT, changeModelMaterial)
// _viewer.addEventListener(Autodesk.Viewing.GEOMETRY_LOADED_EVENT, changeModelMaterial)
return true
};
TestExtension.prototype.unload = function () {
return true
};
Autodesk.Viewing.theExtensionManager.registerExtension('TestExtension', TestExtension);
I found the solution, quite accidentally... from other thing i tried to do
before:
const setMaterialOfDbIds = (dbIds) => {
var color_diffuse = 0xAB00EE;
var color_specular = 0xEEABEE;
var colorM = new THREE.MeshPhongMaterial({
color: color_diffuse,
specular: color_specular
});
_this.viewer.impl.matman().addMaterial("common color material", colorM, true);
for (let dbId of dbIds) {
_this.viewer.model.getData().instanceTree.enumNodeFragments(dbId, function (fragId) {
_this.viewer.model.getFragmentList().setMaterial(fragId, colorM);
});
}
_this.viewer.impl.invalidate(true);
}
after
const setMaterialOfDbIds = (dbIds) => {
var color_diffuse = 0xAB00EE;
var color_specular = 0xEEABEE;
var colorM = new THREE.MeshPhongMaterial({
color: color_diffuse,
specular: color_specular
});
_this.viewer.impl.matman().addMaterial("common color material", colorM, true);
for (let dbId of dbIds) {
_this.viewer.model.getData().instanceTree.enumNodeFragments(dbId, function (fragId) {
_this.viewer.model.getFragmentList().setMaterial(fragId, colorM);
var fragProxy = _this.viewer.impl.getFragmentProxy(_this.viewer.model, fragId)
fragProxy.updateAnimTransform()
});
}
_this.viewer.impl.invalidate(true);
}
Really I don`t know why adding
var fragProxy = _this.viewer.impl.getFragmentProxy(_this.viewer.model, fragId)
fragProxy.updateAnimTransform()
made the difference, i didn`t saw anything like that in any example of updating material.
What is interesting this code is running for only few elements in model, but it works for even those elements that materials changed before (in changeAllElementsMaterial method).
#Philippe Leefsma if you understand it pls tell something more why it works
So far I cannot reproduce the issue on my side, I am using the following code (ES7) extracted from that extension: Viewing.Extension.Material
createColorMaterial (color) {
const material = new THREE.MeshPhongMaterial({
specular: new THREE.Color(color),
side: THREE.DoubleSide,
reflectivity: 0.0,
color
})
const materials = this.viewer.impl.getMaterials()
materials.addMaterial(
this.guid(),
material,
true)
return material
}
async onModelCompletedLoad() {
const material = this.createColorMaterial(0xFF0000)
const model = this.viewer.model
const fragIds = await Toolkit.getFragIds(model)
fragIds.forEach((fragId) => {
model.getFragmentList().setMaterial(
fragId, material)
})
this.viewer.impl.sceneUpdated(true)
}
The onModelCompletedLoad is a custom event fired when both GEOMETRY_LOADED_EVENT and OBJECT_TREE_CREATED_EVENT have been fired.
Take a look at this article for more details: Asynchronous viewer events notification
I doubt you can easily change the materials before the model is first rendered, however you could use a custom overlay that hides the model until your custom logic has performed all required steps, this is the approach I am using in my demos at: https://forge-rcdb.autodesk.io/configurator
After loading a model, all custom materials are being persisted fine:
The material extension can be tested live from there.
Hope that helps

Audio Level Meter for Web RTC Stream

I would like to create a decibel meter for the audio that is playing in a video element. The video element is playing a WebRTC stream.
At the moment WebRTC streams cannot be passed into a Web Audio Analyzer. (Although this might change soon … ) (see Web Audio API analyser node getByteFrequencyData returning blank array)
Is there currently another way to get decibel information from a remote mediastream?
Chrome 50 was released: As of the 13th of April 2016 using an Analyser Node with a MediaStreamAudioSourceNode works fine to get audio levels. The resulting audioLevels value can be animated or simply passed into a html meter element.
var _mediaStream = SOME_LOCAL_OR_RTP_MEDIASTREAM;
var _audioContext = new AudioContext();
var _audioAnalyser = [];
var _freqs = [];
var audioLevels = [0];
var _audioSource = _audioContext.createMediaStreamSource(_mediaStream);
var _audioGain1 = _audioContext.createGain();
var _audioChannelSplitter = _audioContext.createChannelSplitter(_audioSource.channelCount);
_audioSource.connect(_audioGain1);
_audioGain1.connect(_audioChannelSplitter);
_audioGain1.connect(_audioContext.destination);
for (let i = 0; i < _audioSource.channelCount; i++) {
_audioAnalyser[i] = _audioContext.createAnalyser();
_audioAnalyser[i].minDecibels = -100;
_audioAnalyser[i].maxDecibels = 0;
_audioAnalyser[i].smoothingTimeConstant = 0.8;
_audioAnalyser[i].fftSize = 32;
_freqs[i] = new Uint8Array(_audioAnalyser[i].frequencyBinCount);
_audioChannelSplitter.connect(_audioAnalyser[i], i, 0);
}
function calculateAudioLevels() {
setTimeout(() => {
for (let channelI = 0; channelI < _audioAnalyser.length; channelI++) {
_audioAnalyser[channelI].getByteFrequencyData(_freqs[channelI]);
let value = 0;
for (let freqBinI = 0; freqBinI < _audioAnalyser[channelI].frequencyBinCount; freqBinI++) {
value = Math.max(value, _freqs[channelI][freqBinI]);
}
audioLevels[channelI] = value / 256;
}
requestAnimationFrame(calculateAudioLevels.bind(this));
}, 1000 / 15); // Max 15fps — not more needed
}
This is a good example:
https://webrtc.github.io/samples/src/content/getusermedia/volume/
And this is the source code:
https://github.com/webrtc/samples/tree/gh-pages/src/content/getusermedia/volume
And this is a sample:
function recordAudio() {
try {
window.AudioContext = window.AudioContext || window.webkitAudioContext;
window.audioContext = new AudioContext();
const instantMeter = document.querySelector('#sound-meter');
const constraints = {'video': false, 'audio': true};
const stream = await navigator.mediaDevices.getUserMedia(constraints);
window.stream = stream;
const soundMeter = window.soundMeter = new SoundMeter(window.audioContext);
soundMeter.connectToSource(stream, function(e) {
if (e) {
alert(e);
return;
}
setInterval(() => {
instantMeter.value = soundMeter.instant.toFixed(2);
}, 200);
});
$('#sound-meter').show();
$('#audio-icon').hide()
} catch(error) {
console.error('Error recording audio.', error);
}
}

AngularJS function undefined when uncommenting it

I have some weird problems related with defintion of method inside scope. Once it's defined, and when I want to call it (just by entering it into code) it disappears. I have this part of code
if (ConfigurationService.isConfigurationChanged() === true) {
ConfigurationService.getConfiguration().then(function(data) {
$scope.configuration = data.configuration;
$scope.lunchers = data.lunchers;
$scope.configuration.vegies.all_in_one_group = data.configuration.vegies.all_in_one_group;
var lunchersLength = data.lunchers.all.length;
for (var i = 0; i < lunchersLength; i++) {
$scope.selectedVegies[i] = $scope.isVegie(data.lunchers.all[i]);
$scope.selectedSpecialGroup[i] = $scope.isInSpecialGroup(data.lunchers.all[i]);
$scope.isAbsent[i] = $scope.isAbsent(data.lunchers.all[i]);
}
});
} else if (ConfigurationService.isConfigurationChanged() === false) {
var cached = ConfigurationService.getCachedConfiguration();
console.log($scope.isVegie);
$scope.configuration = cached.configuration;
$scope.lunchers = cached.lunchers;
$scope.configuration.vegies.all_in_one_group = cached.configuration.vegies.all_in_one_group;
var lunchersLength = cached.lunchers.all.length;
for (var j = 0; j < lunchersLength; j++) {
$scope.selectedVegies[i] = $scope.isVegie(cached.lunchers.all[j]);
//$scope.selectedSpecialGroup[i] = $scope.isInSpecialGroup(cached.lunchers.all[i]);
//$scope.isAbsent[i] = $scope.isAbsent(cached.lunchers.all[i]);
}
}
Now something weird happens. Can you see:
console.log($scope.isVegie);
When I have:
$scope.selectedVegies[i] = $scope.isVegie(cached.lunchers.all[j]);
commented I can see method in firebug, when I uncomment it, it gets undefined...
Anybody had some similar issues?
Note:
ConfigurationService.getConfiguration()
Is this method:
getConfiguration : function() {
var deferred = $q.defer();
$http({
method :'GET',
url : "cgi-bin/get_configuration.py"
})
.success(function(data, status, headers, config) {
if (status === 200) {
angular.copy(data, configStatus.currentConfig);// = data;
configStatus.configurationChanged = false;
deferred.resolve(data);
}
})
.error(function(data, status, headers, config) {
console.log(data);
console.log(status);
deferred.reject(data);
});
return deferred.promise;
},
Found the issue. The thing is that
$scope.isVegie
was defined after the logic calling it. However, I didn't noticed that when I didn't have else part. When it was without if-else, it was working fine even though method was defined "under" the logic calling it.
This solved the issue.