You want to add your custom text comments to image files and then read them back, without corrupting the images themselves.
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.
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 :
<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.
+