PDA

Просмотр полной версии : Прогресс создания ByteArray


Alex626
23.05.2015, 14:10
Добрый день, господа!

У меня в приложении создаётся объект BitmapData большого размера, а затем конвертируется в ByteArray через BitmapData.encode(), чтобы затем сохранить в JPEG-файл. Процесс занимает до 10 секунд. Хотелось бы создать прогресс-бар, или что-то в этом духе, который показывал бы прогресс перекодировки. Возможно ли такое?

И попутный вопрос: можно ли заранее узнать максимально возможный размер BitmapData на машине пользователя, если ролик компилировался под последний Flash Player? А то у меня слайдер стоит, который позволяет менять масштаб снимаемого BitmapData, и можно сделать, например, масштаб 3:1. Но разрешение при этом подскакивает до потолка (20х20 тысяч пикселей и выше), и часто бывает, что после определённого коэффициента увеличения (2:1 и выше) выскакивает ошибка BitmapData.

Zebestov
23.05.2015, 14:32
Процесс этот синхронный, потому в одно движение ничего не выйдет.
Если это возможно — разбей его на части.
Если нет, но очень нужно — Worker (http://help.adobe.com/ru_RU/FlashPlatform/reference/actionscript/3/flash/system/Worker.html)

Alex626
23.05.2015, 17:11
А можно подробнее, что именно делать с этим Worker?

И, для второго вопроса - можно как-либо оценивать доступную для BitmapData память с помощью System.totalMemory?

undefined
23.05.2015, 21:36
А можно подробнее, что именно делать с этим Worker?
Выносить все,что тормозит в отдельный swf и пихать её в отдельный воркер.
Но ИМХО,тут проще будет нарезать большую битмапДату на несколько маленьких и декодировать их последовательно. Перед скармливанием jpeg-энкодеру все предварительно слить в один ByteArray

Alex626
24.05.2015, 14:37
тут проще будет нарезать большую битмапДату на несколько маленьких и декодировать их последовательно. Перед скармливанием jpeg-энкодеру все предварительно слить в один ByteArray

Это мне нравится. Это решило бы и второй вопрос - с максимальным размером BitmapData. Скажите, как правильно оперировать функцией BitmapData.Draw для снятия снимков с разных частей одного и того-же DisplayObject'a? Ну вот, к примеру, я хочу разбить свой спрайт на сетку из BitmapData размером 10х10. Мне надо как-то с помощью трансформ-матрицы задавать смещение? И ещё, если учитывать, что трансформ-матрицей я задаю Scale, который увеличивает масштаб съёмки.

undefined
24.05.2015, 17:54
А блин я не так понял вопрос.Думал ты с помощью стороннего энкодера в jpg перегоняешь.Тогда(в теории) можно взять этот (https://github.com/mikechambers/as3corelib/tree/master/src/com/adobe) энкодер и подкрутить его чтоб жал кусуками

illuzor
24.05.2015, 18:06
undefined, зачем советовать устаревший тормозной энкодер, когда есть нативный?

Alex626
24.05.2015, 19:21
Нет, я использую нативный энкодер. Сторонние сильно тормознутые и жрут чудовищно много памяти.

У меня система такая:

1) есть спрайт размером 5000х5000 - предположим
2) я создаю BitmapData размером 5000*scale - переменная scale отвечает за масштаб съёмки
3) я создаю матрицу трансформации, и устанавливаю matrix.scale(scale, scale);
4) делаю BitmapData.Draw с заданной выше матрицей трансформации

И тут возникает первая проблема: если размер BitmapData будет больше чем определённое число (если scale=3, то габариты будут 15000х15000) - выходит ошибка *2015*. Тогда ловлю исключение, понижаю коэффициент scale и начинаю заново.

5) создаю byteArray и JPEGEncoderOptions
6) делаю BitmapData.encode на этот byteArray, с размером квадрата 5000*scale

Тут я хочу отследить прогресс создания файла и как-то вывести его на экран.

7) сохраняю через FileReference



Очевидно узкое место - слишком большое разрешение BitmapData, как результат - долгая перекодировка и возможность возникновения ошибки BitmapData (типа нехватка памяти в ОС). Поэтому хочу сделать много разных BitmapData, массивом, и потом каждый по отдельности перекодировать в свой byteArray, и в конце все их склеить в один большой и вывести в файл.

Как мне нарезать мой спрайт на части? Чем пользоваться? В плане, какие параметры юзать для матрицы трансформации и для BitmapData.Draw?

undefined
24.05.2015, 19:40
illuzor, штатный энкодер синхронный раз,
принимает на входе BitmapData а не byteArray два,
реализация недоступна три

Alex626, это я поторопился.Думал метод encode как раз перегоняет битмапдату в ByteArray. В твоем случае нарезка большой битмапдаты скорее всего не прокатит. Т.к. если нарезать её, перегнать куски в ByteArray и потом все назад склеить тупой конкатенацией исходную битмапдату скорее всего не получишь.
Поэтому говорю посмотри исходники энкодера по моей ссылке.Там кодирование идет за счет последовательного кодирования блоков размером 8х8.Немного добработать напильником и можно сделать чтоб за раз кодировалось не все, а лишь кусок.
Но ИМХО это все изобретение велосипеда.Лучше прислушаться к совету Zebestov'а - сделать отдельную флэшку-энкодер и грузить её отдельным воркером. Они как раз для таких ситуаций и были придуманы

UPd:Особо обольщаться по поводу вокеров не стоит.Отдельный воркер для энкода даст только возможность показать какую-то анимацию пока идет кодирование т.к. сама операцию как была так и осталась синхронной и прогресс так просто не получишь.Решение - кодировать по частям как я описал выше. Либо как-то узнавать тактовую частоту проца юзера и из неё как-нибудь вычислять сколько времени может занять вся операция

upd2:
И тут возникает первая проблема: если размер BitmapData будет больше чем определённое число (если scale=3, то габариты будут 15000х15000) - выходит ошибка *2015*. Тогда ловлю исключение, понижаю коэффициент scale и начинаю заново.
Так в чем собственно проблема?

Alex626
24.05.2015, 21:15
Так в чем собственно проблема?

Хотелось бы заранее как-то вычислять максимально доступное разрешение. У меня в интерфейсе стоит слайдер для выбора параметра scale (от 0.3 до 3). Каждый раз через try конечно можно (с выводом сообщения юзеру "выбранный размер недоступен. масштабирование было уменьшено"). Но ведь если машина слабая, и доступно только разрешение, скажем, 1.2, то юзеру который выберет 3, придётся несколько раз нажимать на кнопку и читать сообщение. Либо, как вариант, можно конечно зациклить создание BitmapData, ловить исключение и уменьшать коэффициент понемногу - до момента, когда выбор будет оптимален. Но ведь всё это костыли.

Если только каким-то макаром разбить битмапдату на куски и склеить конкатенацией (но это невозможно, да?) или хоть высчитать объём доступной памяти и по нему посчитать максимальный коэффициент Scale. Эх, вот ведь придумал сложности :D

Добавлено через 3 минуты
Кстати, я пробовал уже этот энкодер, что из ссылки. Ещё пару недель назад. Как и говорил, работает дико медленно, и памяти жрёт непозволительно много. Если нативный кодирует за 10 секунд, то этот работает над тем же материалом больше минуты, и частенько выдаёт "нехватку памяти".

undefined
24.05.2015, 21:32
Хотелось бы заранее как-то вычислять максимально доступное разрешение.
А почему бы не узнать версию Flash Player и по ней определять какая максимальная БитмапДата доступна?
Кстати, я пробовал уже этот энкодер, что из ссылки.
Зато сорцы доступны а с штатным особо ничего не сделаешь единственное что можно - вынести его в отдельный воркер.Но опять таки если прогресс в процентах показывать особо не надо, а просто хочется чтоб не висло все на время кодирования - то можно и штатный кодер задействовать

Alex626
24.05.2015, 21:42
А почему бы не узнать версию Flash Player и по ней определять какая максимальная БитмапДата доступна?

Компилирую под 11.4 - там вроде уже вообще нет ограничений, как, например, в 10 версии. Но что странно - всё равно выдаёт ошибку. Хотя и на гораздо бОльшем разрешении. Там написано в доках, что теперь только возможности ОС как-то ограничивают максимальное разрешение. А вот как именно - тут вопрос.


Зато сорцы доступны а с штатным особо ничего не сделаешь единственное что можно - вынести его в отдельный воркер.Но опять таки если прогресс в процентах показывать особо не надо, а просто хочется чтоб не висло все на время кодирования - то можно и штатный кодер задействовать

Надо будет попробовать. В конце концов, использовать нативный кодер, а вместо прогресс бара сделать псевдо-индикатор, который будет постепенно доходить до 100% с троеточием, либо обрываться, если нужно было меньше времени.

А вообще, для работы с памятью у нас есть какие-либо возможности во флеше?

undefined
24.05.2015, 21:49
А вот как именно - тут вопрос
Делать финт с try..catch() в начале работы 1 раз и запоминать макс. размер и не надо его запускать на каждый чих юзера.В процессе работы флэшки этот параметр меняться не будет

А вообще, для работы с памятью у нас есть какие-либо возможности во флеше?
пакет flash.system (http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/system/System.html)

Alex626
29.05.2015, 01:35
Выяснил, что вариант через цикл вычислять допустимый размер битмап-даты не катит - слишком медленно. Но у меня получилось, что максимальный размер, который можно снять - примерно 21874х24434. Далее уже будет ошибка BitmapData. Но истинно ли это для других компьютеров? Ведь в доках сказано, что возможный размер BitmapData зависит от системы, начиная с Flash Player 11.

Вот сегодня пробовал работать с памятью. Так и не понял, каким образом можно вычислить размер (в байтах) моей BitmapData, и главное, как узнать количество памяти, доступной в системе для флеша?


var sizeX = mySprite.width*scale;

var resmem:String = Number(Math.pow(sizeX,2) *4 / 1024 / 1024).toFixed( 2 )+ "Mb";
var mem:String = Number( System.totalMemory / 1024 / 1024 ).toFixed( 2 ) + "Mb";
var freemem:String = Number( System.freeMemory / 1024 / 1024 ).toFixed( 2 ) + "Mb";

trace("resmem: "+resmem+" total used memory: "+mem + " total free memory: "+freemem);
//пример resmem: 1740.29Mb total used memory: 27.64Mb total used memory: 2.25Mb



Размер битмапдаты высчитывал приблизительно. Остальное выдаёт какие-то совсем уж необычные результаты, вроде "свободно 27 Мб, использовано 2.25 Мб"... Динамически что ли память берётся? Тогда вопрос, как мне узнать, что она вот-вот закончится?

undefined
29.05.2015, 03:22
Выяснил, что вариант через цикл вычислять допустимый размер битмап-даты не катит - слишком медленно.
Еще раз, в начале один раз все посчитать и запомнить не должно сильно затормозить.Это ведь не переменная величина.
Так и не понял, каким образом можно вычислить размер (в байтах) моей BitmapData
Есть метод flash.sampler.getSize(), но он работает только в дебаг-плеере.Альтернатива - перегонять битмапДату в ByteArray через метод writeObject и смотреть его свойство length.Хотя я не понимаю зачем эти выкрутасы с памятью.

in4core
29.05.2015, 12:23
GПрелоадер кстати делается просто : как только операция начинает работу - показываем прилоадер. Как только завеоршает - снимаем.
Прям дословно так
showPreloader()
operationBitmapData() // хоть час пусть выполняется, пока не выполнится следующая строчка не пройдет
hidePreloader();

undefined
29.05.2015, 15:08
in4core,ТС хочет чтоб анимацию прелодера не зависала пока идет
operationBitmapData()

in4core
29.05.2015, 19:32
ну чтоб не зависала то ЖС можно или воркер - да.

AlexCooper
30.05.2015, 14:38
Я для сих целей использую as3corelib (https://code.google.com/p/as3corelib/) и разбиение на блоки с запуском/продолжение по сетТаймаут после последней итерации

Alex626
30.05.2015, 15:46
Разобрался с циклом. Я в начале делал не слишком-то рационально - искал с начала в конец. Решил проблему поиском от бОльшего к меньшему. Теперь где-то на 2 секунды подвисает, и вызывается проверка только после выброса первой ошибки создания BitmapData (в этом случае высчитывается по другой функции макс. разрешение через цикл, устанавливаются новые параметры и корректируется шкала в GUI).

Но вообще, интересно, как можно разбить на блоки. AlexCooper, расскажите, пожалуйста, больше.

AlexCooper
30.05.2015, 16:59
Но вообще, интересно, как можно разбить на блоки. AlexCooper, расскажите, пожалуйста, больше.

Так быстро и не расскажешь. Лучше пример посмотреть, сейчас быстренько соберу чёнить

AlexCooper
30.05.2015, 19:14
FB4.7 31617

Main.as
package
{
import flash.display.BitmapData;
import flash.display.DisplayObject;
import flash.display.JPEGEncoderOptions;
import flash.display.Sprite;

import flash.net.FileReference;
import flash.utils.ByteArray;

import utils.SeparateBuilder;
import utils.SeparateEvent;

import view.RandomView;

[SWF(width="960",height="640",frameRate="60",backgroundColor="#FFFFFF")]
public class Main extends Sprite
{
private var _viewport:Sprite;
private var rview:DisplayObject
private var _separe:SeparateBuilder;


public function Main()
{
_viewport = new Sprite();
addChild( _viewport );

createRandomView();
adaptViewPort();
createSeparator();
createScreenshot();
}

private function adaptViewPort():void
{
_viewport.width = stage.stageWidth;
_viewport.height = stage.stageHeight;
}

private function createRandomView():void
{
rview = new RandomView(5000,5000);

_viewport.addChild( rview );
}

private function createSeparator():void {

_separe = new SeparateBuilder(500);

_separe.addEventListener( SeparateEvent.INIT, separateHandler );
_separe.addEventListener( SeparateEvent.COMPLETE, separateHandler );
_separe.addEventListener( SeparateEvent.PROCESSING, separateHandler );

}

private function createScreenshot():void {
_separe.work( rview );
}

protected function separateHandler(event:SeparateEvent):void
{
switch (event.type) {

case SeparateEvent.INIT:

trace('Start');
break;

case SeparateEvent.PROCESSING :
trace('Processing : '+ event.data);
break;

case SeparateEvent.COMPLETE :

trace('Complete');

encode( _separe.bitmapData );

break;
}
}

private function encode(bitmapData:BitmapData):void
{
var jpeg:JPEGEncoderOptions = new JPEGEncoderOptions();
jpeg.quality = 4;

var byteArray:ByteArray = bitmapData.encode( bitmapData.rect, jpeg);

save( byteArray );
}

private function save(byteArray:ByteArray):void
{
var fr:FileReference = new FileReference();
fr.save(
byteArray ,
'screenshot_'+new Date().time+'.jpg'
);
}
}
}

utils.SeparateBuilder.as
package utils
{
import flash.display.BitmapData;
import flash.display.DisplayObject;
import flash.events.EventDispatcher;
import flash.geom.Point;
import flash.utils.ByteArray;
import flash.utils.setTimeout;

import net.alexscript.hack.Extruder;

public class SeparateBuilder extends EventDispatcher
{
private var _blockSize:Number;
private var _height:Number;

private var _target:DisplayObject;

private var _byteArray:ByteArray;
private var _map:Point;
private var _iteration:uint;
private var _cache:BitmapData;

public function SeparateBuilder(blockSize:Number=500)
{
super();

_blockSize = blockSize;
}



public function work(view:DisplayObject):void
{
if (_target) {
throw new Error('SeparateBuilder is work');
}

target = view;
}

public function get bitmapData():BitmapData
{
return _cache;
}

private function set target(value:DisplayObject):void
{
_target = value;

_iteration = 0;

_byteArray = new ByteArray();

_cache = new BitmapData(_target.width, _target.height,true);

calculation();

say( SeparateEvent.INIT );

processing();
}

private function calculation():void
{
_map = new Point(
Math.floor( target.width / _blockSize ),
Math.floor( target.height / _blockSize )
);

if ( target.width > _map.x * _blockSize ) _map.x++;

if ( target.height > _map.y * _blockSize ) _map.y++;

trace( 'Map Sector : ',_map);
}

private function get target():DisplayObject
{
return _target;
}

private function processing():void
{
var sector:Point = new Point(
_iteration % _map.x,
Math.floor(_iteration/_map.x)
);

snap( sector );

var length:Number = _map.x * _map.y;
var percent:Number= 100/(length/_iteration);

say( SeparateEvent.PROCESSING , String( Math.floor(percent) ) );

if ( ++_iteration < length )
setTimeout( processing, 1 );
else
complete();
}

private function snap(sector:Point):void
{
var anker:Point = new Point(
sector.x*_blockSize,
sector.y*_blockSize
);

var zone:Point = new Point(_blockSize,_blockSize);

if ( (anker.x+zone.x) > target.width ) zone.x = target.width - anker.x;
if ( (anker.y+zone.y) > target.height ) zone.y = target.height - anker.y;

var bd:BitmapData = Extruder.get( target, anker.x, anker.y , zone.x, zone.y );

_cache.copyPixels( bd, bd.rect, anker);
}

private function complete():void
{
_target = null;

say( SeparateEvent.COMPLETE );
}

private function say(type:String, data:String=''):void {
this.dispatchEvent( new SeparateEvent( type, data ) );
}
}
}

utils.SeparateEvent.as
package utils
{
import flash.events.DataEvent;

public class SeparateEvent extends DataEvent
{
public static var INIT:String = 'init';
public static var PROCESSING:String = 'processing';
public static var COMPLETE:String = 'complete';

public function SeparateEvent(type:String, data:String='')
{
super(type, true, false, data);
}
}
}

view.RandowView.as
package view
{
import flash.display.Sprite;
import flash.geom.Point;

public class RandomView extends Sprite
{
private var _h:Number;
private var _w:Number;
public function RandomView(h:Number=5000,w:Number=5000)
{
super();

_h = h;
_w = w;

graphics.beginFill(0xAAAAAA,1);
graphics.drawRect(0,0,_w,_h);
graphics.endFill();

var rnd:Number = Math.floor(Math.random()*10)+10;

while (--rnd) draw();
}

private function draw():void
{
var color:uint = Math.random()*0xFFFFFF;

var size:Number = Math.min(_h,_w)*.1;

var body:Point = new Point( Math.random()*(_w-size*2),Math.random()*(_h-size*2));

body.x += size;
body.y += size;

with (graphics) {

beginFill(color,1);

switch( Math.floor(Math.random()*2) ) {
case 0:
drawRect(body.x,body.y, size, size);
break;

case 1:
drawCircle(body.x,body.y,size);
break;
}

endFill();
}
}
}
}

net.alexscript.hack.Extruder.as
package net.alexscript.hack {

import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.DisplayObject;
import flash.display.PixelSnapping;
import flash.geom.Matrix;
import flash.geom.Rectangle;

public class Extruder {

public function Extruder() {

}

public static function get( ARG_object:DisplayObject, ARG_x:Number = 0, ARG_y:Number = 0, ARG_width:Number = 0, ARG_height:Number = 0,transpend:Boolean=true):BitmapData {

if (!ARG_width) ARG_width = (ARG_object.mask) ? ARG_object.mask.width : ARG_object.width;
if (!ARG_height) ARG_height = (ARG_object.mask) ? ARG_object.mask.height : ARG_object.height;

var cropArea:Rectangle = new Rectangle(0, 0, ARG_width, ARG_height);
var fillColor:uint = transpend
? 0x00000000
: 0x0;
var bmpd:BitmapData = new BitmapData(ARG_width, ARG_height,true,fillColor);

var croppedBitmap:Bitmap = new Bitmap(bmpd, PixelSnapping.ALWAYS, true);

var cropMatrix:Matrix = new Matrix();
cropMatrix.translate(-ARG_x, -ARG_y);

bmpd.draw( ARG_object, cropMatrix, null, null, cropArea, true );

return bmpd;
}

}

}

AlexCooper
30.05.2015, 19:42
P.S. Нативный енкодер так же немного подвисает ( 5000x5000 - 660 ms, 15000х15000 - 5700 ms), если хотите полностью избавиться от подвисания, учите мат.часть (https://code.google.com/p/as3corelib/source/browse/trunk/src/com/adobe/images/JPGEncoder.as) используйте copyPixelsToByteArray, после чего пережимайте в jpeg естественно с разделением на блоки

Добавлено через 3 минуты
или же используйте массив мелких JPEG, повырезать из них заголовки, склеить байтерреи и добавить общий заголовок

Добавлено через 36 минут
или (http://esdot.ca/site/2012/intro-to-as3-workers-part-2-image-processing)

in4core
31.05.2015, 01:43
public static function get
страшно

AlexCooper
31.05.2015, 01:50
страшно

Multilock encapsulation on getter :rolleyes:

Alex626
31.05.2015, 22:26
Щас всё это читаю, изучаю - конечно, сложно. После AS2 и всякого рода игровых интерфейсов, это и правда что-то новенькое. Мозг взорван :D

Добавлено через 2 часа 58 минут
Примеров масса, и пока что не всё понятно. С нарезкой слишком сложно (я даже тему переименовал), надо очень долго разбираться. Пока что мне подойдёт вариант с кодированием в файл отдельный потоком.

Как я понимаю, в этом случае многопоточность будет использоваться для передачи содержимого моего гигантского спрайта в новый поток, который будет преобразовывать его в JPEG, и отдавать массив байтов обратно, чтобы основной ролик выводил его в FileReference.

Но ведь я не смогу передать целый Sprite на обработку, верно? Т.е. в основном потоке сначала придётся вызвать BitmapData.Draw (что даёт лаг на 3 секунды), чтобы получить его массив байтов, а вот уже BitmapData.encode мжно будет исполнять в другом потоке?

meiciuc
05.06.2015, 21:55
мне кажется, самым простым вариантом было бы воспользоваться "асинхрннными" декодерами:
там можно задать скорость кодирования.
получать сообщения о прогрессе

в jpg
https://github.com/alinke/DIYMagicMirror/blob/master/com/pfp/utils/JPEGAsyncEncoder.as

в png
http://www.bit-101.com/blog/?p=2581

вот кусок кода из рабочего примера

if (_typeEncoding == "jpg") {
var j:JPEGAsyncEncoder = new JPEGAsyncEncoder(85);
j.PixelsPerIteration = 300;
j.addEventListener(JPEGAsyncCompleteEvent.JPEGASYNC_COMPLETE, handleJPEGEncoder);
j.addEventListener(ProgressEvent.PROGRESS, handleProgress);
j.encode(_bitmap.bitmapData);
}

if (_typeEncoding == "png") {
var p:PNGEncoderAsync = new PNGEncoderAsync();
p.addEventListener(ProgressEvent.PROGRESS, handleProgress);
p.addEventListener(Event.COMPLETE, handlePNGEncoder);
p.encode(_bitmap.bitmapData);
}

Alex626
06.06.2015, 00:11
Спасибо, сейчас протестирую.

Добавлено через 1 час 48 минут
Опробовал. Круто. Но медленно очень. Беда прям. Вот если бы он работал через обычный энкодер, но в отдельном потоке....

meiciuc
06.06.2015, 03:03
не знаю, что за конкретная задача стоит, но если картинка действительно большая, то хоть в отдельном потоке, хоть где, время все равно будет занимать существенно много.

может, "обмануть" юзера? на необходимое время повесить для него сообщение, показать прогресс? Это объективно долгий процесс.

разве что, есть библиотека на Алхимии - http://blog.inspirit.ru/async-png-encoding-and-merging/ - но она существенно тоже не поможет - проверено

Alex626
06.06.2015, 03:59
не знаю, что за конкретная задача стоит, но если картинка действительно большая, то хоть в отдельном потоке, хоть где, время все равно будет занимать существенно много.

может, "обмануть" юзера? на необходимое время повесить для него сообщение, показать прогресс? Это объективно долгий процесс.

разве что, есть библиотека на Алхимии - http://blog.inspirit.ru/async-png-encoding-and-merging/ - но она существенно тоже не поможет - проверено

Крайне большая картинка. Но можно слайдером выбирать масштаб съёмки изображения (т.е. разрешение от мизерного до огромного). Можно, конечно, повесить сообщение. Либо комбо-бокс дать, чтобы предоставить выбор компилера - асинхронный или синхронный. Или как вариант - смотреть на выбранное разрешение - если оно небольшое, то использовать асинхронный энкодер со статус-баром. А если большое, то выдавать блокирующее сообщение и использовать нативный.


А как-то можно обойти ограничение размера BitmapData.Draw? У меня в системе памяти не хватает на масштабы более 234% (это где-то 20.000 х 20.000 пикс.).

meiciuc
06.06.2015, 09:47
у любой технологии есть ограничения - браузерный плагин не может пользовать постоянную память, как десктопное (любой графический редактор) - это нельзя не учитывать при планировании.

может, использовать аналогию работы гугл-мэп? систему тайлов, где невидимые тайлы удаляются и освобождают системную память, а при необходимости подгружаются с сервера?

Alex626
07.06.2015, 01:40
Я могу разбить одну BitmapData на несколько штук поменьше, затем каждую преобразовать в ByteArray и потом все ByteArray склеить в один, и сохранить. НО! Как я буду склеивать их? Ведь энкодеры дают готовый набор байтов уже с заголовками, да и я думаю ещё с какими-то особенностями, так что простая конкатенация не сработает.

meiciuc
07.06.2015, 04:33
в одном из сообщений выше я давал ссылку на Алхимический проект - сейчас даю уточняющую ссылку - https://code.google.com/p/in-spirit/wiki/PNGEncoder

там как раз то, что нужно - возможность склеивать ну просто огромные картинки

пару лет назад у меня был проект, где надо было огромный мувик преобразовывать в bytearray и отправить на сервер. Мувик был гигантский и просто скопировать в битмапдату было нельзя. Помог проект по-ссылке.

Суть видна и в коде РАБОЧЕГО примера, но на всякий случай продублирую тут:
1. совершенно не обязательно для начала иметь исходный битмап - в моем случае это был контейнер с нарезанными битмапами (а-ля тайлы). Флэш плеер никак не ограничивает размер спрайта - только оперативка.
2. кодировщику в качестве параметра передается экземпляр PNGEncoderInfo в котором есть ссылка на метод, который будет "нарезать" единую "картинку" (контейнер с тайлами) на битмапы. Т.е. - имеется мувик-контейнер, который будет скармливаться энкодеру кусочками битмапов, которые, в свою очередь, будут склеиваться в единый ByteArray внутри этого самого энкодера.

надеюсь, поможет

В моем случае, когда код запускался на машине с (дай Бог памяти) 512 MB, изображения кодировались в ByteArray размером до 100 млн. пикселей. На 4 гигах оперативки кодировались картинки такого размера, что их браузер отобразить не может - можно было только скачать с сервака и в графических редакторах увидеть результат.

AlexCooper
07.06.2015, 13:35
А как-то можно обойти ограничение размера BitmapData.Draw? У меня в системе памяти не хватает на масштабы более 234% (это где-то 20.000 х 20.000 пикс.).

Дык замените

private function encode(bitmapData:BitmapData):void
{
var jpeg:JPEGEncoderOptions = new JPEGEncoderOptions();
jpeg.quality = 4;

var byteArray:ByteArray = bitmapData.encode( bitmapData.rect, jpeg);

save( byteArray );
}

на

private function encode(bitmapData:BitmapData):void
{ var j:JPEGAsyncEncoder = new JPEGAsyncEncoder(85);
j.PixelsPerIteration = 300;
j.addEventListener(JPEGAsyncCompleteEvent.JPEGASYNC_COMPLETE, handleJPEGEncoder);
j.addEventListener(ProgressEvent.PROGRESS, handleProgress);
j.encode(bitmapData);
}

Добавлено через 1 минуту
снимайте битмапу частями и асинхронно конвертируйте

Добавлено через 19 минут
НО! Как я буду склеивать их? Ведь энкодеры дают готовый набор байтов уже с заголовками, да и я думаю ещё с какими-то особенностями, так что простая конкатенация не сработает.
Подсмотрев сюда (https://code.google.com/p/as3corelib/source/browse/trunk/src/com/adobe/images/JPGEncoder.as)

public function encode(image:BitmapData):ByteArray
{
// Initialize bit writer
byteout = new ByteArray();
bytenew=0;
bytepos=7;

// Add JPEG headers
writeWord(0xFFD8); // SOI
writeAPP0();
writeDQT();
writeSOF0(image.width,image.height);
writeDHT();
writeSOS();


// Encode 8x8 macroblocks
var DCY:Number=0;
var DCU:Number=0;
var DCV:Number=0;
bytenew=0;
bytepos=7;
for (var ypos:int=0; ypos<image.height; ypos+=8) {
for (var xpos:int=0; xpos<image.width; xpos+=8) {
RGB2YUV(image, xpos, ypos);
DCY = processDU(YDU, fdtbl_Y, DCY, YDC_HT, YAC_HT);
DCU = processDU(UDU, fdtbl_UV, DCU, UVDC_HT, UVAC_HT);
DCV = processDU(VDU, fdtbl_UV, DCV, UVDC_HT, UVAC_HT);
}
}

// Do the bit alignment of the EOI marker
if ( bytepos >= 0 ) {
var fillbits:BitString = new BitString();
fillbits.len = bytepos+1;
fillbits.val = (1<<(bytepos+1))-1;
writeBits(fillbits);
}

writeWord(0xFFD9); //EOI
return byteout;
}

можно увидеть

// Add JPEG headers
writeWord(0xFFD8); // SOI
writeAPP0();
writeDQT();
writeSOF0(image.width,image.height);
writeDHT();
writeSOS();

соответствено нужно вывести длинну заголовка, и вырезать его перед склейкой.
Только в данном случае стоит учесть что пережимка осужествляется по X, а значит что обычная конкатенация даст распад изображения. Потому или однопиксельная горизонтальная нарезка, или PixelBender перед снятием битмапдаты, который пересоберёт картинку. Но это помоему уже велосипеды

Alex626
08.06.2015, 19:09
В общем, соглашусь, что всё это костыли. Поставлю заставку на время кодирования. Всё равно нативный кодер максимум 10 секунд отнимает, это нормально.

У меня последнее предположение насчёт ограничения BitmapData.Draw на компьютерах. Если флеш зависит от размера оперативной памяти, а плеер 32-битный, то скорее всего программе будет максимально доступно только 3,3 Гб. Если я сделаю 64-битное приложение под десктопы на Air, будет ли оно работать там лучше и без ограничений?