Rendering Images from Byte Arrays and Converting Images to Byte Arrays using AngularJS

By | September 26, 2014

There might be some instances that you want to render Images not from a file but from a byte array because of many different reasons.  For me I am doing this as I am getting the information from LDAP specifically the thumbnailPhoto attribute of a User Principal which is a byte array.  Now in my web form I allow users to change their photos in Active Directory so that on our Contact Search they can be easily identified by a photo.  Now thats one part of the problem, another part would be converting it back to a byte array from an image file uploaded by the user.

Sounds simple, yes but we wont be using any server side conversion in this example we will all be doing it on the client side.

Lets say you have a ApiController with the following Get and Post methods

public MyInformationViewModel Get(int id)
{
    var user = userRepository.GetUser(Authentication.GetCurrentAuthenticatedUserName());
 
    var viewModel = new MyInformationViewModel
    {
        Username = user.SamAccountName,
        LastName = user.Surname,
        FirstName = user.GivenName,
        Position = principalRepository.GetProperty(user, "title"),
        DepartmentId = int.Parse(principalRepository.GetProperty(user, "departmentNumber")),
        EmployeeId = int.Parse(principalRepository.GetProperty(user, "employeeNumber")),
        Department = principalRepository.GetProperty(user, "department"),
        EmailAddress = user.EmailAddress,
        Phone = principalRepository.GetProperty(user, "telephoneNumber"),
        Mobile = principalRepository.GetProperty(user, "mobile"),
        Ddi = principalRepository.GetProperty(user, "ipPhone"),
        Fax = principalRepository.GetProperty(user, "facsimileTelephoneNumber"),
        UserPhoto = principalRepository.GetProperty_Byte(user, "thumbnailPhoto")
    };
 
    return viewModel;
}
 
[Transaction]
public void Post(MyInformationViewModel viewModel)
{
    var isSuccessful = userTasks.UpdateUserDetails(
        viewModel.Username,
        viewModel.Phone,
        viewModel.Mobile,
        viewModel.Fax,
        viewModel.UserPhoto
        );
}

Quite straightforward.  The Get basically grabs the information about the user and Post basically saves the information about the user.  Now lets focus on one property of the MyInformationViewModel which is the UserPhoto.

public byte[] UserPhoto { getset; }

Which of course is a byte[]

Now you might be wondering how to render that in a web form?

Well its straightforward you just set the ng-src of an img tag.  It would then look like this.  The data:image/jpeg;base64 defines the byte array from data.UserPhoto so it can be rendered correctly on the clients browser.

<img ng-src="data:image/jpeg;base64,{{data.UserPhoto}}" id="photo-id"/>

Nothing really special right? So if you look at the code behind you will see its not a path but the image is embedded on the form when its loaded.

01 View Source

Now how do we deal uploading a different image? how do you handle conversion of image to byte array.

Well you can handle this with Ajax calls to the server and convert the image but we are more interested in doing it all in client for performance reasons.  So your solution is to put an input tag below the image and call a AngularJS function which we call uploadFile.  We will then pass the file to that function and convert it even before sending it to the server.  Your input tag should look like this.

<input type="file" name="file" onchange="angular.element(this).scope().uploadFile(this)" id="photo-upload"/>

Now this is how the function looks like

$scope.uploadFile = function (input) {
 
    if (input.files && input.files[0]) {
        var reader = new FileReader();
        reader.onload = function (e) {
 
            //Sets the Old Image to new New Image
            $('#photo-id').attr('src', e.target.result);
 
            //Create a canvas and draw image on Client Side to get the byte[] equivalent
            var canvas = document.createElement("canvas");
            var imageElement = document.createElement("img");
 
            imageElement.setAttribute('src', e.target.result);
            canvas.width = imageElement.width;
            canvas.height = imageElement.height;
            var context = canvas.getContext("2d");
            context.drawImage(imageElement, 0, 0);
            var base64Image = canvas.toDataURL("image/jpeg");
 
            //Removes the Data Type Prefix 
            //And set the view model to the new value
            $scope.data.UserPhoto = base64Image.replace(/data:image\/jpeg;base64,/g'');
        }
                
        //Renders Image on Page
        reader.readAsDataURL(input.files[0]);
    }
};

Now I will explain to you step by step.

First you check whether there is a file first or not before processing.

if (input.files && input.files[0])

Then you need to initialize a FileReader to read the image the user uploads.

var reader = new FileReader();
reader.onload = function (e) { ...... }

Now to give a bit of interaction you definitely want to change the photo displayed on the form while the user chooses it.  The first line below sets it and the last renders it.

$('#photo-id').attr('src', e.target.result);
.
.
.
.
.
reader.readAsDataURL(input.files[0]);

In action it would look like this.

Upload Image

Now let do the conversion.  Before doing so you need to recreate the image in memory by drawing the canvas and assigning the image to the canvas.

var canvas = document.createElement("canvas");
var imageElement = document.createElement("img");
 
imageElement.setAttribute('src', e.target.result);
canvas.width = imageElement.width;
canvas.height = imageElement.height;
var context = canvas.getContext("2d");
context.drawImage(imageElement, 0, 0);

Now that the image is in memory you need to convert it to byte array like this

var base64Image = canvas.toDataURL("image/jpeg");

But on conversion it will be prefixed like this, defining the file type.

02 Base 64 Image

and  you need to remove that prefix before assigning it back to the viewModel otherwise it will save incorrectly.  The data you need is the one after that comma.

$scope.data.UserPhoto = base64Image.replace(/data:image\/jpeg;base64,/g'');

Thats it, you had converted the image to byte array, now you just need to submit it together with other information on your form.

04 Saved Information

 

And if you’re interested how the full form looks like here is the full .cshtml code

@model MySampleApp.Web.Mvc.Controllers.ViewModels.MyInformation.MyInformationViewModel
@{
    ViewBag.Title = "My Information";
    Layout = "~/Views/Shared/_Layout.cshtml";
}
 
<form name="modelForm" ng-controller="getRequest as data" ng-submit="save(modelForm.$valid)" ng-init="init('MyInformation','0')" novalidate class="container-fluid" >
    <h2>My Information</h2>
    <div class="row">
        <div class="form-group col-sm-6">
            @Html.LabelFor(m => m.LastName)
            <label class="form-control">{{data.LastName}}</label>
        </div>
        <div class="form-group col-sm-6">
            <img ng-src="data:image/jpeg;base64,{{data.UserPhoto}}" id="photo-id"/>
            <input type="file" name="file" onchange="angular.element(this).scope().uploadFile(this)" id="photo-upload"/>
        </div>
        <div class="form-group col-sm-6">
            @Html.LabelFor(m => m.FirstName)
            <label class="form-control">{{data.FirstName}}</label>
        </div>
        <div class="form-group col-sm-6">
            @Html.LabelFor(m => m.Position)
            <label class="form-control">{{data.Position}}</label>
        </div>
        <div class="form-group col-sm-6">
            @Html.LabelFor(m => m.Department)
            <label class="form-control">{{data.Department}}</label>
        </div>
        <div class="form-group col-sm-6">
            @Html.LabelFor(m => m.DepartmentId)
            <label class="form-control">{{data.DepartmentId}}</label>
        </div>
        <div class="form-group col-sm-6">
            @Html.LabelFor(m => m.EmployeeId)
            <label class="form-control">{{data.EmployeeId}}</label>
        </div>
        <div class="form-group col-sm-6"> 
            @Html.LabelFor(m => m.EmailAddress)
            <label class="form-control">{{data.EmailAddress}}</label>
        </div>
        <div class="form-group col-sm-6">
            @Html.LabelFor(m => m.Ddi)
            <label class="form-control">{{data.Ddi}}</label>
        </div>
        <div class="form-group col-sm-6">
            @Html.LabelFor(m => m.Phone)
            <input ng-model="data.Phone" class="form-control" required ng-maxlength="50">
        </div>
        <div class="form-group col-sm-6">
            @Html.LabelFor(m => m.Mobile)
            <input ng-model="data.Mobile" class="form-control" required ng-maxlength="50">
        </div>
        
        <div class="form-group col-sm-6">
            @Html.LabelFor(m => m.Fax)
            <input ng-model="data.Fax" class="form-control" required ng-maxlength="50">
        </div>
    </div>
    <div class="button-container">
        <button class="btn btn-link" type="button" ng-click="test()" ng-show="editmode">Test</button>
        <button class="btn btn-primary" type="submit" ng-hide="editmode">Save</button>
        <button class="btn btn-link" type="button" ng-click="edit()" ng-show="editmode">Update</button>
        {{returnMessage}}
        
    </div>
</form>

Have fun coding!

Recommended

4 thoughts on “Rendering Images from Byte Arrays and Converting Images to Byte Arrays using AngularJS

  1. Rudi

    Thanks for your article! All works fine but when i change the image i get a runtime-error in javascript: “$” is undefined. Is there an module which i didnt inject in my angular-app?

    My Array of dependencies:

    [‘ngRoute’, ‘ngResource’, ‘ngAnimate’, ‘LocalStorageModule’, ‘angular-loading-bar’, ‘pascalprecht.translate’, ‘ui.bootstrap’]

    Reply
    1. Rudi

      I found the key to my problem, i didnt load jquery. I thought that isnt necessary. If somebody find a way without jquery, please comment this!

      Reply

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.