Mad_Griffith Posted June 17, 2016 Share Posted June 17, 2016 Hi guys, I have a question concerning saving and restoring the canvas after scaling. This is the scenario: Photo is loaded Context is scaled Animation Loop Photo is drawn Overlay Image is drawn RequestAnimationFrame Where should I put context.save() and context.restore() to prevent the scaling from affecting the Overlay Image? I tried a few solutions but to no avail. Thanks! Link to comment Share on other sites More sharing options...
Ingolme Posted June 17, 2016 Share Posted June 17, 2016 You should save before doing a transform, then call restore once you want to return to the original state.Given the code structure you displayed, this should work:Photo is loaded Animation LoopSave context Context is scaledPhoto is drawnRestore contextOverlay Image is drawn RequestAnimationFrame I don't know what other operations you're doing with the scaled canvas, but if it's just drawing the photo and then the overlay this will work. Link to comment Share on other sites More sharing options...
Mad_Griffith Posted June 17, 2016 Author Share Posted June 17, 2016 The scaling in my case would to be done only once, when the image is loaded, because the image size is then manipulated again (the user can zoom in and out). Link to comment Share on other sites More sharing options...
Ingolme Posted June 17, 2016 Share Posted June 17, 2016 You have to scale every time you plan to draw the object that needs to be scaled. If you only draw the object once then you only need to scale once. If you draw it on every frame then you need to scale it on every frame. Link to comment Share on other sites More sharing options...
Mad_Griffith Posted June 17, 2016 Author Share Posted June 17, 2016 I am using two different ways for scaling. In the orientation(), I am using the scale() method as a means to actually flip the picture horizontally/vertically, whereas in the zoom() I am increasing the image object width and height. Link to comment Share on other sites More sharing options...
Ingolme Posted June 17, 2016 Share Posted June 17, 2016 You're going to have to show some actual code if you want me to understand what's going on. Link to comment Share on other sites More sharing options...
Mad_Griffith Posted June 17, 2016 Author Share Posted June 17, 2016 here it is. The processProfileImg() is where I am having the problem at the moment: window.onload = drawCanvas; var canvas = document.getElementById('composition-image'), context = canvas.getContext('2d'), distanceFromRightSide = 990, distanceFromBottomSide = 520, startX = 0, startY = 0, drag = false, sliderPrevPos = parseInt($('#zoom input[type="range"]').attr('value')), zoomSpeed = 3.5; function drawCanvas() { canvasImg = new Image(); canvas.width = 1200; canvas.height = 900; canvasImg.onload = function() { context.drawImage(canvasImg, 0, 0); } canvasImg.src = './img/bg.jpg'; mouseActions(); fileIsUploaded(); } function fileIsUploaded() { $('input[type="file"]').change(function() { files = this.files[0]; openUploadedFile(files); }); } function openUploadedFile(file) { try { var openedFile = new FileReader(); openedFile.readAsDataURL(file); openedFile.onload = processProfileImg; } catch (error) { // Fail silently if user cancels the 'Browse' window } } function processProfileImg(event) { profileImg = new Image(); profileImg.src = event.target.result; profileImg.onload = function() { setAspectRatio(); profileImgX = canvas.width - distanceFromRightSide - ((profileImg.width - 183) / 2), profileImgY = canvas.height - distanceFromBottomSide - ((profileImg.height - 242) / 2); EXIF.getData(profileImg, function() { orientateProfileImg(); }); (function animationLoop(event) { save.context(); renderProfileImg(event); requestAnimationFrame(animationLoop, profileImg); drawOverlay(); })() hideDefaultProfileImg(); } enableZoomSlider(); drawProfileImgBoundaries(); resetZoom(); } function drawOverlay() { overlayImg = new Image(); overlayImg.src = './img/profile_default_red_frame.png'; context.drawImage(overlayImg, canvas.width - distanceFromRightSide, canvas.height - distanceFromBottomSide, 183, 242); } function drawProfileImgBoundaries() { context.rect(canvas.width - distanceFromRightSide, canvas.height - distanceFromBottomSide, 183, 242); context.clip(); } function renderProfileImg(event) { context.clearRect(210, 380, 183, 242); var leftBoundary = 210, rightBoundary = 393, topBoundary = 380, bottomBoundary = 622, left = profileImgX, right = profileImgX + profileImg.width, top = profileImgY, bottom = profileImgY + profileImg.height; dragProfileImg(left, right, top, bottom, leftBoundary, rightBoundary, topBoundary, bottomBoundary); zoomProfileImg(leftBoundary, rightBoundary, topBoundary, bottomBoundary); context.drawImage(profileImg, profileImgX, profileImgY, profileImg.width, profileImg.height); } /** * Clicks **/ function mouseActions() { mouseX = 0, mouseY = 0, mousePressed = false; dragging = false; canvas.onmousemove = function(e) { mouseX = e.offsetX; mouseY = e.offsetY; } $(document).mousedown(function() { mousePressed = true; }).mouseup(function() { mousePressed = false; dragging = false; }); $('#profile-blue').click( function() { $('#panel1').css({ 'display': 'block' }); } ); $('#continue-button').click( function() { $('#panel1').css({ 'display': 'none' }); $('#panel2').css({ 'display': 'block' }); } ); $('#back-button').click( function() { $('#panel2').css({ 'display': 'none' }); } ); $('#facebook').click(function() { canvasToData(); formData.append('message', $('input[type="file"]')[0].files.length !== 0 ? 'I’ve joined team remain for the EU Referendum. Which side are you on? http://www.euwhichsideareyouon.com' : 'It’s time to decide which side are you on for the EU Referendum. Choose you team here: http://www.euwhichsideareyouon.com'); FB.login(function(response) { var auth = response.authResponse; $.ajax({ url: 'https://graph.facebook.com/' + auth.userID + '/photos?access_token=' + auth.accessToken, type: 'POST', data: formData, processData: false, contentType: false, cache: false, beforeSend: function(formData) { $('#facebook').addClass('facebook-beforesend'); }, complete: function() { $('#facebook').removeClass('facebook-beforesend'); $('#facebook').addClass('facebook-success'); setTimeout( function() { $('#facebook').removeClass('facebook-success'); }, 3000); } }); }, { scope: 'publish_actions' }); }); $('#twitter').click(function() { canvasToData(); formData.append('message', $('input[type="file"]')[0].files.length !== 0 ? 'photo' : 'nophoto'); $.ajax({ url: './twitter/tweet-with-media.php', type: 'POST', data: formData, processData: false, contentType: false, cache: false, beforeSend: function() { $('#twitter').addClass('twitter-beforesend'); } }).then(function(data) { window.open(data); // authorisation URL }).done(function() { $('#twitter').removeClass('twitter-beforesend'); $('#twitter').addClass('twitter-success'); setTimeout( function() { $('#twitter').removeClass('twitter-success'); }, 3000); }); }); function facebook() { window.fbAsyncInit = function() { FB.init({ appId: '1851476801746058', xfbml: true, version: 'v2.5' }); }; (function(d, s, id) { var js, fjs = d.getElementsByTagName(s)[0]; if (d.getElementById(id)) { return; } js = d.createElement(s); js.id = id; js.src = "//connect.facebook.net/en_US/sdk.js"; fjs.parentNode.insertBefore(js, fjs); }(document, 'script', 'facebook-jssdk')); } facebook(); } /** * Helpers **/ function resetZoom() { sliderPrevPos = $('#zoom input[type="range"]')[0].defaultValue; $('#zoom input[type="range"]')[0].value = $('#zoom input[type="range"]')[0].defaultValue; } function zoomProfileImg(leftBoundary, rightBoundary, topBoundary, bottomBoundary) { $('#zoom input[type="range"]').change(function() { var sliderCurrPos = parseInt(this.value), zoomDelta = parseInt(Math.abs(sliderCurrPos - sliderPrevPos) * zoomSpeed); if (sliderCurrPos > sliderPrevPos) { profileImg.width += zoomDelta; profileImg.height += zoomDelta; } else if (sliderCurrPos < sliderPrevPos) { profileImg.width -= zoomDelta; profileImg.height -= zoomDelta; } sliderPrevPos = sliderCurrPos; }); } function orientateProfileImg() { orientation = EXIF.getTag(this, "Orientation"); switch (orientation) { case 0: // 90° rotate left context.translate(canvas.width / 2 + 183, canvas.height / 2 - 242); context.rotate(90 * (Math.PI / 180)); break; case 2: // horizontal flip context.scale(-1, 1); break; case 3: // 180° rotate left context.rotate(Math.PI); break; case 4, -90: // vertical flip context.translate(0, 1000); context.scale(1, -1); break; case 5: // vertical flip + 90 rotate right context.translate(0, 1000); context.rotate(-90 * (Math.PI / 180)); context.scale(1, -1); break; case 6: // 90° rotate right context.rotate(-90 * (Math.PI / 180)); break; case 7: // horizontal flip + 90 rotate right context.rotate(-90 * (Math.PI / 180)); context.scale(-1, 1); break; case 8, 90: // 90° rotate left context.translate(canvas.width / 2 + 183, canvas.height / 2 - 242); context.rotate(90 * (Math.PI / 180)); break; default: // vertical flip context.translate(0, 1000); context.scale(1, -1); break; } } function setAspectRatio() { profileImg.width = profileImg.naturalWidth; profileImg.height = profileImg.naturalHeight; if (profileImg.width > profileImg.height) { profileImg.width = 242 * profileImg.width / profileImg.height; profileImg.height = 242; } else if (profileImg.width < profileImg.height) { profileImg.height = 183 * profileImg.height / profileImg.width; profileImg.width = 183; } else if (profileImg.height == profileImg.width) { profileImg.width = 242; profileImg.height = 242; } } function dragProfileImg(left, right, top, bottom, leftBoundary, rightBoundary, topBoundary, bottomBoundary) { if (mouseX < rightBoundary && mouseX > leftBoundary && mouseY < bottomBoundary && mouseY > topBoundary) { canvas.style.cursor = 'move'; } else { canvas.style.cursor = 'default'; drag = false; } if (mousePressed) { if (!drag) { startX = mouseX - profileImgX; startY = mouseY - profileImgY; } if (mouseX < right && mouseX > left && mouseY < bottom && mouseY > top) { if (!dragging) { dragging = true; drag = true; } } } else { drag = false; } if (drag) { profileImgX = mouseX - startX; profileImgY = mouseY - startY; } if (profileImgX > leftBoundary) { profileImgX = leftBoundary; } if ((profileImgX + profileImg.width) < rightBoundary) { profileImgX = rightBoundary - profileImg.width; } if (profileImgY > topBoundary) { profileImgY = topBoundary; } if ((profileImgY + profileImg.height) < bottomBoundary) { profileImgY = bottomBoundary - profileImg.height; } } function hideDefaultProfileImg() { $('#profile-red').fadeOut('fast'); } function enableZoomSlider() { $('#zoom input[type="range"]').prop("disabled", false); } function canvasToData() { canvasDataUrl = canvas.toDataURL("image/jpeg"), canvasBlob = dataURItoBlob(canvasDataUrl); formData = new FormData(); formData.append("canvasToData", canvasBlob); } function dataURItoBlob(dataURI) { var byteString = atob(dataURI.split(',')[1]), mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0], ab = new ArrayBuffer(byteString.length), ia = new Uint8Array(ab); for (var i = 0; i < byteString.length; i++) { ia[i] = byteString.charCodeAt(i); } var bb = new Blob([ab], { "type": mimeString }); return bb; } Link to comment Share on other sites More sharing options...
Ingolme Posted June 17, 2016 Share Posted June 17, 2016 You've forgotten to declare all your variables with the var keyword, which means that most of them are in the global scope. I think you meant to write context.save() here: save.context(); You're making an unnecessary function call here, just set it to EXIF.getData(profileImg, orientateProfileImg) EXIF.getData(profileImg, function() { orientateProfileImg(); }); For efficiency, you should create this outside the loop: overlayImg = new Image(); overlayImg.src = './img/profile_default_red_frame.png'; Now, as I said earlier, to solve your problem you need to do the transformation right before drawing the object you want transformed. /* --- Some code here --- */ function processProfileImg(event) { var overlayImg = new Image(); overlayImg.src = './img/profile_default_red_frame.png'; var profileImg = new Image(); profileImg.src = event.target.result; profileImg.onload = function() { /* --- Some code here --- */ // Get image orientation var orientation; EXIF.getData(profileImg, function() { orientation = EXIF.getTag(this, "Orientation") }); function animationLoop() { // Save context.save(); // Transform transformContext(orientation); // Draw renderProfileImg(); // Restore context.restore(); // Draw overlay context.drawImage(overlayImg, canvas.width - distanceFromRightSide, canvas.height - distanceFromBottomSide, 183, 242); // Next iteration requestAnimationFrame(animationLoop, profileImg); } animationLoop(); hideDefaultProfileImg(); } /* --- Some code here --- */ } function transformContext(orientation) { switch (orientation) { case 0: // 90° rotate left context.translate(canvas.width / 2 + 183, canvas.height / 2 - 242); context.rotate(90 * (Math.PI / 180)); break; case 2: // horizontal flip context.scale(-1, 1); break; case 3: // 180° rotate left context.rotate(Math.PI); break; case 4, -90: // vertical flip context.translate(0, 1000); context.scale(1, -1); break; case 5: // vertical flip + 90 rotate right context.translate(0, 1000); context.rotate(-90 * (Math.PI / 180)); context.scale(1, -1); break; case 6: // 90° rotate right context.rotate(-90 * (Math.PI / 180)); break; case 7: // horizontal flip + 90 rotate right context.rotate(-90 * (Math.PI / 180)); context.scale(-1, 1); break; case 8, 90: // 90° rotate left context.translate(canvas.width / 2 + 183, canvas.height / 2 - 242); context.rotate(90 * (Math.PI / 180)); break; default: // vertical flip context.translate(0, 1000); context.scale(1, -1); break; } } /* --- Some code here --- */ Link to comment Share on other sites More sharing options...
Mad_Griffith Posted June 17, 2016 Author Share Posted June 17, 2016 Thanks a lot! I got it now. Also good to know about the efficiency improvement (it was in fact downloading the picture at every loop iteration... I hope my bill won't be too high at the end of the month ! ). One more thing is that the orientation I am doing also inverts the dragging... is there any native html5 method I can use to prevent such a behaviour or do I have to stick with adding an exception to dragProfileImg() ? Link to comment Share on other sites More sharing options...
Ingolme Posted June 17, 2016 Share Posted June 17, 2016 You would have to invert the operation on the profileImgX and profileImgY variables when you transform, then change it back after the transformation is done. Another option is to transform the image before starting the animation and paste it on a new canvas, then draw that canvas onto the other canvas. var transformedImage = new Canvas(); var transformContext = transformedImage.getContext("2d"); switch(orientation) { /* ... Set width, height x and y and do transformations to the context ... */ } transformedImage.drawImage(profileImg, ... width, height, etc. ...); Then in your animation loop just draw the image: function animationLoop() { // Draw image renderProfileImg(transformedImage); // Draw overlay context.drawImage(overlayImg, canvas.width - distanceFromRightSide, canvas.height - distanceFromBottomSide, 183, 242); // Next iteration requestAnimationFrame(animationLoop, profileImg); } function renderProfileImg(img) { /* Calculate position, width, height, etc) */ context.drawImage(img, ... ... ); } Link to comment Share on other sites More sharing options...
Mad_Griffith Posted June 17, 2016 Author Share Posted June 17, 2016 Thanks. How efficient is the latter? Link to comment Share on other sites More sharing options...
Ingolme Posted June 17, 2016 Share Posted June 17, 2016 It's more efficient. In the first case you're doing extra calculations to figure out where things would be in the previous coordinate system, in the second case you're transforming the image only once and then not doing any other calculations. Overall, the performance difference between the two is negligible for a simple application like this one. I would go with storing the transformed image on a new canvas so that I wouldn't have to figure out what inverse operations to do to calculate the drag position. Link to comment Share on other sites More sharing options...
Mad_Griffith Posted June 19, 2016 Author Share Posted June 19, 2016 Thanks a bunch! Link to comment Share on other sites More sharing options...
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now