Page Example

JPEG Exif Editor Sample Overview

Mobile Web

Related Info

The JPEG Exif Editor sample application demonstrates how you can browse JPEG images stored on device and read and edit the Exif data of these images.

The following figure illustrates the main screens of the JPEG Exif Editor.

Figure: JPEG Exif Editor screens

JPEG Exif Editor screens

The application opens with the main screen, which shows a list of JPEG images found on the device.

If you click on a list item, the Exif screen is displayed, showing the current Exif data of the selected image. To edit the data, modify the values and click Save.

The following Exif data is displayed:

  • Width
  • Height
  • Manufacturer
  • Model
  • Date
  • Orientation
  • Focal ratio
  • ISO speed
  • Exposure time
  • Exposure program
  • Focal length
  • White balance
  • Latitude
  • Longitude
  • Altitude
  • GPS processing method
  • GPS time and user comment

Prerequisites

To ensure proper application execution, the following privileges must be set:

  • http://tizen.org/privilege/content.write
  • http://tizen.org/privilege/content.read

Source Files

You can create and view the sample application project including the source files in the IDE.

The application uses a simple MV (Model View) architecture, where the core part determines the architecture and the app part determines the application behavior. The core.js file implements a simple AMD (Asynchronous Module Definition) and specifies module defining.

File name Description
config.xml This file contains the application information for the platform to install and launch the application, including the view mode and the icon to be used in the device menu.
ccs/ This directory contains the CSS styling for the application UI.
index.html This is a starting file from which the application starts loading. It also contains the layout of the application screens.
js/ This directory contains the application code.
js/core This directory contains application framework.
js/helpers This directory contains the helper functions used by the view and model modules.
js/models This directory contains the application model modules.
js/views/ This directory contains the files implementing the application views.
templates/ This directory contains the templates for the list items.

Implementation

Defining the Application Layout

The application contains 2 screens that are defined in the index.html file:

  • Main screen displays a list of JPEG images found on the device.

    The main screen layout consists of a header and an image list:

    <!--index.html-->
    <div class="ui-page ui-page-active" id="chooseImage">
       <div data-role="header">
          <h1>Choose an image file</h1>
       </div>
       <div data-role="content">
          <p id="noImagesMsg" class="hidden">
             No images were found on the device
          </p>
          <ul id="imagesList" class="ui-listview"></ul>
       </div>
    </div>

    The list content is generated from the templates/imageFile.tpl template file:

    <!--templates/imageFile.tpl-->
    {{#images}}
    <li class="ui-li-has-multiline">
       <a  href="#edit" data-uri="{{this.path}}">
          <div  class="title">{{this.title}}</div>
          <span  class="ui-li-text-sub">{{this.path}}</span>
          <img  src="{{this.thumbnailPath}}">
       </a>
    </li>
    {{/images}}
  • Exif screen displays a form that contains EXIF properties loaded for the selected image file.

    The Exif screen layout consists of a header with the file name, an Exif property list, and a footer with a Save button:

    <!--index.html-->
    <div class="ui-page" id="edit">
       <div class="ui-header">
          <h1></h1>
       </div>
       <div class="ui-content">
          <ul class="ui-listview">
             <li data-role="list-divider">Width</li>
             <li>
                <input id="width" type="number">
             </li>
             <li data-role="list-divider">Height</li>
             <li>
                <input id="height" type="number">
             </li>
             <li data-role="list-divider">Camera manufacturer</li>
             <li>
                <input id="manufacturer" type="text">
             </li>
             <li data-role="list-divider">Camera model</li>
             <li>
                <input id="model" type="text">
             </li>
             <li data-role="list-divider">Creation date</li>
             <li>
                <input id="date" type="date">
             </li>
             <li data-role="list-divider">Orientation</li>
             <li>
                <select data-native-menu="true" id="orientation">
                   <option value="NORMAL">Normal</option>
                   <option value="ROTATE_90">Rotate 90&deg;</option>
                   <option value="ROTATE_180">Rotate 180&deg;</option>
                   <option value="ROTATE_270">Rotate 270&deg;</option>
                   <option value="FLIP_VERTICAL">Flip vertical</option>
                   <option value="FLIP_HORIZONTAL">Flip horizontal</option>
                   <option value="TRANSPOSE">Transpose</option>
                   <option value="TRANSVERSE">Transverse</option>
                </select>
             </li>
             <li data-role="list-divider">
                <label for="fNumber">Focal ratio</label>
             </li>
             <li>
                <input id="fNumber" type="text" data-validate="double">
             </li>
             <li data-role="list-divider">ISO speed</li>
             <li>
                <ul id="isoSpeed" class="ui-listview"></ul>
             </li>
             <li>
                <button id="addIsoSpeedElement">Add element</button>
             </li>
             <li data-role="list-divider">Exposure time</li>
             <li>
                <input id="exposureTime" type="text">
             </li>
             <li data-role="list-divider">Exposure program</li>
             <li>
                <select data-native-menu="true" id="exposureProgram">
                   <option value="NOT_DEFINED">Not defined</option>
                   <option value="MANUAL">Manual</option>
                   <option value="NORMAL">Normal</option>
                   <option value="APERTURE_PRIORITY">Aperture priority</option>
                   <option value="SHUTTER_PRIORITY">Shutter priority</option>
                   <option value="CREATIVE_PROGRAM">Creative program</option>
                   <option value="ACTION_PROGRAM">Action program</option>
                   <option value="PORTRAIT_MODE">Portrait mode</option>
                   <option value="LANDSCAPE_MODE">Landscape mode</option>
                </select>
             </li>
             <li data-role="list-divider">
                <label for="focalLength">Focal length</label>
             </li>
             <li>
                <input id="focalLength" type="text" data-validate="double">
             </li>
             <li data-role="list-divider">White balance</li>
             <li>
                <select data-native-menu="true" id="whiteBalance">
                   <option value="AUTO">Auto</option>
                   <option value="MANUAL">Manual</option>
                </select>
             </li>
             <li data-role="list-divider">
                <label for="latitude">Latitude</label>
             </li>
             <li>
                <input id="latitude" type="text" data-validate="double">
             </li>
             <li data-role="list-divider">
                <label for="longitude">Longitude</label>
             </li>
             <li>
                <input id="longitude" type="text" data-validate="double">
             </li>
             <li data-role="list-divider">
                <label for="altitude">Altitude</label>
             </li>
             <li>
                <input id="altitude" type="text" data-validate="double">
             </li>
             <li data-role="list-divider">GPS processing method</li>
             <li>
                <input id="gpsProcessingMethod" type="text">
             </li>
             <li data-role="list-divider">GPS time</li>
             <li>
                <input id="gpsDateTime" type="date">
             </li>
             <li data-role="list-divider">User comment</li>
             <li>
                <textarea id="userComment"></textarea>
             </li>
          </ul>
       </div>
       <div class="ui-footer">
          <button id="save">Save</button>
       </div>
    </div>

    The screen also contains 3 popups: save succeeded message, operation failed message, and validation error message:

    <!--index.html-->
    <div id="saveSucceededPopup" data-role="notification" data-type="popup"
         data-interval="3000">
       <div class="ui-popup-text">
          <p>Saved successfully</p>
       </div>
    </div>
        
    <div id="saveFailedPopup" data-role="notification" data-type="popup"
         data-interval="3000">
       <div class="ui-popup-text">
          <p>Operation failed: <span id="errorMessage"></span></p>
       </div>
    </div>
        
    <div id="invalidFields" data-role="popup" class="center_title_1btn">
       <div class="ui-popup-title">
          <h1>Validation error</h1>
       </div>
       <div class="ui-popup-text">
          Please correct values in the following fields:
          <ul id="invalidFieldsList"></ul>
       </div>
       <div class="ui-popup-button-bg">
          <a data-role="button" data-rel="back"
             data-inline="true">OK</a>
       </div>
    </div> 

    The ISO speed element creates a list of numeric values. The list is defined by the <ul> element, and its content is generated using the templates/isoSpeed.tpl template:

    <!--templates/isoSpeed.tpl-->
    <li class="ui-li-has-right-btn">
       <div class="input-container">
          <input type="number" value="{{value}}">
       </div>
        
       <div data-role="button" data-inline="true"
            data-icon="delete" data-style="circle"></div>
    </li> 

Finding JPEG Images on the Device

To find all JPEG images stored in the device media database:

  1. Define the getImages() method that finds the JPEG images.
  2. Inside the getImages() method, call the find() method of the Content API. To limit the image search define a suitable AttributeFilter as a parameter.
  3. Define the onContentFound() callback.
  4. After the images files have been found, they are transformed into an object array of the following structure:

    {
       title: {string} – title of the image
       path: {string} – absolute path of the image
       thumbnailPath: {string|null} – absolute path of the thumbnail image or null
    }
  5. The comparePaths() method sorts the result array by the image path:

    function comparePaths(file1, file2) 
    {
       return file1.path.localeCompare(file2.path);
    }

The getImages() method returns an array of JPEG images found on the device. If the getImages() method executes successfully, the onSuccess callback is called with a list of found content files. Otherwise, the onError callback is called.

/* modules/exif.js */
function getImages(onSuccess, onError) 
{
   tizen.content.find(function onContentFound(contents) 
   {
      var result = [], content = null, i = 0;

      for (i = 0; i < contents.length; i += 1) 
      {
         content = contents[i];
         result.push(
         {
            title: content.title,
            path: content.contentURI,
            thumbnailPath: content.thumbnailURIs.length ? content.thumbnailURIs[0] : null
         });
      }
      onSuccess(result.sort(comparePaths));
   }, onError, null,
   new tizen.AttributeFilter('mimeType', 'EXACTLY', 'image/jpeg'));
}

Reading Exif Information

To get the Exif information, use the getExifInfo method from the Exif API:

/* models/exif.js */
function readExifInfo(imageUri, onSuccess, onError) 
{
   try 
   {
      tizen.exif.getExifInfo(imageUri, onSuccess, onError);
   } 
   catch (e) 
   {
      onError(e);
   }
}

If the imageUri parameter is correct, the onSuccess callback is called with the ExifInformation object as an input parameter. If the specified JPEG file does not exists or has no EXIF data, the onError callback is executed instead.

Writing Exif Information

To write the Exif information to a file, you need an ExifInformation object. This object can be created from scratch or obtained from the JPEG image.

To write Exif information:

  1. Prepare the tizen.ExifInformation instance to be written.

    To create an tizen.ExifInformation object from scratch, create an empty instance with the ExifInformation constructor. The constructor takes an image URI as an input parameter, creates an empty ExifInformation object, and sets the specified image URI to this object.

    /* models/exif.js */
    function createExifInfo(imageUri) 
    {
       var exifInfo = new tizen.ExifInformation();
       exifInfo.uri = imageUri;
    
       return exifInfo;
    }
  2. When you have access to the ExifInformation object (you have created it from scratch or retrieved it from an image file), you can overwrite its fields.

    Save the modified ExifInformation object with the tizen.exif.saveExifInfo method:

    /* models/exif.js */
    function saveExifInfo(exifInfo, onSuccess, onError) 
    {
       try 
       {
          tizen.exif.saveExifInfo(exifInfo, onSuccess, onError);
       } 
       catch (e) 
       {
          onError(e);
       }
    }