La sauvegarde et le chargement de l'image locale SQLite BLOB à l'aide de Flex 4.5

J'ai besoin d'être en mesure d'enregistrer et de charger des images dans une base de données SQLite à l'aide de Flex 4.5 pour une application mobile. Le cas d'utilisation est: est-ce

  • À l'intérieur de la vue, il est une étincelle de l'Image de l'objet avec une URL en tant que source
  • Lorsque l'utilisateur clique sur le bouton, l'Image est enregistrée sur un SQLite db à l'intérieur d'un champ BLOB.
  • Dans une Image distincte, la source est définie à l'objet ByteArray stockées dans la base de données.

La question la plus importante à ce jour est celle-ci: où puis-je obtenir de l'objet ByteArray pour une Image chargée? J'ai essayé de débogage et de l'inspection de l'Image, BitmapImage, et BitMapData mais il n'y a aucun signe que le tableau d'octets.... c'est peut-être à l'intérieur de la ContentLoader? Mais c'est nul à moins que j'ai activer la mise en cache.

J'ai fait quelques recherches et il y a pas des exemples pour savoir comment gérer cela. J'ai écrit un simple point de Vue que n'importe qui peut copier et coller dans un nouveau projet. Elle compile sans erreurs et peut être utilisé pour les tests. Je mettrai à jour ce code que j'ai d'obtenir les réponses dont j'ai besoin, de sorte que n'importe qui peut avoir un travail entièrement exemple de ce flux de travail. Le seul inconvénient: j'ai utilisé une connexion synchrone à la DB pour éviter de compliquer le code avec les gestionnaires d'événements, je voulais le garder aussi simple que possible.

Je ne marque cette question répondu une fois et il est entièrement fonctionnel dans les deux sens.

MISE À JOUR SEPT. 09 2011:

Le code ci-dessous est à présent pleinement opérationnel dans les deux directions (enregistrer et charger). Voici certaines des choses que j'ai appris:

  • Il s'avère que l'objet ByteArray d'une étincelle de l'Image peut être trouvé à l'intérieur de la LoaderInfo de l'image elle-même. Comme ceci:

image.loaderInfo.octets

  • Cependant, en essayant de définir ce ByteArray comme la source d'une autre image ( image2.source = image1.loaderInfo.octets) des résultats de cette erreur de sécurité:

Erreur n ° 3226: Impossible d'importer un fichier SWF lors de la
LoaderContext.allowCodeImport est faux.

  • Qui est dommage car il permettrait de sauver beaucoup de temps et de puissance de traitement. Si quelqu'un sait comment sortir de cette erreur, il serait très apprécié!
  • De toute façon, la solution est de coder l'objet ByteArray l'aide d'un JPEGEncoder ou un PNGEncoder. J'ai utilisé le JPEGEncoder qui produit des fichiers de plus petite taille. Voici comment:
var encoder:JPEGEncoder = new JPEGEncoder(75);  
var imageByteArray:ByteArray = encoder.encode(imageToSave.bitmapData);
  • Maintenant, le problème est de sauver ces octets à la DB. D'économie d'eux comme ils sont maintenant, ne fonctionnera pas une fois que nous avons lu, je ne sais pas pourquoi. Donc la solution est de les encoder en base64 chaîne comme ceci:
var baseEncoder:Base64Encoder = new Base64Encoder();
baseEncoder.encodeBytes(imageByteArray); 
var encodedBytes:String = baseEncoder.toString();
  • Alors maintenant, il suffit d'enregistrer une chaîne dans le champ BLOB et plus tard le lire. Cependant, vous avez à le décoder à partir d'une chaîne base64 à un objet ByteArray comme ceci:
var encodedBytes:String = result.data[0].imagedata;
var baseDecoder:Base64Decoder = new Base64Decoder();
baseDecoder.decode(encodedBytes);
var byteArray:ByteArray = baseDecoder.toByteArray();
  • D'assigner à l'objet bytearray comme la source de votre image et vous avez terminé. Simple droit?
  • Merci à tous ceux qui ont aidé et merci de commenter si vous trouvez des optimisations ou d'autres moyens pour ce faire. Je vais répondre et conserver ce code mis à jour en cas de besoin.

REMARQUE: Le code suivant est entièrement fonctionnel.

<?xml version="1.0" encoding="utf-8"?>
<s:View 
xmlns:fx="http://ns.adobe.com/mxml/2009" 
xmlns:s="library://ns.adobe.com/flex/spark" 
actionBarVisible="false"
creationComplete="init(event)">
<s:layout>
<s:VerticalLayout />
</s:layout>
<s:TextArea id="logOutput" width="100%" height="200" fontSize="10" editable="false" />
<s:Image id="remoteImage" source="http://l.yimg.com/g/images/soup_hero-02.jpg.v1" enableLoadingState="true" />
<s:Button label="Save Image" click="saveImage()" />
<s:Image id="savedImage" source="" enableLoadingState="true" />
<s:Button label="Load Image" click="loadImage()" />
<s:Button label="Copy Image" click="copyImage()" />
<fx:Script>
<![CDATA[
import mx.core.FlexGlobals;
import mx.events.FlexEvent;
import mx.graphics.codec.JPEGEncoder;
import mx.utils.Base64Decoder;
import mx.utils.Base64Encoder;
public var dbName:String;
public var file:File;
public var sqlConnection:SQLConnection;
import spark.components.supportClasses.StyleableTextField;
protected function init(event:FlexEvent):void
{
dbName = FlexGlobals.topLevelApplication.className+".db";
file = File.documentsDirectory.resolvePath(dbName);
printToLog("Database resolved to path '"+file.nativePath+"'");
sqlConnection = new SQLConnection();
//open the database in synchronous mode to avoid complicating code with event handlers.
//alternatively use the openAsync function.
sqlConnection.open(file); 
printToLog("Connection to database has been opened successfully.");
var sql:String = "CREATE TABLE IF NOT EXISTS my_table(id INTEGER PRIMARY KEY, title VARCHAR(100), width INTEGER, height INTEGER, imagedata BLOB)";
var createStatement:SQLStatement = new SQLStatement();
createStatement.sqlConnection = sqlConnection;
createStatement.text = sql;
try
{
printToLog("Executing sql statement:\n"+sql);
createStatement.execute();
printToLog("Success.");
}
catch(err:SQLError)
{
printToLog(err.message + " Details: " + err.details);
}
}
public function saveImage():void
{
//create some dummy parameters for now
var id:int = 1;
var imageTitle:String = "Test Image";
var imageToSave:Image = remoteImage;
//The JPEGEncoder and PNGEncoder allow you to convert BitmapData object into a ByteArray, 
//ready for storage in an SQLite blob field
var encoder:JPEGEncoder = new JPEGEncoder(75);  //quality of compression. 75 is a good compromise
//var encoder:PNGEncoder = new PNGEncoder();
var imageByteArray:ByteArray = encoder.encode(imageToSave.bitmapData);
//insert data to db
var insertStatement:SQLStatement = new SQLStatement();
insertStatement.sqlConnection = sqlConnection;
insertStatement.text = "INSERT INTO my_table (id, title, width, height, imagedata) VALUES (@id, @title, @width, @height, @imageByteArray)";
insertStatement.parameters["@id"] = id; //Integer with id
insertStatement.parameters["@title"] = imageTitle; //String containing title
//also save width and height of image so you can recreate the image when you get it out of the db.
//NOTE: the width and height will be those of the originally loaded image, 
//     even if you explicitly set width and height on your Image instance.
insertStatement.parameters["@width"] = imageToSave.bitmapData.width; 
insertStatement.parameters["@height"] = imageToSave.bitmapData.height;
//Encode the ByteArray into a base64 string, otherwise it won't work when reading it back in
var baseEncoder:Base64Encoder = new Base64Encoder();
baseEncoder.encodeBytes(imageByteArray);
var encodedBytes:String = baseEncoder.toString();
insertStatement.parameters["@imageByteArray"] = encodedBytes; //ByteArray containing image
try
{
printToLog("Executing sql statement:\n"+insertStatement.text);
printToLog("id="+id);
printToLog("title="+imageTitle);
printToLog("imageByteArray="+imageByteArray);
insertStatement.execute();
printToLog("Success.");
}
catch(err:SQLError)
{
printToLog(err.message + " Details: " + err.details);
}
}
public function loadImage():void
{
//select data from db
var selectStatement:SQLStatement = new SQLStatement();
selectStatement.sqlConnection = sqlConnection;
selectStatement.text = "SELECT title, width, height, imagedata FROM my_table WHERE id = @id;";
selectStatement.parameters["@id"] = 1; //Id of target record
try
{
printToLog("Executing sql statement:\n"+selectStatement.text);
selectStatement.execute();
printToLog("Success.");
}
catch(err:SQLError)
{
printToLog(err.message + " Details: " + err.details);
return;
}
//parse results
var result:SQLResult = selectStatement.getResult();
if (result.data != null) 
{
var row:Object = result.data[0];
var title:String = result.data[0].title;
var width:int = result.data[0].width;
var height:int = result.data[0].height;
//read the image data as a base64 encoded String, then decode it into a ByteArray
var encodedBytes:String = result.data[0].imagedata;
var baseDecoder:Base64Decoder = new Base64Decoder();
baseDecoder.decode(encodedBytes);
var byteArray:ByteArray = baseDecoder.toByteArray();
//assign the ByteArray to the image source
savedImage.width = width;
savedImage.height = height;
savedImage.source = byteArray;
}
}
//This is just a quick test method to see how we can pull out the 
//original ByteArray without passing through the DB
public function copyImage():void
{
//var imageByteArray:ByteArray = remoteImage.loaderInfo.bytes;
//This throws the following error: 
//Error #3226: Cannot import a SWF file when LoaderContext.allowCodeImport is false.
//That's too bad because it would save a lot of time encoding the same bytes we 
//already have (not to mention the loss of quality if we compress JPEG less than 100).
//This is the only solution I've found so far, but slows everything up
var encoder:JPEGEncoder = new JPEGEncoder(75);
//var encoder:PNGEncoder = new PNGEncoder();  -- alternative encoder: huge files
var imageByteArray:ByteArray = encoder.encode(remoteImage.bitmapData);
savedImage.source = imageByteArray;
}
public function printToLog(msg:String):void
{
logOutput.appendText(msg + "\n");
StyleableTextField(logOutput.textDisplay).scrollV++;  //this is to scroll automatically when text is added.
}
]]>
</fx:Script>
</s:View>

OriginalL'auteur Andy | 2011-09-08