Avg. Rating 4.3

Problem

You want to add your custom text comments to image files and then read them back, without corrupting the images themselves.

Solution

With the AIR File API it is very easy to read and write variable length data into and from binary files. The glitch is that you can't add your data in the beginning of the image file where you'd easily find it, but you have to write it at the end of the file instead.

Detailed explanation

The AIR File API implements a wide variety of methods for reading and writing text and binary data. In this example, you'll use FileStream.bytesAvailable property to read the length of the file and FileStream.position to move forward and backward inside the file and also to determine how much bytes you wrote, when adding the custom tags.

You'll use the method FileStream.writeUnsignedInt and FileStream.writeUTFBytes to write fixed length binary data. For our custom text comments, you'll use a very convenient method provided by the API (FileStream.writeObject) it takes any object and saves it in AMF serialized format, so you won't bother how much and what data you actually store. After you use FileStream.writeObject, you'll check with how many bytes the FileStream.position increased to determine the length of you your object in bytes.

We will build an image viewer giving the users the possibility to enter titles and stories about their photos and save this text into the image file itself. When we add our custom data to the end of the image, the standard image viewers will just ignore it, while our application will actually use it.

This is what we are going to add at the end of the image file :

The number we add at the end tells us how many bytes back to look in order to start reading at the beginning of the AMF object.

So the application provided as an example will do the following :

  • Open photos dragged from the OS.
  • Detect if at the end of the file there is a custom signature, and if so read how long the custom comments object is, and then read the object and show the title and description in the form.
  • When the user enters a title and description and clicks Save (check if there's already custom tags, if so) truncate them from the file stream. Finally, add the new custom tags at the end of the file and save it.
<html>
    <head>
        <title>My custom tags image viewer</title>
        <script type="text/javascript" src="AIRIntrospector.js"></script>
        <script type="text/javascript" src="AIRAliases.js"></script>
        <script type="text/javascript">             //global vars to hold image data             var div = null;             var imgFile = null;             var imgCustSignatureFound = false;             var imgCustTagsLen = 0;                         //our custom tailored signature
            var mySignatureString = "MYCUSTSIG";
            var mySignature = new air.ByteArray();
                mySignature.writeUTFBytes(mySignatureString);

            //on load prepare the image view area div
            function onApplicationLoad(){
                div = document.getElementById('imgWrap');
                div.addEventListener("dragover", onDragEnter);
                div.addEventListener("drop", onDrop);
            }
           
            //accept dragging content in the image view div
            function onDragEnter(e) {
                e.preventDefault();               
            }
           
            //create img element and load the dropped image
            function onDrop(e) {
                var files = e.dataTransfer.getData( "application/x-vnd.adobe.air.file-list" );
                if (files.length > 0) {
                   
                    //if more files are dropped we'll take the first one
                    imgFile = new air.File();
                    imgFile.nativePath = files[0].nativePath;
                   
                    var elImg = document.createElement('img');
                    elImg.src = "file://"+files[0].nativePath;
                    elImg.id = "elImg";
                    elImg.addEventListener('load', onLoadImage);

                    div.innerHTML = "";
                    div.appendChild(elImg);
                }
               
            }
           
            //when the image is loaded do some refining and check for signature
            function onLoadImage() {
                //if the image is too big shrink it to fit the window
                var ratio = this.width/this.height;
                if (this.width > 600 || this.height > 450) {
                    if (this.width > this.height) {
                        this.width = 600;
                        this.height = 600 / ratio;
                    } else {
                        this.height = 450;
                        this.width = 450 * ratio;
                    }
                }
                //check for our custom signature
                detectSignature();
            }
           
            //if custom signature is found read the custom tags and show them
            function detectSignature() {
                var stream = new air.FileStream();
                stream.open( imgFile, air.FileMode.READ );
               
                //go to the position where we expect our signature
                stream.position = stream.bytesAvailable - 13;
               
                //read 9 bytes which could be our signature
                var sigString = stream.readUTFBytes(9);
               
                //check if what we found is what we look for
                if (sigString == mySignatureString) {
                   
                    //my custom signature found
                    imgCustSignatureFound = true;
                   
                    //read the followin 4 bytes, where is the length of the AMF object
                    var custTagsLen = stream.readUnsignedInt();
                   
                    imgCustTagsLen = 13 + custTagsLen;
                   
                    //from the end of the file go back by 13 bytes + AMF object length
                    stream.position -= imgCustTagsLen;
                   
                    //read the object
                    var custTags = stream.readObject();
                   
                    //load the info in the form
                    document.getElementById( 'title' ).value = custTags.title;
                    document.getElementById( 'description' ).value = custTags.description;
                } else {
                    //no signature found, be sure the forms are empty
                    imgCustSignatureFound = false;
                    imgCustTagsLen = 0;
                    document.getElementById( 'title' ).value = "";
                    document.getElementById( 'description' ).value = "";
                }
                stream.close();
            }
           
            function doSave() {
               
                var stream = new air.FileStream();
                stream.open( imgFile, air.FileMode.UPDATE );
               
                //if there's custom signature - remove it before writing the new one
                if (imgCustSignatureFound) {
                    //truncate the signature from the image
                    stream.position = stream.bytesAvailable - imgCustTagsLen;
                    stream.truncate();
                }
                else {
                    //write at the end of the file
                    stream.position = stream.bytesAvailable;
                }
               
                //store the current position in the file
                var originalSize = stream.position;

                stream.writeObject(
                    {
                        title: document.getElementById('title').value,
                        description: document.getElementById('description').value
                    }
                );
               
                //now check how many bytes we wrote, that's the length of the AMF object
                var customTagsSize = stream.position - originalSize;
               
                //write 9 bytes signature
                stream.writeBytes(mySignature);
               
                //write 4 bytes length of tags object
                stream.writeUnsignedInt(customTagsSize);
               
                stream.close();       
            }
           
        </script>
        <style>
            body {margin:0px; padding:2px; background: #333;}
            input, textarea { width:190px; }
            #imgWrap { width:600px; height: 450px; background:#666; text-align: center; color: white;}
            #formDiv  { position:absolute; left:605px; top:5px; width: 195px; height: 150px; color: #ccc;}
        </style>
    </head>
    <body onload="onApplicationLoad()">
        <div id="imgWrap">[ Drag a photo in this area to open it ]</div>
        <div id="formDiv">
        My custom tags :<br /><hr />
        Title:<br />
            <input type="text" id="title" /> <br />
        Story:<br />
             <textarea id="description" rows="5"></textarea> <br />
        <input type="button" value="save" onClick="doSave()" />
    </body>
</html>

You now have a full-featured photo tagging application in less than 150 lines of code. The possibilities are endless: building photo library applications, image viewers, embedding secret messages in innocent image files, and so on.

 

Schema1.gif
Screen1.jpg
file_api2.air.zip
[The example application AIR file]

+
This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 Unported License. Permissions beyond the scope of this license, pertaining to the examples of code included within this work are available at Adobe.

Report abuse

Related recipes