locked
let user draw on photo and save marked up photo RRS feed

  • Question

  • Hi.  I am making a Windows 8.1 tablet app and it has to accommodate this use case:

     - the user takes a photo

     - the user marks up the photo, for example they may circle something on the photo and write, "here"

     - the user clicks Save, and the app saves the photo with the users markings

     - the app has to be able to

    - display the photo with the users markings at various places and sizes in the app and 

    - upload the photo w markings to the database  (if I get the above functionality in place I can handle this step.)

    I have the app taking a photo and am able to re-display that photo where ever I want in the app, but I'm clueless as to what code to use for the mark-up-photo-and-save-with-mark-up functionality.

    Thanks


    Phil

    Sunday, November 17, 2013 4:16 PM

Answers

  • Hmmmm Kraig.  Thanks for getting me started with the <canvas> element and the getContext('2d') methods.  I didn't realize that stuff was so powerful.  Here is what I did.

    Create your context object

    var c = document.getElementById('canvas_1');
    context = c.getContext('2d');

    Load the image into the canvass. The path the image was like,    blob: a GUID

    function LoadPictureToTheCanvas() {
    // create an Image object
    var i = new Image();  
    if (my_photo_path) {
        var the_path = my_photo_path;
        // this runs after the image is loaded
        i.onload = function () {
            // params are: theImage, startX, startY, w of image, h of image
            context.drawImage(i, 0, 0, 400, 300);
        }
        // load my image into the Image object using the path to the image
        i.src = the_path;
    } else {
        return;
    }
    }
    

    Add event listeners for mousedown, mousemove, mouseup, and mouseout  (code not shown).  Note: I made mouseup and mouseout use the same method.

    Write event handlers for these events. Since I am on a table I call them pen_up, pen_down, etc

    // figure out the correct x and y coords for the canvas and return those when needed
    function pos(e, coord) {
        var the_position = 0;
        if (coord == 'x') {
            // ALERT: we have put a new element (<img>) in the DOM when we loaded the image so we need Parent Offset x and y too
            var opl = e.currentTarget.offsetParent.offsetLeft;
            the_position = e.pageX - c.offsetLeft - opl;
        } else if (coord == 'y') {
            var opt = e.currentTarget.offsetParent.offsetTop;
            the_position = e.pageY - c.offsetTop - opt;
        } else {
            var m = new Windows.UI.Popups.MessageDialog('Cant tell which coord you want.');
            m.showAsync();
        }
        return the_position;
    }
    function pen_down(e) {   
        is_drawing = true;
        context.beginPath();  // a canvas method
        context.moveTo(pos(e, 'x'), pos(e, 'y') );
    }
    function pen_moving(e) {
        if (is_drawing) {
            context.lineTo(pos(e, 'x'), pos(e, 'y'));
            context.stroke();
        }
        // return;
    }
    function pen_up(e) {
        is_drawing = false;
        // return;
    }
    

    Save the marked up image

    var appData = Windows.Storage.ApplicationData.current;
    var localFolder = appData.localFolder;
    
    function SaveImage(e) {
        // next line creates a base64 data url so I just save it as text
        var url = c.toDataURL();
        localFolder.createFileAsync('my_image.txt', Windows.Storage.CreationCollisionOption.replaceExisting).then(
            function (the_file) {
                return Windows.Storage.FileIO.writeTextAsync(the_file, url);
            }
            ).done(function () { });
    }
    

    Retrieve the base64 string and display it in an <img>.  For me it has the user's markups on it.

    function GetImage(e) {
        localFolder.getFileAsync('my_image.txt')
            .then(function (the_file) {
                return Windows.Storage.FileIO.readTextAsync(the_file);
            }).done(function (the_contents) {
                // write the_contents back to an <img>
                var img_target = document.getElementById('img_target');
                img_target.src = the_contents;
            });
        return;
    }
    

    HTH.


    Phil

    • Marked as answer by pdschuller_ Wednesday, November 20, 2013 2:34 PM
    Wednesday, November 20, 2013 12:42 AM

All replies

  • You use a <canvas> element, copy the image to the canvas using its context.drawImage method, make edits using other canvas context APIs, and then get the bits from context.getImageData, which you then pass to an image encoder. Here's a piece of code from my book (Chapter 10 of the first edition) that shows the process (choosing a file and saving canvas content):

            var picker = new Windows.Storage.Pickers.FileSavePicker();
            picker.suggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.picturesLibrary;
            picker.suggestedFileName = imageFile.name + " - grayscale";
            picker.fileTypeChoices.insert("PNG file", [".png"]);
    
            var imgData, fileStream = null;
    
            picker.pickSaveFileAsync().then(function (file) {
                if (file) {
                    return file.openAsync(Windows.Storage.FileAccessMode.readWrite);                
                } else {
                    return WinJS.Promise.wrapError("No file selected");
                }
            }).then(function (stream) {
                fileStream = stream;
    
                var canvas = document.getElementById("canvas1");
                var ctx = canvas.getContext("2d");
                imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
                
                return Imaging.BitmapEncoder.createAsync(Imaging.BitmapEncoder.pngEncoderId, stream);
            }).then(function (encoder) {
                showProgress(true);  //this is just UI
                
                //Set the pixel data--assume "encoding" object has options from elsewhere.
                //Conversion from canvas data to Uint8Array is necessary because the array type
                //from the canvas doesn't match what WinRT needs here.
              
                encoder.setPixelData(encoding.pixelFormat, encoding.alphaMode,
                    encoding.width, encoding.height, encoding.dpiX, encoding.dpiY,
                    new Uint8Array(imgData.data));
    
                //Go do the encoding
                return encoder.flushAsync();
            }).done(function () {
                showProgress(false);
    
                //Make sure to do this at the end
                fileStream.close();
            }, function () {
                showProgress(false);
            });
        }
    

    This is from an example in the book called ImageManipulation in the companion content. Be sure to look at the first edition as the second edition preview doesn't have the media chapter in it.

    Kraig

    Author, Programming Windows Store Apps with HTML, CSS, and JavaScript, Second Edition, a free ebook from Microsoft Press. First edition (for Windows 8) also available.


    Monday, November 18, 2013 4:35 PM
  • Thanks but I don't see the code that handles this

     the user marks up the photo, for example they may circle something on the photo and write, "here"

    What code does that?


    Phil

    Monday, November 18, 2013 6:47 PM
  • I didn't show anything like that--it's all in what I said about using the canvas APIs to draw on the canvas. This is standard HTML5, and refer to http://msdn.microsoft.com/en-us/library/windows/apps/hh465064.aspx for a bunch on that subject.
    Monday, November 18, 2013 7:06 PM
  • That article is going to be helpful but it doesn't show what I need.  In the article they are drawing to the canvass programatically with javascript.

    I need to draw to the canvass starting with the user's drawing actions.   The user will have a stylus (or maybe just use their finger. I don't know if that is possible.) and draw on the screen. 

    I need to convert that to a corresponding mark on the image.

    Thanks.

     The use case is that the user has to be able to draw a circle on the image and write out the word, "here" on the image.


    Phil

    Monday, November 18, 2013 8:18 PM
  • This is just a matter of translating input events (e.g. pointerdown + pointermove + pointerup) into a higher level UI for drawing shapes, etc. via the canvas APIs shown in the article. level API.

    I can recommend that that to show the interactive part of drawing a shape that you use a second canvas element overlaid on top of the one with the image. The overlay canvas is where you show the lines, circles, etc. as the user is moving around, and when they release and thus commit to a shape, you then make the equivalent canvas API calls onto the one with the image.

    Monday, November 18, 2013 9:23 PM
  • Hmmmm Kraig.  Thanks for getting me started with the <canvas> element and the getContext('2d') methods.  I didn't realize that stuff was so powerful.  Here is what I did.

    Create your context object

    var c = document.getElementById('canvas_1');
    context = c.getContext('2d');

    Load the image into the canvass. The path the image was like,    blob: a GUID

    function LoadPictureToTheCanvas() {
    // create an Image object
    var i = new Image();  
    if (my_photo_path) {
        var the_path = my_photo_path;
        // this runs after the image is loaded
        i.onload = function () {
            // params are: theImage, startX, startY, w of image, h of image
            context.drawImage(i, 0, 0, 400, 300);
        }
        // load my image into the Image object using the path to the image
        i.src = the_path;
    } else {
        return;
    }
    }
    

    Add event listeners for mousedown, mousemove, mouseup, and mouseout  (code not shown).  Note: I made mouseup and mouseout use the same method.

    Write event handlers for these events. Since I am on a table I call them pen_up, pen_down, etc

    // figure out the correct x and y coords for the canvas and return those when needed
    function pos(e, coord) {
        var the_position = 0;
        if (coord == 'x') {
            // ALERT: we have put a new element (<img>) in the DOM when we loaded the image so we need Parent Offset x and y too
            var opl = e.currentTarget.offsetParent.offsetLeft;
            the_position = e.pageX - c.offsetLeft - opl;
        } else if (coord == 'y') {
            var opt = e.currentTarget.offsetParent.offsetTop;
            the_position = e.pageY - c.offsetTop - opt;
        } else {
            var m = new Windows.UI.Popups.MessageDialog('Cant tell which coord you want.');
            m.showAsync();
        }
        return the_position;
    }
    function pen_down(e) {   
        is_drawing = true;
        context.beginPath();  // a canvas method
        context.moveTo(pos(e, 'x'), pos(e, 'y') );
    }
    function pen_moving(e) {
        if (is_drawing) {
            context.lineTo(pos(e, 'x'), pos(e, 'y'));
            context.stroke();
        }
        // return;
    }
    function pen_up(e) {
        is_drawing = false;
        // return;
    }
    

    Save the marked up image

    var appData = Windows.Storage.ApplicationData.current;
    var localFolder = appData.localFolder;
    
    function SaveImage(e) {
        // next line creates a base64 data url so I just save it as text
        var url = c.toDataURL();
        localFolder.createFileAsync('my_image.txt', Windows.Storage.CreationCollisionOption.replaceExisting).then(
            function (the_file) {
                return Windows.Storage.FileIO.writeTextAsync(the_file, url);
            }
            ).done(function () { });
    }
    

    Retrieve the base64 string and display it in an <img>.  For me it has the user's markups on it.

    function GetImage(e) {
        localFolder.getFileAsync('my_image.txt')
            .then(function (the_file) {
                return Windows.Storage.FileIO.readTextAsync(the_file);
            }).done(function (the_contents) {
                // write the_contents back to an <img>
                var img_target = document.getElementById('img_target');
                img_target.src = the_contents;
            });
        return;
    }
    

    HTH.


    Phil

    • Marked as answer by pdschuller_ Wednesday, November 20, 2013 2:34 PM
    Wednesday, November 20, 2013 12:42 AM