Home > English > Introducing DoodlePad!

Introducing DoodlePad!

Another simple but fun application that I wrote for my boys. DoodlePad is yet another paint-like clone that is touch enabled. I wanted to provide a simple means for my boys to play around and make me some cool drawings.

Here are a couple screens shots:

screenshot_02262013_073728
This is the splash screen.

screenshot_02262013_073831
Here is a screen showing my Appbar and NavBar.

screenshot_02262013_073838
Here is an example of the color picker I use.

screenshot_02262013_073848
Here is a simple example of my size picker I use as well.

Like my games, this application relies heavily on HTML5 canvas to get its job done. I also want to provide a link to a pure JavaScript color picker I used in the app in case any of you are looking for one.

I tried to find a control or some pure JavaScript that would allow me to do the size picker and I didn’t find one. I ended up building it myself as the process is really simple and can be used anywhere you need something to reflect size. First, I used an Input type of “range”. Next I positioned a DIV to the right of it.

Here is the HTML snippet of the flyout that I am using in my application:

<div class="flyout" id="sizeFlyout" data-win-control="WinJS.UI.Flyout"><label>Size: <input id="penSize" type="range" max="100" min="1" step="1" value="5" /></label>
<div id="actualSize"></div>
</div>

Next, I will show you the CSS I used:

#sizeFlyout {
    height:100px;
    width: 425px;
}
#actualSize {
    float:right;
    height:5px;
    width:5px;
    background-color: black;
}

Finally, here is the JavaScript I used to adjust the size of the DIV and color:

var brushColor = "#df4b26";
var actualSize = document.getElementById("actualSize");
var penSize = document.getElementById("penSize");
penSize.addEventListener("change", function (element, e) {
    actualSize.style.height = penSize.value + "px";
    actualSize.style.width = penSize.value + "px";
}, false);

As you can see, it is pretty simple and is a great technique to represent the size of an object based on a range or scale.

Another interesting feature that I struggled with a bit was allowing my boys to pick a background color or even open an existing image and use it as a background. My first approach was to simple paint this on to the canvas and it worked fine if the boys would pick a background color and start drawing but the moment they tried to pick a different background color it would overlay and cover up their drawing. I also noticed that the more I put into the canvas the more I had to worry about performance.

It turns out that my solution was simple, an HTML5 canvas is transparent by default and this turns out to be great. All I had to do was simply set the background color of the DIV that was wrapping my canvas.

This worked perfect until I tried to save my canvas out to a file. When I performed a save, I would get a transparent background because the “real” background was not part of the canvas. Here is the save function that I used. I am showing the full cycle from when the user clicks on the save button:

function saveAs() {
    // Verify that we are currently not snapped, or that we can unsnap to open the picker
    var currentState = Windows.UI.ViewManagement.ApplicationView.value;
    if (currentState === Windows.UI.ViewManagement.ApplicationViewState.snapped &&
        !Windows.UI.ViewManagement.ApplicationView.tryUnsnap()) {
        // Fail silently if we can't unsnap
        return;
    }

    var encoderId;
    var filename;
    var stream;

    var imgData;

    // Create the picker object and set options
    var picker = getFilePicker("save");

    picker.pickSaveFileAsync().then(function (file) {
        if (file == null) return;
        filename = file.name;

        if (file) {
            switch (file.fileType) {
                case ".jpg":
                    encoderId = Windows.Graphics.Imaging.BitmapEncoder.jpegEncoderId;
                    break;
                case ".bmp":
                    encoderId = Windows.Graphics.Imaging.BitmapEncoder.bmpEncoderId;
                    break;
                case ".png":
                default:
                    encoderId = Windows.Graphics.Imaging.BitmapEncoder.pngEncoderId;
                    break;
            }
            return file.openAsync(Windows.Storage.FileAccessMode.readWrite);
        } else {
            return WinJS.Promise.wrapError("No file selected");
        }
    }).then(function (_stream) {
        stream = _stream;

        // BitmapEncoder expects an empty output stream; the user may have selected a
        // pre-existing file.
        stream.size = 0;

        return Windows.Graphics.Imaging.BitmapEncoder.createAsync(encoderId, stream);
        //return Windows.Graphics.Imaging.BitmapEncoder.createAsync(Windows.Graphics.Imaging.BitmapEncoder.pngEncoderId, stream);
    }).then(function (encoder) {
        var canvas = id("doodleCanvas");
        var width = canvas.width;
        var height = canvas.height;
        var ctx = canvas.getContext("2d");

        var canvas2 = id("canvas2");
        canvas2.width = width;
        canvas2.height = height;
        WinJS.Utilities.removeClass(canvas2, "hidden");
        var ctx2 = canvas2.getContext("2d");
        var root = document.getElementById("root");
        if (root.style.backgroundImage !== "") {
            ctx2.drawImage(currentImage, 0, 0, width, height);
        }
        else if (root.style.backgroundColor !== "") {
            ctx2.fillStyle = root.style.backgroundColor;
            ctx2.fillRect(0, 0, width, height);
        }
        ctx2.drawImage(canvas, 0, 0);

        imgData = ctx2.getImageData(0, 0, width, height);
        WinJS.Utilities.addClass(canvas2, "hidden");

        encoder.setPixelData(
            Windows.Graphics.Imaging.BitmapPixelFormat.rgba8,
            Windows.Graphics.Imaging.BitmapAlphaMode.straight,
            width,
            height,
            96, // Horizontal DPI
            96, // Vertical DPI
            imgData.data
        );

        //Go do the encoding
        return encoder.flushAsync();
    }).then(function () {
        WinJS.log && WinJS.log("Saved new file: " + filename, "sample", "status");
        //id("buttonSave").disabled = false;
        //id("buttonRender").disabled = false;
    }).then(null, function (error) {
        WinJS.log && WinJS.log("Failed to save file: " + error.message, "sample", "error");
    }).done(function () {
        stream && stream.close();
    });
};

If you haven’t played with Promises, I would suggest you do as they make asynchronous programming a lot easier in JavaScript. My function saveAs gets called whenever the save button is pressed. I then proceed to display a Save File Picker. Once the user has picked a file name or existing file, I grab the stream object and pass it to my encoding. It is here that I perform a simple swap operation where I use two canvas objects. This allows me to “paint” the background on the original canvas while still saving its content on a dummy canvas. I then just dump the contents from the dummy back onto the original canvas.

All in all, I have plans to continue to improve the application. The boys have requested the ability to have stamps or stencils. They would like to have simple shapes to draw as well as the ability to have stock images for them to color. They also wanted to have a crayon effect and some others that they have used in the past.

Hope this helps….

Advertisements
  1. No comments yet.
  1. February 26, 2013 at 10:10 pm

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: