Мне нужно иметь возможность сохранять и загружать изображения в базу данных SQLite, используя Flex 4.5 для мобильного приложения. Вариант использования такой:
- Внутри вида находится объект Spark Image с URL-адресом в качестве источника
- Когда пользователь нажимает кнопку, изображение сохраняется в базе данных SQLite внутри поля BLOB.
- В отдельном изображении для источника задан ByteArray, хранящийся в БД.
Самый большой вопрос на данный момент: где я могу получить ByteArray для загруженного изображения? Я пытался отладить и проверить Image, BitmapImage и BitMapData, но нет никаких признаков байтового массива .... возможно, он находится внутри ContentLoader? Но это ноль, если я не включу кеширование.
Я провел некоторое исследование, и нет полных примеров того, как с этим справиться. Я написал простое представление, которое любой может скопировать и вставить в новый проект. Он скомпилируется без ошибок и может быть использован для тестирования. Я буду обновлять этот код по мере получения необходимых ответов, чтобы у каждого был полностью рабочий пример этого рабочего процесса. Единственное предостережение: я использовал синхронное соединение с БД, чтобы не усложнять код обработчиками событий, я хотел сделать его как можно более простым.
Я отмечу этот вопрос как ответивший, только когда он полностью функционирует в обоих направлениях.
ОБНОВЛЕНИЕ СЕНТ. 09 2011:
Код ниже теперь полностью функционирует в обоих направлениях (сохранение и загрузка). Вот некоторые из вещей, которые я узнал:
- Оказывается, что ByteArray изображения искры можно найти внутри LoaderInfo самого изображения. Как это:
image.loaderInfo.bytes
- Однако попытка установить этот ByteArray в качестве источника другого изображения ( image2.source = image1.loaderInfo.bytes ) приводит к этой ошибке безопасности:
Ошибка № 3226: невозможно импортировать SWF-файл, когда
LoaderContext.allowCodeImport имеет значение false.
Что очень плохо, потому что это сэкономит так много времени и вычислительной мощности. Если кто-нибудь знает, как обойти эту ошибку, она будет очень признательна!
В любом случае, обходной путь заключается в кодировании ByteArray с использованием JPEGEncoder или PNGEncoder. Я использовал JPEGEncoder, который производит файлы намного меньшего размера. Вот как:
var encoder:JPEGEncoder = new JPEGEncoder(75);
var imageByteArray:ByteArray = encoder.encode(imageToSave.bitmapData);
- Теперь проблема заключается в сохранении этих байтов в БД. Сохранение их, как они есть, не сработает, как только мы их зачитаем, я не знаю почему. Таким образом, решение заключается в том, чтобы закодировать их в строку base64 следующим образом:
var baseEncoder:Base64Encoder = new Base64Encoder();
baseEncoder.encodeBytes(imageByteArray);
var encodedBytes:String = baseEncoder.toString();
- Так что теперь просто сохраните эту строку в поле BLOB и позже прочитайте ее. Однако вы должны декодировать его из строки base64 в ByteArray следующим образом:
var encodedBytes:String = result.data[0].imagedata;
var baseDecoder:Base64Decoder = new Base64Decoder();
baseDecoder.decode(encodedBytes);
var byteArray:ByteArray = baseDecoder.toByteArray();
Теперь назначьте bytearray в качестве источника вашего изображения, и все готово. Просто верно?
Спасибо всем, кто помог, и, пожалуйста, прокомментируйте, если вы найдете какие-либо оптимизации или альтернативные способы сделать это. Я отвечу и буду обновлять этот код в случае необходимости.
ПРИМЕЧАНИЕ. Следующий код полностью функционален.
<?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>