Об упаковке видео для ZX Упаковка видео для ZX не нужна при проигрывании видео с IDE устройств, поскольку их объёмы очень велики, а скорость обмена с ними достаточно высокая, чтобы добавить даже звук (для упакован─ ного видео сложно выдавать отсчёты цифрового звука с постоянной скоростью). Но распространять ZX-программы большим тиражом реа─ льно только на дискетах, что даёт свои ограничения: программа должна иметь скромные размеры, исчисляемые от долей дискеты до 3 дискет, а видеоролики нужно, соответственно, паковать. Особенно нужно паковать ролики к небольшим программам - наподобие газеты. Ведь нелепо занимать роликом больше половины объёма релиза. Общая идея упаковки по знакоместам Интуитивно понятно, что хранить в видеоролике надо только изменяющиеся места, но как кодировать, какое место изменилось, а какое нет? Банальный XOR кадра на кадр никакого выигрыша в разархивированном размере не даёт, разве что в архивированном. Соответственно, возникает идея как-то помечать изменившиеся кле─ точки, конечно, размером не в пиксель, а, скажем, 8x8 пикселей - знакоместами оперировать очень удобно. Идея стара, как мир, она использовалась ещё в екатеринбургском Bomber'е и описана в ка─ ком-то дремучем номере Plutonium'а. Естественно, в настоящем видео экран прямо "дышит", картинка изменяется по всей площади. Поэтому нужно запоминать не все из─ менившиеся знакоместа, а только те, которые изменились сильно. То есть разница между текущим изображением в этом знакоместе и желаемым изображением составляет больше N пикселов или больше N цветовых градаций, считаемых как 4·dG+2·dR+dB (где dG, dR, dB - ошибки по цветовым составляющим). В принципе, я использовал другую формулу ошибки: 2·dG+dR+dB, результат был не хуже. Главный вопрос в этом методе: КАК эффективнее хранить инфор─ мацию о том, какие знакоместа изменились? Иногда изменившиеся знакоместа располагаются группами, иногда врассыпную. Управляющий поток Для этого создаём управляющий поток или набор спецкодов, вно─ симый в видеопоток. Первое лучше, поскольку (см. также об этом ниже) характеристики потока знакомест и потока,описывающего гео─ метрию картинки (в каком бы то ни было виде) существенно различ─ ны,что влияет на степень будущего сжатия этих потоков (сейчас мы занимаемся кодированием, а сжатием результата займёмся потом). Сообщу сразу лучший найденный в результате экспериментов спо─ соб кодирования: 1. Для ч/б видео: 0..84 - пропустить 0..84 знакомест, новое - нестандартное; 85..169 - пропустить 0..84 знакомест, новое - стандартное; 170..254 - пропустить 0..84 знакомест, новое - 0-е стандартное (пустое); 255 - конец кадра. 2. Для цветного видео: 0..54,55..109 - атрибут #01..#37,#41..#77. Нестандартное знакоместо; 110..164,165..219 - атрибут #01..#37,#41..#77. Стандартное знакоместо; 220..255 - пропуск 1..36 знакомест. О том, что такое стандартное и нестандартное, расскажу ниже, потерпите... Как получить набор кадров из ролика Хотя почти все видеоролики имеют одинаковые расширения .avi или .mpg, у них внутри могут применяться десятки различных мето─ дов сжатия. Ни один из них не является общепринятым стандартом. Разработчики методов сжатия распространяют свои кодеки (кодер, декодер) в виде драйверов под Windows. Соответственно,юзер часто должен перед просмотром нового фильма слазить в инет и скачать новые версии кодеков. Проигрыватели даже имеют наглость лезть за этим в инет без спроса. Некоторые кодеки, заявленные как новые версии старых, не играют ролики старых версий, поэтому иногда приходится жонглировать этими кодеками, переустанавливая одну версию за другой. Параноидальные капиталисты организовали два "стандарта" коде─ ков. Один вариант декодирует на какой-то больной экранный кон─ текст, с которого невозможно снять скриншот (видать, чтобы не нарушали копирайт авторов фильма).Этими кодеками пользуется ста─ ндартный Windows Media Player. Другой вариант (он, наверно, до─ роже, но у нас структуру сдирания за это денег организовать пока не успели) декодирует в массив (который проигрыватель отображает самостоятельно), что даёт возможность юзеру снять снапшот,а про─ грамме - записать видео по кадрам. Естественно, нам нужен второй вариант. Далее, требуется Virtual Dub. Никакие редакторы видео с кра─ сивым интерфейсом на практике никуда не годятся - в них нет выг─ рузки покадрово и очень ограничено число фильтров. Virtual Dub - небольшая и мощная программа, которая не страдает этими недоста─ тками. Правда, она поглючивает: сохраняет последовательность ка─ дров не в указанную директорию, а в ту,куда последний раз сохра─ няли avi; а вместо bmp может сохранить непонятный формат,связан─ ный с используемым методом сжатия (поэтому перед сохранением по─ следовательности кадров сжатие нужно выключить). Впрочем, возмо─ жно,что по крайней мере первый глюк относится к одной конкретной версии. Перед выгрузкой покадрово (save image sequence) вы должны произвести следующие действия: 1. Удалить ненужные фрагменты фильма; 2. Добавить фильтр null transform, кадрирующий картинку (обре─ зающий кадр), чтобы в кадр не попали чёрные поля сверху и снизу (такие бывают) или какая-нибудь "фирменная" эмблемка в уголке (такие тоже бывают: о том, что капиталисты - параноики, я уже сказал выше. Между прочим, это умственное расстройство - лейбло─ копирайтомания - по моим наблюдениям, очень заразное и чрезвы─ чайно тяжело лечится). 3. Добавить фильтр, масштабирующий картинку: лучше всего,чтобы ширина стала 256 пикселей, высота - любая. 4. По вкусу добавить размазывание картинки во времени, таких фильтров там два вида: motion blur (накладывается 2 соседних кадра) и temporal smoother (накладывается до 4 соседних кадров, причём сила эффекта настраивается). Это особенно нужно в том случае, если на картинке "прыгает" яркость (рисованный советский мультфильм или совсем старая плёнка). Учтите, что выгрузка покадрово требует много мегабайт диско─ вого пространства, не жадничайте - тысячи кадров вам,скорее все─ го, ни к чему, достаточно нескольких сотен. Shiru Otaku предложил, как можно вытащить кадры, не имея Virtual Dub (маленькими процедурками на Си). ──────────────────────────────────────────────────────────────── Ты указываешь номер кадра в фильме, принимающий буфер, вызы─ ваешь декодер - получаешь кадр. Hесколько строк кода. #include <vfw.h> //#pragma comment(lib,"vfw32.lib") //это стандартная либа,можешь //просто в проект включить, можешь (в msvc) через прагму... AVISTREAMINFO vzx_pstream; PAVISTREAM vzx_avi; PGETFRAME vzx_frame; int vzx_width,vzx_height,vzx_msek,vzx_allframes; //параметры ви- //део, определяются на открытии файла char* vzx_data; //указатель на память,куда будут пихаться данные //(не забудь выделить эту память!) открываем файл: AVIFileInit(); if(AVIStreamOpenFromFile(&vzx_avi,szFile,streamtypeVIDEO,0, OF_READ,NULL)!=0) //не вышло открыть файл AVIStreamInfo(vzx_avi,&vzx_pstream,sizeof(vzx_pstream)); vzx_width=vzx_pstream.rcFrame.right-vzx_pstream.rcFrame.left; //ширина кадра vzx_height=vzx_pstream.rcFrame.bottom-vzx_pstream.rcFrame.top; //высота кадра vzx_allframes=AVIStreamLength(vzx_avi); //сколько всего кадров vzx_msek=AVIStreamSampleToTime(vzx_avi,vzx_allframes)/ vzx_allframes; //сколько мсек на кадр (отсюда знаем и fps) vzx_frame=AVIStreamGetFrameOpen(vzx_avi,NULL); if(vzx_frame==NULL) //не вышло открыть кадр из файла (например, //кодека нету) читаем кадр: LPBITMAPINFOHEADER lpbi; lpbi=(LPBITMAPINFOHEADER)AVIStreamGetFrame(vzx_frame, номер кадра); vzx_data=(char*)lpbi+lpbi->biSize+lpbi-> biClrUsed*sizeof(RGBQUAD); //получили указатель на память, где //лежит декодированный кадр, в виде rgbquad - r,g,b,0 закрываем файл: AVIStreamGetFrameClose(vzx_frame); AVIStreamRelease(vzx_avi); AVIFileExit(); Собственно, и всё. Hичего сложного. ──────────────────────────────────────────────────────────────── Конверсия в ZX экран Поскольку существуют различные виды рисунка видеокадров (съё─ мка, с крупными или мелкими деталями, с хорошим или плохим осве─ щением, контурный мультфильм, рендеренный мультфильм...), сущес─ твуют и разничные критерии качества переноса картинки на ZX. Я сначала конвертировал довольно экзотическим алгоритмом,име─ ющим даже некий параметр подавления очанковки (аналог bright, применяемый на этапе дизеринга). Но позже, после написания кон─ вертора статичных экранов,я взял алгоритм уже из него ( ordered/ diamond вариант - потому что Флойд-Штейнберг непригоден для упа─ ковки). Об этом конверторе и его алгоритме я напишу в другой статье. Естественно, пришлось внести некоторые различия. По-видимому,нужно запрещать использовать дополнительные цвета (сумма которых равна белому) в качестве атрибутов в знакоместе (за исключением случая ink=7,paper=0 ). Художники их практически не применяют, а их постоянное перемаргивание с ч/б (компьютер не сможет понять, что лучше, даже при незначительных изменениях видеокадра) ничего хорошего для упаковки не даст. Аналогично с красно-зелёными и красно-синими знакоместами. Применение в одном знакоместе цветов, отличающиеся только си─ ней составляющей,однозначно нужно запретить,такие тонкости прак─ тически не заметны в видео. Стандартные знакоместа 4 байта на знакоместо - слишком расточительно, если учитывать тот факт, что знакоместа могут часто иметь весьма похожие рисун─ ки. Хотелось бы кодировать хотя бы половину знакомест одним бай─ том. Как? Изначально я нарисовал 9 равномерно штрихованных знакомест,от чёрного до белого. Это логично. Они действительно часто исполь─ зуются.Но в сумме по ролику они не охватывают даже половины всех знакомест, поэтому необходимо было нарисовать ещё. Первая идея была - автоматизировать этот процесс. В то время я по просьбе Slip'а пытался сделать видео для иг─ ры, заставку с военным сюжетом. Нужные фрагменты я достал из фи─ льма "Wild Wild West" ("Дикий Дикий Запад"). Slip разрешил испо─ льзовать 256k, и мне хотелось реализовать ролик подлиннее. На тот момент видео ещё не очанковывалось (см. следующую главку), потому что я ещё не придумал способ генерации таблицы перевода 2 байтов в 1. Соответственно, нестандартные знакоместа занимали аж по 8 байт. Я включил 9 стандартных знакомест в поток нестандартных (зап─ ретив в верхней строчке знакоместа использовать несколько редких кодов вроде %10100101, каковое ограничение на глаз незаметно) и огранизовал ещё один 1-байтный код, за которым должен был следо─ вать номер знакоместа в кольцевом списке. Кольцевой список соде─ ржал 256 знакомест, изначально был пустой, и пополнялся только нестандартными знакоместами, встреченными в потоке. Если упаков─ щик находил похожее (не обязательно идентичное - разница могла достигать 6 пикселов) знакоместо в кольцевом списке, он ставил в поток этот хитрый код и номер. Тот ролик имел плохое качество и не пошёл в игру, его 128k вариант ещё лежит у меня на дисках в собранном виде. Каковы ошибки этого метода? Почему он при архивации даёт сли─ шком большую длину файла? 1. Стандартными становятся наборы знакомест,вовсе не охватыва─ ющие весь спектр самых распространённых форм знакомест.Они вооб─ ще получаются практически случайными,а полезные знакоместа могут совсем не попасть в список (их могли ненароком заменить при упа─ ковке на какое-нибудь корявое и волосатое знакоместо, отличающе─ еся на 6 пикселов). 2. Коды стандартных знакомест и номера в кольцевом списке по─ падают в тот же поток, что и байты нестандартных знакомест. А у них совершенно различные математические свойства! Например,номер в кольцевом списке - число равномерно распределённое. А коды стандартных знакомест мало того, что делают из одного распреде─ ления частот байтов - два распределения (для верхнего байта и для всех остальных), - но ещё и разрушают структуру потока этих самых байтов,из-за чего LZ сжатие может весьма и весьма потерять в силе. 3. В таком видео нельзя зациклить нужные фрагменты - потому что состояние кольцевого списка в первый и во второй проход бу─ дет разное. 4. Сама идея потока байт никуда не годится - например, чётные байты распределены совершенно не так, как нечётные. Vitamin поз─ же предложил наXORивать соседние байты друг на друга, при этом равномерная штриховка превращается в последовательность одинако─ вых байт. Очанковка Я решил пойти по более трудному пути: найти 256 стандартных пар байт (нижний-верхний), чтобы любую пару байт можно было с некоторой не очень большой ошибкой привести к одной из стандарт─ ных. Вообще-то это очень перспективное направление: например, так можно запаковать крупные спрайты в играх,они станут в 2 раза меньше по объёму и при этом не станут выводиться медленнее. Сделал так: 1. Штрихованную картинку превращаем в чанки 2x2 (на глаз почти незаметно, если нет тонких линий). 2. Сортируем 625 возможных комбинаций чанков по частоте встре─ чи на каком-нибудь видео или картинке (результат будет использо─ ваться и для других картинок и роликов). 3. Начиная с самых редких, отсеиваем те комбинации, которые почти совпадают с какой-нибудь другой комбинацией (отличаются одной пиксел·градацией ). 4. Из оставшихся отсеиваем те, которые отличаются от какой-ни─ будь другой комбинации двумя пиксел·градациями. В результате сохраняем 2 таблицы: одна для кодирования (из 625-вариантных комбинаций пересчитывает в 256-вариантные),вторая для декодирования (из 256 вариантов пересчитывает в 2 байта). Обе лежат в приложении. Первая - Code256.dat. Изменениям левых чанков соответствуют бОльшие изменения номера комбинации, то есть нужно использовать множители 125, 25, 5 и 1 для градаций левого, средних и правого чанков соответственно. Вторая - deco256.C. В ней нижние байты лежат через 256 байт после верхних. Прилагается также исходник интро к журналу (там видео закодировано без сжатия (энтропийного кодирования) по Хаф─ фману, так что программа короткая и понятная). Сжатие по Хаффману Возможно направления экономии: экономия памяти и экомония ме─ ста на диске. Если ролик короткий, то лучше его хранить в памяти в распакованном виде (без сжатия Хаффманом ),а на диске - в Rar. Так убиваются два зайца: ролик занимает меньше места на диске и меньше тормозит при проигрывании. Но если он длинный, то и в па─ мяти надо хранить сжатым по Хаффману. А сжатым по LZ лучше не хранить - ему для распаковки нужно окно, поэтому выигрыш в сжа─ тии "скомпенсируется" потерей памяти под нужды окна. А у нас вся память, насколько возможно, отведена на хранение видео. Если же ролик сверхдлинный (с подгрузкой), по можно использовать и LZ. Сжатие видеопотока по Хаффману я применил в ролике Dimon128 (с ParaDIGMus'03). Этот ролик я подготавливал одновременно с на─ писанием ZXRar, поэтому упаковщик по Хаффману у меня был в исхо─ днике. Поток "стандартных" знакомест в этом ролике не упакован (он и не упакуется), атрибутный тоже (хотя он упаковался бы в 2 раза). ZXRar может, в отличие от своего пцшного прообраза, паковать чистым Хаффманом, не отступая при этом от формата Rar. Для этого установите окно LZ-метода равным 0 k. При этом видеопоток, нап─ ример, для нашего интро сжимается лучше, чем настоящим методом Rar. Если же использовать LZ, рекомендуется обрабатывать знако─ места не строками, а столбцами - так сжатие эффективнее! Я так сделал и в конверторе,и в интро,хотя без LZ это не обязательно - только замедляет распаковку. Снова стандартные знакоместа И вот мне наконец-то стукнула в голову идея, какие знакоместа должны быть стандартными. Цветопереходы! Цветопереходы в 16 направлениях, между 5 основными градациями серого, на 3-5 основных позициях от центра знакоместа - их вовсе не так много! Изначально я нарисовал 512 знакомест,не считая 9 старых. Сде─ лал я это на bmp -картинке, разместив знакоместа (4x4) сеткой с промежутком в 1 пиксел между знакоместами. Понятно,что многие (сразу не предскажешь,какие) из этих рису─ нков используются настолько редко, что хранить их в распаковщике просто невыгодно ( 1 символ в дереве Хаффмана занимает 4 байта,а само знакоместо - 4 "очанкованно-задокированных" или 8 обычных байт, при том что даёт после упаковки выигрыш где-то 2 байта на каждое вхождение - следовательно, надо не меньше 5-7 вхождений для какой-нибудь выгоды). Но как выяснить, какие из них лишние? Мысль:знакоместо может не использоваться, а) если такая форма вообще непопулярная; б) если такая форма редка именно в конкрет─ ном видео; в) если все такие рисунки выгоднее передать близкими по форме знакоментами. Некоторые знакоместа, получившиеся сильно (меньше 4 пиксе─ лов·градаций разницы ) похожими на другие знакоместа набора, я сразу пометил красным пятнышком снизу. Пытаемся в упаковщике применить все знакоместа и подсчитываем для каждого из них число вхождений, на экран выводим, какие из них редкие. После этого убираем редкие знакоместа с картинки (целыми группами симметрии, поскольку переходы влево, вправо, вверх и вниз должны быть равновероятны по большому счёту). Точ─ нее, утаскиваем их на периферию bmp - вдруг потом пригодятся? Далее перезапускаем наш конвертор видео, прогоняем на том же ро─ лике, смотрим длину выходных потоков в упакованном Rar'ом виде - выиграли или проиграли?- и статистику знакомест. Убираем редкие, возвращаем удалённые,если ошиблись в предположении об их невыго─ дности,снова перезапускаем конвертор,и так далее. Задача-минимум - получить <=256 знакомест, задача-максимум - получить самые вы─ годные 256 знакомест (или меньше 256, если выгодно меньше). Хотелось получить ровно 256 стандартных знакомест,но не вышло - для выбора наиболее полезных (наименее бесполезных) из кучи выброшенных нужно слишком долго перебирать варианты на разных видеороликах, у меня нет такого количества свободного времени. А выигрыш будет - копеечный, где-то около 1-2 процентов по самой оптимистической оценке... Аналогичные проценты, если совсем не хватает памяти, проще выиграть за счёт незаметного на глаз сни─ жения качества ролика, для чего и придуманы настройки стратегии конверсии. Сравнив результаты на нескольких видео, я вообще бросил это занятие - поиск эффективнейших стандартных знакомест - поскольку типов картинок бывает огромное количество. На всякий случай я просто добавил (чтобы стандартных знакомест было 256 ) в набор знакоместа для контурной анимации (белые линии на чёрном фоне). Паковать такие ролики нужно (см. ниже про настройки) с парамет─ рами: standard error dots = 9; и standard error, соответственно, 9·4 = 36. Стратегия конверсии Ошибки представления, как я уже говорил,удобнее всего считать по формуле: ошибкасинего+ошибкакрасного+ошибказелёного·2. Можно с коэффициентами 1, 2, 3 соответственно, можно 1, 2, 4 соответс─ твенно. Стандартное знакоместо выгодно, если: ошибкастандартного-ошибканестандартного<StError. Старая ошибка - переменная dif, ошибка - переменная max. Менять знакоместо выгодно, если: новое - стандартное, ошибка<=StErLevel, стараяошибка>StErLevel (чуть сниженное качество на быстрых движениях) иначе если стараяошибка>BadErLevel [,задержка>BadDelay(маленькая задержка)] (основная ветка, аналогично простому методу упаковки. Может чуть отставать от ветки со стандартным знакоместом) иначе если стараяошибка>GoodErLevel, задержка>GoodDelay (большая задержка) (стирание небольших дефектов на медленных движениях) иначе если новое - стандартное, (стараяошибка-ошибка)>0, задержка>ClDelay (стирание отдельных пикселов, оставшихся на сплошном фоне - их не снимает никакая другая ветка алгоритма). Рекомендации по настройке параметров стратегии Нельзя задавать для стандартных знакомест предел ошибки ниже, чем для "good" знакомест. Из-за этого "good" будут просто пере─ хватывать часть знакомест, которые МОГЛИ БЫ быть закодированы стандартными (т. е. более коротко). При конверсии мультика про дядю Фёдора нужно было убрать постоянное изменение цвета стен (ведь это рисованный мультик).Для этого я применил в Virtual Dub фильтр temporal smoother с параметром 9, а в своём конверторе поставил оба предела на уровень 20 (а для "bad" - 50 ). Задержку для "good" ставил 1, для "bad" - 0, а для "pixels" (напомню, "pixels" - это тоже стандартные знакоместа, только без предела ошибки - снимает шальные пиксели, этого фильтра нет в Video Stu─ dio ) - 9. Средний объём кадра даже без упаковки составил 240 байт (с упаковкой - 150 ). Это при запрете ярко-белого. А с ним (так картинка кажется объёмнее) - 285 и 200 байт на кадр соотве─ тственно. Можно учитывать тот факт, что ошибки на периферии менее заме─ тны, чем в центре. Как этим воспользоваться, решайте сами: можно увеличивать задержки (delays) при удалении от центра (расстояние можно грубо посчитать как сумму dx+dy, где dx и dy - расстояния от центра по соотв. оси в знакоместах), а можно при удалении от центра повышать лимиты на ошибки. Я экспериментировал в этим на роликах в final cut'е Wolf'2004, но это годится не для всех видеосюжетов. Как соединить все потоки в один Хранить 3 раздельных потока неудобно: их трудно загнать в странички (приходится начинать с некруглых адресов, т. е. резать файлы побайтно),а при проигрывании страницы постоянно щёлкаются. Если бы они не щёлкались, производительность видеоплейера была бы больше. Сразу отметаем идею об одном потоке, генерируемом ещё на ста─ дии упаковки (с последующим сжатием файла rar'ом ). Потому что это в чистом виде проигрыш - помесь разнородных потоков с разны─ ми свойствами пакуется хуже, чем эти потоки по отдельности. Сле─ довательно, нужно именно чередовать байты (или биты) трёх уже сжатых потоков. Для формирования такой чехарды нужна отдельная программа,удобнее всего в виде исходника. Если чередовать именно биты, то распаковщик будет короче - можно пользоваться одной и той же процедурой выборки битов для всех потоков, заодно хранить текущий сдвинутый байт в одном и том же A' (см. описание распа─ ковщиков Hrust в IG#5 ), да и вообще, даже декодер Хаффмана за счёт этого использовать один-единственый. Рекомендации по проигрыванию видео Информация о кадре для среднего цветного видео с медленно движущейся камерой составляет в распакованном виде около 500 байт, считая все 4 потока: управляющий/атрибутный, чанковый и знакоместный. Это значит, что в хорошо упакованном видео мы на 3.5 MHz сможем играть до 20 fps (самая удобная реализация деко─ дирования по Хаффману декодирует до 10 k/с ). Выбранную скорость воспроизведения нужно поддерживать постоянной в среднем, подсчи─ тывая фреймы (для IM 1 - системная переменная FRAMES: 23672/3/4, для IM 2 легко можете реализовать аналогичную). Реализуется эта синхронизация путём записи в 23672 числа 0 с последующим контро─ лем получившегося там в конце построения кадра числа (назовём это число M ). Если оно (M) меньше заданного N (N=5 для 10 fps), то ожидаем до заданного (т.е. до 5 ). Если больше заданного - то кадр не успел построиться за нужное время (может быть,он "ключе─ вой": сменилась вся картинка), и на следующем цикле нужно вместо нуля записать в 23672 число M-N. Вот такой фрагмент (в конце обработки кадра): LD A,(23672) <┐ SUB N │ JR C,─────────┘ LD (23672),A 0 надо заносить только в начале проигрывателя видео, до цикла обработки кадров. Источником синхронизации может служить и дисковод,если с него постоянно идёт подгрузка (как в ролике "Locomotion" или в демо "Power Up" ). Размер упакованного кадра даже теоретически можно подгонять к фиксированному числу секторов - хотя и очень трудно. Если памяти не хватает, первый кадр можно грузить прямо в экран,а потом запускать видео со следующего кадра. Всё-таки пара килобайт на дороге не валяется. Но на диске такое видео займёт столько же места. Мой упаковщик (на Delphi ) - это не релиз,а чисто эксперимен─ тальная вещь, после моих объяснений вы сами сможете написать такой же. Но всё-таки помещаю исходники в приложение - лучше меняйте по готовому, зачем тратить время на написание того, что уже написано? А пределов экспериментам нет - я не полностью реализовал даже своих идей упаковки (например, нет даже простейшего скроллинга, который очень требуется на многих роликах), у вас же могут поя─ виться ещё более интересные идеи. Если придумаете что-то удачное - пишите нам... A. Coder